├── client
├── .env
├── public
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── robots.txt
│ ├── manifest.json
│ └── index.html
├── src
│ ├── react-app-env.d.ts
│ ├── setupTests.ts
│ ├── index.css
│ ├── components
│ │ ├── HomePage
│ │ │ ├── __tests__
│ │ │ │ └── HomePage.test.js
│ │ │ └── HomePage.tsx
│ │ ├── Shop
│ │ │ ├── __tests__
│ │ │ │ └── Shop.test.js
│ │ │ └── Shop.tsx
│ │ ├── ProductItem
│ │ │ ├── __tests__
│ │ │ │ └── ProductItem.test.js
│ │ │ └── ProductItem.tsx
│ │ ├── Cart
│ │ │ ├── __tests__
│ │ │ │ └── Cart.test.js
│ │ │ └── Cart.tsx
│ │ └── Navbar
│ │ │ ├── __tests__
│ │ │ └── Navbar.test.js
│ │ │ └── Navbar.tsx
│ ├── theme.ts
│ ├── store
│ │ ├── inventory
│ │ │ ├── types.ts
│ │ │ ├── reducer.ts
│ │ │ └── action.ts
│ │ ├── cart
│ │ │ ├── types.ts
│ │ │ ├── reducer.ts
│ │ │ └── action.ts
│ │ └── index.ts
│ ├── Routes.tsx
│ ├── index.tsx
│ ├── App.tsx
│ ├── configureStore.ts
│ ├── test-utils.js
│ └── serviceWorker.ts
├── tsconfig.json
└── package.json
├── server
├── tsconfig-build.json
├── express
│ ├── public
│ │ └── images
│ │ │ └── products
│ │ │ ├── chair1.png
│ │ │ ├── chair2.png
│ │ │ ├── chair3.png
│ │ │ ├── chair4.png
│ │ │ ├── chair5.png
│ │ │ ├── chair6.png
│ │ │ ├── chair7.png
│ │ │ ├── chair8.png
│ │ │ ├── chair9.png
│ │ │ ├── couch1.png
│ │ │ ├── couch2.png
│ │ │ ├── couch3.png
│ │ │ ├── couch4.png
│ │ │ ├── couch5.png
│ │ │ ├── couch6.png
│ │ │ ├── couch7.png
│ │ │ ├── couch8.png
│ │ │ ├── couch9.png
│ │ │ ├── chair10.png
│ │ │ ├── couch10.png
│ │ │ ├── couch11.png
│ │ │ ├── couch12.png
│ │ │ ├── couch13.png
│ │ │ ├── couch14.png
│ │ │ └── couch15.png
│ ├── __tests__
│ │ └── getProducts.test.ts
│ ├── App.ts
│ └── data
│ │ └── products.json
├── .mocharc.json
├── electron
│ ├── Renderer.ts
│ ├── Preload.ts
│ └── ElectronStarter.ts
├── .nycrc.json
├── tsconfig.json
├── tslint.json
└── package.json
├── assets
├── mac
│ └── icon.png
├── win
│ ├── icon.png
│ └── logo.ico
└── linux
│ └── icon.png
├── .gitignore
├── package.json
├── README.md
└── yarn.lock
/client/.env:
--------------------------------------------------------------------------------
1 | NODE_PATH=src
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/server/tsconfig-build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "**/*.test.*"
5 | ]
6 | }
--------------------------------------------------------------------------------
/assets/mac/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/assets/mac/icon.png
--------------------------------------------------------------------------------
/assets/win/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/assets/win/icon.png
--------------------------------------------------------------------------------
/assets/win/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/assets/win/logo.ico
--------------------------------------------------------------------------------
/assets/linux/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/assets/linux/icon.png
--------------------------------------------------------------------------------
/server/express/public/images/products/chair1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/chair1.png
--------------------------------------------------------------------------------
/server/express/public/images/products/chair2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/chair2.png
--------------------------------------------------------------------------------
/server/express/public/images/products/chair3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/chair3.png
--------------------------------------------------------------------------------
/server/express/public/images/products/chair4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/chair4.png
--------------------------------------------------------------------------------
/server/express/public/images/products/chair5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/chair5.png
--------------------------------------------------------------------------------
/server/express/public/images/products/chair6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/chair6.png
--------------------------------------------------------------------------------
/server/express/public/images/products/chair7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/chair7.png
--------------------------------------------------------------------------------
/server/express/public/images/products/chair8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/chair8.png
--------------------------------------------------------------------------------
/server/express/public/images/products/chair9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/chair9.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch1.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch2.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch3.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch4.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch5.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch6.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch7.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch8.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch9.png
--------------------------------------------------------------------------------
/server/express/public/images/products/chair10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/chair10.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch10.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch11.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch12.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch13.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch14.png
--------------------------------------------------------------------------------
/server/express/public/images/products/couch15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/engPabloMartinez/react-express-electron-boilerplate/HEAD/server/express/public/images/products/couch15.png
--------------------------------------------------------------------------------
/server/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": ["ts-node/register", "source-map-support/register"],
3 | "recursive": true,
4 | "extension": "ts",
5 | "spec": "express/**/*.test.ts"
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # project
2 | .git
3 | .idea
4 | .vscode
5 | .DS_Store
6 |
7 | # node-modules
8 | node_modules
9 | server/node_modules
10 | client/node_modules
11 |
12 | # builds
13 | build
14 | dist
15 | server/dist
16 |
17 | # test coverage
18 | server/coverage
19 | client/coverage
--------------------------------------------------------------------------------
/client/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
--------------------------------------------------------------------------------
/server/electron/Renderer.ts:
--------------------------------------------------------------------------------
1 | // This file is required by the index.html file and will
2 | // be executed in the renderer process for that window.
3 | // No Node.js APIs are available in this process because
4 | // `nodeIntegration` is turned off. Use `Preload.ts` to
5 | // selectively enable features needed in the rendering
6 | // process.
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | overflow: hidden;
9 | background: white;
10 | }
11 |
--------------------------------------------------------------------------------
/server/.nycrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "all": true,
3 | "extends": "@istanbuljs/nyc-config-typescript",
4 | "include": "express/**/*.ts",
5 | "report-dir": "./coverage",
6 | "reporter": ["lcov", "text-summary"],
7 | "temp-dir": "./coverage/.nyc_output",
8 | "check-coverage": true,
9 | "branches": 75,
10 | "lines": 75,
11 | "functions": 75,
12 | "statements": 75
13 | }
--------------------------------------------------------------------------------
/client/src/components/HomePage/__tests__/HomePage.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from 'test-utils';
3 | import HomePage from '../HomePage';
4 |
5 | describe('Test Home Page Component', () => {
6 | it('can render without crashing', () => {
7 | render();
8 | expect(screen.getByText('Welcome to the Boilerplate of React/Express/Electron Application')).toBeInTheDocument();
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "dist",
6 | "strict": true,
7 | "strictNullChecks": false,
8 | "esModuleInterop": true,
9 | "moduleResolution": "node",
10 | "resolveJsonModule": true,
11 | "forceConsistentCasingInFileNames": true
12 | },
13 | "include": [
14 | "**/*.ts"
15 | ],
16 | "exclude": [
17 | "node_modules"
18 | ]
19 | }
--------------------------------------------------------------------------------
/server/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint:recommended",
3 | "rules": {
4 | "max-line-length": {
5 | "options": [
6 | 120
7 | ]
8 | },
9 | "new-parens": true,
10 | "no-arg": true,
11 | "no-bitwise": true,
12 | "no-conditional-assignment": true,
13 | "no-consecutive-blank-lines": false
14 | },
15 | "jsRules": {
16 | "max-line-length": {
17 | "options": [
18 | 120
19 | ]
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/server/electron/Preload.ts:
--------------------------------------------------------------------------------
1 | // All of the Node.js APIs are available in the preload process.
2 | // It has the same sandbox as a Chrome extension.
3 | window.addEventListener("DOMContentLoaded", () => {
4 | const replaceText = (selector: string, text: string) => {
5 | const element = document.getElementById(selector);
6 | if (element) {
7 | element.innerText = text;
8 | }
9 | };
10 |
11 | for (const type of ["chrome", "node", "electron"]) {
12 | replaceText(`${type}-version`, (process.versions as any)[type]);
13 | }
14 | });
--------------------------------------------------------------------------------
/client/src/theme.ts:
--------------------------------------------------------------------------------
1 | import { red } from '@material-ui/core/colors';
2 | import { createMuiTheme } from '@material-ui/core/styles';
3 |
4 | // Let's say you want to add custom colors
5 | const Theme = createMuiTheme({
6 | palette: {
7 | primary: {
8 | main: '#556cd6',
9 | },
10 | secondary: {
11 | main: '#19857b',
12 | },
13 | error: {
14 | main: red.A400,
15 | },
16 | background: {
17 | default: '#fff',
18 | },
19 | },
20 | });
21 |
22 | export default Theme;
23 |
--------------------------------------------------------------------------------
/client/src/store/inventory/types.ts:
--------------------------------------------------------------------------------
1 | export interface Inventory {
2 | id: string;
3 | name: string;
4 | price: string;
5 | image: string;
6 | description: string;
7 | brand?: string;
8 | currentInventory: number;
9 | }
10 |
11 | export enum InventoryActionTypes {
12 | FETCH_REQUEST = "@@inventory/FETCH_REQUEST",
13 | FETCH_SUCCESS = "@@inventory/FETCH_SUCCESS",
14 | FETCH_ERROR = "@@inventory/FETCH_ERROR"
15 | }
16 |
17 | export interface InventoryState {
18 | readonly loading: boolean;
19 | readonly data: Inventory[];
20 | readonly errors?: string;
21 | }
22 |
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "sourceMap": true,
21 | "noEmit": true,
22 | "jsx": "react"
23 | },
24 | "include": [
25 | "src"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/client/src/Routes.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Switch, Redirect } from 'react-router-dom';
3 |
4 | import Navbar from './components/Navbar/Navbar';
5 | import HomePage from './components/HomePage/HomePage';
6 | import Cart from './components/Cart/Cart';
7 | import Shop from './components/Shop/Shop';
8 |
9 | const Routes: React.FunctionComponent = () => (
10 | <>
11 |
12 |
13 |
14 |
15 |
16 | }/>
17 |
18 | >
19 | );
20 |
21 | export default Routes;
22 |
--------------------------------------------------------------------------------
/client/src/store/cart/types.ts:
--------------------------------------------------------------------------------
1 | import { Inventory } from '../inventory/types';
2 |
3 | export interface Cart {
4 | id: number;
5 | items: Inventory[];
6 | }
7 |
8 | export enum CartActionTypes {
9 | ADD_TO_CART = "@@cart/ADD_TO_CART",
10 | ADD_TO_CART_FAILURE = "@@cart/ADD_TO_CART_FAILURE",
11 | REMOVE_FROM_CART = "@@cart/REMOVE_FROM_CART",
12 | REMOVE_FROM_CART_FAILURE = "@@cart/ADD_TO_CART_FAILURE",
13 | FETCH_CART_REQUEST = "@@cart/FETCH_CART_REQUEST",
14 | FETCH_CART_SUCCESS = "@@cart/FETCH_CART_SUCCESS",
15 | FETCH_CART_ERROR = "@@cart/FETCH_CART_ERROR"
16 | }
17 |
18 | export interface cartState {
19 | readonly loading: boolean;
20 | readonly data: Cart;
21 | readonly errors?: string;
22 | }
23 |
--------------------------------------------------------------------------------
/client/src/components/HomePage/HomePage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Grid, Typography } from '@material-ui/core';
3 | import { makeStyles } from "@material-ui/core/styles";
4 |
5 | const useStyles = makeStyles({
6 | root: {
7 | overflow: 'auto',
8 | height: 'calc(100vh - 64px)'
9 | },
10 | });
11 |
12 | const HomePage: React.FunctionComponent = () => {
13 | const classes = useStyles();
14 | return (
15 |
16 |
17 | Welcome to the Boilerplate of React/Express/Electron Application
18 |
19 |
20 | );
21 | };
22 |
23 | export default HomePage;
24 |
--------------------------------------------------------------------------------
/server/express/__tests__/getProducts.test.ts:
--------------------------------------------------------------------------------
1 | // Import the dependencies for testing
2 | import chai from'chai';
3 | import chaiHttp from 'chai-http';
4 | import app from '../App';
5 |
6 | // Configure chai
7 | chai.use(chaiHttp);
8 | chai.should();
9 |
10 | describe("Products", () => {
11 | describe("GET /products", () => {
12 | // Test to get all students record
13 | it("should get all products", (done) => {
14 | chai.request(app)
15 | .get('/products')
16 | .end((err, res) => {
17 | res.should.have.status(200);
18 | res.body.should.be.a('array');
19 | res.body.should.have.length(21);
20 | done();
21 | });
22 | });
23 | });
24 | });
--------------------------------------------------------------------------------
/client/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { connectRouter } from 'connected-react-router';
3 | import { History } from 'history';
4 | import { RouterState } from 'connected-react-router';
5 |
6 | import { InventoryReducer } from './inventory/reducer';
7 | import { InventoryState } from './inventory/types';
8 |
9 | import { cartReducer } from './cart/reducer';
10 | import { cartState } from './cart/types';
11 |
12 | export interface ApplicationState {
13 | cart: cartState;
14 | inventory: InventoryState;
15 | router?: RouterState;
16 | }
17 |
18 | export const createRootReducer = (history: History) =>
19 | combineReducers({
20 | cart: cartReducer,
21 | inventory: InventoryReducer,
22 | router: connectRouter(history)
23 | });
24 |
--------------------------------------------------------------------------------
/client/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import './index.css';
5 | import App from './App';
6 | import * as serviceWorker from './serviceWorker';
7 | import { createHashHistory } from 'history';
8 | import configureStore from './configureStore';
9 | const history = createHashHistory();
10 |
11 | const initialState: any = {};
12 |
13 | const store = configureStore(history, initialState);
14 |
15 | ReactDOM.render(
16 | ,
17 | document.getElementById("root")
18 | );
19 |
20 | // If you want your app to work offline and load faster, you can change
21 | // unregister() to register() below. Note this comes with some pitfalls.
22 | // Learn more about service workers: https://bit.ly/CRA-PWA
23 | serviceWorker.unregister();
24 |
--------------------------------------------------------------------------------
/client/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 | import { MuiThemeProvider } from '@material-ui/core';
4 | import { Store } from 'redux';
5 | import { History } from 'history';
6 | import { ConnectedRouter } from 'connected-react-router';
7 |
8 | import theme from './theme';
9 | import { ApplicationState } from './store';
10 | import Routes from './Routes';
11 |
12 | interface MainProps {
13 | store: Store;
14 | history: History;
15 | }
16 |
17 | const App: React.FC = ({ store, history }) => {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | };
28 |
29 | export default App;
--------------------------------------------------------------------------------
/client/src/configureStore.ts:
--------------------------------------------------------------------------------
1 | import { Store, createStore, applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { routerMiddleware } from 'connected-react-router';
4 | import { History } from 'history';
5 |
6 | import { ApplicationState, createRootReducer } from './store';
7 |
8 | export default function configureStore(
9 | history: History,
10 | initialState: ApplicationState
11 | ): Store {
12 | const store = createStore(
13 | createRootReducer(history),
14 | initialState,
15 | compose(
16 | applyMiddleware(routerMiddleware(history), thunk),
17 | // eslint-disable-next-line no-underscore-dangle
18 | // @ts-ignore
19 | window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f,
20 | ),
21 | );
22 |
23 | return store;
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/components/Shop/__tests__/Shop.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, fireEvent, screen } from 'test-utils';
3 | import axios from 'axios';
4 | import Shop from '../Shop';
5 |
6 | jest.mock('axios');
7 |
8 | describe('Test Shop Component', () => {
9 | it('can render with products', () => {
10 | render();
11 | expect(screen.getByText('Test 1')).toBeInTheDocument();
12 | });
13 |
14 | it('can render without products', () => {
15 | render(, { initialState: { inventory: { data: [] } } });
16 | expect(screen.getByText('You don\'t have any products in the Shop')).toBeInTheDocument();
17 | });
18 |
19 | it('can add to cart', () => {
20 | render();
21 | expect(screen.getAllByRole('button')).toHaveLength(2);
22 |
23 | fireEvent.click(screen.getAllByText('Add To Cart')[1]);
24 | });
25 | })
26 |
--------------------------------------------------------------------------------
/client/src/store/inventory/reducer.ts:
--------------------------------------------------------------------------------
1 | import { Reducer } from 'redux';
2 | import { InventoryActionTypes, InventoryState } from './types';
3 |
4 | export const initialState: InventoryState = {
5 | data: [],
6 | errors: undefined,
7 | loading: false
8 | };
9 |
10 | const reducer: Reducer = (state = initialState, action) => {
11 | switch (action.type) {
12 | case InventoryActionTypes.FETCH_REQUEST: {
13 | return { ...state, loading: true };
14 | }
15 | case InventoryActionTypes.FETCH_SUCCESS: {
16 | return { ...state, loading: false, data: action.payload };
17 | }
18 | case InventoryActionTypes.FETCH_ERROR: {
19 | return { ...state, loading: false, errors: action.payload };
20 | }
21 | default: {
22 | return state;
23 | }
24 | }
25 | };
26 |
27 | export { reducer as InventoryReducer };
28 |
--------------------------------------------------------------------------------
/client/src/components/ProductItem/__tests__/ProductItem.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from 'test-utils';
3 | import ProductItem from '../ProductItem';
4 |
5 | describe('Test ProductItem Component', () => {
6 | it('can render without crashing inside shop', () => {
7 | render();
8 |
9 | expect(screen.getByText('Test')).toBeInTheDocument();
10 | expect(screen.queryByText('Remove From Cart')).toBeNull();
11 | expect(screen.getByText('Add To Cart')).toBeInTheDocument();
12 | });
13 |
14 | it('can render without crashing inside cart', () => {
15 | render();
16 |
17 | expect(screen.getByText('Test')).toBeInTheDocument();
18 | expect(screen.getByText('Remove From Cart')).toBeInTheDocument();
19 | expect(screen.queryByText('Add To Cart')).toBeNull();
20 | });
21 | })
22 |
--------------------------------------------------------------------------------
/client/src/components/Cart/__tests__/Cart.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, fireEvent, screen } from 'test-utils';
3 | import Cart from '../Cart';
4 |
5 | describe('Test Cart Component', () => {
6 | it('can render with products', () => {
7 | render();
8 | expect(screen.getByText('Test 1')).toBeInTheDocument();
9 | });
10 |
11 | it('can render without products', () => {
12 | render(, { initialState: { cart: { data: { items: [] }} } });
13 | expect(screen.getByText('You don\'t have any products in the Cart')).toBeInTheDocument();
14 | });
15 |
16 | it('can remove from cart', () => {
17 | render();
18 | expect(screen.getAllByRole('button')).toHaveLength(1);
19 |
20 | fireEvent.click(screen.getAllByText('Remove From Cart')[0]);
21 |
22 | expect(screen.getByText('You don\'t have any products in the Cart')).toBeInTheDocument();
23 | });
24 | })
25 |
--------------------------------------------------------------------------------
/client/src/store/inventory/action.ts:
--------------------------------------------------------------------------------
1 | import { ActionCreator, Action, Dispatch } from 'redux';
2 | import { ThunkAction } from 'redux-thunk';
3 | import axios from 'axios';
4 |
5 | import { InventoryActionTypes } from './types';
6 | import { ApplicationState } from '../index';
7 |
8 | export type AppThunk = ActionCreator<
9 | ThunkAction>
10 | >;
11 |
12 | export const fetchRequest: AppThunk = () => {
13 | return (dispatch: Dispatch): any => {
14 | try {
15 | dispatch({
16 | type: InventoryActionTypes.FETCH_REQUEST,
17 | });
18 |
19 | return axios({
20 | method: 'GET',
21 | url: "http://localhost:3001/products",
22 | responseType: "json"
23 | }).then((response) =>
24 | dispatch({
25 | type: InventoryActionTypes.FETCH_SUCCESS,
26 | payload: response.data,
27 | })
28 | )
29 | } catch (e) {
30 | return dispatch({
31 | type: InventoryActionTypes.FETCH_ERROR
32 | });
33 | }
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/client/src/store/cart/reducer.ts:
--------------------------------------------------------------------------------
1 | import { Reducer } from 'redux';
2 |
3 | import { CartActionTypes, cartState } from './types';
4 |
5 | export const initialState: cartState = {
6 | data: {
7 | id: 0,
8 | items: []
9 | },
10 | errors: undefined,
11 | loading: false
12 | };
13 |
14 | const reducer: Reducer = (state = initialState, action) => {
15 | switch (action.type) {
16 | case CartActionTypes.ADD_TO_CART: {
17 | return {
18 | errors: state.errors,
19 | loading: state.loading,
20 | data: {
21 | ...state.data,
22 | id: state.data.id,
23 | items: [...state.data.items.filter(item => item.id !== action.payload.id), action.payload]
24 | }
25 | };
26 | }
27 | case CartActionTypes.REMOVE_FROM_CART: {
28 | return {
29 | errors: state.errors,
30 | loading: state.loading,
31 | data: {
32 | ...state.data,
33 | id: state.data.id,
34 | items: state.data.items.filter(item => item.id !== action.payload.id)
35 | }
36 | };
37 | }
38 | default: {
39 | return state;
40 | }
41 | }
42 | };
43 |
44 | export { reducer as cartReducer };
--------------------------------------------------------------------------------
/server/express/App.ts:
--------------------------------------------------------------------------------
1 | import bodyParser from 'body-parser';
2 | import express from 'express';
3 | import cors from 'cors';
4 | import logger from 'morgan';
5 | import path from 'path';
6 | import products from './data/products.json';
7 |
8 | // Creates and configures an ExpressJS web server2.
9 | class App {
10 |
11 | // ref to Express instance
12 | public express: express.Application;
13 |
14 | // Run configuration methods on the Express instance.
15 | constructor() {
16 | this.express = express();
17 | this.middleware();
18 | this.routes();
19 | }
20 |
21 | // Configure Express middleware.
22 | private middleware(): void {
23 | this.express.use(logger("dev"));
24 | this.express.use(cors());
25 | this.express.use('/images', express.static(path.join(__dirname, 'public/images')));
26 | this.express.use(bodyParser.json());
27 | this.express.use(bodyParser.urlencoded({ extended: false }));
28 | }
29 |
30 | // Configure API endpoints.
31 | private routes(): void {
32 | /* This is just to get up and running, and to make sure what we've got is
33 | * working so far. This function will change when we start to add more
34 | * API endpoints */
35 | const router = express.Router();
36 | // placeholder route handler
37 | router.get("/products", (req, res, next) => {
38 | res.json(products);
39 | });
40 | this.express.use("/", router);
41 | }
42 | }
43 |
44 | export default new App().express;
--------------------------------------------------------------------------------
/client/src/store/cart/action.ts:
--------------------------------------------------------------------------------
1 | import { ActionCreator, Action, Dispatch } from 'redux';
2 | import { ThunkAction } from 'redux-thunk';
3 |
4 | import { CartActionTypes } from './types';
5 | import { Inventory } from '../inventory/types';
6 | import { ApplicationState } from '../index';
7 |
8 | export type AppThunk = ThunkAction<
9 | void,
10 | ApplicationState,
11 | null,
12 | Action
13 | >;
14 |
15 | export const addToCart: ActionCreator
20 | >> = item => {
21 | return (dispatch: Dispatch): Action => {
22 | try {
23 | return dispatch({
24 | type: CartActionTypes.ADD_TO_CART,
25 | payload: item
26 | });
27 | } catch (e) {
28 | return dispatch({
29 | type: CartActionTypes.ADD_TO_CART_FAILURE,
30 | payload: null
31 | });
32 | }
33 | };
34 | };
35 |
36 | export const removeFromCart: ActionCreator
41 | >> = item => {
42 | return (dispatch: Dispatch): Action => {
43 | try {
44 | return dispatch({
45 | type: CartActionTypes.REMOVE_FROM_CART,
46 | payload: item
47 | });
48 | } catch (e) {
49 | return dispatch({
50 | type: CartActionTypes.REMOVE_FROM_CART_FAILURE,
51 | payload: null
52 | });
53 | }
54 | };
55 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-express-electron-boilerplate",
3 | "version": "1.0.0",
4 | "description": "A boilerplate to generate an Electron app with a React Front end and an Express BackEnd embedded.",
5 | "scripts": {
6 | "start-dev": "concurrently \"cd client && npm start\" \"wait-on http://localhost:3000 && cd server && yarn run electron-dev \"",
7 | "install-all": "yarn install && cd server && yarn install && cd ../client && yarn install",
8 | "remove-create-dir": "rm -rf dist && rm -rf build && rm -rf server/dist && mkdir dist && mkdir build && mkdir server/dist",
9 | "build-all": "yarn run remove-create-dir && cd client && yarn react-build && cd ../server && yarn run build && yarn run electron-build-all",
10 | "build-win": "yarn run remove-create-dir && cd client && yarn react-build && cd ../server && yarn run build && yarn run electron-build-win && yarn run remove-package-folder && yarn run move-build",
11 | "build-linux": "yarn run remove-create-dir && cd client && yarn react-build && cd ../server && yarn run build && yarn run electron-build-linux && yarn run remove-package-folder && yarn run move-build",
12 | "build-mac": "yarn run remove-create-dir && cd client && yarn react-build && cd ../server && yarn run build && yarn run electron-build-mac && yarn run remove-package-folder && yarn run move-build",
13 | "test": "cd client && yarn run test-no-watch && cd ../server && yarn run test",
14 | "test-coverage": "cd client && yarn run test-coverage && cd ../server && yarn run test-coverage"
15 | },
16 | "author": "Pablo Martinez",
17 | "license": "ISC",
18 | "devDependencies": {
19 | "concurrently": "^5.3.0",
20 | "wait-on": "^5.2.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/client/src/components/Cart/Cart.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { Grid, Typography } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import { ApplicationState } from '../../store';
7 | import { Cart } from '../../store/cart/types';
8 | import ProductItem from '../ProductItem/ProductItem';
9 |
10 | interface propsFromState {
11 | cartItems: Cart;
12 | }
13 |
14 | const useStyles = makeStyles({
15 | root: {
16 | overflow: 'auto',
17 | height: 'calc(100vh - 64px)',
18 | padding: 18,
19 | },
20 | });
21 |
22 | const CartComponent: React.FunctionComponent = ({ cartItems }) => {
23 | const classes = useStyles();
24 | return (
25 |
26 | {
27 | cartItems.items.length > 0 ?
28 | cartItems.items.map(item => {
29 | return (
30 |
31 |
32 |
33 | )
34 | })
35 | : (
36 |
37 | You don't have any products in the Cart
38 |
39 | )
40 | }
41 |
42 | );
43 | };
44 |
45 | const mapStateToProps = ({ cart }: ApplicationState) => ({
46 | cartItems: cart.data
47 | });
48 |
49 | export default connect(mapStateToProps)(CartComponent);
50 |
--------------------------------------------------------------------------------
/client/src/components/Navbar/__tests__/Navbar.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, fireEvent, screen } from 'test-utils';
3 | import { createBrowserHistory } from 'history';
4 | import Navbar from '../Navbar';
5 |
6 | const history = createBrowserHistory();
7 |
8 | // mock push function
9 | history.push = jest.fn();
10 |
11 | describe('Test Navbar Component', () => {
12 | it('can render without crashing', () => {
13 | render();
14 |
15 | expect(screen.getByText('Home')).toBeInTheDocument();
16 | expect(screen.getByText('Shop')).toBeInTheDocument();
17 | expect(screen.getByText('Cart')).toBeInTheDocument();
18 | });
19 |
20 | it('can navigate with the navbar to the shop', () => {
21 | render(, { history: history });
22 |
23 | fireEvent.click(screen.getByText('Shop'));
24 |
25 | // spy on push calls, assert on url (parameter)
26 | expect(history.push).toHaveBeenCalledWith({ "hash": "", "pathname": "/shop", "search": "", "state": null });
27 | });
28 |
29 | it('can navigate with the navbar to the cart', () => {
30 | render(, { history: history });
31 |
32 | fireEvent.click(screen.getByText('Cart'));
33 |
34 | // spy on push calls, assert on url (parameter)
35 | expect(history.push).toHaveBeenCalledWith({ "hash": "", "pathname": "/cart", "search": "", "state": null });
36 | });
37 |
38 | it('can navigate with the navbar to home', () => {
39 | render(, { history: history });
40 |
41 | fireEvent.click(screen.getByText('Home'));
42 |
43 | // spy on push calls, assert on url (parameter)
44 | expect(history.push).toHaveBeenCalledWith({ "hash": "", "pathname": "/", "search": "", "state": null });
45 | });
46 | })
47 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React/Express/Electron Boilerplate
28 |
29 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/client/src/components/Navbar/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { NavLink } from 'react-router-dom';
4 | import { AppBar, Theme, Toolbar, makeStyles, Grid } from '@material-ui/core';
5 |
6 | import { ApplicationState } from '../../store';
7 | import { Cart } from '../../store/cart/types';
8 |
9 | interface propsFromState {
10 | data: Cart;
11 | loading: boolean;
12 | errors?: string;
13 | }
14 |
15 | const useStyles = makeStyles((theme: Theme) => ({
16 | root: {
17 | flexGrow: 1,
18 | },
19 | item: {
20 | color: 'white',
21 | textDecoration: 'none',
22 | },
23 | centered: {
24 | textAlign: 'center',
25 | },
26 | activeLink: {
27 | color: 'darkblue',
28 | },
29 | }));
30 |
31 | const Navbar: React.FunctionComponent = ({ data, loading, errors, children }) => {
32 | const classes = useStyles();
33 |
34 | return (
35 |
36 |
37 |
38 |
39 |
40 | Home
41 |
42 |
43 | Shop
44 |
45 |
46 |
47 | Cart {data.items.length}
48 |
49 |
50 |
51 |
52 |
53 |
54 | );
55 | };
56 |
57 | const mapStateToProps = ({ cart }: ApplicationState) => ({
58 | data: cart.data,
59 | loading: cart.loading,
60 | errors: cart.errors
61 | });
62 |
63 | export default connect(mapStateToProps)(Navbar);
64 |
--------------------------------------------------------------------------------
/client/src/components/Shop/Shop.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { connect } from 'react-redux';
3 | import { Grid, Typography } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import ProductItem from '../ProductItem/ProductItem';
7 | import { ApplicationState } from '../../store';
8 | import { Inventory } from '../../store/inventory/types';
9 | import { fetchRequest } from '../../store/inventory/action';
10 |
11 | interface PropsFromState {
12 | loading: boolean;
13 | data: Inventory[];
14 | errors?: string;
15 | fetchRequest: () => any;
16 | }
17 |
18 | const useStyles = makeStyles({
19 | root: {
20 | overflow: 'auto',
21 | height: 'calc(100vh - 64px)',
22 | padding: 18,
23 | },
24 | });
25 |
26 | const Shop: React.FunctionComponent = ({
27 | loading,
28 | errors,
29 | data,
30 | fetchRequest
31 | }) => {
32 |
33 | useEffect(() => {
34 | fetchRequest();
35 | }, [fetchRequest]);
36 |
37 | const classes = useStyles();
38 | return (
39 |
40 | {
41 | data.length > 0 ?
42 | data.map(item => {
43 | return (
44 |
45 |
46 |
47 | )
48 | })
49 | : (
50 |
51 | You don't have any products in the Shop
52 |
53 | )
54 | }
55 |
56 | );
57 | };
58 |
59 | const mapStateToProps = ({ inventory }: ApplicationState) => ({
60 | loading: inventory.loading,
61 | errors: inventory.errors,
62 | data: inventory.data
63 | });
64 |
65 | const mapDispatchToProps = {
66 | fetchRequest
67 | };
68 |
69 | export default connect(mapStateToProps, mapDispatchToProps)(Shop);
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-express-electron-boilerplate
2 | A boilerplate to generate an Electron app with a React Front end and an Express BackEnd embedded.
3 |
4 | ## Setup
5 | After cloning the project run:
6 |
7 | yarn run install-all
8 |
9 | ## Development
10 | To run a development version of it run
11 |
12 | yarn run start-dev
13 |
14 | This will:
15 |
16 | 1. Start the React Application in the port 3000:
17 | You can open [http://localhost:3000](http://localhost:3000) to view it in the browser.
18 |
19 | 2. Start the ExpressJS Application in the port 3001:
20 | You can open [http://localhost:3001/products](http://localhost:3001/products) to see if it is running (should return a JSON of products).
21 |
22 | 3. Start the Electron App Automatically with DevTools open as default.
23 |
24 | ### IMPORTANT
25 | The app **WILL** reload if you make edits to the React App.
26 |
27 | The app **WILL NOT** reload if you make edits to the ExpressJS or Electron App.
28 |
29 | ## Testing
30 | To run a test of the whole application run
31 |
32 | yarn run test
33 |
34 | To run a test of the whole application with _coverage_ run
35 |
36 | yarn run test-coverage
37 |
38 | ### NOTE
39 | This will run tests of the React App and of the ExpressJS App
40 |
41 | For Coverage you will find the HTML Report in the following directories:
42 | - **React App**: `client/coverage/lcov-report/index.html`
43 | - **Express App**: `server/coverage/lcov-report/index.html`
44 |
45 | ## Production Build
46 | To run a production build, you have several options:
47 |
48 | 1. `yarn run build-all` This will create a package for MacOS, Windows and Linux.
49 | 2. `yarn run build-win` This will create a package for Windows.
50 | 3. `yarn run build-linux` This will create a package for Linux.
51 | 4. `yarn run build-mac` This will create a package for MacOS.
52 |
53 | ### NOTE
54 | The packages will be generated in the `dist` folder.
55 | You can also find the compiled files in the `build` directory (In case you want to use the React Build in another project or only for reviewing what will Electron use for the package).
56 |
--------------------------------------------------------------------------------
/client/src/test-utils.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render as rtlRender } from '@testing-library/react'
3 | import { createBrowserHistory } from 'history';
4 | import { Provider } from 'react-redux';
5 | import { ConnectedRouter } from 'connected-react-router';
6 | import { MuiThemeProvider } from '@material-ui/core';
7 |
8 | import theme from './theme';
9 | import configureStore from './configureStore';
10 |
11 | const initialHistory = createBrowserHistory();
12 |
13 | const products = [
14 | {
15 | id: 'test1',
16 | name: 'Test 1',
17 | description: 'Test Description 1',
18 | brand: 'Test Brand 1',
19 | price: 10,
20 | image: "",
21 | currentInventory: 1
22 | },
23 | {
24 | id: 'test2',
25 | name: 'Test 2',
26 | description: 'Test Description 2',
27 | brand: 'Test Brand 2',
28 | price: 20,
29 | image: "",
30 | currentInventory: 2
31 | }
32 | ];
33 |
34 | const initialStateData = {
35 | cart: {
36 | loading: false,
37 | errors: "",
38 | data: {
39 | id: 0,
40 | items: [products[0]]
41 | }
42 | },
43 | inventory: {
44 | loading: false,
45 | errors: "",
46 | data: products
47 | }
48 | }
49 |
50 | function render(
51 | ui,
52 | {
53 | initialState = initialStateData,
54 | history = initialHistory,
55 | store = configureStore(history, initialState),
56 | ...renderOptions
57 | } = {}
58 | ) {
59 | function Wrapper({ children }) {
60 | return (
61 |
62 |
63 |
64 | {children}
65 |
66 |
67 |
68 | )
69 | }
70 | return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
71 | }
72 |
73 | // re-export everything
74 | export * from '@testing-library/react'
75 |
76 | // override render method
77 | export { render }
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-express-electron-boilerplate",
3 | "version": "1.0.0",
4 | "description": "A boilerplate to generate an Electron app with a React Front end and an Express BackEnd embedded. (React Side)",
5 | "author": "Pablo Martinez",
6 | "license": "ISC",
7 | "scripts": {
8 | "react-start": "react-scripts start",
9 | "move-client": "mv build ../server/dist/client || move build ../server/dist/client",
10 | "react-build": "react-scripts build && yarn run move-client",
11 | "test": "react-scripts test --env=jsdom",
12 | "test-no-watch": "react-scripts test --env=jsdom --watchAll=false",
13 | "test-coverage": "react-scripts test --coverage --env=jsdom --watchAll=false",
14 | "start": "cross-env BROWSER=none yarn run react-start"
15 | },
16 | "jest": {
17 | "collectCoverageFrom": [
18 | "src/**/*.{js,jsx,ts,tsx}",
19 | "!/node_modules/",
20 | "!/src/index.tsx",
21 | "!/src/serviceWorker.ts",
22 | "!/src/App.tsx",
23 | "!/src/Routes.tsx",
24 | "!src/**/types.ts",
25 | "!src/**/*.d.ts"
26 | ],
27 | "coverageThreshold": {
28 | "global": {
29 | "branches": 75,
30 | "functions": 75,
31 | "lines": 75,
32 | "statements": 75
33 | }
34 | }
35 | },
36 | "eslintConfig": {
37 | "extends": "react-app"
38 | },
39 | "browserslist": {
40 | "production": [
41 | ">0.2%",
42 | "not dead",
43 | "not op_mini all"
44 | ],
45 | "development": [
46 | "last 1 chrome version",
47 | "last 1 firefox version",
48 | "last 1 safari version"
49 | ]
50 | },
51 | "homepage": "./",
52 | "devDependencies": {
53 | "@material-ui/core": "^4.11.0",
54 | "@material-ui/icons": "^4.9.1",
55 | "@testing-library/jest-dom": "^4.2.4",
56 | "@testing-library/react": "^9.3.2",
57 | "@testing-library/user-event": "^7.1.2",
58 | "@types/classnames": "^2.2.10",
59 | "@types/debug": "^4.1.5",
60 | "@types/history": "^4.7.7",
61 | "@types/jest": "^24.0.0",
62 | "@types/node": "^12.0.0",
63 | "@types/react": "^16.9.0",
64 | "@types/react-dom": "^16.9.0",
65 | "@types/react-redux": "^7.1.9",
66 | "@types/react-router-dom": "^5.1.5",
67 | "@types/redux": "^3.6.0",
68 | "classnames": "^2.2.6",
69 | "connected-react-router": "^6.8.0",
70 | "cross-env": "^7.0.2",
71 | "debug": "^4.1.1",
72 | "history": "4.10.1",
73 | "react": "^16.13.1",
74 | "react-dom": "^16.13.1",
75 | "react-redux": "^7.2.1",
76 | "react-router-dom": "^5.2.0",
77 | "react-scripts": "3.4.0",
78 | "redux": "^4.0.5",
79 | "redux-thunk": "^2.3.0",
80 | "typescript": "^3.9.7"
81 | },
82 | "dependencies": {
83 | "@types/axios": "^0.14.0",
84 | "axios": "^0.19.2"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-express-electron-boilerplate",
3 | "version": "1.0.0",
4 | "description": "A boilerplate to generate an Electron app with a React Front end and an Express BackEnd embedded. (Server side)",
5 | "main": "./dist/electron/ElectronStarter.js",
6 | "build": {
7 | "appId": "com.example-full.app",
8 | "productName": "Example Full App",
9 | "copyright": "Copyright © 2020 Pablo Martinez",
10 | "extends": null,
11 | "files": [
12 | "dist/**/*"
13 | ],
14 | "directories": {
15 | "buildResources": "../assets",
16 | "output": "./dist/packages"
17 | },
18 | "linux": {
19 | "category": "Development",
20 | "icon": "../assets/linux/icon.png"
21 | },
22 | "mac": {
23 | "category": "public.app-category.developer-tools",
24 | "icon": "../assets/mac/icon.png"
25 | },
26 | "win": {
27 | "icon": "../assets/win/icon.png"
28 | },
29 | "nsis": {
30 | "installerIcon": "../assets/win/logo.ico",
31 | "installerHeaderIcon": "../assets/win/logo.ico",
32 | "deleteAppDataOnUninstall": true
33 | }
34 | },
35 | "scripts": {
36 | "build": "tsc -p tsconfig-build.json && cp -r ./express/public ./dist/express",
37 | "remove-package-folder": "rm -rf dist/packages",
38 | "move-packages": "cp -r dist/packages/.[0-z]* dist/packages/* ../dist && rm -rf dist/packages/*",
39 | "move-build": "yarn run remove-package-folder && cp -r dist/* ../build && rm -rf dist/* dist/.[0-z]* && rm -rf dist",
40 | "electron-build-all": "yarn run electron-build-mac && yarn run electron-build-win && yarn run electron-build-linux && yarn run move-build",
41 | "electron-build-mac": "electron-builder -m && yarn run move-packages",
42 | "electron-build-win": "electron-builder -w && yarn run move-packages",
43 | "electron-build-linux": "electron-builder -l && yarn run move-packages",
44 | "electron-dev": "yarn run build && cross-env ELECTRON_START_URL=http://localhost:3000 electron --inspect ./dist/electron/ElectronStarter.js",
45 | "test": "mocha",
46 | "test-coverage": "nyc yarn run test"
47 | },
48 | "author": "Pablo Martinez",
49 | "license": "ISC",
50 | "devDependencies": {
51 | "@istanbuljs/nyc-config-typescript": "^1.0.1",
52 | "@types/body-parser": "^1.19.0",
53 | "@types/chai": "^4.2.12",
54 | "@types/cors": "^2.8.7",
55 | "@types/debug": "^4.1.5",
56 | "@types/electron": "^1.6.10",
57 | "@types/electron-devtools-installer": "^2.2.0",
58 | "@types/express": "^4.17.7",
59 | "@types/mocha": "^8.0.2",
60 | "@types/morgan": "^1.9.1",
61 | "@types/node": "^12.12.6",
62 | "chai": "^4.2.0",
63 | "chai-http": "^4.3.0",
64 | "cross-env": "^7.0.2",
65 | "electron": "^9.2.0",
66 | "electron-builder": "^22.8.0",
67 | "electron-devtools-installer": "^3.1.1",
68 | "mocha": "^8.1.1",
69 | "nyc": "^15.1.0",
70 | "source-map-support": "^0.5.19",
71 | "ts-node": "^8.10.2",
72 | "typescript": "^3.9.7"
73 | },
74 | "dependencies": {
75 | "body-parser": "^1.19.0",
76 | "cors": "^2.8.5",
77 | "debug": "^4.1.1",
78 | "electron-is-dev": "^1.2.0",
79 | "express": "^4.17.1",
80 | "morgan": "^1.10.0"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/client/src/components/ProductItem/ProductItem.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import {Button, Card, CardActions, CardContent, CardMedia, Grid, Typography} from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import { Inventory } from '../../store/inventory/types';
7 | import { addToCart, removeFromCart } from '../../store/cart/action';
8 |
9 | interface propsFromComponent {
10 | item: Inventory;
11 | cart?: boolean;
12 | addToCart: (item: any) => any;
13 | removeFromCart: (item: any) => any;
14 | }
15 |
16 | const useStyles = makeStyles({
17 | root: {
18 | maxWidth: 345,
19 | minHeight: 650,
20 | marginBottom: 16,
21 | display: 'flex',
22 | flexDirection: 'column',
23 | },
24 | image: {
25 | marginTop: 8,
26 | },
27 | actions: {
28 | marginTop: 'auto',
29 | marginBottom: 8,
30 | },
31 | });
32 |
33 | const ProductItem: React.FunctionComponent = ({ item, addToCart, cart = false, removeFromCart }) => {
34 | const classes = useStyles();
35 |
36 | const AddItemToCart = (item: any) => {
37 | addToCart(item);
38 | };
39 |
40 | const RemoveItemFromCart = (item: any) => {
41 | removeFromCart(item);
42 | };
43 |
44 | return (
45 |
46 |
54 |
55 |
56 | {item.name}
57 |
58 | Brand: {item.brand}
59 |
60 |
61 |
62 | {item.description}
63 |
64 |
65 |
66 |
67 |
68 |
69 | $ {parseFloat(item.price).toFixed(2)}
70 |
71 |
72 |
73 | {
74 | !cart ? (
75 |
78 | )
79 | : (
80 |
83 | )
84 | }
85 |
86 |
87 |
88 |
89 |
90 | );
91 | };
92 |
93 | const mapDispatchToProps = {
94 | addToCart,
95 | removeFromCart
96 | };
97 |
98 | export default connect(null, mapDispatchToProps)(ProductItem);
99 |
--------------------------------------------------------------------------------
/server/electron/ElectronStarter.ts:
--------------------------------------------------------------------------------
1 | import debug from 'debug';
2 | import Electron from 'electron';
3 | import isDev from 'electron-is-dev';
4 | import http from 'http';
5 | import path from 'path';
6 |
7 | import App from '../express/App';
8 |
9 | let logger: debug.Debugger;
10 |
11 | export default class Main {
12 | private static application: Electron.App;
13 | private static BrowserWindow: typeof Electron.BrowserWindow;
14 | private static mainWindow: Electron.BrowserWindow;
15 | private static port: string | number | boolean;
16 | private static server: http.Server;
17 |
18 | // if this variable is set to true in the main constructor, the app will quit when closing it in macOS
19 | private static quitOnCloseOSX: boolean;
20 |
21 | public static main(app: Electron.App, browserWindow: typeof Electron.BrowserWindow) {
22 | Main.BrowserWindow = browserWindow;
23 | Main.application = app;
24 | Main.application.on("window-all-closed", Main.onWindowAllClosed);
25 | Main.application.on("ready", Main.onReady);
26 | Main.application.on("activate", Main.onActivate);
27 | Main.quitOnCloseOSX = true;
28 | Main.bootServer();
29 | }
30 |
31 | private static onReady() {
32 | // development
33 | if (isDev) {
34 | const { default: installExtension, REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } = require('electron-devtools-installer');
35 | // extensions
36 | installExtension([REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS])
37 | .then((name: string) => logger.log(`Added Extension: ${name}`))
38 | .catch((err: any) => logger.log('An error occurred: ', err));
39 | }
40 |
41 | Main.mainWindow = new Main.BrowserWindow({
42 | width: 800,
43 | height: 600,
44 | webPreferences: {
45 | nodeIntegration: false,
46 | worldSafeExecuteJavaScript: true,
47 | contextIsolation: true,
48 | preload: path.join(__dirname, 'Preload.js')
49 | }
50 | });
51 | const startUrl = process.env.ELECTRON_START_URL || `file://${path.join(__dirname, '../client/index.html')}`;
52 | Main.mainWindow.loadURL(startUrl);
53 |
54 | // development
55 | if (isDev) {
56 | Main.mainWindow.webContents.openDevTools();
57 | }
58 |
59 | Main.mainWindow.on("closed", Main.onClose);
60 | }
61 |
62 | private static onWindowAllClosed() {
63 | if (process.platform !== "darwin" || Main.quitOnCloseOSX) {
64 | Main.application.quit();
65 | }
66 | }
67 |
68 | private static onActivate() {
69 | if (Main.mainWindow === null) {
70 | Main.onReady();
71 | }
72 | }
73 |
74 | private static onClose() {
75 | // Dereference the window object.
76 | Main.mainWindow = null;
77 | }
78 |
79 | private static bootServer() {
80 | // logger
81 | logger = debug("server");
82 | logger.log = console.log.bind(console);
83 |
84 | if (isDev) {
85 | debug.enable("server");
86 | }
87 |
88 | Main.port = Main.normalizePort(process.env.PORT || 3001);
89 | App.set("port", Main.port);
90 |
91 | Main.server = http.createServer(App);
92 | Main.server.listen(Main.port);
93 | Main.server.on("error", Main.onError);
94 | Main.server.on("listening", Main.onListening);
95 | }
96 |
97 | private static normalizePort(val: number|string): number|string|boolean {
98 | const port: number = (typeof val === "string") ? parseInt(val, 10) : val;
99 | if (isNaN(port)) {
100 | return val;
101 | } else if (port >= 0) {
102 | return port;
103 | } else {
104 | return false;
105 | }
106 | }
107 |
108 | private static onError(error: NodeJS.ErrnoException): void {
109 | if (error.syscall !== "listen") {
110 | throw error;
111 | }
112 | const bind = (typeof Main.port === "string") ? "Pipe " + Main.port : "Port " + Main.port;
113 | switch (error.code) {
114 | case "EACCES":
115 | // tslint:disable-next-line:no-console
116 | console.error(`${bind} requires elevated privileges`);
117 | process.exit(1);
118 | break;
119 | case "EADDRINUSE":
120 | // tslint:disable-next-line:no-console
121 | console.error(`${bind} is already in use`);
122 | process.exit(1);
123 | break;
124 | default:
125 | throw error;
126 | }
127 | }
128 |
129 | private static onListening(): void {
130 | const addr = Main.server.address();
131 | const bind = (typeof addr === "string") ? `pipe ${addr}` : `port ${addr.port}`;
132 | logger.log(`Listening on ${bind}`);
133 | }
134 | }
135 |
136 | Main.main(Electron.app, Electron.BrowserWindow);
--------------------------------------------------------------------------------
/client/src/serviceWorker.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | type Config = {
24 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
25 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
26 | };
27 |
28 | export function register(config?: Config) {
29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
30 | // The URL constructor is available in all browsers that support SW.
31 | const publicUrl = new URL(
32 | process.env.PUBLIC_URL,
33 | window.location.href
34 | );
35 | if (publicUrl.origin !== window.location.origin) {
36 | // Our service worker won't work if PUBLIC_URL is on a different origin
37 | // from what our page is served on. This might happen if a CDN is used to
38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
39 | return;
40 | }
41 |
42 | window.addEventListener('load', () => {
43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
44 |
45 | if (isLocalhost) {
46 | // This is running on localhost. Let's check if a service worker still exists or not.
47 | checkValidServiceWorker(swUrl, config);
48 |
49 | // Add some additional logging to localhost, pointing developers to the
50 | // service worker/PWA documentation.
51 | navigator.serviceWorker.ready.then(() => {
52 | console.log(
53 | 'This web app is being served cache-first by a service ' +
54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
55 | );
56 | });
57 | } else {
58 | // Is not localhost. Just register service worker
59 | registerValidSW(swUrl, config);
60 | }
61 | });
62 | }
63 | }
64 |
65 | function registerValidSW(swUrl: string, config?: Config) {
66 | navigator.serviceWorker
67 | .register(swUrl)
68 | .then(registration => {
69 | registration.onupdatefound = () => {
70 | const installingWorker = registration.installing;
71 | if (installingWorker == null) {
72 | return;
73 | }
74 | installingWorker.onstatechange = () => {
75 | if (installingWorker.state === 'installed') {
76 | if (navigator.serviceWorker.controller) {
77 | // At this point, the updated precached content has been fetched,
78 | // but the previous service worker will still serve the older
79 | // content until all client tabs are closed.
80 | console.log(
81 | 'New content is available and will be used when all ' +
82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
83 | );
84 |
85 | // Execute callback
86 | if (config && config.onUpdate) {
87 | config.onUpdate(registration);
88 | }
89 | } else {
90 | // At this point, everything has been precached.
91 | // It's the perfect time to display a
92 | // "Content is cached for offline use." message.
93 | console.log('Content is cached for offline use.');
94 |
95 | // Execute callback
96 | if (config && config.onSuccess) {
97 | config.onSuccess(registration);
98 | }
99 | }
100 | }
101 | };
102 | };
103 | })
104 | .catch(error => {
105 | console.error('Error during service worker registration:', error);
106 | });
107 | }
108 |
109 | function checkValidServiceWorker(swUrl: string, config?: Config) {
110 | // Check if the service worker can be found. If it can't reload the page.
111 | fetch(swUrl, {
112 | headers: { 'Service-Worker': 'script' }
113 | })
114 | .then(response => {
115 | // Ensure service worker exists, and that we really are getting a JS file.
116 | const contentType = response.headers.get('content-type');
117 | if (
118 | response.status === 404 ||
119 | (contentType != null && contentType.indexOf('javascript') === -1)
120 | ) {
121 | // No service worker found. Probably a different app. Reload the page.
122 | navigator.serviceWorker.ready.then(registration => {
123 | registration.unregister().then(() => {
124 | window.location.reload();
125 | });
126 | });
127 | } else {
128 | // Service worker found. Proceed as normal.
129 | registerValidSW(swUrl, config);
130 | }
131 | })
132 | .catch(() => {
133 | console.log(
134 | 'No internet connection found. App is running in offline mode.'
135 | );
136 | });
137 | }
138 |
139 | export function unregister() {
140 | if ('serviceWorker' in navigator) {
141 | navigator.serviceWorker.ready
142 | .then(registration => {
143 | registration.unregister();
144 | })
145 | .catch(error => {
146 | console.error(error.message);
147 | });
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/server/express/data/products.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Timber Gray Sofa",
4 | "price": "1000",
5 | "image": "http://localhost:3001/images/products/couch1.png",
6 | "description": "This is a Test Description",
7 | "brand": "Jason Bourne",
8 | "currentInventory": 4,
9 | "id": "fb94f208-6d34-425f-a3f8-e5b87794aef1"
10 | },
11 | {
12 | "name": "Carmel Brown Sofa",
13 | "price": "1000",
14 | "image": "http://localhost:3001/images/products/couch5.png",
15 | "description": "This is a test description",
16 | "brand": "Jason Bourne",
17 | "currentInventory": 2,
18 | "id": "4c95788a-1fa2-4f5c-ab97-7a98c1862584"
19 | },
20 | {
21 | "name": "Mod Leather Sofa",
22 | "price": "800",
23 | "image": "http://localhost:3001/images/products/couch6.png",
24 | "description":
25 | "Easy to love. The Sven in birch ivory looks cozy and refined, like a sweater that a fancy lady wears on a coastal vacation. This ivory loveseat has a tufted bench seat, loose back pillows and bolsters, solid walnut legs, and is ready to make your apartment the adult oasis you dream of. Nestle it with plants, an ottoman, an accent chair, or 8 dogs. Your call.",
26 | "brand": "Jason Bourne",
27 | "currentInventory": 8,
28 | "id": "8338db8c-91c5-4341-81e1-fa350c6baa70"
29 | },
30 | {
31 | "name": "Thetis Gray Love Seat",
32 | "price": "900",
33 | "image": "http://localhost:3001/images/products/couch7.png",
34 | "description":
35 | "You know your dad’s incredible vintage bomber jacket? The Nirvana dakota tan leather sofa is that jacket, but in couch form. With super-plush down-filled cushions, a corner-blocked wooden frame, and a leather patina that only gets better with age, the Nirvana will have you looking cool and feeling peaceful every time you take a seat. Looks pretty great with a sheepskinthrow, if we may say so. With use, this leather will become softer and more wrinkled and the cushions will take on a lived-inlook, like your favorite leather jacket.",
36 | "brand": "Jason Bourne",
37 | "currentInventory": 10,
38 | "id": "d414f81e-0f34-49ca-8fb6-a4b47f622eb9"
39 | },
40 | {
41 | "name": "Sven Tan Matte",
42 | "price": "1200",
43 | "image": "http://localhost:3001/images/products/couch8.png",
44 | "description":
45 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
46 | "brand": "Jason Bourne",
47 | "currentInventory": 7,
48 | "id": "f193961b-7716-48f9-9c81-8720224dccbf"
49 | },
50 | {
51 | "name": "Otis Malt Sofa",
52 | "price": "500",
53 | "image": "http://localhost:3001/images/products/couch9.png",
54 | "description":
55 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
56 | "brand": "Jason Bourne",
57 | "currentInventory": 13,
58 | "id": "7bbdc630-ff67-4897-ba43-1bcc7919fc6c"
59 | },
60 | {
61 | "name": "Ceni Brown 3 Seater",
62 | "price": "650",
63 | "image": "http://localhost:3001/images/products/couch10.png",
64 | "description":
65 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
66 | "brand": "Jason Bourne",
67 | "currentInventory": 9,
68 | "id": "4b7c67b3-0c65-47a4-b7e9-b99f07dfabc2"
69 | },
70 | {
71 | "name": "Jameson Jack Lounger",
72 | "price": "1230",
73 | "image": "http://localhost:3001/images/products/couch11.png",
74 | "description":
75 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
76 | "brand": "Jason Bourne",
77 | "currentInventory": 24,
78 | "id": "e31876fe-34fb-4721-a6ac-7fc3938a352e"
79 | },
80 | {
81 | "name": "Galaxy Blue Sofa",
82 | "price": "800",
83 | "image": "http://localhost:3001/images/products/couch2.png",
84 | "description":
85 | "Easy to love. The Sven in birch ivory looks cozy and refined, like a sweater that a fancy lady wears on a coastal vacation. This ivory loveseat has a tufted bench seat, loose back pillows and bolsters, solid walnut legs, and is ready to make your apartment the adult oasis you dream of. Nestle it with plants, an ottoman, an accent chair, or 8 dogs. Your call.",
86 | "brand": "Jason Bourne",
87 | "currentInventory": 43,
88 | "id": "e60a4f85-4899-431d-816d-72134cae07a0"
89 | },
90 | {
91 | "name": "Markus Green Love Seat",
92 | "price": "900",
93 | "image": "http://localhost:3001/images/products/couch3.png",
94 | "description":
95 | "You know your dad’s incredible vintage bomber jacket? The Nirvana dakota tan leather sofa is that jacket, but in couch form. With super-plush down-filled cushions, a corner-blocked wooden frame, and a leather patina that only gets better with age, the Nirvana will have you looking cool and feeling peaceful every time you take a seat. Looks pretty great with a sheepskinthrow, if we may say so. With use, this leather will become softer and more wrinkled and the cushions will take on a lived-inlook, like your favorite leather jacket.",
96 | "brand": "Jason Bourne",
97 | "currentInventory": 2,
98 | "id": "69cdde2b-17f3-411c-a5af-7c552fc6648e"
99 | },
100 | {
101 | "name": "Dabit Matte Black",
102 | "price": "1200",
103 | "image": "http://localhost:3001/images/products/couch4.png",
104 | "description":
105 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
106 | "currentInventory": 14,
107 | "id": "0008c6dd-725a-4eac-b13e-795808cfeedc"
108 | },
109 | {
110 | "name": "Embrace Blue",
111 | "price": "300",
112 | "image": "http://localhost:3001/images/products/chair1.png",
113 | "description":
114 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
115 | "brand": "Jason Bourne",
116 | "currentInventory": 12,
117 | "id": "8baefb38-6ff4-462d-880a-b192f972d939"
118 | },
119 | {
120 | "name": "Nord Lounger",
121 | "price": "825",
122 | "image": "http://localhost:3001/images/products/chair2.png",
123 | "description":
124 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
125 | "brand": "Jason Bourne",
126 | "currentInventory": 13,
127 | "id": "91067165-7135-4310-b5f9-e99eb2b7410e"
128 | },
129 | {
130 | "name": "Ceni Matte Oranve",
131 | "price": "720",
132 | "image": "http://localhost:3001/images/products/chair3.png",
133 | "description":
134 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
135 | "brand": "Jason Bourne",
136 | "currentInventory": 33,
137 | "id": "ab149e2a-7421-4012-b341-1309f8bdc417"
138 | },
139 | {
140 | "name": "Abisko Green Recliner",
141 | "price": "2000",
142 | "image": "http://localhost:3001/images/products/chair4.png",
143 | "description":
144 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
145 | "brand": "Jason Bourne",
146 | "currentInventory": 23,
147 | "id": "8eac7c82-e095-49a3-b4f7-c2df518ca1d4"
148 | },
149 | {
150 | "name": "Denim on Denim Single",
151 | "price": "1100",
152 | "image": "http://localhost:3001/images/products/chair5.png",
153 | "description":
154 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
155 | "brand": "Jason Bourne",
156 | "currentInventory": 13,
157 | "id": "e2b84bfb-2b60-4b82-b899-fe9483d5d2e8"
158 | },
159 | {
160 | "name": "Levo Tan Lounge Chair",
161 | "price": "600",
162 | "image": "http://localhost:3001/images/products/chair6.png",
163 | "description":
164 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
165 | "brand": "Jason Bourne",
166 | "currentInventory": 15,
167 | "id": "994d33fb-7ee3-43a6-ae51-1687f9cd7c15"
168 | },
169 | {
170 | "name": "Anime Tint Recliner",
171 | "price": "775",
172 | "image": "http://localhost:3001/images/products/chair7.png",
173 | "description":
174 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
175 | "brand": "Jason Bourne",
176 | "currentInventory": 44,
177 | "id": "7f390c3f-f59b-4dfd-9d79-03e6f5d3c096"
178 | },
179 | {
180 | "name": "Josh Jones Red Chair",
181 | "price": "1200",
182 | "image": "http://localhost:3001/images/products/chair8.png",
183 | "description":
184 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
185 | "brand": "Jason Bourne",
186 | "currentInventory": 17,
187 | "id": "e6995bf1-3a50-4934-8777-22c2043fc3f9"
188 | },
189 | {
190 | "name": "Black Sand Lounge",
191 | "price": "1600",
192 | "image": "http://localhost:3001/images/products/chair9.png",
193 | "description":
194 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
195 | "brand": "Jason Bourne",
196 | "currentInventory": 28,
197 | "id": "77b98369-c6e3-4ebf-86a3-8d0e52024631"
198 | },
199 | {
200 | "name": "Mint Beige Workchair",
201 | "price": "550",
202 | "image": "http://localhost:3001/images/products/chair10.png",
203 | "description":
204 | "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and rawseams for that Malboro-person look. This brown leather sofa iscozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.",
205 | "brand": "Jason Bourne",
206 | "currentInventory": 31,
207 | "id": "af65a773-9243-4e9e-b192-5eb3b22b0571"
208 | }
209 | ]
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@hapi/address@^4.1.0":
6 | version "4.1.0"
7 | resolved "https://registry.yarnpkg.com/@hapi/address/-/address-4.1.0.tgz#d60c5c0d930e77456fdcde2598e77302e2955e1d"
8 | integrity sha512-SkszZf13HVgGmChdHo/PxchnSaCJ6cetVqLzyciudzZRT0jcOouIF/Q93mgjw8cce+D+4F4C1Z/WrfFN+O3VHQ==
9 | dependencies:
10 | "@hapi/hoek" "^9.0.0"
11 |
12 | "@hapi/formula@^2.0.0":
13 | version "2.0.0"
14 | resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-2.0.0.tgz#edade0619ed58c8e4f164f233cda70211e787128"
15 | integrity sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==
16 |
17 | "@hapi/hoek@^9.0.0":
18 | version "9.0.4"
19 | resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.0.4.tgz#e80ad4e8e8d2adc6c77d985f698447e8628b6010"
20 | integrity sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==
21 |
22 | "@hapi/pinpoint@^2.0.0":
23 | version "2.0.0"
24 | resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.0.tgz#805b40d4dbec04fc116a73089494e00f073de8df"
25 | integrity sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==
26 |
27 | "@hapi/topo@^5.0.0":
28 | version "5.0.0"
29 | resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7"
30 | integrity sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==
31 | dependencies:
32 | "@hapi/hoek" "^9.0.0"
33 |
34 | ansi-regex@^4.1.0:
35 | version "4.1.0"
36 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
37 | integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
38 |
39 | ansi-styles@^3.2.0, ansi-styles@^3.2.1:
40 | version "3.2.1"
41 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
42 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
43 | dependencies:
44 | color-convert "^1.9.0"
45 |
46 | axios@^0.19.2:
47 | version "0.19.2"
48 | resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
49 | integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
50 | dependencies:
51 | follow-redirects "1.5.10"
52 |
53 | camelcase@^5.0.0:
54 | version "5.3.1"
55 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
56 | integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
57 |
58 | chalk@^2.4.2:
59 | version "2.4.2"
60 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
61 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
62 | dependencies:
63 | ansi-styles "^3.2.1"
64 | escape-string-regexp "^1.0.5"
65 | supports-color "^5.3.0"
66 |
67 | cliui@^5.0.0:
68 | version "5.0.0"
69 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
70 | integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
71 | dependencies:
72 | string-width "^3.1.0"
73 | strip-ansi "^5.2.0"
74 | wrap-ansi "^5.1.0"
75 |
76 | color-convert@^1.9.0:
77 | version "1.9.3"
78 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
79 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
80 | dependencies:
81 | color-name "1.1.3"
82 |
83 | color-name@1.1.3:
84 | version "1.1.3"
85 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
86 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
87 |
88 | concurrently@^5.3.0:
89 | version "5.3.0"
90 | resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.3.0.tgz#7500de6410d043c912b2da27de3202cb489b1e7b"
91 | integrity sha512-8MhqOB6PWlBfA2vJ8a0bSFKATOdWlHiQlk11IfmQBPaHVP8oP2gsh2MObE6UR3hqDHqvaIvLTyceNW6obVuFHQ==
92 | dependencies:
93 | chalk "^2.4.2"
94 | date-fns "^2.0.1"
95 | lodash "^4.17.15"
96 | read-pkg "^4.0.1"
97 | rxjs "^6.5.2"
98 | spawn-command "^0.0.2-1"
99 | supports-color "^6.1.0"
100 | tree-kill "^1.2.2"
101 | yargs "^13.3.0"
102 |
103 | date-fns@^2.0.1:
104 | version "2.15.0"
105 | resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.15.0.tgz#424de6b3778e4e69d3ff27046ec136af58ae5d5f"
106 | integrity sha512-ZCPzAMJZn3rNUvvQIMlXhDr4A+Ar07eLeGsGREoWU19a3Pqf5oYa+ccd+B3F6XVtQY6HANMFdOQ8A+ipFnvJdQ==
107 |
108 | debug@=3.1.0:
109 | version "3.1.0"
110 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
111 | integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
112 | dependencies:
113 | ms "2.0.0"
114 |
115 | decamelize@^1.2.0:
116 | version "1.2.0"
117 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
118 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
119 |
120 | emoji-regex@^7.0.1:
121 | version "7.0.3"
122 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
123 | integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
124 |
125 | error-ex@^1.3.1:
126 | version "1.3.2"
127 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
128 | integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
129 | dependencies:
130 | is-arrayish "^0.2.1"
131 |
132 | escape-string-regexp@^1.0.5:
133 | version "1.0.5"
134 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
135 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
136 |
137 | find-up@^3.0.0:
138 | version "3.0.0"
139 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
140 | integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
141 | dependencies:
142 | locate-path "^3.0.0"
143 |
144 | follow-redirects@1.5.10:
145 | version "1.5.10"
146 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
147 | integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
148 | dependencies:
149 | debug "=3.1.0"
150 |
151 | get-caller-file@^2.0.1:
152 | version "2.0.5"
153 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
154 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
155 |
156 | has-flag@^3.0.0:
157 | version "3.0.0"
158 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
159 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
160 |
161 | hosted-git-info@^2.1.4:
162 | version "2.8.8"
163 | resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
164 | integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
165 |
166 | is-arrayish@^0.2.1:
167 | version "0.2.1"
168 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
169 | integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
170 |
171 | is-fullwidth-code-point@^2.0.0:
172 | version "2.0.0"
173 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
174 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
175 |
176 | joi@^17.1.1:
177 | version "17.2.0"
178 | resolved "https://registry.yarnpkg.com/joi/-/joi-17.2.0.tgz#81cba6c1145130482d57b6d50129c7ab0e7d8b0a"
179 | integrity sha512-9ZC8pMSitNlenuwKARENBGVvvGYHNlwWe5rexo2WxyogaxCB5dNHAgFA1BJQ6nsJrt/jz1p5vSqDT6W6kciDDw==
180 | dependencies:
181 | "@hapi/address" "^4.1.0"
182 | "@hapi/formula" "^2.0.0"
183 | "@hapi/hoek" "^9.0.0"
184 | "@hapi/pinpoint" "^2.0.0"
185 | "@hapi/topo" "^5.0.0"
186 |
187 | json-parse-better-errors@^1.0.1:
188 | version "1.0.2"
189 | resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
190 | integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
191 |
192 | locate-path@^3.0.0:
193 | version "3.0.0"
194 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
195 | integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
196 | dependencies:
197 | p-locate "^3.0.0"
198 | path-exists "^3.0.0"
199 |
200 | lodash@^4.17.15, lodash@^4.17.19:
201 | version "4.17.20"
202 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
203 | integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
204 |
205 | minimist@^1.2.5:
206 | version "1.2.5"
207 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
208 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
209 |
210 | ms@2.0.0:
211 | version "2.0.0"
212 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
213 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
214 |
215 | normalize-package-data@^2.3.2:
216 | version "2.5.0"
217 | resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
218 | integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
219 | dependencies:
220 | hosted-git-info "^2.1.4"
221 | resolve "^1.10.0"
222 | semver "2 || 3 || 4 || 5"
223 | validate-npm-package-license "^3.0.1"
224 |
225 | p-limit@^2.0.0:
226 | version "2.3.0"
227 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
228 | integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
229 | dependencies:
230 | p-try "^2.0.0"
231 |
232 | p-locate@^3.0.0:
233 | version "3.0.0"
234 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
235 | integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
236 | dependencies:
237 | p-limit "^2.0.0"
238 |
239 | p-try@^2.0.0:
240 | version "2.2.0"
241 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
242 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
243 |
244 | parse-json@^4.0.0:
245 | version "4.0.0"
246 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
247 | integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
248 | dependencies:
249 | error-ex "^1.3.1"
250 | json-parse-better-errors "^1.0.1"
251 |
252 | path-exists@^3.0.0:
253 | version "3.0.0"
254 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
255 | integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
256 |
257 | path-parse@^1.0.6:
258 | version "1.0.6"
259 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
260 | integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
261 |
262 | pify@^3.0.0:
263 | version "3.0.0"
264 | resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
265 | integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
266 |
267 | read-pkg@^4.0.1:
268 | version "4.0.1"
269 | resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237"
270 | integrity sha1-ljYlN48+HE1IyFhytabsfV0JMjc=
271 | dependencies:
272 | normalize-package-data "^2.3.2"
273 | parse-json "^4.0.0"
274 | pify "^3.0.0"
275 |
276 | require-directory@^2.1.1:
277 | version "2.1.1"
278 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
279 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
280 |
281 | require-main-filename@^2.0.0:
282 | version "2.0.0"
283 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
284 | integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
285 |
286 | resolve@^1.10.0:
287 | version "1.17.0"
288 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
289 | integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
290 | dependencies:
291 | path-parse "^1.0.6"
292 |
293 | rxjs@^6.5.2, rxjs@^6.5.5:
294 | version "6.6.2"
295 | resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2"
296 | integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==
297 | dependencies:
298 | tslib "^1.9.0"
299 |
300 | "semver@2 || 3 || 4 || 5":
301 | version "5.7.1"
302 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
303 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
304 |
305 | set-blocking@^2.0.0:
306 | version "2.0.0"
307 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
308 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
309 |
310 | spawn-command@^0.0.2-1:
311 | version "0.0.2-1"
312 | resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
313 | integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=
314 |
315 | spdx-correct@^3.0.0:
316 | version "3.1.1"
317 | resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
318 | integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==
319 | dependencies:
320 | spdx-expression-parse "^3.0.0"
321 | spdx-license-ids "^3.0.0"
322 |
323 | spdx-exceptions@^2.1.0:
324 | version "2.3.0"
325 | resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
326 | integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
327 |
328 | spdx-expression-parse@^3.0.0:
329 | version "3.0.1"
330 | resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
331 | integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
332 | dependencies:
333 | spdx-exceptions "^2.1.0"
334 | spdx-license-ids "^3.0.0"
335 |
336 | spdx-license-ids@^3.0.0:
337 | version "3.0.5"
338 | resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654"
339 | integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==
340 |
341 | string-width@^3.0.0, string-width@^3.1.0:
342 | version "3.1.0"
343 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
344 | integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
345 | dependencies:
346 | emoji-regex "^7.0.1"
347 | is-fullwidth-code-point "^2.0.0"
348 | strip-ansi "^5.1.0"
349 |
350 | strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
351 | version "5.2.0"
352 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
353 | integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
354 | dependencies:
355 | ansi-regex "^4.1.0"
356 |
357 | supports-color@^5.3.0:
358 | version "5.5.0"
359 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
360 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
361 | dependencies:
362 | has-flag "^3.0.0"
363 |
364 | supports-color@^6.1.0:
365 | version "6.1.0"
366 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
367 | integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
368 | dependencies:
369 | has-flag "^3.0.0"
370 |
371 | tree-kill@^1.2.2:
372 | version "1.2.2"
373 | resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
374 | integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
375 |
376 | tslib@^1.9.0:
377 | version "1.13.0"
378 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
379 | integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
380 |
381 | validate-npm-package-license@^3.0.1:
382 | version "3.0.4"
383 | resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
384 | integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
385 | dependencies:
386 | spdx-correct "^3.0.0"
387 | spdx-expression-parse "^3.0.0"
388 |
389 | wait-on@^5.2.0:
390 | version "5.2.0"
391 | resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-5.2.0.tgz#6711e74422523279714a36d52cf49fb47c9d9597"
392 | integrity sha512-U1D9PBgGw2XFc6iZqn45VBubw02VsLwnZWteQ1au4hUVHasTZuFSKRzlTB2dqgLhji16YVI8fgpEpwUdCr8B6g==
393 | dependencies:
394 | axios "^0.19.2"
395 | joi "^17.1.1"
396 | lodash "^4.17.19"
397 | minimist "^1.2.5"
398 | rxjs "^6.5.5"
399 |
400 | which-module@^2.0.0:
401 | version "2.0.0"
402 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
403 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
404 |
405 | wrap-ansi@^5.1.0:
406 | version "5.1.0"
407 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
408 | integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
409 | dependencies:
410 | ansi-styles "^3.2.0"
411 | string-width "^3.0.0"
412 | strip-ansi "^5.0.0"
413 |
414 | y18n@^4.0.0:
415 | version "4.0.0"
416 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
417 | integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
418 |
419 | yargs-parser@^13.1.2:
420 | version "13.1.2"
421 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
422 | integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
423 | dependencies:
424 | camelcase "^5.0.0"
425 | decamelize "^1.2.0"
426 |
427 | yargs@^13.3.0:
428 | version "13.3.2"
429 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
430 | integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
431 | dependencies:
432 | cliui "^5.0.0"
433 | find-up "^3.0.0"
434 | get-caller-file "^2.0.1"
435 | require-directory "^2.1.1"
436 | require-main-filename "^2.0.0"
437 | set-blocking "^2.0.0"
438 | string-width "^3.0.0"
439 | which-module "^2.0.0"
440 | y18n "^4.0.0"
441 | yargs-parser "^13.1.2"
442 |
--------------------------------------------------------------------------------