├── .gitignore
├── LICENSE.md
├── README.md
├── chapter-10
├── .babel-plugin-macrosrc.js
├── .env.local
├── .env.production
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── README.md
├── cypress.json
├── cypress
│ ├── fixtures
│ │ ├── example.json
│ │ ├── profile.json
│ │ └── users.json
│ ├── integration
│ │ └── examples
│ │ │ ├── actions.spec.js
│ │ │ ├── aliasing.spec.js
│ │ │ ├── assertions.spec.js
│ │ │ ├── connectors.spec.js
│ │ │ ├── cookies.spec.js
│ │ │ ├── cypress_api.spec.js
│ │ │ ├── files.spec.js
│ │ │ ├── local_storage.spec.js
│ │ │ ├── location.spec.js
│ │ │ ├── misc.spec.js
│ │ │ ├── navigation.spec.js
│ │ │ ├── network_requests.spec.js
│ │ │ ├── querying.spec.js
│ │ │ ├── spies_stubs_clocks.spec.js
│ │ │ ├── traversal.spec.js
│ │ │ ├── utilities.spec.js
│ │ │ ├── viewport.spec.js
│ │ │ ├── waiting.spec.js
│ │ │ └── window.spec.js
│ ├── plugins
│ │ └── index.js
│ ├── screenshots
│ │ └── All Integration Specs
│ │ │ └── my-image.png
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── db.json
├── internals
│ ├── generators
│ │ ├── component
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ └── loadable.ts.hbs
│ │ ├── container
│ │ │ ├── appendRootState.hbs
│ │ │ ├── importContainerState.hbs
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ ├── loadable.ts.hbs
│ │ │ ├── saga.ts.hbs
│ │ │ ├── selectors.ts.hbs
│ │ │ ├── slice.ts.hbs
│ │ │ └── types.ts.hbs
│ │ ├── plopfile.ts
│ │ └── utils
│ │ │ └── index.ts
│ ├── testing
│ │ ├── loadable.mock.tsx
│ │ └── test-generators.ts
│ └── ts-node.tsconfig.json
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── images
│ │ └── products
│ │ │ └── add_file.svg
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── api
│ │ └── axios.ts
│ ├── app
│ │ ├── __tests__
│ │ │ └── index.test.tsx
│ │ ├── components
│ │ │ ├── files-dropzone.tsx
│ │ │ ├── label.tsx
│ │ │ ├── page.tsx
│ │ │ └── quill-editor.tsx
│ │ ├── index.tsx
│ │ ├── layouts
│ │ │ ├── dashboard-layout
│ │ │ │ ├── dashboard-sidebar-navigation.tsx
│ │ │ │ └── index.tsx
│ │ │ └── main-layout
│ │ │ │ ├── index.tsx
│ │ │ │ └── navigation-bar.tsx
│ │ ├── routes.tsx
│ │ └── views
│ │ │ ├── dashboard
│ │ │ ├── calendar
│ │ │ │ └── CalendarView
│ │ │ │ │ ├── AddEditEventForm.tsx
│ │ │ │ │ ├── Header.tsx
│ │ │ │ │ ├── Toolbar.tsx
│ │ │ │ │ └── index.tsx
│ │ │ ├── dashboard-default-content.tsx
│ │ │ └── product
│ │ │ │ ├── ProductCreateView
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── ProductCreateForm.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── schema
│ │ │ │ │ ├── productDefaultValue.ts
│ │ │ │ │ └── yupProductValidation.ts
│ │ │ │ └── ProductListView
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── Results.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── tableResultsHelpers.tsx
│ │ │ └── pages
│ │ │ ├── AboutPage.tsx
│ │ │ ├── HomePage.tsx
│ │ │ └── NotFoundPage.tsx
│ ├── features
│ │ └── calendar
│ │ │ └── calendarSlice.ts
│ ├── helpers
│ │ └── inputProductOptions.ts
│ ├── index.tsx
│ ├── locales
│ │ ├── __tests__
│ │ │ └── i18n.test.ts
│ │ ├── en
│ │ │ └── translation.json
│ │ ├── i18n.ts
│ │ └── types.ts
│ ├── models
│ │ ├── calendar-type.ts
│ │ ├── product-type.ts
│ │ └── sale-type.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ ├── services
│ │ ├── productService.ts
│ │ └── saleService.ts
│ ├── setupTests.ts
│ ├── store
│ │ ├── configureStore.ts
│ │ └── reducers.ts
│ ├── styles
│ │ ├── __tests__
│ │ │ └── media.test.ts
│ │ ├── global-styles.ts
│ │ └── media.ts
│ └── utils
│ │ └── bytes-to-size.ts
└── tsconfig.json
├── chapter-11
├── .babel-plugin-macrosrc.js
├── .env.local
├── .env.production
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── README.md
├── cypress.json
├── cypress
│ ├── fixtures
│ │ ├── example.json
│ │ ├── profile.json
│ │ └── users.json
│ ├── integration
│ │ └── examples
│ │ │ ├── actions.spec.js
│ │ │ ├── aliasing.spec.js
│ │ │ ├── assertions.spec.js
│ │ │ ├── connectors.spec.js
│ │ │ ├── cookies.spec.js
│ │ │ ├── cypress_api.spec.js
│ │ │ ├── files.spec.js
│ │ │ ├── local_storage.spec.js
│ │ │ ├── location.spec.js
│ │ │ ├── misc.spec.js
│ │ │ ├── navigation.spec.js
│ │ │ ├── network_requests.spec.js
│ │ │ ├── querying.spec.js
│ │ │ ├── spies_stubs_clocks.spec.js
│ │ │ ├── traversal.spec.js
│ │ │ ├── utilities.spec.js
│ │ │ ├── viewport.spec.js
│ │ │ ├── waiting.spec.js
│ │ │ └── window.spec.js
│ ├── plugins
│ │ └── index.js
│ ├── screenshots
│ │ └── All Integration Specs
│ │ │ └── my-image.png
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── db.json
├── internals
│ ├── generators
│ │ ├── component
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ └── loadable.ts.hbs
│ │ ├── container
│ │ │ ├── appendRootState.hbs
│ │ │ ├── importContainerState.hbs
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ ├── loadable.ts.hbs
│ │ │ ├── saga.ts.hbs
│ │ │ ├── selectors.ts.hbs
│ │ │ ├── slice.ts.hbs
│ │ │ └── types.ts.hbs
│ │ ├── plopfile.ts
│ │ └── utils
│ │ │ └── index.ts
│ ├── testing
│ │ ├── loadable.mock.tsx
│ │ └── test-generators.ts
│ └── ts-node.tsconfig.json
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── images
│ │ └── products
│ │ │ └── add_file.svg
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── api
│ │ └── axios.ts
│ ├── app
│ │ ├── __tests__
│ │ │ └── index.test.tsx
│ │ ├── components
│ │ │ ├── files-dropzone.tsx
│ │ │ ├── label.tsx
│ │ │ ├── page.tsx
│ │ │ ├── protected-route.tsx
│ │ │ └── quill-editor.tsx
│ │ ├── index.tsx
│ │ ├── layouts
│ │ │ ├── dashboard-layout
│ │ │ │ ├── dashboard-sidebar-navigation.tsx
│ │ │ │ └── index.tsx
│ │ │ └── main-layout
│ │ │ │ ├── index.tsx
│ │ │ │ └── navigation-bar.tsx
│ │ ├── routes.tsx
│ │ └── views
│ │ │ ├── dashboard
│ │ │ ├── calendar
│ │ │ │ └── CalendarView
│ │ │ │ │ ├── AddEditEventForm.tsx
│ │ │ │ │ ├── Header.tsx
│ │ │ │ │ ├── Toolbar.tsx
│ │ │ │ │ └── index.tsx
│ │ │ ├── dashboard-default-content.tsx
│ │ │ └── product
│ │ │ │ ├── ProductCreateView
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── ProductCreateForm.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── schema
│ │ │ │ │ ├── productDefaultValue.ts
│ │ │ │ │ └── yupProductValidation.ts
│ │ │ │ └── ProductListView
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── Results.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── tableResultsHelpers.tsx
│ │ │ └── pages
│ │ │ ├── AboutPage.tsx
│ │ │ ├── HomePage.tsx
│ │ │ ├── NotFoundPage.tsx
│ │ │ └── auth
│ │ │ ├── LoginPage.tsx
│ │ │ └── components
│ │ │ ├── LoginForm.tsx
│ │ │ └── RegisterForm.tsx
│ ├── features
│ │ └── calendar
│ │ │ └── calendarSlice.ts
│ ├── helpers
│ │ └── inputProductOptions.ts
│ ├── index.tsx
│ ├── locales
│ │ ├── __tests__
│ │ │ └── i18n.test.ts
│ │ ├── en
│ │ │ └── translation.json
│ │ ├── i18n.ts
│ │ └── types.ts
│ ├── models
│ │ ├── calendar-type.ts
│ │ ├── product-type.ts
│ │ └── sale-type.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ ├── services
│ │ ├── authService.ts
│ │ ├── productService.ts
│ │ └── saleService.ts
│ ├── setupTests.ts
│ ├── store
│ │ ├── configureStore.ts
│ │ └── reducers.ts
│ ├── styles
│ │ ├── __tests__
│ │ │ └── media.test.ts
│ │ ├── global-styles.ts
│ │ └── media.ts
│ └── utils
│ │ └── bytes-to-size.ts
└── tsconfig.json
├── chapter-12
├── .babel-plugin-macrosrc.js
├── .env.local
├── .env.production
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── README.md
├── cypress.json
├── cypress
│ ├── fixtures
│ │ ├── example.json
│ │ ├── profile.json
│ │ └── users.json
│ ├── integration
│ │ └── examples
│ │ │ ├── actions.spec.js
│ │ │ ├── aliasing.spec.js
│ │ │ ├── assertions.spec.js
│ │ │ ├── connectors.spec.js
│ │ │ ├── cookies.spec.js
│ │ │ ├── cypress_api.spec.js
│ │ │ ├── files.spec.js
│ │ │ ├── local_storage.spec.js
│ │ │ ├── location.spec.js
│ │ │ ├── misc.spec.js
│ │ │ ├── navigation.spec.js
│ │ │ ├── network_requests.spec.js
│ │ │ ├── querying.spec.js
│ │ │ ├── spies_stubs_clocks.spec.js
│ │ │ ├── traversal.spec.js
│ │ │ ├── utilities.spec.js
│ │ │ ├── viewport.spec.js
│ │ │ ├── waiting.spec.js
│ │ │ └── window.spec.js
│ ├── plugins
│ │ └── index.js
│ ├── screenshots
│ │ └── All Integration Specs
│ │ │ └── my-image.png
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── db.json
├── internals
│ ├── generators
│ │ ├── component
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ └── loadable.ts.hbs
│ │ ├── container
│ │ │ ├── appendRootState.hbs
│ │ │ ├── importContainerState.hbs
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ ├── loadable.ts.hbs
│ │ │ ├── saga.ts.hbs
│ │ │ ├── selectors.ts.hbs
│ │ │ ├── slice.ts.hbs
│ │ │ └── types.ts.hbs
│ │ ├── plopfile.ts
│ │ └── utils
│ │ │ └── index.ts
│ ├── testing
│ │ ├── loadable.mock.tsx
│ │ └── test-generators.ts
│ └── ts-node.tsconfig.json
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── images
│ │ ├── avatar_6.png
│ │ └── products
│ │ │ ├── add_file.svg
│ │ │ ├── product_extended.svg
│ │ │ ├── product_premium--outlined.svg
│ │ │ ├── product_premium.svg
│ │ │ └── product_standard.svg
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── api
│ │ └── axios.ts
│ ├── app
│ │ ├── __tests__
│ │ │ └── index.test.tsx
│ │ ├── components
│ │ │ ├── files-dropzone.tsx
│ │ │ ├── header-profile.tsx
│ │ │ ├── label.tsx
│ │ │ ├── page.tsx
│ │ │ ├── protected-route.tsx
│ │ │ └── quill-editor.tsx
│ │ ├── index.tsx
│ │ ├── layouts
│ │ │ ├── dashboard-layout
│ │ │ │ ├── dashboard-sidebar-navigation.tsx
│ │ │ │ └── index.tsx
│ │ │ └── main-layout
│ │ │ │ ├── index.tsx
│ │ │ │ └── navigation-bar.tsx
│ │ ├── routes.tsx
│ │ └── views
│ │ │ ├── dashboard
│ │ │ ├── account
│ │ │ │ └── AccountView
│ │ │ │ │ ├── General
│ │ │ │ │ ├── GeneralSettings.tsx
│ │ │ │ │ ├── ProfileDetails.tsx
│ │ │ │ │ └── index.tsx
│ │ │ │ │ ├── Header.tsx
│ │ │ │ │ ├── Notifications.tsx
│ │ │ │ │ ├── Security.tsx
│ │ │ │ │ ├── Subscription.tsx
│ │ │ │ │ └── index.tsx
│ │ │ ├── calendar
│ │ │ │ └── CalendarView
│ │ │ │ │ ├── AddEditEventForm.tsx
│ │ │ │ │ ├── Header.tsx
│ │ │ │ │ ├── Toolbar.tsx
│ │ │ │ │ └── index.tsx
│ │ │ ├── dashboard-default-content.tsx
│ │ │ └── product
│ │ │ │ ├── ProductCreateView
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── ProductCreateForm.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── schema
│ │ │ │ │ ├── productDefaultValue.ts
│ │ │ │ │ └── yupProductValidation.ts
│ │ │ │ └── ProductListView
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── Results.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── tableResultsHelpers.tsx
│ │ │ └── pages
│ │ │ ├── AboutPage.tsx
│ │ │ ├── HomePage.tsx
│ │ │ ├── NotFoundPage.tsx
│ │ │ ├── auth
│ │ │ ├── LoginPage.tsx
│ │ │ └── components
│ │ │ │ ├── LoginForm.tsx
│ │ │ │ └── RegisterForm.tsx
│ │ │ └── pricing
│ │ │ └── PricingPage.tsx
│ ├── features
│ │ ├── auth
│ │ │ └── authSlice.ts
│ │ ├── calendar
│ │ │ └── calendarSlice.ts
│ │ └── profile
│ │ │ ├── profileActionTypes.ts
│ │ │ ├── profileAsyncActions.ts
│ │ │ ├── profileSlice.ts
│ │ │ └── yup
│ │ │ └── profile.validation.ts
│ ├── helpers
│ │ └── inputProductOptions.ts
│ ├── index.tsx
│ ├── locales
│ │ ├── __tests__
│ │ │ └── i18n.test.ts
│ │ ├── en
│ │ │ └── translation.json
│ │ ├── i18n.ts
│ │ └── types.ts
│ ├── models
│ │ ├── calendar-type.ts
│ │ ├── claims-type.ts
│ │ ├── product-type.ts
│ │ ├── sale-type.ts
│ │ └── user-type.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ ├── services
│ │ ├── authService.ts
│ │ ├── productService.ts
│ │ ├── saleService.ts
│ │ └── userDbService.ts
│ ├── setupTests.ts
│ ├── store
│ │ ├── configureStore.ts
│ │ └── reducers.ts
│ ├── styles
│ │ ├── __tests__
│ │ │ └── media.test.ts
│ │ ├── global-styles.ts
│ │ └── media.ts
│ └── utils
│ │ └── bytes-to-size.ts
└── tsconfig.json
├── chapter-13
├── .babel-plugin-macrosrc.js
├── .env.local
├── .env.production
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── README.md
├── cypress.json
├── cypress
│ ├── fixtures
│ │ ├── example.json
│ │ ├── profile.json
│ │ └── users.json
│ ├── integration
│ │ └── examples
│ │ │ ├── actions.spec.js
│ │ │ ├── aliasing.spec.js
│ │ │ ├── assertions.spec.js
│ │ │ ├── connectors.spec.js
│ │ │ ├── cookies.spec.js
│ │ │ ├── cypress_api.spec.js
│ │ │ ├── files.spec.js
│ │ │ ├── local_storage.spec.js
│ │ │ ├── location.spec.js
│ │ │ ├── misc.spec.js
│ │ │ ├── navigation.spec.js
│ │ │ ├── network_requests.spec.js
│ │ │ ├── querying.spec.js
│ │ │ ├── spies_stubs_clocks.spec.js
│ │ │ ├── traversal.spec.js
│ │ │ ├── utilities.spec.js
│ │ │ ├── viewport.spec.js
│ │ │ ├── waiting.spec.js
│ │ │ └── window.spec.js
│ ├── plugins
│ │ └── index.js
│ ├── screenshots
│ │ └── All Integration Specs
│ │ │ └── my-image.png
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── db.json
├── internals
│ ├── generators
│ │ ├── component
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ └── loadable.ts.hbs
│ │ ├── container
│ │ │ ├── appendRootState.hbs
│ │ │ ├── importContainerState.hbs
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ ├── loadable.ts.hbs
│ │ │ ├── saga.ts.hbs
│ │ │ ├── selectors.ts.hbs
│ │ │ ├── slice.ts.hbs
│ │ │ └── types.ts.hbs
│ │ ├── plopfile.ts
│ │ └── utils
│ │ │ └── index.ts
│ ├── testing
│ │ ├── loadable.mock.tsx
│ │ └── test-generators.ts
│ └── ts-node.tsconfig.json
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── images
│ │ ├── avatar_6.png
│ │ └── products
│ │ │ ├── add_file.svg
│ │ │ ├── product_extended.svg
│ │ │ ├── product_premium--outlined.svg
│ │ │ ├── product_premium.svg
│ │ │ └── product_standard.svg
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── api
│ │ └── axios.ts
│ ├── app
│ │ ├── __tests__
│ │ │ └── index.test.tsx
│ │ ├── components
│ │ │ ├── files-dropzone.tsx
│ │ │ ├── header-profile.tsx
│ │ │ ├── label.tsx
│ │ │ ├── page.tsx
│ │ │ ├── protected-route.tsx
│ │ │ └── quill-editor.tsx
│ │ ├── index.tsx
│ │ ├── layouts
│ │ │ ├── dashboard-layout
│ │ │ │ ├── dashboard-sidebar-navigation.tsx
│ │ │ │ └── index.tsx
│ │ │ └── main-layout
│ │ │ │ ├── index.tsx
│ │ │ │ └── navigation-bar.tsx
│ │ ├── routes.tsx
│ │ └── views
│ │ │ ├── dashboard
│ │ │ ├── account
│ │ │ │ └── AccountView
│ │ │ │ │ ├── General
│ │ │ │ │ ├── GeneralSettings.tsx
│ │ │ │ │ ├── ProfileDetails.tsx
│ │ │ │ │ └── index.tsx
│ │ │ │ │ ├── Header.tsx
│ │ │ │ │ ├── Notifications.tsx
│ │ │ │ │ ├── Security.tsx
│ │ │ │ │ ├── Subscription.tsx
│ │ │ │ │ └── index.tsx
│ │ │ ├── calendar
│ │ │ │ └── CalendarView
│ │ │ │ │ ├── AddEditEventForm.tsx
│ │ │ │ │ ├── Header.tsx
│ │ │ │ │ ├── Toolbar.tsx
│ │ │ │ │ └── index.tsx
│ │ │ ├── dashboard-default-content.tsx
│ │ │ └── product
│ │ │ │ ├── ProductCreateView
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── ProductCreateForm.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── schema
│ │ │ │ │ ├── productDefaultValue.ts
│ │ │ │ │ └── yupProductValidation.ts
│ │ │ │ └── ProductListView
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── Results.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── tableResultsHelpers.tsx
│ │ │ └── pages
│ │ │ ├── AboutPage.tsx
│ │ │ ├── HomePage.tsx
│ │ │ ├── NotFoundPage.tsx
│ │ │ ├── auth
│ │ │ ├── LoginPage.tsx
│ │ │ └── components
│ │ │ │ ├── LoginForm.tsx
│ │ │ │ └── RegisterForm.tsx
│ │ │ └── pricing
│ │ │ └── PricingPage.tsx
│ ├── features
│ │ ├── auth
│ │ │ └── authSlice.ts
│ │ ├── calendar
│ │ │ └── calendarSlice.ts
│ │ └── profile
│ │ │ ├── profileActionTypes.ts
│ │ │ ├── profileAsyncActions.ts
│ │ │ ├── profileSlice.ts
│ │ │ └── yup
│ │ │ └── profile.validation.ts
│ ├── helpers
│ │ └── inputProductOptions.ts
│ ├── index.tsx
│ ├── locales
│ │ ├── __tests__
│ │ │ └── i18n.test.ts
│ │ ├── en
│ │ │ └── translation.json
│ │ ├── i18n.ts
│ │ └── types.ts
│ ├── models
│ │ ├── calendar-type.ts
│ │ ├── claims-type.ts
│ │ ├── product-type.ts
│ │ ├── sale-type.ts
│ │ └── user-type.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ ├── services
│ │ ├── authService.ts
│ │ ├── productService.ts
│ │ ├── saleService.ts
│ │ └── userDbService.ts
│ ├── setupTests.ts
│ ├── store
│ │ ├── configureStore.ts
│ │ └── reducers.ts
│ ├── styles
│ │ ├── __tests__
│ │ │ └── media.test.ts
│ │ ├── global-styles.ts
│ │ └── media.ts
│ └── utils
│ │ └── bytes-to-size.ts
└── tsconfig.json
├── chapter-14
└── README.md
├── chapter-15
├── .babel-plugin-macrosrc.js
├── .dockerignore
├── .env.local
├── .env.production
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── Dockerfile
├── README.md
├── cypress.json
├── cypress
│ ├── fixtures
│ │ ├── example.json
│ │ ├── profile.json
│ │ └── users.json
│ ├── integration
│ │ └── examples
│ │ │ ├── actions.spec.js
│ │ │ ├── aliasing.spec.js
│ │ │ ├── assertions.spec.js
│ │ │ ├── connectors.spec.js
│ │ │ ├── cookies.spec.js
│ │ │ ├── cypress_api.spec.js
│ │ │ ├── files.spec.js
│ │ │ ├── local_storage.spec.js
│ │ │ ├── location.spec.js
│ │ │ ├── misc.spec.js
│ │ │ ├── navigation.spec.js
│ │ │ ├── network_requests.spec.js
│ │ │ ├── querying.spec.js
│ │ │ ├── spies_stubs_clocks.spec.js
│ │ │ ├── traversal.spec.js
│ │ │ ├── utilities.spec.js
│ │ │ ├── viewport.spec.js
│ │ │ ├── waiting.spec.js
│ │ │ └── window.spec.js
│ ├── plugins
│ │ └── index.js
│ ├── screenshots
│ │ └── All Integration Specs
│ │ │ └── my-image.png
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── db.json
├── internals
│ ├── generators
│ │ ├── component
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ └── loadable.ts.hbs
│ │ ├── container
│ │ │ ├── appendRootState.hbs
│ │ │ ├── importContainerState.hbs
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ ├── loadable.ts.hbs
│ │ │ ├── saga.ts.hbs
│ │ │ ├── selectors.ts.hbs
│ │ │ ├── slice.ts.hbs
│ │ │ └── types.ts.hbs
│ │ ├── plopfile.ts
│ │ └── utils
│ │ │ └── index.ts
│ ├── testing
│ │ ├── loadable.mock.tsx
│ │ └── test-generators.ts
│ └── ts-node.tsconfig.json
├── nginx.conf
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── images
│ │ ├── avatar_6.png
│ │ └── products
│ │ │ ├── add_file.svg
│ │ │ ├── product_extended.svg
│ │ │ ├── product_premium--outlined.svg
│ │ │ ├── product_premium.svg
│ │ │ └── product_standard.svg
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── api
│ │ └── axios.ts
│ ├── app
│ │ ├── __tests__
│ │ │ └── index.test.tsx
│ │ ├── components
│ │ │ ├── files-dropzone.tsx
│ │ │ ├── header-profile.tsx
│ │ │ ├── label.tsx
│ │ │ ├── page.tsx
│ │ │ ├── protected-route.tsx
│ │ │ └── quill-editor.tsx
│ │ ├── index.tsx
│ │ ├── layouts
│ │ │ ├── dashboard-layout
│ │ │ │ ├── dashboard-sidebar-navigation.tsx
│ │ │ │ └── index.tsx
│ │ │ └── main-layout
│ │ │ │ ├── index.tsx
│ │ │ │ └── navigation-bar.tsx
│ │ ├── routes.tsx
│ │ └── views
│ │ │ ├── dashboard
│ │ │ ├── account
│ │ │ │ └── AccountView
│ │ │ │ │ ├── General
│ │ │ │ │ ├── GeneralSettings.tsx
│ │ │ │ │ ├── ProfileDetails.tsx
│ │ │ │ │ └── index.tsx
│ │ │ │ │ ├── Header.tsx
│ │ │ │ │ ├── Notifications.tsx
│ │ │ │ │ ├── Security.tsx
│ │ │ │ │ ├── Subscription.tsx
│ │ │ │ │ └── index.tsx
│ │ │ ├── calendar
│ │ │ │ └── CalendarView
│ │ │ │ │ ├── AddEditEventForm.tsx
│ │ │ │ │ ├── Header.tsx
│ │ │ │ │ ├── Toolbar.tsx
│ │ │ │ │ └── index.tsx
│ │ │ ├── dashboard-default-content.tsx
│ │ │ └── product
│ │ │ │ ├── ProductCreateView
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── ProductCreateForm.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── schema
│ │ │ │ │ ├── productDefaultValue.ts
│ │ │ │ │ └── yupProductValidation.ts
│ │ │ │ └── ProductListView
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── Results.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── tableResultsHelpers.tsx
│ │ │ └── pages
│ │ │ ├── AboutPage.tsx
│ │ │ ├── HomePage.tsx
│ │ │ ├── NotFoundPage.tsx
│ │ │ ├── auth
│ │ │ ├── LoginPage.tsx
│ │ │ └── components
│ │ │ │ ├── LoginForm.tsx
│ │ │ │ └── RegisterForm.tsx
│ │ │ └── pricing
│ │ │ └── PricingPage.tsx
│ ├── features
│ │ ├── auth
│ │ │ └── authSlice.ts
│ │ ├── calendar
│ │ │ └── calendarSlice.ts
│ │ └── profile
│ │ │ ├── profileActionTypes.ts
│ │ │ ├── profileAsyncActions.ts
│ │ │ ├── profileSlice.ts
│ │ │ └── yup
│ │ │ └── profile.validation.ts
│ ├── helpers
│ │ └── inputProductOptions.ts
│ ├── index.tsx
│ ├── locales
│ │ ├── __tests__
│ │ │ └── i18n.test.ts
│ │ ├── en
│ │ │ └── translation.json
│ │ ├── i18n.ts
│ │ └── types.ts
│ ├── models
│ │ ├── calendar-type.ts
│ │ ├── claims-type.ts
│ │ ├── product-type.ts
│ │ ├── sale-type.ts
│ │ └── user-type.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ ├── services
│ │ ├── authService.ts
│ │ ├── productService.ts
│ │ ├── saleService.ts
│ │ └── userDbService.ts
│ ├── setupTests.ts
│ ├── store
│ │ ├── configureStore.ts
│ │ └── reducers.ts
│ ├── styles
│ │ ├── __tests__
│ │ │ └── media.test.ts
│ │ ├── global-styles.ts
│ │ └── media.ts
│ └── utils
│ │ └── bytes-to-size.ts
└── tsconfig.json
├── chapter-4
├── finished-installing-libraries-boilerplate
│ ├── .babel-plugin-macrosrc.js
│ ├── .env.local
│ ├── .env.production
│ ├── .eslintignore
│ ├── .eslintrc.js
│ ├── .gitattributes
│ ├── .gitignore
│ ├── .npmrc
│ ├── .nvmrc
│ ├── .prettierignore
│ ├── .prettierrc
│ ├── .stylelintrc
│ ├── README.md
│ ├── cypress.json
│ ├── cypress
│ │ ├── fixtures
│ │ │ ├── example.json
│ │ │ ├── profile.json
│ │ │ └── users.json
│ │ ├── integration
│ │ │ └── examples
│ │ │ │ ├── actions.spec.js
│ │ │ │ ├── aliasing.spec.js
│ │ │ │ ├── assertions.spec.js
│ │ │ │ ├── connectors.spec.js
│ │ │ │ ├── cookies.spec.js
│ │ │ │ ├── cypress_api.spec.js
│ │ │ │ ├── files.spec.js
│ │ │ │ ├── local_storage.spec.js
│ │ │ │ ├── location.spec.js
│ │ │ │ ├── misc.spec.js
│ │ │ │ ├── navigation.spec.js
│ │ │ │ ├── network_requests.spec.js
│ │ │ │ ├── querying.spec.js
│ │ │ │ ├── spies_stubs_clocks.spec.js
│ │ │ │ ├── traversal.spec.js
│ │ │ │ ├── utilities.spec.js
│ │ │ │ ├── viewport.spec.js
│ │ │ │ ├── waiting.spec.js
│ │ │ │ └── window.spec.js
│ │ ├── plugins
│ │ │ └── index.js
│ │ ├── screenshots
│ │ │ └── All Integration Specs
│ │ │ │ └── my-image.png
│ │ └── support
│ │ │ ├── commands.js
│ │ │ └── index.js
│ ├── internals
│ │ ├── generators
│ │ │ ├── component
│ │ │ │ ├── index.test.tsx.hbs
│ │ │ │ ├── index.ts
│ │ │ │ ├── index.tsx.hbs
│ │ │ │ └── loadable.ts.hbs
│ │ │ ├── container
│ │ │ │ ├── appendRootState.hbs
│ │ │ │ ├── importContainerState.hbs
│ │ │ │ ├── index.test.tsx.hbs
│ │ │ │ ├── index.ts
│ │ │ │ ├── index.tsx.hbs
│ │ │ │ ├── loadable.ts.hbs
│ │ │ │ ├── saga.ts.hbs
│ │ │ │ ├── selectors.ts.hbs
│ │ │ │ ├── slice.ts.hbs
│ │ │ │ └── types.ts.hbs
│ │ │ ├── plopfile.ts
│ │ │ └── utils
│ │ │ │ └── index.ts
│ │ ├── testing
│ │ │ ├── loadable.mock.tsx
│ │ │ └── test-generators.ts
│ │ └── ts-node.tsconfig.json
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── src
│ │ ├── app
│ │ │ ├── __tests__
│ │ │ │ └── index.test.tsx
│ │ │ ├── components
│ │ │ │ └── NotFoundPage
│ │ │ │ │ ├── Loadable.tsx
│ │ │ │ │ ├── P.ts
│ │ │ │ │ └── index.tsx
│ │ │ ├── containers
│ │ │ │ └── HomePage
│ │ │ │ │ ├── Loadable.tsx
│ │ │ │ │ └── index.tsx
│ │ │ └── index.tsx
│ │ ├── index.tsx
│ │ ├── locales
│ │ │ ├── __tests__
│ │ │ │ └── i18n.test.ts
│ │ │ ├── en
│ │ │ │ └── translation.json
│ │ │ ├── i18n.ts
│ │ │ └── types.ts
│ │ ├── react-app-env.d.ts
│ │ ├── serviceWorker.ts
│ │ ├── setupTests.ts
│ │ ├── store
│ │ │ ├── __tests__
│ │ │ │ ├── configureStore.test.ts
│ │ │ │ └── reducer.test.ts
│ │ │ ├── configureStore.ts
│ │ │ └── reducers.ts
│ │ ├── styles
│ │ │ ├── __tests__
│ │ │ │ └── media.test.ts
│ │ │ ├── global-styles.ts
│ │ │ └── media.ts
│ │ ├── types
│ │ │ ├── RootState.ts
│ │ │ └── index.ts
│ │ └── utils
│ │ │ ├── @reduxjs
│ │ │ └── toolkit.tsx
│ │ │ ├── loadable.tsx
│ │ │ ├── redux-injectors.ts
│ │ │ └── types
│ │ │ └── injector-typings.ts
│ └── tsconfig.json
└── starter-boilerplate
│ ├── .babel-plugin-macrosrc.js
│ ├── .env.local
│ ├── .env.production
│ ├── .eslintrc.js
│ ├── .gitattributes
│ ├── .gitignore
│ ├── .npmrc
│ ├── .nvmrc
│ ├── .prettierignore
│ ├── .prettierrc
│ ├── .stylelintrc
│ ├── README.md
│ ├── internals
│ ├── generators
│ │ ├── component
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ └── loadable.ts.hbs
│ │ ├── container
│ │ │ ├── appendRootState.hbs
│ │ │ ├── importContainerState.hbs
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ ├── loadable.ts.hbs
│ │ │ ├── saga.ts.hbs
│ │ │ ├── selectors.ts.hbs
│ │ │ ├── slice.ts.hbs
│ │ │ └── types.ts.hbs
│ │ ├── plopfile.ts
│ │ └── utils
│ │ │ └── index.ts
│ ├── scripts
│ │ └── clean.ts
│ ├── startingTemplate
│ │ ├── public
│ │ │ ├── favicon.ico
│ │ │ ├── index.html
│ │ │ ├── logo192.png
│ │ │ ├── logo512.png
│ │ │ ├── manifest.json
│ │ │ └── robots.txt
│ │ ├── src
│ │ │ ├── app
│ │ │ │ ├── __tests__
│ │ │ │ │ └── index.test.tsx
│ │ │ │ ├── components
│ │ │ │ │ └── NotFoundPage
│ │ │ │ │ │ ├── Loadable.tsx
│ │ │ │ │ │ ├── P.ts
│ │ │ │ │ │ └── index.tsx
│ │ │ │ ├── containers
│ │ │ │ │ └── HomePage
│ │ │ │ │ │ ├── Loadable.tsx
│ │ │ │ │ │ └── index.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── index.tsx
│ │ │ ├── locales
│ │ │ │ ├── __tests__
│ │ │ │ │ └── i18n.test.ts
│ │ │ │ ├── en
│ │ │ │ │ └── translation.json
│ │ │ │ ├── i18n.ts
│ │ │ │ └── types.ts
│ │ │ ├── react-app-env.d.ts
│ │ │ ├── serviceWorker.ts
│ │ │ ├── setupTests.ts
│ │ │ ├── store
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── configureStore.test.ts
│ │ │ │ │ └── reducer.test.ts
│ │ │ │ ├── configureStore.ts
│ │ │ │ └── reducers.ts
│ │ │ ├── styles
│ │ │ │ ├── __tests__
│ │ │ │ │ └── media.test.ts
│ │ │ │ ├── global-styles.ts
│ │ │ │ └── media.ts
│ │ │ ├── types
│ │ │ │ ├── RootState.ts
│ │ │ │ └── index.ts
│ │ │ └── utils
│ │ │ │ ├── @reduxjs
│ │ │ │ └── toolkit.tsx
│ │ │ │ ├── loadable.tsx
│ │ │ │ ├── redux-injectors.ts
│ │ │ │ └── types
│ │ │ │ └── injector-typings.ts
│ │ └── tsconfig.json
│ ├── testing
│ │ ├── loadable.mock.tsx
│ │ └── test-generators.ts
│ └── ts-node.tsconfig.json
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
│ ├── src
│ ├── app
│ │ ├── __tests__
│ │ │ ├── __snapshots__
│ │ │ │ └── index.test.tsx.snap
│ │ │ └── index.test.tsx
│ │ ├── components
│ │ │ ├── A
│ │ │ │ ├── __tests__
│ │ │ │ │ └── index.test.tsx
│ │ │ │ └── index.ts
│ │ │ ├── FormLabel
│ │ │ │ ├── __tests__
│ │ │ │ │ └── index.test.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Link
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── index.test.tsx.snap
│ │ │ │ │ └── index.test.tsx
│ │ │ │ └── index.ts
│ │ │ ├── LoadingIndicator
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── index.test.tsx.snap
│ │ │ │ │ └── index.test.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── PageWrapper
│ │ │ │ └── index.ts
│ │ │ └── Radio
│ │ │ │ └── index.tsx
│ │ ├── containers
│ │ │ ├── GithubRepoForm
│ │ │ │ ├── RepoItem.tsx
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── RepoItem.tsx
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ ├── RepoItem.tsx.snap
│ │ │ │ │ │ └── saga.test.ts.snap
│ │ │ │ │ ├── index.test.tsx
│ │ │ │ │ ├── saga.test.ts
│ │ │ │ │ ├── selectors.test.ts
│ │ │ │ │ └── slice.test.ts
│ │ │ │ ├── assets
│ │ │ │ │ ├── new-window.svg
│ │ │ │ │ └── star.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── Input.ts
│ │ │ │ │ ├── TextButton.ts
│ │ │ │ │ └── __tests__
│ │ │ │ │ │ ├── TextButton.test.tsx
│ │ │ │ │ │ └── input.test.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ ├── saga.ts
│ │ │ │ ├── selectors.ts
│ │ │ │ ├── slice.ts
│ │ │ │ └── types.ts
│ │ │ ├── HomePage
│ │ │ │ ├── Features.tsx
│ │ │ │ ├── Loadable.tsx
│ │ │ │ ├── Logos.tsx
│ │ │ │ ├── Masthead.tsx
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── Features.test.tsx
│ │ │ │ │ ├── Logos.test.tsx
│ │ │ │ │ ├── Masthead.test.tsx
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ ├── Features.test.tsx.snap
│ │ │ │ │ │ ├── Logos.test.tsx.snap
│ │ │ │ │ │ ├── Masthead.test.tsx.snap
│ │ │ │ │ │ └── index.test.tsx.snap
│ │ │ │ │ └── index.test.tsx
│ │ │ │ ├── assets
│ │ │ │ │ ├── code-analysis.svg
│ │ │ │ │ ├── cra-logo.svg
│ │ │ │ │ ├── css.svg
│ │ │ │ │ ├── instant-feedback.svg
│ │ │ │ │ ├── intl.svg
│ │ │ │ │ ├── offline-first.svg
│ │ │ │ │ ├── plus-sign.svg
│ │ │ │ │ ├── route.svg
│ │ │ │ │ ├── rp-logo.svg
│ │ │ │ │ ├── scaffolding.svg
│ │ │ │ │ ├── seo.svg
│ │ │ │ │ ├── state.svg
│ │ │ │ │ └── ts.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── Lead.ts
│ │ │ │ │ ├── P.ts
│ │ │ │ │ ├── SubTitle.ts
│ │ │ │ │ ├── Title.ts
│ │ │ │ │ └── __tests__
│ │ │ │ │ │ ├── Lead.test.tsx
│ │ │ │ │ │ ├── P.test.tsx
│ │ │ │ │ │ ├── Subtitle.test.tsx
│ │ │ │ │ │ ├── Title.test.tsx
│ │ │ │ │ │ └── __snapshots__
│ │ │ │ │ │ ├── Lead.test.tsx.snap
│ │ │ │ │ │ ├── P.test.tsx.snap
│ │ │ │ │ │ ├── Subtitle.test.tsx.snap
│ │ │ │ │ │ └── Title.test.tsx.snap
│ │ │ │ └── index.tsx
│ │ │ ├── LanguageSwitch
│ │ │ │ ├── __tests__
│ │ │ │ │ └── index.test.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── NavBar
│ │ │ │ ├── Logo.tsx
│ │ │ │ ├── Nav.tsx
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── Logo.test.tsx
│ │ │ │ │ ├── Nav.test.tsx
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ ├── Logo.test.tsx.snap
│ │ │ │ │ │ ├── Nav.test.tsx.snap
│ │ │ │ │ │ └── index.test.tsx.snap
│ │ │ │ │ └── index.test.tsx
│ │ │ │ ├── assets
│ │ │ │ │ ├── documentation-icon.svg
│ │ │ │ │ └── github-icon.svg
│ │ │ │ └── index.tsx
│ │ │ ├── NotFoundPage
│ │ │ │ ├── Loadable.tsx
│ │ │ │ ├── P.ts
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── index.test.tsx.snap
│ │ │ │ │ └── index.test.tsx
│ │ │ │ └── index.tsx
│ │ │ └── ThemeSwitch
│ │ │ │ ├── __tests__
│ │ │ │ └── index.test.tsx
│ │ │ │ └── index.tsx
│ │ └── index.tsx
│ ├── index.tsx
│ ├── locales
│ │ ├── __tests__
│ │ │ └── i18n.test.ts
│ │ ├── de
│ │ │ └── translation.json
│ │ ├── en
│ │ │ └── translation.json
│ │ ├── i18n.ts
│ │ └── types.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ ├── setupTests.ts
│ ├── store
│ │ ├── __tests__
│ │ │ ├── configureStore.test.ts
│ │ │ └── reducer.test.ts
│ │ ├── configureStore.ts
│ │ └── reducers.ts
│ ├── styles
│ │ ├── StyleConstants.ts
│ │ ├── __tests__
│ │ │ └── media.test.ts
│ │ ├── global-styles.ts
│ │ ├── media.ts
│ │ └── theme
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── __tests__
│ │ │ ├── ThemeProvider.test.tsx
│ │ │ ├── slice.test.ts
│ │ │ └── utils.test.ts
│ │ │ ├── slice.ts
│ │ │ ├── styled.d.ts
│ │ │ ├── themes.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ ├── types
│ │ ├── Repo.d.ts
│ │ ├── RootState.ts
│ │ └── index.ts
│ └── utils
│ │ ├── @reduxjs
│ │ └── toolkit.tsx
│ │ ├── __tests__
│ │ ├── __snapshots__
│ │ │ └── loadable.test.tsx.snap
│ │ ├── loadable.test.tsx
│ │ └── request.test.ts
│ │ ├── loadable.tsx
│ │ ├── redux-injectors.ts
│ │ ├── request.ts
│ │ └── types
│ │ └── injector-typings.ts
│ └── tsconfig.json
├── chapter-5
├── .babel-plugin-macrosrc.js
├── .env.local
├── .env.production
├── .eslintcache
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── settings.json
├── README.md
├── cypress.json
├── cypress
│ ├── fixtures
│ │ ├── example.json
│ │ ├── profile.json
│ │ └── users.json
│ ├── integration
│ │ └── examples
│ │ │ ├── actions.spec.js
│ │ │ ├── aliasing.spec.js
│ │ │ ├── assertions.spec.js
│ │ │ ├── connectors.spec.js
│ │ │ ├── cookies.spec.js
│ │ │ ├── cypress_api.spec.js
│ │ │ ├── files.spec.js
│ │ │ ├── local_storage.spec.js
│ │ │ ├── location.spec.js
│ │ │ ├── misc.spec.js
│ │ │ ├── navigation.spec.js
│ │ │ ├── network_requests.spec.js
│ │ │ ├── querying.spec.js
│ │ │ ├── spies_stubs_clocks.spec.js
│ │ │ ├── traversal.spec.js
│ │ │ ├── utilities.spec.js
│ │ │ ├── viewport.spec.js
│ │ │ ├── waiting.spec.js
│ │ │ └── window.spec.js
│ ├── plugins
│ │ └── index.js
│ ├── screenshots
│ │ └── All Integration Specs
│ │ │ └── my-image.png
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── internals
│ ├── generators
│ │ ├── component
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ └── loadable.ts.hbs
│ │ ├── container
│ │ │ ├── appendRootState.hbs
│ │ │ ├── importContainerState.hbs
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ ├── loadable.ts.hbs
│ │ │ ├── saga.ts.hbs
│ │ │ ├── selectors.ts.hbs
│ │ │ ├── slice.ts.hbs
│ │ │ └── types.ts.hbs
│ │ ├── plopfile.ts
│ │ └── utils
│ │ │ └── index.ts
│ ├── testing
│ │ ├── loadable.mock.tsx
│ │ └── test-generators.ts
│ └── ts-node.tsconfig.json
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── app
│ │ ├── __tests__
│ │ │ └── index.test.tsx
│ │ ├── components
│ │ │ └── navigation-bar.tsx
│ │ ├── index.tsx
│ │ ├── layouts
│ │ │ └── dashboard-layout
│ │ │ │ ├── dashboard-sidebar-navigation.tsx
│ │ │ │ └── index.tsx
│ │ ├── routes.tsx
│ │ └── views
│ │ │ ├── dashboard
│ │ │ ├── dashboard-default-content.tsx
│ │ │ └── settings-and-privacy.tsx
│ │ │ └── pages
│ │ │ ├── AboutPage.tsx
│ │ │ ├── HomePage.tsx
│ │ │ └── NotFoundPage.tsx
│ ├── index.tsx
│ ├── locales
│ │ ├── __tests__
│ │ │ └── i18n.test.ts
│ │ ├── en
│ │ │ └── translation.json
│ │ ├── i18n.ts
│ │ └── types.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ ├── setupTests.ts
│ ├── store
│ │ ├── __tests__
│ │ │ ├── configureStore.test.ts
│ │ │ └── reducer.test.ts
│ │ ├── configureStore.ts
│ │ └── reducers.ts
│ ├── styles
│ │ ├── __tests__
│ │ │ └── media.test.ts
│ │ ├── global-styles.ts
│ │ └── media.ts
│ ├── types
│ │ ├── RootState.ts
│ │ └── index.ts
│ └── utils
│ │ ├── @reduxjs
│ │ └── toolkit.tsx
│ │ ├── loadable.tsx
│ │ ├── redux-injectors.ts
│ │ └── types
│ │ └── injector-typings.ts
└── tsconfig.json
├── chapter-6
├── .babel-plugin-macrosrc.js
├── .env.local
├── .env.production
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── README.md
├── cypress.json
├── cypress
│ ├── fixtures
│ │ ├── example.json
│ │ ├── profile.json
│ │ └── users.json
│ ├── integration
│ │ └── examples
│ │ │ ├── actions.spec.js
│ │ │ ├── aliasing.spec.js
│ │ │ ├── assertions.spec.js
│ │ │ ├── connectors.spec.js
│ │ │ ├── cookies.spec.js
│ │ │ ├── cypress_api.spec.js
│ │ │ ├── files.spec.js
│ │ │ ├── local_storage.spec.js
│ │ │ ├── location.spec.js
│ │ │ ├── misc.spec.js
│ │ │ ├── navigation.spec.js
│ │ │ ├── network_requests.spec.js
│ │ │ ├── querying.spec.js
│ │ │ ├── spies_stubs_clocks.spec.js
│ │ │ ├── traversal.spec.js
│ │ │ ├── utilities.spec.js
│ │ │ ├── viewport.spec.js
│ │ │ ├── waiting.spec.js
│ │ │ └── window.spec.js
│ ├── plugins
│ │ └── index.js
│ ├── screenshots
│ │ └── All Integration Specs
│ │ │ └── my-image.png
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── db.json
├── internals
│ ├── generators
│ │ ├── component
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ └── loadable.ts.hbs
│ │ ├── container
│ │ │ ├── appendRootState.hbs
│ │ │ ├── importContainerState.hbs
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ ├── loadable.ts.hbs
│ │ │ ├── saga.ts.hbs
│ │ │ ├── selectors.ts.hbs
│ │ │ ├── slice.ts.hbs
│ │ │ └── types.ts.hbs
│ │ ├── plopfile.ts
│ │ └── utils
│ │ │ └── index.ts
│ ├── testing
│ │ ├── loadable.mock.tsx
│ │ └── test-generators.ts
│ └── ts-node.tsconfig.json
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── api
│ │ └── axios.ts
│ ├── app
│ │ ├── __tests__
│ │ │ └── index.test.tsx
│ │ ├── components
│ │ │ └── page.tsx
│ │ ├── index.tsx
│ │ ├── layouts
│ │ │ ├── dashboard-layout
│ │ │ │ ├── dashboard-sidebar-navigation.tsx
│ │ │ │ └── index.tsx
│ │ │ └── main-layout
│ │ │ │ ├── index.tsx
│ │ │ │ └── navigation-bar.tsx
│ │ ├── routes.tsx
│ │ └── views
│ │ │ ├── dashboard
│ │ │ ├── dashboard-default-content.tsx
│ │ │ └── settings-and-privacy.tsx
│ │ │ └── pages
│ │ │ ├── AboutPage.tsx
│ │ │ ├── HomePage.tsx
│ │ │ └── NotFoundPage.tsx
│ ├── index.tsx
│ ├── locales
│ │ ├── __tests__
│ │ │ └── i18n.test.ts
│ │ ├── en
│ │ │ └── translation.json
│ │ ├── i18n.ts
│ │ └── types.ts
│ ├── models
│ │ └── sale-type.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ ├── services
│ │ └── saleService.ts
│ ├── setupTests.ts
│ ├── store
│ │ ├── __tests__
│ │ │ ├── configureStore.test.ts
│ │ │ └── reducer.test.ts
│ │ ├── configureStore.ts
│ │ └── reducers.ts
│ ├── styles
│ │ ├── __tests__
│ │ │ └── media.test.ts
│ │ ├── global-styles.ts
│ │ └── media.ts
│ ├── types
│ │ ├── RootState.ts
│ │ └── index.ts
│ └── utils
│ │ ├── @reduxjs
│ │ └── toolkit.tsx
│ │ ├── loadable.tsx
│ │ ├── redux-injectors.ts
│ │ └── types
│ │ └── injector-typings.ts
└── tsconfig.json
├── chapter-7
├── .babel-plugin-macrosrc.js
├── .env.local
├── .env.production
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── README.md
├── cypress.json
├── cypress
│ ├── fixtures
│ │ ├── example.json
│ │ ├── profile.json
│ │ └── users.json
│ ├── integration
│ │ └── examples
│ │ │ ├── actions.spec.js
│ │ │ ├── aliasing.spec.js
│ │ │ ├── assertions.spec.js
│ │ │ ├── connectors.spec.js
│ │ │ ├── cookies.spec.js
│ │ │ ├── cypress_api.spec.js
│ │ │ ├── files.spec.js
│ │ │ ├── local_storage.spec.js
│ │ │ ├── location.spec.js
│ │ │ ├── misc.spec.js
│ │ │ ├── navigation.spec.js
│ │ │ ├── network_requests.spec.js
│ │ │ ├── querying.spec.js
│ │ │ ├── spies_stubs_clocks.spec.js
│ │ │ ├── traversal.spec.js
│ │ │ ├── utilities.spec.js
│ │ │ ├── viewport.spec.js
│ │ │ ├── waiting.spec.js
│ │ │ └── window.spec.js
│ ├── plugins
│ │ └── index.js
│ ├── screenshots
│ │ └── All Integration Specs
│ │ │ └── my-image.png
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── db.json
├── internals
│ ├── generators
│ │ ├── component
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ └── loadable.ts.hbs
│ │ ├── container
│ │ │ ├── appendRootState.hbs
│ │ │ ├── importContainerState.hbs
│ │ │ ├── index.test.tsx.hbs
│ │ │ ├── index.ts
│ │ │ ├── index.tsx.hbs
│ │ │ ├── loadable.ts.hbs
│ │ │ ├── saga.ts.hbs
│ │ │ ├── selectors.ts.hbs
│ │ │ ├── slice.ts.hbs
│ │ │ └── types.ts.hbs
│ │ ├── plopfile.ts
│ │ └── utils
│ │ │ └── index.ts
│ ├── testing
│ │ ├── loadable.mock.tsx
│ │ └── test-generators.ts
│ └── ts-node.tsconfig.json
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── images
│ │ └── products
│ │ │ └── add_file.svg
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── api
│ │ └── axios.ts
│ ├── app
│ │ ├── __tests__
│ │ │ └── index.test.tsx
│ │ ├── components
│ │ │ ├── files-dropzone.tsx
│ │ │ ├── label.tsx
│ │ │ ├── page.tsx
│ │ │ └── quill-editor.tsx
│ │ ├── index.tsx
│ │ ├── layouts
│ │ │ ├── dashboard-layout
│ │ │ │ ├── dashboard-sidebar-navigation.tsx
│ │ │ │ └── index.tsx
│ │ │ └── main-layout
│ │ │ │ ├── index.tsx
│ │ │ │ └── navigation-bar.tsx
│ │ ├── routes.tsx
│ │ └── views
│ │ │ ├── dashboard
│ │ │ ├── dashboard-default-content.tsx
│ │ │ └── product
│ │ │ │ ├── ProductCreateView
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── ProductCreateForm.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── schema
│ │ │ │ │ ├── productDefaultValue.ts
│ │ │ │ │ └── yupProductValidation.ts
│ │ │ │ └── ProductListView
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── Results.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── tableResultsHelpers.tsx
│ │ │ └── pages
│ │ │ ├── AboutPage.tsx
│ │ │ ├── HomePage.tsx
│ │ │ └── NotFoundPage.tsx
│ ├── helpers
│ │ └── inputProductOptions.ts
│ ├── index.tsx
│ ├── locales
│ │ ├── __tests__
│ │ │ └── i18n.test.ts
│ │ ├── en
│ │ │ └── translation.json
│ │ ├── i18n.ts
│ │ └── types.ts
│ ├── models
│ │ ├── product-type.ts
│ │ └── sale-type.ts
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ ├── services
│ │ ├── productService.ts
│ │ └── saleService.ts
│ ├── setupTests.ts
│ ├── store
│ │ ├── __tests__
│ │ │ ├── configureStore.test.ts
│ │ │ └── reducer.test.ts
│ │ ├── configureStore.ts
│ │ └── reducers.ts
│ ├── styles
│ │ ├── __tests__
│ │ │ └── media.test.ts
│ │ ├── global-styles.ts
│ │ └── media.ts
│ ├── types
│ │ ├── RootState.ts
│ │ └── index.ts
│ └── utils
│ │ ├── @reduxjs
│ │ └── toolkit.tsx
│ │ ├── bytes-to-size.ts
│ │ ├── loadable.tsx
│ │ ├── redux-injectors.ts
│ │ └── types
│ │ └── injector-typings.ts
└── tsconfig.json
├── chapter-8
└── README.md
└── chapter-9
├── .babel-plugin-macrosrc.js
├── .env.local
├── .env.production
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── README.md
├── cypress.json
├── cypress
├── fixtures
│ ├── example.json
│ ├── profile.json
│ └── users.json
├── integration
│ └── examples
│ │ ├── actions.spec.js
│ │ ├── aliasing.spec.js
│ │ ├── assertions.spec.js
│ │ ├── connectors.spec.js
│ │ ├── cookies.spec.js
│ │ ├── cypress_api.spec.js
│ │ ├── files.spec.js
│ │ ├── local_storage.spec.js
│ │ ├── location.spec.js
│ │ ├── misc.spec.js
│ │ ├── navigation.spec.js
│ │ ├── network_requests.spec.js
│ │ ├── querying.spec.js
│ │ ├── spies_stubs_clocks.spec.js
│ │ ├── traversal.spec.js
│ │ ├── utilities.spec.js
│ │ ├── viewport.spec.js
│ │ ├── waiting.spec.js
│ │ └── window.spec.js
├── plugins
│ └── index.js
├── screenshots
│ └── All Integration Specs
│ │ └── my-image.png
└── support
│ ├── commands.js
│ └── index.js
├── db.json
├── internals
├── generators
│ ├── component
│ │ ├── index.test.tsx.hbs
│ │ ├── index.ts
│ │ ├── index.tsx.hbs
│ │ └── loadable.ts.hbs
│ ├── container
│ │ ├── appendRootState.hbs
│ │ ├── importContainerState.hbs
│ │ ├── index.test.tsx.hbs
│ │ ├── index.ts
│ │ ├── index.tsx.hbs
│ │ ├── loadable.ts.hbs
│ │ ├── saga.ts.hbs
│ │ ├── selectors.ts.hbs
│ │ ├── slice.ts.hbs
│ │ └── types.ts.hbs
│ ├── plopfile.ts
│ └── utils
│ │ └── index.ts
├── testing
│ ├── loadable.mock.tsx
│ └── test-generators.ts
└── ts-node.tsconfig.json
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── images
│ └── products
│ │ └── add_file.svg
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── api
│ └── axios.ts
├── app
│ ├── __tests__
│ │ └── index.test.tsx
│ ├── components
│ │ ├── files-dropzone.tsx
│ │ ├── label.tsx
│ │ ├── page.tsx
│ │ └── quill-editor.tsx
│ ├── index.tsx
│ ├── layouts
│ │ ├── dashboard-layout
│ │ │ ├── dashboard-sidebar-navigation.tsx
│ │ │ └── index.tsx
│ │ └── main-layout
│ │ │ ├── index.tsx
│ │ │ └── navigation-bar.tsx
│ ├── routes.tsx
│ └── views
│ │ ├── dashboard
│ │ ├── calendar
│ │ │ └── CalendarView
│ │ │ │ └── index.tsx
│ │ ├── dashboard-default-content.tsx
│ │ └── product
│ │ │ ├── ProductCreateView
│ │ │ ├── Header.tsx
│ │ │ ├── ProductCreateForm.tsx
│ │ │ ├── index.tsx
│ │ │ └── schema
│ │ │ │ ├── productDefaultValue.ts
│ │ │ │ └── yupProductValidation.ts
│ │ │ └── ProductListView
│ │ │ ├── Header.tsx
│ │ │ ├── Results.tsx
│ │ │ ├── index.tsx
│ │ │ └── tableResultsHelpers.tsx
│ │ └── pages
│ │ ├── AboutPage.tsx
│ │ ├── HomePage.tsx
│ │ └── NotFoundPage.tsx
├── features
│ └── calendar
│ │ └── calendarSlice.ts
├── helpers
│ └── inputProductOptions.ts
├── index.tsx
├── locales
│ ├── __tests__
│ │ └── i18n.test.ts
│ ├── en
│ │ └── translation.json
│ ├── i18n.ts
│ └── types.ts
├── models
│ ├── calendar-type.ts
│ ├── product-type.ts
│ └── sale-type.ts
├── react-app-env.d.ts
├── serviceWorker.ts
├── services
│ ├── productService.ts
│ └── saleService.ts
├── setupTests.ts
├── store
│ ├── configureStore.ts
│ └── reducers.ts
├── styles
│ ├── __tests__
│ │ └── media.test.ts
│ ├── global-styles.ts
│ └── media.ts
└── utils
│ └── bytes-to-size.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea
3 | .vscode
4 | .eslintcache
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | to follow..
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | to follow..
2 |
--------------------------------------------------------------------------------
/chapter-10/.babel-plugin-macrosrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styledComponents: {
3 | displayName: process.env.NODE_ENV !== 'production',
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/chapter-10/.env.local:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | EXTEND_ESLINT=true
--------------------------------------------------------------------------------
/chapter-10/.env.production:
--------------------------------------------------------------------------------
1 | GENERATE_SOURCEMAP=false
--------------------------------------------------------------------------------
/chapter-10/.eslintignore:
--------------------------------------------------------------------------------
1 | cypress
--------------------------------------------------------------------------------
/chapter-10/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't check auto-generated stuff into git
2 | coverage
3 | build
4 | node_modules
5 | stats.json
6 | .pnp
7 | .pnp.js
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | # boilerplate internals
20 | generated-cra-app
21 | .cra-template-rb
22 | template
23 |
24 | .eslintcache
--------------------------------------------------------------------------------
/chapter-10/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact = true
2 |
--------------------------------------------------------------------------------
/chapter-10/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/dubnium
2 |
--------------------------------------------------------------------------------
/chapter-10/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 | yarn.lock
--------------------------------------------------------------------------------
/chapter-10/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "arrowParens": "avoid"
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-10/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "processors": ["stylelint-processor-styled-components"],
3 | "extends": [
4 | "stylelint-config-recommended",
5 | "stylelint-config-styled-components"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/chapter-10/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/chapter-10/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-10/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-10/cypress/screenshots/All Integration Specs/my-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-10/cypress/screenshots/All Integration Specs/my-image.png
--------------------------------------------------------------------------------
/chapter-10/internals/generators/component/index.test.tsx.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 |
4 | import { {{ properCase ComponentName }} } from '..';
5 |
6 | describe('<{{ properCase ComponentName }} />', () => {
7 | it('should match snapshot', () => {
8 | const loadingIndicator = render(<{{ properCase ComponentName }} />);
9 | expect(loadingIndicator.container.firstChild).toMatchSnapshot();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/chapter-10/internals/generators/component/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-10/internals/generators/container/appendRootState.hbs:
--------------------------------------------------------------------------------
1 | {{ camelCase ComponentName }}?: {{ properCase ComponentName }}State;
2 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-10/internals/generators/container/importContainerState.hbs:
--------------------------------------------------------------------------------
1 | import { {{ properCase ComponentName }}State } from 'app/containers/{{ properCase ComponentName }}/types';
2 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-10/internals/generators/container/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-10/internals/generators/container/saga.ts.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select, takeLatest } from 'redux-saga/effects';
2 | // import { actions } from './slice';
3 |
4 | // export function* doSomething() {}
5 |
6 | export function* {{ camelCase ComponentName }}Saga() {
7 | // yield takeLatest(actions.someAction.type, doSomething);
8 | }
9 |
--------------------------------------------------------------------------------
/chapter-10/internals/generators/container/selectors.ts.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from '@reduxjs/toolkit';
2 |
3 | import { RootState } from 'types';
4 | import { initialState } from './slice';
5 |
6 | const selectDomain = (state: RootState) => state.{{ camelCase ComponentName }} || initialState;
7 |
8 | export const select{{ properCase ComponentName }} = createSelector(
9 | [selectDomain],
10 | {{ camelCase ComponentName }}State => {{ camelCase ComponentName }}State,
11 | );
12 |
--------------------------------------------------------------------------------
/chapter-10/internals/generators/container/types.ts.hbs:
--------------------------------------------------------------------------------
1 | /* --- STATE --- */
2 | export interface {{ properCase ComponentName }}State {}
3 |
4 | export type ContainerState = {{ properCase ComponentName }}State;
--------------------------------------------------------------------------------
/chapter-10/internals/testing/loadable.mock.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function ExportedFunc() {
4 | return
My lazy-loaded component
;
5 | }
6 | export default ExportedFunc;
7 |
--------------------------------------------------------------------------------
/chapter-10/internals/ts-node.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "noEmit": true,
7 | "allowSyntheticDefaultImports": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-10/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-10/public/favicon.ico
--------------------------------------------------------------------------------
/chapter-10/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-10/public/logo192.png
--------------------------------------------------------------------------------
/chapter-10/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-10/public/logo512.png
--------------------------------------------------------------------------------
/chapter-10/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/chapter-10/src/api/axios.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | /*create an instance of axios with a default base URI when sending HTTP requests*/
4 | /*JSON Server has CORS Policy by default*/
5 | const api = axios.create({
6 | baseURL: 'http://localhost:5000/',
7 | });
8 |
9 | export default api;
10 |
11 | export const EndPoints = {
12 | sales: 'sales',
13 | products: 'products',
14 | events: 'events',
15 | };
16 |
--------------------------------------------------------------------------------
/chapter-10/src/app/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 |
4 | import { App } from '../index';
5 |
6 | const renderer = createRenderer();
7 |
8 | describe('', () => {
9 | it('should render and match the snapshot', () => {
10 | renderer.render();
11 | const renderedOutput = renderer.getRenderOutput();
12 | expect(renderedOutput).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-10/src/app/views/dashboard/product/ProductCreateView/schema/yupProductValidation.ts:
--------------------------------------------------------------------------------
1 | import * as Yup from 'yup';
2 |
3 | export const yupProductValidation = Yup.object().shape({
4 | category: Yup.string().max(255),
5 | description: Yup.string().max(5000),
6 | images: Yup.array(),
7 | includesTaxes: Yup.bool().required(),
8 | isTaxable: Yup.bool().required(),
9 | name: Yup.string().max(255).required(),
10 | price: Yup.number().min(0).required(),
11 | productCode: Yup.string().max(255),
12 | productSku: Yup.string().max(255),
13 | salePrice: Yup.number().min(0),
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-10/src/app/views/pages/AboutPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const AboutPage = () => {
4 | return (
5 |
6 |
This is About Page
7 |
8 | );
9 | };
10 |
11 | export default AboutPage;
12 |
--------------------------------------------------------------------------------
/chapter-10/src/app/views/pages/HomePage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | const Main = () => {
4 | return (
5 |
6 |
Main Page
7 |
8 | );
9 | };
10 |
11 | export default Main;
12 |
--------------------------------------------------------------------------------
/chapter-10/src/app/views/pages/NotFoundPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFoundPage = () => {
4 | return (
5 |
6 |
404 Page Not Found
7 |
8 | );
9 | };
10 |
11 | export default NotFoundPage;
12 |
--------------------------------------------------------------------------------
/chapter-10/src/locales/__tests__/i18n.test.ts:
--------------------------------------------------------------------------------
1 | import { i18n } from '../i18n';
2 |
3 | describe('i18n', () => {
4 | it('should initate i18n', async () => {
5 | const t = await i18n;
6 | expect(t).toBeDefined();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/chapter-10/src/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "welcome"
3 | }
4 |
--------------------------------------------------------------------------------
/chapter-10/src/locales/types.ts:
--------------------------------------------------------------------------------
1 | export type ConvertedToObjectType = {
2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType;
3 | };
4 |
--------------------------------------------------------------------------------
/chapter-10/src/models/calendar-type.ts:
--------------------------------------------------------------------------------
1 | export type EventType = {
2 | id: string;
3 | allDay: boolean;
4 | color?: string;
5 | description: string;
6 | end: Date;
7 | start: Date;
8 | title: string;
9 | };
10 |
11 | export type ViewType =
12 | | 'dayGridMonth'
13 | | 'timeGridWeek'
14 | | 'timeGridDay'
15 | | 'listWeek';
16 |
--------------------------------------------------------------------------------
/chapter-10/src/models/sale-type.ts:
--------------------------------------------------------------------------------
1 | export type SaleType = {
2 | name: string;
3 | data: number[];
4 | };
5 |
--------------------------------------------------------------------------------
/chapter-10/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
4 | ///
5 |
--------------------------------------------------------------------------------
/chapter-10/src/services/productService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { ProductType } from 'models/product-type';
3 |
4 | export async function getProductsAxios() {
5 | return await api.get(EndPoints.products);
6 | }
7 |
8 | export async function postProductAxios(product: ProductType) {
9 | return await api.post(EndPoints.products, product);
10 | }
11 |
--------------------------------------------------------------------------------
/chapter-10/src/services/saleService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { SaleType } from 'models/sale-type';
3 |
4 | export async function getSalesAxios() {
5 | return await api.get(EndPoints.sales);
6 | }
7 |
--------------------------------------------------------------------------------
/chapter-10/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';
6 |
7 | import 'react-app-polyfill/ie11';
8 | import 'react-app-polyfill/stable';
9 |
10 | import 'jest-styled-components';
11 |
--------------------------------------------------------------------------------
/chapter-10/src/store/reducers.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Combine all reducers in this file and export the combined reducers.
3 | */
4 |
5 | import { combineReducers } from '@reduxjs/toolkit';
6 | import calendarReducer from 'features/calendar/calendarSlice';
7 |
8 | const injectedReducers = {
9 | calendar: calendarReducer,
10 | };
11 |
12 | const rootReducer = combineReducers({
13 | ...injectedReducers,
14 | });
15 |
16 | export type RootState = ReturnType;
17 |
18 | export const createReducer = () => rootReducer;
19 |
--------------------------------------------------------------------------------
/chapter-10/src/styles/__tests__/media.test.ts:
--------------------------------------------------------------------------------
1 | import { media, sizes } from '../media';
2 | import { css } from 'styled-components/macro';
3 |
4 | describe('media', () => {
5 | it('should return media query in css', () => {
6 | const mediaQuery = media.small`color:red;`.join('');
7 | const cssVersion = css`
8 | @media (min-width: ${sizes.small}px) {
9 | color: red;
10 | }
11 | `.join('');
12 | expect(mediaQuery).toEqual(cssVersion);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-10/src/utils/bytes-to-size.ts:
--------------------------------------------------------------------------------
1 | const bytesToSize = (bytes: number, decimals: number = 2) => {
2 | if (bytes === 0) return '0 Bytes';
3 |
4 | const k = 1024;
5 | const dm = decimals < 0 ? 0 : decimals;
6 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
7 | const i = Math.floor(Math.log(bytes) / Math.log(k));
8 |
9 | return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
10 | };
11 |
12 | export default bytesToSize;
13 |
--------------------------------------------------------------------------------
/chapter-11/.babel-plugin-macrosrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styledComponents: {
3 | displayName: process.env.NODE_ENV !== 'production',
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/chapter-11/.env.local:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | EXTEND_ESLINT=true
--------------------------------------------------------------------------------
/chapter-11/.env.production:
--------------------------------------------------------------------------------
1 | GENERATE_SOURCEMAP=false
--------------------------------------------------------------------------------
/chapter-11/.eslintignore:
--------------------------------------------------------------------------------
1 | cypress
--------------------------------------------------------------------------------
/chapter-11/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't check auto-generated stuff into git
2 | coverage
3 | build
4 | node_modules
5 | stats.json
6 | .pnp
7 | .pnp.js
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | # boilerplate internals
20 | generated-cra-app
21 | .cra-template-rb
22 | template
23 |
24 | .eslintcache
--------------------------------------------------------------------------------
/chapter-11/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact = true
2 |
--------------------------------------------------------------------------------
/chapter-11/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/dubnium
2 |
--------------------------------------------------------------------------------
/chapter-11/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 | yarn.lock
--------------------------------------------------------------------------------
/chapter-11/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "arrowParens": "avoid"
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-11/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "processors": ["stylelint-processor-styled-components"],
3 | "extends": [
4 | "stylelint-config-recommended",
5 | "stylelint-config-styled-components"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/chapter-11/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/chapter-11/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-11/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-11/cypress/screenshots/All Integration Specs/my-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-11/cypress/screenshots/All Integration Specs/my-image.png
--------------------------------------------------------------------------------
/chapter-11/internals/generators/component/index.test.tsx.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 |
4 | import { {{ properCase ComponentName }} } from '..';
5 |
6 | describe('<{{ properCase ComponentName }} />', () => {
7 | it('should match snapshot', () => {
8 | const loadingIndicator = render(<{{ properCase ComponentName }} />);
9 | expect(loadingIndicator.container.firstChild).toMatchSnapshot();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/chapter-11/internals/generators/component/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-11/internals/generators/container/appendRootState.hbs:
--------------------------------------------------------------------------------
1 | {{ camelCase ComponentName }}?: {{ properCase ComponentName }}State;
2 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-11/internals/generators/container/importContainerState.hbs:
--------------------------------------------------------------------------------
1 | import { {{ properCase ComponentName }}State } from 'app/containers/{{ properCase ComponentName }}/types';
2 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-11/internals/generators/container/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-11/internals/generators/container/saga.ts.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select, takeLatest } from 'redux-saga/effects';
2 | // import { actions } from './slice';
3 |
4 | // export function* doSomething() {}
5 |
6 | export function* {{ camelCase ComponentName }}Saga() {
7 | // yield takeLatest(actions.someAction.type, doSomething);
8 | }
9 |
--------------------------------------------------------------------------------
/chapter-11/internals/generators/container/selectors.ts.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from '@reduxjs/toolkit';
2 |
3 | import { RootState } from 'types';
4 | import { initialState } from './slice';
5 |
6 | const selectDomain = (state: RootState) => state.{{ camelCase ComponentName }} || initialState;
7 |
8 | export const select{{ properCase ComponentName }} = createSelector(
9 | [selectDomain],
10 | {{ camelCase ComponentName }}State => {{ camelCase ComponentName }}State,
11 | );
12 |
--------------------------------------------------------------------------------
/chapter-11/internals/generators/container/types.ts.hbs:
--------------------------------------------------------------------------------
1 | /* --- STATE --- */
2 | export interface {{ properCase ComponentName }}State {}
3 |
4 | export type ContainerState = {{ properCase ComponentName }}State;
--------------------------------------------------------------------------------
/chapter-11/internals/testing/loadable.mock.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function ExportedFunc() {
4 | return My lazy-loaded component
;
5 | }
6 | export default ExportedFunc;
7 |
--------------------------------------------------------------------------------
/chapter-11/internals/ts-node.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "noEmit": true,
7 | "allowSyntheticDefaultImports": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-11/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-11/public/favicon.ico
--------------------------------------------------------------------------------
/chapter-11/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-11/public/logo192.png
--------------------------------------------------------------------------------
/chapter-11/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-11/public/logo512.png
--------------------------------------------------------------------------------
/chapter-11/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/chapter-11/src/api/axios.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | /*create an instance of axios with a default base URI when sending HTTP requests*/
4 | /*JSON Server has CORS Policy by default*/
5 | const api = axios.create({
6 | baseURL: 'http://localhost:5000/',
7 | });
8 |
9 | export default api;
10 |
11 | export const EndPoints = {
12 | sales: 'sales',
13 | products: 'products',
14 | events: 'events',
15 | login: 'login',
16 | register: 'register',
17 | };
18 |
--------------------------------------------------------------------------------
/chapter-11/src/app/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 |
4 | import { App } from '../index';
5 |
6 | const renderer = createRenderer();
7 |
8 | describe('', () => {
9 | it('should render and match the snapshot', () => {
10 | renderer.render();
11 | const renderedOutput = renderer.getRenderOutput();
12 | expect(renderedOutput).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-11/src/app/components/protected-route.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Redirect, Route } from 'react-router-dom';
3 |
4 | const ProtectedRoute = props => {
5 | const token = localStorage.getItem('token');
6 |
7 | return token ? (
8 |
9 | ) : (
10 |
11 | );
12 | };
13 |
14 | export default ProtectedRoute;
15 |
--------------------------------------------------------------------------------
/chapter-11/src/app/views/dashboard/product/ProductCreateView/schema/yupProductValidation.ts:
--------------------------------------------------------------------------------
1 | import * as Yup from 'yup';
2 |
3 | export const yupProductValidation = Yup.object().shape({
4 | category: Yup.string().max(255),
5 | description: Yup.string().max(5000),
6 | images: Yup.array(),
7 | includesTaxes: Yup.bool().required(),
8 | isTaxable: Yup.bool().required(),
9 | name: Yup.string().max(255).required(),
10 | price: Yup.number().min(0).required(),
11 | productCode: Yup.string().max(255),
12 | productSku: Yup.string().max(255),
13 | salePrice: Yup.number().min(0),
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-11/src/app/views/pages/AboutPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const AboutPage = () => {
4 | return (
5 |
6 |
This is About Page
7 |
8 | );
9 | };
10 |
11 | export default AboutPage;
12 |
--------------------------------------------------------------------------------
/chapter-11/src/app/views/pages/HomePage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | const Main = () => {
4 | return (
5 |
6 |
Main Page
7 |
8 | );
9 | };
10 |
11 | export default Main;
12 |
--------------------------------------------------------------------------------
/chapter-11/src/app/views/pages/NotFoundPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFoundPage = () => {
4 | return (
5 |
6 |
404 Page Not Found
7 |
8 | );
9 | };
10 |
11 | export default NotFoundPage;
12 |
--------------------------------------------------------------------------------
/chapter-11/src/locales/__tests__/i18n.test.ts:
--------------------------------------------------------------------------------
1 | import { i18n } from '../i18n';
2 |
3 | describe('i18n', () => {
4 | it('should initate i18n', async () => {
5 | const t = await i18n;
6 | expect(t).toBeDefined();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/chapter-11/src/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "welcome"
3 | }
4 |
--------------------------------------------------------------------------------
/chapter-11/src/locales/types.ts:
--------------------------------------------------------------------------------
1 | export type ConvertedToObjectType = {
2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType;
3 | };
4 |
--------------------------------------------------------------------------------
/chapter-11/src/models/calendar-type.ts:
--------------------------------------------------------------------------------
1 | export type EventType = {
2 | id: string;
3 | allDay: boolean;
4 | color?: string;
5 | description: string;
6 | end: Date;
7 | start: Date;
8 | title: string;
9 | };
10 |
11 | export type ViewType =
12 | | 'dayGridMonth'
13 | | 'timeGridWeek'
14 | | 'timeGridDay'
15 | | 'listWeek';
16 |
--------------------------------------------------------------------------------
/chapter-11/src/models/sale-type.ts:
--------------------------------------------------------------------------------
1 | export type SaleType = {
2 | name: string;
3 | data: number[];
4 | };
5 |
--------------------------------------------------------------------------------
/chapter-11/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
4 | ///
5 |
--------------------------------------------------------------------------------
/chapter-11/src/services/productService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { ProductType } from 'models/product-type';
3 |
4 | export async function getProductsAxios() {
5 | return await api.get(EndPoints.products);
6 | }
7 |
8 | export async function postProductAxios(product: ProductType) {
9 | return await api.post(EndPoints.products, product);
10 | }
11 |
--------------------------------------------------------------------------------
/chapter-11/src/services/saleService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { SaleType } from 'models/sale-type';
3 |
4 | export async function getSalesAxios() {
5 | return await api.get(EndPoints.sales);
6 | }
7 |
--------------------------------------------------------------------------------
/chapter-11/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';
6 |
7 | import 'react-app-polyfill/ie11';
8 | import 'react-app-polyfill/stable';
9 |
10 | import 'jest-styled-components';
11 |
--------------------------------------------------------------------------------
/chapter-11/src/store/reducers.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Combine all reducers in this file and export the combined reducers.
3 | */
4 |
5 | import { combineReducers } from '@reduxjs/toolkit';
6 | import calendarReducer from 'features/calendar/calendarSlice';
7 |
8 | const injectedReducers = {
9 | calendar: calendarReducer,
10 | };
11 |
12 | const rootReducer = combineReducers({
13 | ...injectedReducers,
14 | });
15 |
16 | export type RootState = ReturnType;
17 |
18 | export const createReducer = () => rootReducer;
19 |
--------------------------------------------------------------------------------
/chapter-11/src/styles/__tests__/media.test.ts:
--------------------------------------------------------------------------------
1 | import { media, sizes } from '../media';
2 | import { css } from 'styled-components/macro';
3 |
4 | describe('media', () => {
5 | it('should return media query in css', () => {
6 | const mediaQuery = media.small`color:red;`.join('');
7 | const cssVersion = css`
8 | @media (min-width: ${sizes.small}px) {
9 | color: red;
10 | }
11 | `.join('');
12 | expect(mediaQuery).toEqual(cssVersion);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-11/src/utils/bytes-to-size.ts:
--------------------------------------------------------------------------------
1 | const bytesToSize = (bytes: number, decimals: number = 2) => {
2 | if (bytes === 0) return '0 Bytes';
3 |
4 | const k = 1024;
5 | const dm = decimals < 0 ? 0 : decimals;
6 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
7 | const i = Math.floor(Math.log(bytes) / Math.log(k));
8 |
9 | return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
10 | };
11 |
12 | export default bytesToSize;
13 |
--------------------------------------------------------------------------------
/chapter-12/.babel-plugin-macrosrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styledComponents: {
3 | displayName: process.env.NODE_ENV !== 'production',
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/chapter-12/.env.local:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | EXTEND_ESLINT=true
--------------------------------------------------------------------------------
/chapter-12/.env.production:
--------------------------------------------------------------------------------
1 | GENERATE_SOURCEMAP=false
--------------------------------------------------------------------------------
/chapter-12/.eslintignore:
--------------------------------------------------------------------------------
1 | cypress
--------------------------------------------------------------------------------
/chapter-12/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't check auto-generated stuff into git
2 | coverage
3 | build
4 | node_modules
5 | stats.json
6 | .pnp
7 | .pnp.js
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | # boilerplate internals
20 | generated-cra-app
21 | .cra-template-rb
22 | template
23 |
24 | .eslintcache
--------------------------------------------------------------------------------
/chapter-12/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact = true
2 |
--------------------------------------------------------------------------------
/chapter-12/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/dubnium
2 |
--------------------------------------------------------------------------------
/chapter-12/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 | yarn.lock
--------------------------------------------------------------------------------
/chapter-12/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "arrowParens": "avoid"
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-12/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "processors": ["stylelint-processor-styled-components"],
3 | "extends": [
4 | "stylelint-config-recommended",
5 | "stylelint-config-styled-components"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/chapter-12/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/chapter-12/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-12/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-12/cypress/screenshots/All Integration Specs/my-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-12/cypress/screenshots/All Integration Specs/my-image.png
--------------------------------------------------------------------------------
/chapter-12/internals/generators/component/index.test.tsx.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 |
4 | import { {{ properCase ComponentName }} } from '..';
5 |
6 | describe('<{{ properCase ComponentName }} />', () => {
7 | it('should match snapshot', () => {
8 | const loadingIndicator = render(<{{ properCase ComponentName }} />);
9 | expect(loadingIndicator.container.firstChild).toMatchSnapshot();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/chapter-12/internals/generators/component/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-12/internals/generators/container/appendRootState.hbs:
--------------------------------------------------------------------------------
1 | {{ camelCase ComponentName }}?: {{ properCase ComponentName }}State;
2 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-12/internals/generators/container/importContainerState.hbs:
--------------------------------------------------------------------------------
1 | import { {{ properCase ComponentName }}State } from 'app/containers/{{ properCase ComponentName }}/types';
2 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-12/internals/generators/container/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-12/internals/generators/container/saga.ts.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select, takeLatest } from 'redux-saga/effects';
2 | // import { actions } from './slice';
3 |
4 | // export function* doSomething() {}
5 |
6 | export function* {{ camelCase ComponentName }}Saga() {
7 | // yield takeLatest(actions.someAction.type, doSomething);
8 | }
9 |
--------------------------------------------------------------------------------
/chapter-12/internals/generators/container/selectors.ts.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from '@reduxjs/toolkit';
2 |
3 | import { RootState } from 'types';
4 | import { initialState } from './slice';
5 |
6 | const selectDomain = (state: RootState) => state.{{ camelCase ComponentName }} || initialState;
7 |
8 | export const select{{ properCase ComponentName }} = createSelector(
9 | [selectDomain],
10 | {{ camelCase ComponentName }}State => {{ camelCase ComponentName }}State,
11 | );
12 |
--------------------------------------------------------------------------------
/chapter-12/internals/generators/container/types.ts.hbs:
--------------------------------------------------------------------------------
1 | /* --- STATE --- */
2 | export interface {{ properCase ComponentName }}State {}
3 |
4 | export type ContainerState = {{ properCase ComponentName }}State;
--------------------------------------------------------------------------------
/chapter-12/internals/testing/loadable.mock.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function ExportedFunc() {
4 | return My lazy-loaded component
;
5 | }
6 | export default ExportedFunc;
7 |
--------------------------------------------------------------------------------
/chapter-12/internals/ts-node.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "noEmit": true,
7 | "allowSyntheticDefaultImports": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-12/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-12/public/favicon.ico
--------------------------------------------------------------------------------
/chapter-12/public/images/avatar_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-12/public/images/avatar_6.png
--------------------------------------------------------------------------------
/chapter-12/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-12/public/logo192.png
--------------------------------------------------------------------------------
/chapter-12/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-12/public/logo512.png
--------------------------------------------------------------------------------
/chapter-12/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/chapter-12/src/api/axios.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | /*create an instance of axios with a default base URI when sending HTTP requests*/
4 | /*JSON Server has CORS Policy by default*/
5 | const api = axios.create({
6 | baseURL: 'http://localhost:5000/',
7 | });
8 |
9 | export default api;
10 |
11 | export const EndPoints = {
12 | sales: 'sales',
13 | products: 'products',
14 | events: 'events',
15 | login: 'login',
16 | register: 'register',
17 | users: 'users',
18 | usersDb: 'users-db',
19 | };
20 |
--------------------------------------------------------------------------------
/chapter-12/src/app/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 |
4 | import { App } from '../index';
5 |
6 | const renderer = createRenderer();
7 |
8 | describe('', () => {
9 | it('should render and match the snapshot', () => {
10 | renderer.render();
11 | const renderedOutput = renderer.getRenderOutput();
12 | expect(renderedOutput).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-12/src/app/views/dashboard/product/ProductCreateView/schema/yupProductValidation.ts:
--------------------------------------------------------------------------------
1 | import * as Yup from 'yup';
2 |
3 | export const yupProductValidation = Yup.object().shape({
4 | category: Yup.string().max(255),
5 | description: Yup.string().max(5000),
6 | images: Yup.array(),
7 | includesTaxes: Yup.bool().required(),
8 | isTaxable: Yup.bool().required(),
9 | name: Yup.string().max(255).required(),
10 | price: Yup.number().min(0).required(),
11 | productCode: Yup.string().max(255),
12 | productSku: Yup.string().max(255),
13 | salePrice: Yup.number().min(0),
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-12/src/app/views/pages/AboutPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const AboutPage = () => {
4 | return (
5 |
6 |
This is About Page
7 |
8 | );
9 | };
10 |
11 | export default AboutPage;
12 |
--------------------------------------------------------------------------------
/chapter-12/src/app/views/pages/HomePage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | const Main = () => {
4 | return (
5 |
6 |
Main Page
7 |
8 | );
9 | };
10 |
11 | export default Main;
12 |
--------------------------------------------------------------------------------
/chapter-12/src/app/views/pages/NotFoundPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFoundPage = () => {
4 | return (
5 |
6 |
404 Page Not Found
7 |
8 | );
9 | };
10 |
11 | export default NotFoundPage;
12 |
--------------------------------------------------------------------------------
/chapter-12/src/features/profile/profileActionTypes.ts:
--------------------------------------------------------------------------------
1 | import { UserType } from 'models/user-type';
2 |
3 | export type ProfileStateType = {
4 | readonly profile: UserType;
5 | readonly loading: boolean;
6 | readonly error: string;
7 | };
8 |
9 | export const profileNamespace = 'profile';
10 |
11 | /* action types */
12 |
13 | export const ProfileActionTypes = {
14 | FETCH_AND_SAVE_PROFILE: `${profileNamespace}/FETCH_AND_SAVE_PROFILE`,
15 | UPDATE_PROFILE: `${profileNamespace}/UPDATE_PROFILE`,
16 | };
17 |
--------------------------------------------------------------------------------
/chapter-12/src/features/profile/yup/profile.validation.ts:
--------------------------------------------------------------------------------
1 | /* Validation for each input of profile form */
2 | import * as Yup from 'yup';
3 |
4 | const profileYupObject = Yup.object().shape({
5 | canHire: Yup.bool(),
6 | city: Yup.string().max(255),
7 | country: Yup.string().max(255),
8 | email: Yup.string()
9 | .email('Must be a valid email')
10 | .max(255)
11 | .required('Email is required'),
12 | isPublic: Yup.bool(),
13 | name: Yup.string().max(255).required('Name is required'),
14 | phone: Yup.string(),
15 | state: Yup.string(),
16 | });
17 |
18 | export { profileYupObject };
19 |
--------------------------------------------------------------------------------
/chapter-12/src/locales/__tests__/i18n.test.ts:
--------------------------------------------------------------------------------
1 | import { i18n } from '../i18n';
2 |
3 | describe('i18n', () => {
4 | it('should initate i18n', async () => {
5 | const t = await i18n;
6 | expect(t).toBeDefined();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/chapter-12/src/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "welcome"
3 | }
4 |
--------------------------------------------------------------------------------
/chapter-12/src/locales/types.ts:
--------------------------------------------------------------------------------
1 | export type ConvertedToObjectType = {
2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType;
3 | };
4 |
--------------------------------------------------------------------------------
/chapter-12/src/models/calendar-type.ts:
--------------------------------------------------------------------------------
1 | export type EventType = {
2 | id: string;
3 | allDay: boolean;
4 | color?: string;
5 | description: string;
6 | end: Date;
7 | start: Date;
8 | title: string;
9 | };
10 |
11 | export type ViewType =
12 | | 'dayGridMonth'
13 | | 'timeGridWeek'
14 | | 'timeGridDay'
15 | | 'listWeek';
16 |
--------------------------------------------------------------------------------
/chapter-12/src/models/claims-type.ts:
--------------------------------------------------------------------------------
1 | export type ClaimsType = {
2 | readonly email: string;
3 | readonly iat: number;
4 | readonly exp: number;
5 | readonly sub: string;
6 | };
7 |
--------------------------------------------------------------------------------
/chapter-12/src/models/sale-type.ts:
--------------------------------------------------------------------------------
1 | export type SaleType = {
2 | name: string;
3 | data: number[];
4 | };
5 |
--------------------------------------------------------------------------------
/chapter-12/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
4 | ///
5 |
--------------------------------------------------------------------------------
/chapter-12/src/services/productService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { ProductType } from 'models/product-type';
3 |
4 | export async function getProductsAxios() {
5 | return await api.get(EndPoints.products);
6 | }
7 |
8 | export async function postProductAxios(product: ProductType) {
9 | return await api.post(EndPoints.products, product);
10 | }
11 |
--------------------------------------------------------------------------------
/chapter-12/src/services/saleService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { SaleType } from 'models/sale-type';
3 |
4 | export async function getSalesAxios() {
5 | return await api.get(EndPoints.sales);
6 | }
7 |
--------------------------------------------------------------------------------
/chapter-12/src/services/userDbService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { UserType } from 'models/user-type';
3 |
4 | export async function getUserByIdFromDbAxios(id: string) {
5 | return await api.get(`${EndPoints.usersDb}/${id}`);
6 | }
7 |
8 | export async function putUserFromDbAxios(user: UserType) {
9 | return await api.put(`${EndPoints.usersDb}/${user.id}`, user);
10 | }
11 |
--------------------------------------------------------------------------------
/chapter-12/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';
6 |
7 | import 'react-app-polyfill/ie11';
8 | import 'react-app-polyfill/stable';
9 |
10 | import 'jest-styled-components';
11 |
--------------------------------------------------------------------------------
/chapter-12/src/styles/__tests__/media.test.ts:
--------------------------------------------------------------------------------
1 | import { media, sizes } from '../media';
2 | import { css } from 'styled-components/macro';
3 |
4 | describe('media', () => {
5 | it('should return media query in css', () => {
6 | const mediaQuery = media.small`color:red;`.join('');
7 | const cssVersion = css`
8 | @media (min-width: ${sizes.small}px) {
9 | color: red;
10 | }
11 | `.join('');
12 | expect(mediaQuery).toEqual(cssVersion);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-12/src/utils/bytes-to-size.ts:
--------------------------------------------------------------------------------
1 | const bytesToSize = (bytes: number, decimals: number = 2) => {
2 | if (bytes === 0) return '0 Bytes';
3 |
4 | const k = 1024;
5 | const dm = decimals < 0 ? 0 : decimals;
6 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
7 | const i = Math.floor(Math.log(bytes) / Math.log(k));
8 |
9 | return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
10 | };
11 |
12 | export default bytesToSize;
13 |
--------------------------------------------------------------------------------
/chapter-13/.babel-plugin-macrosrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styledComponents: {
3 | displayName: process.env.NODE_ENV !== 'production',
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/chapter-13/.env.local:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | EXTEND_ESLINT=true
--------------------------------------------------------------------------------
/chapter-13/.env.production:
--------------------------------------------------------------------------------
1 | GENERATE_SOURCEMAP=false
--------------------------------------------------------------------------------
/chapter-13/.eslintignore:
--------------------------------------------------------------------------------
1 | cypress
--------------------------------------------------------------------------------
/chapter-13/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't check auto-generated stuff into git
2 | coverage
3 | build
4 | node_modules
5 | stats.json
6 | .pnp
7 | .pnp.js
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | # boilerplate internals
20 | generated-cra-app
21 | .cra-template-rb
22 | template
23 |
24 | .eslintcache
--------------------------------------------------------------------------------
/chapter-13/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact = true
2 |
--------------------------------------------------------------------------------
/chapter-13/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/dubnium
2 |
--------------------------------------------------------------------------------
/chapter-13/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 | yarn.lock
--------------------------------------------------------------------------------
/chapter-13/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "arrowParens": "avoid"
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-13/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "processors": ["stylelint-processor-styled-components"],
3 | "extends": [
4 | "stylelint-config-recommended",
5 | "stylelint-config-styled-components"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/chapter-13/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/chapter-13/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-13/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-13/cypress/screenshots/All Integration Specs/my-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-13/cypress/screenshots/All Integration Specs/my-image.png
--------------------------------------------------------------------------------
/chapter-13/internals/generators/component/index.test.tsx.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 |
4 | import { {{ properCase ComponentName }} } from '..';
5 |
6 | describe('<{{ properCase ComponentName }} />', () => {
7 | it('should match snapshot', () => {
8 | const loadingIndicator = render(<{{ properCase ComponentName }} />);
9 | expect(loadingIndicator.container.firstChild).toMatchSnapshot();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/chapter-13/internals/generators/component/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-13/internals/generators/container/appendRootState.hbs:
--------------------------------------------------------------------------------
1 | {{ camelCase ComponentName }}?: {{ properCase ComponentName }}State;
2 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-13/internals/generators/container/importContainerState.hbs:
--------------------------------------------------------------------------------
1 | import { {{ properCase ComponentName }}State } from 'app/containers/{{ properCase ComponentName }}/types';
2 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-13/internals/generators/container/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-13/internals/generators/container/saga.ts.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select, takeLatest } from 'redux-saga/effects';
2 | // import { actions } from './slice';
3 |
4 | // export function* doSomething() {}
5 |
6 | export function* {{ camelCase ComponentName }}Saga() {
7 | // yield takeLatest(actions.someAction.type, doSomething);
8 | }
9 |
--------------------------------------------------------------------------------
/chapter-13/internals/generators/container/selectors.ts.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from '@reduxjs/toolkit';
2 |
3 | import { RootState } from 'types';
4 | import { initialState } from './slice';
5 |
6 | const selectDomain = (state: RootState) => state.{{ camelCase ComponentName }} || initialState;
7 |
8 | export const select{{ properCase ComponentName }} = createSelector(
9 | [selectDomain],
10 | {{ camelCase ComponentName }}State => {{ camelCase ComponentName }}State,
11 | );
12 |
--------------------------------------------------------------------------------
/chapter-13/internals/generators/container/types.ts.hbs:
--------------------------------------------------------------------------------
1 | /* --- STATE --- */
2 | export interface {{ properCase ComponentName }}State {}
3 |
4 | export type ContainerState = {{ properCase ComponentName }}State;
--------------------------------------------------------------------------------
/chapter-13/internals/testing/loadable.mock.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function ExportedFunc() {
4 | return My lazy-loaded component
;
5 | }
6 | export default ExportedFunc;
7 |
--------------------------------------------------------------------------------
/chapter-13/internals/ts-node.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "noEmit": true,
7 | "allowSyntheticDefaultImports": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-13/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-13/public/favicon.ico
--------------------------------------------------------------------------------
/chapter-13/public/images/avatar_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-13/public/images/avatar_6.png
--------------------------------------------------------------------------------
/chapter-13/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-13/public/logo192.png
--------------------------------------------------------------------------------
/chapter-13/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-13/public/logo512.png
--------------------------------------------------------------------------------
/chapter-13/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/chapter-13/src/api/axios.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | /*create an instance of axios with a default base URI when sending HTTP requests*/
4 | /*JSON Server has CORS Policy by default*/
5 | const api = axios.create({
6 | baseURL: 'http://localhost:5000/',
7 | });
8 |
9 | export default api;
10 |
11 | export const EndPoints = {
12 | sales: 'sales',
13 | products: 'products',
14 | events: 'events',
15 | login: 'login',
16 | register: 'register',
17 | users: 'users',
18 | usersDb: 'users-db',
19 | };
20 |
--------------------------------------------------------------------------------
/chapter-13/src/app/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 |
4 | import { App } from '../index';
5 |
6 | const renderer = createRenderer();
7 |
8 | describe('', () => {
9 | it('should render and match the snapshot', () => {
10 | renderer.render();
11 | const renderedOutput = renderer.getRenderOutput();
12 | expect(renderedOutput).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-13/src/app/views/dashboard/product/ProductCreateView/schema/yupProductValidation.ts:
--------------------------------------------------------------------------------
1 | import * as Yup from 'yup';
2 |
3 | export const yupProductValidation = Yup.object().shape({
4 | category: Yup.string().max(255),
5 | description: Yup.string().max(5000),
6 | images: Yup.array(),
7 | includesTaxes: Yup.bool().required(),
8 | isTaxable: Yup.bool().required(),
9 | name: Yup.string().max(255).required(),
10 | price: Yup.number().min(0).required(),
11 | productCode: Yup.string().max(255),
12 | productSku: Yup.string().max(255),
13 | salePrice: Yup.number().min(0),
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-13/src/features/profile/profileActionTypes.ts:
--------------------------------------------------------------------------------
1 | import { UserType } from 'models/user-type';
2 |
3 | export type ProfileStateType = {
4 | readonly profile: UserType;
5 | readonly loading: boolean;
6 | readonly error: string;
7 | };
8 |
9 | export const profileNamespace = 'profile';
10 |
11 | /* action types */
12 |
13 | export const ProfileActionTypes = {
14 | FETCH_AND_SAVE_PROFILE: `${profileNamespace}/FETCH_AND_SAVE_PROFILE`,
15 | UPDATE_PROFILE: `${profileNamespace}/UPDATE_PROFILE`,
16 | };
17 |
--------------------------------------------------------------------------------
/chapter-13/src/features/profile/yup/profile.validation.ts:
--------------------------------------------------------------------------------
1 | /* Validation for each input of profile form */
2 | import * as Yup from 'yup';
3 |
4 | const profileYupObject = Yup.object().shape({
5 | canHire: Yup.bool(),
6 | city: Yup.string().max(255),
7 | country: Yup.string().max(255),
8 | email: Yup.string()
9 | .email('Must be a valid email')
10 | .max(255)
11 | .required('Email is required'),
12 | isPublic: Yup.bool(),
13 | name: Yup.string().max(255).required('Name is required'),
14 | phone: Yup.string(),
15 | state: Yup.string(),
16 | });
17 |
18 | export { profileYupObject };
19 |
--------------------------------------------------------------------------------
/chapter-13/src/locales/__tests__/i18n.test.ts:
--------------------------------------------------------------------------------
1 | import { i18n } from '../i18n';
2 |
3 | describe('i18n', () => {
4 | it('should initate i18n', async () => {
5 | const t = await i18n;
6 | expect(t).toBeDefined();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/chapter-13/src/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "welcome"
3 | }
4 |
--------------------------------------------------------------------------------
/chapter-13/src/locales/types.ts:
--------------------------------------------------------------------------------
1 | export type ConvertedToObjectType = {
2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType;
3 | };
4 |
--------------------------------------------------------------------------------
/chapter-13/src/models/calendar-type.ts:
--------------------------------------------------------------------------------
1 | export type EventType = {
2 | id: string;
3 | allDay: boolean;
4 | color?: string;
5 | description: string;
6 | end: Date;
7 | start: Date;
8 | title: string;
9 | };
10 |
11 | export type ViewType =
12 | | 'dayGridMonth'
13 | | 'timeGridWeek'
14 | | 'timeGridDay'
15 | | 'listWeek';
16 |
--------------------------------------------------------------------------------
/chapter-13/src/models/claims-type.ts:
--------------------------------------------------------------------------------
1 | export type ClaimsType = {
2 | readonly email: string;
3 | readonly iat: number;
4 | readonly exp: number;
5 | readonly sub: string;
6 | };
7 |
--------------------------------------------------------------------------------
/chapter-13/src/models/sale-type.ts:
--------------------------------------------------------------------------------
1 | export type SaleType = {
2 | name: string;
3 | data: number[];
4 | };
5 |
--------------------------------------------------------------------------------
/chapter-13/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
4 | ///
5 |
--------------------------------------------------------------------------------
/chapter-13/src/services/productService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { ProductType } from 'models/product-type';
3 |
4 | export async function getProductsAxios() {
5 | return await api.get(EndPoints.products);
6 | }
7 |
8 | export async function postProductAxios(product: ProductType) {
9 | return await api.post(EndPoints.products, product);
10 | }
11 |
--------------------------------------------------------------------------------
/chapter-13/src/services/saleService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { SaleType } from 'models/sale-type';
3 |
4 | export async function getSalesAxios() {
5 | return await api.get(EndPoints.sales);
6 | }
7 |
--------------------------------------------------------------------------------
/chapter-13/src/services/userDbService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { UserType } from 'models/user-type';
3 |
4 | export async function getUserByIdFromDbAxios(id: string) {
5 | return await api.get(`${EndPoints.usersDb}/${id}`);
6 | }
7 |
8 | export async function putUserFromDbAxios(user: UserType) {
9 | return await api.put(`${EndPoints.usersDb}/${user.id}`, user);
10 | }
11 |
--------------------------------------------------------------------------------
/chapter-13/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';
6 |
7 | import 'react-app-polyfill/ie11';
8 | import 'react-app-polyfill/stable';
9 |
10 | import 'jest-styled-components';
11 |
--------------------------------------------------------------------------------
/chapter-13/src/styles/__tests__/media.test.ts:
--------------------------------------------------------------------------------
1 | import { media, sizes } from '../media';
2 | import { css } from 'styled-components/macro';
3 |
4 | describe('media', () => {
5 | it('should return media query in css', () => {
6 | const mediaQuery = media.small`color:red;`.join('');
7 | const cssVersion = css`
8 | @media (min-width: ${sizes.small}px) {
9 | color: red;
10 | }
11 | `.join('');
12 | expect(mediaQuery).toEqual(cssVersion);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-13/src/utils/bytes-to-size.ts:
--------------------------------------------------------------------------------
1 | const bytesToSize = (bytes: number, decimals: number = 2) => {
2 | if (bytes === 0) return '0 Bytes';
3 |
4 | const k = 1024;
5 | const dm = decimals < 0 ? 0 : decimals;
6 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
7 | const i = Math.floor(Math.log(bytes) / Math.log(k));
8 |
9 | return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
10 | };
11 |
12 | export default bytesToSize;
13 |
--------------------------------------------------------------------------------
/chapter-14/README.md:
--------------------------------------------------------------------------------
1 | # No coding in this chapter
--------------------------------------------------------------------------------
/chapter-15/.babel-plugin-macrosrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styledComponents: {
3 | displayName: process.env.NODE_ENV !== 'production',
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/chapter-15/.dockerignore:
--------------------------------------------------------------------------------
1 | cypress
2 | node_modules
--------------------------------------------------------------------------------
/chapter-15/.env.local:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | EXTEND_ESLINT=true
--------------------------------------------------------------------------------
/chapter-15/.env.production:
--------------------------------------------------------------------------------
1 | GENERATE_SOURCEMAP=false
--------------------------------------------------------------------------------
/chapter-15/.eslintignore:
--------------------------------------------------------------------------------
1 | cypress
--------------------------------------------------------------------------------
/chapter-15/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't check auto-generated stuff into git
2 | coverage
3 | build
4 | node_modules
5 | stats.json
6 | .pnp
7 | .pnp.js
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | # boilerplate internals
20 | generated-cra-app
21 | .cra-template-rb
22 | template
23 |
24 | .eslintcache
--------------------------------------------------------------------------------
/chapter-15/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact = true
2 |
--------------------------------------------------------------------------------
/chapter-15/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/dubnium
2 |
--------------------------------------------------------------------------------
/chapter-15/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 | yarn.lock
--------------------------------------------------------------------------------
/chapter-15/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "arrowParens": "avoid"
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-15/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "processors": ["stylelint-processor-styled-components"],
3 | "extends": [
4 | "stylelint-config-recommended",
5 | "stylelint-config-styled-components"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/chapter-15/Dockerfile:
--------------------------------------------------------------------------------
1 | # Stage 1
2 | FROM node:15-alpine as build
3 | WORKDIR /app
4 | ENV PATH /app/node_modules/.bin:$PATH
5 | COPY package.json ./
6 | COPY package-lock.json ./
7 | RUN npm install
8 | COPY . ./
9 | RUN npm run build
10 |
11 | # Stage 2
12 | FROM nginx:stable-alpine
13 | COPY --from=build /app/build /usr/share/nginx/html
14 | COPY nginx.conf /etc/nginx/conf.d/default.conf
15 | EXPOSE 80
16 | CMD ["nginx", "-g", "daemon off;"]
--------------------------------------------------------------------------------
/chapter-15/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/chapter-15/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-15/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-15/cypress/screenshots/All Integration Specs/my-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-15/cypress/screenshots/All Integration Specs/my-image.png
--------------------------------------------------------------------------------
/chapter-15/internals/generators/component/index.test.tsx.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 |
4 | import { {{ properCase ComponentName }} } from '..';
5 |
6 | describe('<{{ properCase ComponentName }} />', () => {
7 | it('should match snapshot', () => {
8 | const loadingIndicator = render(<{{ properCase ComponentName }} />);
9 | expect(loadingIndicator.container.firstChild).toMatchSnapshot();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/chapter-15/internals/generators/component/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-15/internals/generators/container/appendRootState.hbs:
--------------------------------------------------------------------------------
1 | {{ camelCase ComponentName }}?: {{ properCase ComponentName }}State;
2 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-15/internals/generators/container/importContainerState.hbs:
--------------------------------------------------------------------------------
1 | import { {{ properCase ComponentName }}State } from 'app/containers/{{ properCase ComponentName }}/types';
2 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-15/internals/generators/container/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-15/internals/generators/container/saga.ts.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select, takeLatest } from 'redux-saga/effects';
2 | // import { actions } from './slice';
3 |
4 | // export function* doSomething() {}
5 |
6 | export function* {{ camelCase ComponentName }}Saga() {
7 | // yield takeLatest(actions.someAction.type, doSomething);
8 | }
9 |
--------------------------------------------------------------------------------
/chapter-15/internals/generators/container/selectors.ts.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from '@reduxjs/toolkit';
2 |
3 | import { RootState } from 'types';
4 | import { initialState } from './slice';
5 |
6 | const selectDomain = (state: RootState) => state.{{ camelCase ComponentName }} || initialState;
7 |
8 | export const select{{ properCase ComponentName }} = createSelector(
9 | [selectDomain],
10 | {{ camelCase ComponentName }}State => {{ camelCase ComponentName }}State,
11 | );
12 |
--------------------------------------------------------------------------------
/chapter-15/internals/generators/container/types.ts.hbs:
--------------------------------------------------------------------------------
1 | /* --- STATE --- */
2 | export interface {{ properCase ComponentName }}State {}
3 |
4 | export type ContainerState = {{ properCase ComponentName }}State;
--------------------------------------------------------------------------------
/chapter-15/internals/testing/loadable.mock.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function ExportedFunc() {
4 | return My lazy-loaded component
;
5 | }
6 | export default ExportedFunc;
7 |
--------------------------------------------------------------------------------
/chapter-15/internals/ts-node.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "noEmit": true,
7 | "allowSyntheticDefaultImports": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-15/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 |
3 | listen 80;
4 |
5 | location / {
6 | root /usr/share/nginx/html;
7 | index index.html index.htm;
8 | try_files $uri $uri/ /index.html;
9 | }
10 |
11 | error_page 500 502 503 504 /50x.html;
12 |
13 | location = /50x.html {
14 | root /usr/share/nginx/html;
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/chapter-15/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-15/public/favicon.ico
--------------------------------------------------------------------------------
/chapter-15/public/images/avatar_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-15/public/images/avatar_6.png
--------------------------------------------------------------------------------
/chapter-15/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-15/public/logo192.png
--------------------------------------------------------------------------------
/chapter-15/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-15/public/logo512.png
--------------------------------------------------------------------------------
/chapter-15/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/chapter-15/src/api/axios.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | /*create an instance of axios with a default base URI when sending HTTP requests*/
4 | /*JSON Server has CORS Policy by default*/
5 | const api = axios.create({
6 | baseURL: 'http://localhost:5000/',
7 | });
8 |
9 | export default api;
10 |
11 | export const EndPoints = {
12 | sales: 'sales',
13 | products: 'products',
14 | events: 'events',
15 | login: 'login',
16 | register: 'register',
17 | users: 'users',
18 | usersDb: 'users-db',
19 | };
20 |
--------------------------------------------------------------------------------
/chapter-15/src/app/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 |
4 | import { App } from '../index';
5 |
6 | const renderer = createRenderer();
7 |
8 | describe('', () => {
9 | it('should render and match the snapshot', () => {
10 | renderer.render();
11 | const renderedOutput = renderer.getRenderOutput();
12 | expect(renderedOutput).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-15/src/app/views/dashboard/product/ProductCreateView/schema/yupProductValidation.ts:
--------------------------------------------------------------------------------
1 | import * as Yup from 'yup';
2 |
3 | export const yupProductValidation = Yup.object().shape({
4 | category: Yup.string().max(255),
5 | description: Yup.string().max(5000),
6 | images: Yup.array(),
7 | includesTaxes: Yup.bool().required(),
8 | isTaxable: Yup.bool().required(),
9 | name: Yup.string().max(255).required(),
10 | price: Yup.number().min(0).required(),
11 | productCode: Yup.string().max(255),
12 | productSku: Yup.string().max(255),
13 | salePrice: Yup.number().min(0),
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-15/src/features/profile/profileActionTypes.ts:
--------------------------------------------------------------------------------
1 | import { UserType } from 'models/user-type';
2 |
3 | export type ProfileStateType = {
4 | readonly profile: UserType;
5 | readonly loading: boolean;
6 | readonly error: string;
7 | };
8 |
9 | export const profileNamespace = 'profile';
10 |
11 | /* action types */
12 |
13 | export const ProfileActionTypes = {
14 | FETCH_AND_SAVE_PROFILE: `${profileNamespace}/FETCH_AND_SAVE_PROFILE`,
15 | UPDATE_PROFILE: `${profileNamespace}/UPDATE_PROFILE`,
16 | };
17 |
--------------------------------------------------------------------------------
/chapter-15/src/features/profile/yup/profile.validation.ts:
--------------------------------------------------------------------------------
1 | /* Validation for each input of profile form */
2 | import * as Yup from 'yup';
3 |
4 | const profileYupObject = Yup.object().shape({
5 | canHire: Yup.bool(),
6 | city: Yup.string().max(255),
7 | country: Yup.string().max(255),
8 | email: Yup.string()
9 | .email('Must be a valid email')
10 | .max(255)
11 | .required('Email is required'),
12 | isPublic: Yup.bool(),
13 | name: Yup.string().max(255).required('Name is required'),
14 | phone: Yup.string(),
15 | state: Yup.string(),
16 | });
17 |
18 | export { profileYupObject };
19 |
--------------------------------------------------------------------------------
/chapter-15/src/locales/__tests__/i18n.test.ts:
--------------------------------------------------------------------------------
1 | import { i18n } from '../i18n';
2 |
3 | describe('i18n', () => {
4 | it('should initate i18n', async () => {
5 | const t = await i18n;
6 | expect(t).toBeDefined();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/chapter-15/src/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "welcome"
3 | }
4 |
--------------------------------------------------------------------------------
/chapter-15/src/locales/types.ts:
--------------------------------------------------------------------------------
1 | export type ConvertedToObjectType = {
2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType;
3 | };
4 |
--------------------------------------------------------------------------------
/chapter-15/src/models/calendar-type.ts:
--------------------------------------------------------------------------------
1 | export type EventType = {
2 | id: string;
3 | allDay: boolean;
4 | color?: string;
5 | description: string;
6 | end: Date;
7 | start: Date;
8 | title: string;
9 | };
10 |
11 | export type ViewType =
12 | | 'dayGridMonth'
13 | | 'timeGridWeek'
14 | | 'timeGridDay'
15 | | 'listWeek';
16 |
--------------------------------------------------------------------------------
/chapter-15/src/models/claims-type.ts:
--------------------------------------------------------------------------------
1 | export type ClaimsType = {
2 | readonly email: string;
3 | readonly iat: number;
4 | readonly exp: number;
5 | readonly sub: string;
6 | };
7 |
--------------------------------------------------------------------------------
/chapter-15/src/models/sale-type.ts:
--------------------------------------------------------------------------------
1 | export type SaleType = {
2 | name: string;
3 | data: number[];
4 | };
5 |
--------------------------------------------------------------------------------
/chapter-15/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
4 | ///
5 |
--------------------------------------------------------------------------------
/chapter-15/src/services/productService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { ProductType } from 'models/product-type';
3 |
4 | export async function getProductsAxios() {
5 | return await api.get(EndPoints.products);
6 | }
7 |
8 | export async function postProductAxios(product: ProductType) {
9 | return await api.post(EndPoints.products, product);
10 | }
11 |
--------------------------------------------------------------------------------
/chapter-15/src/services/saleService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { SaleType } from 'models/sale-type';
3 |
4 | export async function getSalesAxios() {
5 | return await api.get(EndPoints.sales);
6 | }
7 |
--------------------------------------------------------------------------------
/chapter-15/src/services/userDbService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { UserType } from 'models/user-type';
3 |
4 | export async function getUserByIdFromDbAxios(id: string) {
5 | return await api.get(`${EndPoints.usersDb}/${id}`);
6 | }
7 |
8 | export async function putUserFromDbAxios(user: UserType) {
9 | return await api.put(`${EndPoints.usersDb}/${user.id}`, user);
10 | }
11 |
--------------------------------------------------------------------------------
/chapter-15/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';
6 |
7 | import 'react-app-polyfill/ie11';
8 | import 'react-app-polyfill/stable';
9 |
10 | import 'jest-styled-components';
11 |
--------------------------------------------------------------------------------
/chapter-15/src/styles/__tests__/media.test.ts:
--------------------------------------------------------------------------------
1 | import { media, sizes } from '../media';
2 | import { css } from 'styled-components/macro';
3 |
4 | describe('media', () => {
5 | it('should return media query in css', () => {
6 | const mediaQuery = media.small`color:red;`.join('');
7 | const cssVersion = css`
8 | @media (min-width: ${sizes.small}px) {
9 | color: red;
10 | }
11 | `.join('');
12 | expect(mediaQuery).toEqual(cssVersion);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-15/src/utils/bytes-to-size.ts:
--------------------------------------------------------------------------------
1 | const bytesToSize = (bytes: number, decimals: number = 2) => {
2 | if (bytes === 0) return '0 Bytes';
3 |
4 | const k = 1024;
5 | const dm = decimals < 0 ? 0 : decimals;
6 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
7 | const i = Math.floor(Math.log(bytes) / Math.log(k));
8 |
9 | return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
10 | };
11 |
12 | export default bytesToSize;
13 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/.babel-plugin-macrosrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styledComponents: {
3 | displayName: process.env.NODE_ENV !== 'production',
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/.env.local:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | EXTEND_ESLINT=true
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/.env.production:
--------------------------------------------------------------------------------
1 | GENERATE_SOURCEMAP=false
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/.eslintignore:
--------------------------------------------------------------------------------
1 | cypress
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't check auto-generated stuff into git
2 | coverage
3 | build
4 | node_modules
5 | stats.json
6 | .pnp
7 | .pnp.js
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | # boilerplate internals
20 | generated-cra-app
21 | .cra-template-rb
22 | template
23 |
24 | .eslintcache
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact = true
2 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/dubnium
2 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 | yarn.lock
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "arrowParens": "avoid"
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "processors": ["stylelint-processor-styled-components"],
3 | "extends": [
4 | "stylelint-config-recommended",
5 | "stylelint-config-styled-components"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/cypress/screenshots/All Integration Specs/my-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-4/finished-installing-libraries-boilerplate/cypress/screenshots/All Integration Specs/my-image.png
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/internals/generators/component/index.test.tsx.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 |
4 | import { {{ properCase ComponentName }} } from '..';
5 |
6 | describe('<{{ properCase ComponentName }} />', () => {
7 | it('should match snapshot', () => {
8 | const loadingIndicator = render(<{{ properCase ComponentName }} />);
9 | expect(loadingIndicator.container.firstChild).toMatchSnapshot();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/internals/generators/component/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/internals/generators/container/appendRootState.hbs:
--------------------------------------------------------------------------------
1 | {{ camelCase ComponentName }}?: {{ properCase ComponentName }}State;
2 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/internals/generators/container/importContainerState.hbs:
--------------------------------------------------------------------------------
1 | import { {{ properCase ComponentName }}State } from 'app/containers/{{ properCase ComponentName }}/types';
2 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/internals/generators/container/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/internals/generators/container/saga.ts.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select, takeLatest } from 'redux-saga/effects';
2 | // import { actions } from './slice';
3 |
4 | // export function* doSomething() {}
5 |
6 | export function* {{ camelCase ComponentName }}Saga() {
7 | // yield takeLatest(actions.someAction.type, doSomething);
8 | }
9 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/internals/generators/container/selectors.ts.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from '@reduxjs/toolkit';
2 |
3 | import { RootState } from 'types';
4 | import { initialState } from './slice';
5 |
6 | const selectDomain = (state: RootState) => state.{{ camelCase ComponentName }} || initialState;
7 |
8 | export const select{{ properCase ComponentName }} = createSelector(
9 | [selectDomain],
10 | {{ camelCase ComponentName }}State => {{ camelCase ComponentName }}State,
11 | );
12 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/internals/generators/container/types.ts.hbs:
--------------------------------------------------------------------------------
1 | /* --- STATE --- */
2 | export interface {{ properCase ComponentName }}State {}
3 |
4 | export type ContainerState = {{ properCase ComponentName }}State;
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/internals/testing/loadable.mock.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function ExportedFunc() {
4 | return My lazy-loaded component
;
5 | }
6 | export default ExportedFunc;
7 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/internals/ts-node.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "noEmit": true,
7 | "allowSyntheticDefaultImports": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-4/finished-installing-libraries-boilerplate/public/favicon.ico
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-4/finished-installing-libraries-boilerplate/public/logo192.png
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-4/finished-installing-libraries-boilerplate/public/logo512.png
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/src/app/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 |
4 | import { App } from '../index';
5 |
6 | const renderer = createRenderer();
7 |
8 | describe('', () => {
9 | it('should render and match the snapshot', () => {
10 | renderer.render();
11 | const renderedOutput = renderer.getRenderOutput();
12 | expect(renderedOutput).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/src/app/components/NotFoundPage/Loadable.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Asynchronously loads the component for NotFoundPage
3 | */
4 |
5 | import { lazyLoad } from 'utils/loadable';
6 |
7 | export const NotFoundPage = lazyLoad(
8 | () => import('./index'),
9 | module => module.NotFoundPage,
10 | );
11 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/src/app/components/NotFoundPage/P.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/macro';
2 |
3 | export const P = styled.p`
4 | font-size: 1rem;
5 | line-height: 1.5;
6 | color: black;
7 | margin: 0.625rem 0 1.5rem 0;
8 | `;
9 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/src/app/containers/HomePage/Loadable.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Asynchronously loads the component for HomePage
3 | */
4 |
5 | import { lazyLoad } from 'utils/loadable';
6 |
7 | export const HomePage = lazyLoad(
8 | () => import('./index'),
9 | module => module.HomePage,
10 | );
11 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/src/app/containers/HomePage/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Helmet } from 'react-helmet-async';
3 |
4 | export function HomePage() {
5 | return (
6 | <>
7 |
8 | Home Page
9 |
10 |
11 | HomePage container
12 | >
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/src/locales/__tests__/i18n.test.ts:
--------------------------------------------------------------------------------
1 | import { i18n } from '../i18n';
2 |
3 | describe('i18n', () => {
4 | it('should initate i18n', async () => {
5 | const t = await i18n;
6 | expect(t).toBeDefined();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/src/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "welcome"
3 | }
4 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/src/locales/types.ts:
--------------------------------------------------------------------------------
1 | export type ConvertedToObjectType = {
2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType;
3 | };
4 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
4 | ///
5 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/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';
6 |
7 | import 'react-app-polyfill/ie11';
8 | import 'react-app-polyfill/stable';
9 |
10 | import 'jest-styled-components';
11 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/src/styles/__tests__/media.test.ts:
--------------------------------------------------------------------------------
1 | import { media, sizes } from '../media';
2 | import { css } from 'styled-components/macro';
3 |
4 | describe('media', () => {
5 | it('should return media query in css', () => {
6 | const mediaQuery = media.small`color:red;`.join('');
7 | const cssVersion = css`
8 | @media (min-width: ${sizes.small}px) {
9 | color: red;
10 | }
11 | `.join('');
12 | expect(mediaQuery).toEqual(cssVersion);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/src/types/RootState.ts:
--------------------------------------------------------------------------------
1 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
2 |
3 | /*
4 | Because the redux-injectors injects your reducers asynchronously somewhere in your code
5 | You have to declare them here manually
6 | */
7 | export interface RootState {
8 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-4/finished-installing-libraries-boilerplate/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import { RootState } from './RootState';
2 |
3 | export type { RootState };
4 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/.babel-plugin-macrosrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styledComponents: {
3 | displayName: process.env.NODE_ENV !== 'production',
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/.env.local:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | EXTEND_ESLINT=true
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/.env.production:
--------------------------------------------------------------------------------
1 | GENERATE_SOURCEMAP=false
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't check auto-generated stuff into git
2 | coverage
3 | build
4 | node_modules
5 | stats.json
6 | .pnp
7 | .pnp.js
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | # boilerplate internals
20 | generated-cra-app
21 | .cra-template-rb
22 | template
23 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact = true
2 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/dubnium
2 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 | yarn.lock
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "arrowParens": "avoid"
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "processors": ["stylelint-processor-styled-components"],
3 | "extends": [
4 | "stylelint-config-recommended",
5 | "stylelint-config-styled-components"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/generators/component/index.test.tsx.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 |
4 | import { {{ properCase ComponentName }} } from '..';
5 |
6 | describe('<{{ properCase ComponentName }} />', () => {
7 | it('should match snapshot', () => {
8 | const loadingIndicator = render(<{{ properCase ComponentName }} />);
9 | expect(loadingIndicator.container.firstChild).toMatchSnapshot();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/generators/component/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/generators/container/appendRootState.hbs:
--------------------------------------------------------------------------------
1 | {{ camelCase ComponentName }}?: {{ properCase ComponentName }}State;
2 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/generators/container/importContainerState.hbs:
--------------------------------------------------------------------------------
1 | import { {{ properCase ComponentName }}State } from 'app/containers/{{ properCase ComponentName }}/types';
2 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/generators/container/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/generators/container/saga.ts.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select, takeLatest } from 'redux-saga/effects';
2 | // import { actions } from './slice';
3 |
4 | // export function* doSomething() {}
5 |
6 | export function* {{ camelCase ComponentName }}Saga() {
7 | // yield takeLatest(actions.someAction.type, doSomething);
8 | }
9 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/generators/container/selectors.ts.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from '@reduxjs/toolkit';
2 |
3 | import { RootState } from 'types';
4 | import { initialState } from './slice';
5 |
6 | const selectDomain = (state: RootState) => state.{{ camelCase ComponentName }} || initialState;
7 |
8 | export const select{{ properCase ComponentName }} = createSelector(
9 | [selectDomain],
10 | {{ camelCase ComponentName }}State => {{ camelCase ComponentName }}State,
11 | );
12 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/generators/container/types.ts.hbs:
--------------------------------------------------------------------------------
1 | /* --- STATE --- */
2 | export interface {{ properCase ComponentName }}State {}
3 |
4 | export type ContainerState = {{ properCase ComponentName }}State;
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-4/starter-boilerplate/internals/startingTemplate/public/favicon.ico
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-4/starter-boilerplate/internals/startingTemplate/public/logo192.png
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-4/starter-boilerplate/internals/startingTemplate/public/logo512.png
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/src/app/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 |
4 | import { App } from '../index';
5 |
6 | const renderer = createRenderer();
7 |
8 | describe('', () => {
9 | it('should render and match the snapshot', () => {
10 | renderer.render();
11 | const renderedOutput = renderer.getRenderOutput();
12 | expect(renderedOutput).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/src/app/components/NotFoundPage/Loadable.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Asynchronously loads the component for NotFoundPage
3 | */
4 |
5 | import { lazyLoad } from 'utils/loadable';
6 |
7 | export const NotFoundPage = lazyLoad(
8 | () => import('./index'),
9 | module => module.NotFoundPage,
10 | );
11 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/src/app/components/NotFoundPage/P.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/macro';
2 |
3 | export const P = styled.p`
4 | font-size: 1rem;
5 | line-height: 1.5;
6 | color: black;
7 | margin: 0.625rem 0 1.5rem 0;
8 | `;
9 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/src/app/containers/HomePage/Loadable.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Asynchronously loads the component for HomePage
3 | */
4 |
5 | import { lazyLoad } from 'utils/loadable';
6 |
7 | export const HomePage = lazyLoad(
8 | () => import('./index'),
9 | module => module.HomePage,
10 | );
11 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/src/app/containers/HomePage/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Helmet } from 'react-helmet-async';
3 |
4 | export function HomePage() {
5 | return (
6 | <>
7 |
8 | Home Page
9 |
10 |
11 | HomePage container
12 | >
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/src/locales/__tests__/i18n.test.ts:
--------------------------------------------------------------------------------
1 | import { i18n } from '../i18n';
2 |
3 | describe('i18n', () => {
4 | it('should initate i18n', async () => {
5 | const t = await i18n;
6 | expect(t).toBeDefined();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/src/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "welcome"
3 | }
4 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/src/locales/types.ts:
--------------------------------------------------------------------------------
1 | export type ConvertedToObjectType = {
2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType;
3 | };
4 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
4 | ///
5 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/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';
6 |
7 | import 'react-app-polyfill/ie11';
8 | import 'react-app-polyfill/stable';
9 |
10 | import 'jest-styled-components';
11 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/src/styles/__tests__/media.test.ts:
--------------------------------------------------------------------------------
1 | import { media, sizes } from '../media';
2 | import { css } from 'styled-components/macro';
3 |
4 | describe('media', () => {
5 | it('should return media query in css', () => {
6 | const mediaQuery = media.small`color:red;`.join('');
7 | const cssVersion = css`
8 | @media (min-width: ${sizes.small}px) {
9 | color: red;
10 | }
11 | `.join('');
12 | expect(mediaQuery).toEqual(cssVersion);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/src/types/RootState.ts:
--------------------------------------------------------------------------------
1 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
2 |
3 | /*
4 | Because the redux-injectors injects your reducers asynchronously somewhere in your code
5 | You have to declare them here manually
6 | */
7 | export interface RootState {
8 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/startingTemplate/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import { RootState } from './RootState';
2 |
3 | export type { RootState };
4 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/testing/loadable.mock.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function ExportedFunc() {
4 | return My lazy-loaded component
;
5 | }
6 | export default ExportedFunc;
7 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/internals/ts-node.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "noEmit": true,
7 | "allowSyntheticDefaultImports": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-4/starter-boilerplate/public/favicon.ico
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-4/starter-boilerplate/public/logo192.png
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-4/starter-boilerplate/public/logo512.png
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 |
4 | import { App } from '../index';
5 |
6 | const renderer = createRenderer();
7 |
8 | describe('', () => {
9 | it('should render and match the snapshot', () => {
10 | renderer.render();
11 | const renderedOutput = renderer.getRenderOutput();
12 | expect(renderedOutput).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/components/A/index.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/macro';
2 |
3 | export const A = styled.a`
4 | color: ${p => p.theme.primary};
5 | text-decoration: none;
6 |
7 | &:hover {
8 | text-decoration: underline;
9 | opacity: 0.8;
10 | }
11 |
12 | &:active {
13 | opacity: 0.4;
14 | }
15 | `;
16 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/components/FormLabel/index.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/macro';
2 |
3 | export const FormLabel = styled.label`
4 | text-transform: uppercase;
5 | font-weight: normal;
6 | margin: 0;
7 | padding: 0;
8 | color: ${p => p.theme.textSecondary};
9 | font-size: 0.75rem;
10 | `;
11 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/components/Link/__tests__/__snapshots__/index.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` should match snapshot 1`] = `
4 | .c0 {
5 | color: rgba(215,113,88,1);
6 | -webkit-text-decoration: none;
7 | text-decoration: none;
8 | }
9 |
10 | .c0:hover {
11 | -webkit-text-decoration: underline;
12 | text-decoration: underline;
13 | opacity: 0.8;
14 | }
15 |
16 | .c0:active {
17 | opacity: 0.4;
18 | }
19 |
20 |
25 | HeaderLink
26 |
27 | `;
28 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/components/Link/index.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/macro';
2 | import { Link as RouterLink } from 'react-router-dom';
3 |
4 | export const Link = styled(RouterLink)`
5 | color: ${p => p.theme.primary};
6 | text-decoration: none;
7 |
8 | &:hover {
9 | text-decoration: underline;
10 | opacity: 0.8;
11 | }
12 |
13 | &:active {
14 | opacity: 0.4;
15 | }
16 | `;
17 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/components/PageWrapper/index.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/macro';
2 |
3 | export const PageWrapper = styled.div`
4 | width: 960px;
5 | margin: 0 auto;
6 | padding: 0 1.5rem;
7 | box-sizing: content-box;
8 | `;
9 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/GithubRepoForm/assets/new-window.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/GithubRepoForm/assets/star.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/GithubRepoForm/components/TextButton.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/macro';
2 |
3 | export const TextButton = styled.button`
4 | background: none;
5 | outline: none;
6 | padding: 0;
7 | margin: 0;
8 | border: none;
9 | color: ${p => p.theme.primary};
10 | cursor: pointer;
11 |
12 | &:hover {
13 | opacity: 0.8;
14 | text-decoration: underline;
15 | }
16 |
17 | &:active {
18 | opacity: 0.4;
19 | }
20 | `;
21 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/HomePage/__tests__/Masthead.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Masthead } from '../Masthead';
3 | import { createRenderer } from 'react-test-renderer/shallow';
4 |
5 | const shallowRenderer = createRenderer();
6 |
7 | describe('', () => {
8 | it('should render and match the snapshot', () => {
9 | shallowRenderer.render();
10 | const renderedOutput = shallowRenderer.getRenderOutput();
11 | expect(renderedOutput).toMatchSnapshot();
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/HomePage/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 |
4 | import { HomePage } from '..';
5 |
6 | const shallowRenderer = createRenderer();
7 |
8 | describe('', () => {
9 | it('should render and match the snapshot', () => {
10 | shallowRenderer.render();
11 | const renderedOutput = shallowRenderer.getRenderOutput();
12 | expect(renderedOutput).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/HomePage/assets/plus-sign.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/HomePage/components/Lead.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/macro';
2 |
3 | export const Lead = styled.p`
4 | font-size: 1.5rem;
5 | font-weight: 300;
6 | line-height: 1.5;
7 | color: ${p => p.theme.textSecondary};
8 | margin: 0 0 1.5rem 0;
9 |
10 | strong {
11 | color: ${p => p.theme.text};
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/HomePage/components/P.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/macro';
2 |
3 | export const P = styled.p`
4 | font-size: 1rem;
5 | line-height: 1.5;
6 | color: ${p => p.theme.textSecondary};
7 | margin: 0.625rem 0 1.5rem 0;
8 | `;
9 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/HomePage/components/SubTitle.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/macro';
2 |
3 | export const SubTitle = styled.h3`
4 | font-size: 1.25rem;
5 | margin: 0;
6 | color: ${p => p.theme.text};
7 | `;
8 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/HomePage/components/Title.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/macro';
2 |
3 | export const Title = styled.h1`
4 | font-size: 32px;
5 | font-weight: bold;
6 | color: ${p => p.theme.text};
7 | margin: 1rem 0;
8 | `;
9 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/HomePage/components/__tests__/__snapshots__/Lead.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` should render and match the snapshot 1`] = `
4 | .c0 {
5 | font-size: 1.5rem;
6 | font-weight: 300;
7 | line-height: 1.5;
8 | color: rgba(58,52,51,0.7);
9 | margin: 0 0 1.5rem 0;
10 | }
11 |
12 | .c0 strong {
13 | color: rgba(58,52,51,1);
14 | }
15 |
16 |
19 | `;
20 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/HomePage/components/__tests__/__snapshots__/P.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` should render and match the snapshot 1`] = `
4 | .c0 {
5 | font-size: 1rem;
6 | line-height: 1.5;
7 | color: rgba(58,52,51,0.7);
8 | margin: 0.625rem 0 1.5rem 0;
9 | }
10 |
11 |
14 | `;
15 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/HomePage/components/__tests__/__snapshots__/Subtitle.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` should render and match the snapshot 1`] = `
4 | .c0 {
5 | font-size: 1.25rem;
6 | margin: 0;
7 | color: rgba(58,52,51,1);
8 | }
9 |
10 |
13 | `;
14 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/HomePage/components/__tests__/__snapshots__/Title.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` should render and match the snapshot 1`] = `
4 | .c0 {
5 | font-size: 32px;
6 | font-weight: bold;
7 | color: rgba(58,52,51,1);
8 | margin: 1rem 0;
9 | }
10 |
11 |
14 | `;
15 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/NavBar/__tests__/Logo.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { Logo } from '../Logo';
4 |
5 | describe('', () => {
6 | it('should match snapshot', () => {
7 | const logo = render();
8 | expect(logo.container.firstChild).toMatchSnapshot();
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/NavBar/__tests__/Nav.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { Nav } from '../Nav';
4 | import { MemoryRouter } from 'react-router-dom';
5 |
6 | describe('', () => {
7 | it('should match the snapshot', () => {
8 | const logo = render(
9 |
10 |
11 | ,
12 | );
13 | expect(logo.container.firstChild).toMatchSnapshot();
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/NavBar/__tests__/__snapshots__/index.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` should match snapshot 1`] = `
4 |
5 |
6 |
7 |
8 |
9 |
10 | `;
11 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/NavBar/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 | import { NavBar } from '../index';
4 |
5 | const shallowRenderer = createRenderer();
6 |
7 | describe('', () => {
8 | it('should match snapshot', () => {
9 | shallowRenderer.render();
10 | const renderedOutput = shallowRenderer.getRenderOutput();
11 | expect(renderedOutput).toMatchSnapshot();
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/NavBar/assets/documentation-icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/NotFoundPage/Loadable.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Asynchronously loads the component for NotFoundPage
3 | */
4 |
5 | import * as React from 'react';
6 | import { lazyLoad } from 'utils/loadable';
7 | import { LoadingIndicator } from 'app/components/LoadingIndicator';
8 |
9 | export const NotFoundPage = lazyLoad(
10 | () => import('./index'),
11 | module => module.NotFoundPage,
12 | {
13 | fallback: ,
14 | },
15 | );
16 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/app/containers/NotFoundPage/P.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/macro';
2 |
3 | export const P = styled.p`
4 | font-size: 1rem;
5 | line-height: 1.5;
6 | color: ${p => p.theme.textSecondary};
7 | margin: 0.625rem 0 1.5rem 0;
8 | `;
9 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/locales/__tests__/i18n.test.ts:
--------------------------------------------------------------------------------
1 | import { i18n, translations } from '../i18n';
2 |
3 | describe('i18n', () => {
4 | it('should initate i18n', async () => {
5 | const t = await i18n;
6 | expect(t).toBeDefined();
7 | });
8 |
9 | it('should initate i18n with translations', async () => {
10 | const t = await i18n;
11 | expect(t(translations.feedbackFeature.description).length).toBeGreaterThan(
12 | 0,
13 | );
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/locales/types.ts:
--------------------------------------------------------------------------------
1 | export type ConvertedToObjectType = {
2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType;
3 | };
4 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
4 | ///
5 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // react-testing-library renders your components to document.body,
2 | // this adds jest-dom's custom assertions
3 | import '@testing-library/jest-dom/extend-expect';
4 |
5 | import 'react-app-polyfill/ie11';
6 | import 'react-app-polyfill/stable';
7 |
8 | import 'jest-styled-components';
9 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/styles/StyleConstants.ts:
--------------------------------------------------------------------------------
1 | export enum StyleConstants {
2 | NAV_BAR_HEIGHT = '4rem',
3 | }
4 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/styles/__tests__/media.test.ts:
--------------------------------------------------------------------------------
1 | import { media, sizes } from '../media';
2 | import { css } from 'styled-components/macro';
3 |
4 | describe('media', () => {
5 | it('should return media query in css', () => {
6 | const mediaQuery = media.small`color:red;`.join('');
7 | const cssVersion = css`
8 | @media (min-width: ${sizes.small}px) {
9 | color: red;
10 | }
11 | `.join('');
12 | expect(mediaQuery).toEqual(cssVersion);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/styles/theme/__tests__/utils.test.ts:
--------------------------------------------------------------------------------
1 | import * as utils from '../utils';
2 |
3 | describe('theme utils', () => {
4 | it('should get storage item', () => {
5 | utils.saveTheme('system');
6 | expect(utils.getThemeFromStorage()).toBe('system');
7 | });
8 | it('should check system theme', () => {
9 | expect(utils.isSystemDark).toBeUndefined();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/styles/theme/styled.d.ts:
--------------------------------------------------------------------------------
1 | import 'styled-components';
2 | import { Theme } from './themes';
3 |
4 | /* This is the suggested way of declaring theme types */
5 | declare module 'styled-components' {
6 | export interface DefaultTheme extends Theme {}
7 | }
8 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/styles/theme/types.ts:
--------------------------------------------------------------------------------
1 | import { themes } from './themes';
2 |
3 | export type ThemeKeyType = keyof typeof themes | 'system';
4 |
5 | export interface ThemeState {
6 | selected: ThemeKeyType;
7 | }
8 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import { RootState } from './RootState';
2 |
3 | export type { RootState };
4 |
--------------------------------------------------------------------------------
/chapter-4/starter-boilerplate/src/utils/__tests__/__snapshots__/loadable.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`loadable should render LazyComponent after waiting for it to load 1`] = `
4 |
5 | My lazy-loaded component
6 |
7 | `;
8 |
9 | exports[`loadable should render fallback if given one 1`] = `
10 |
11 | Loading
12 |
13 | `;
14 |
15 | exports[`loadable should render null by default 1`] = `null`;
16 |
17 | exports[`loadable should render null by default with empty options 1`] = `null`;
18 |
--------------------------------------------------------------------------------
/chapter-5/.babel-plugin-macrosrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styledComponents: {
3 | displayName: process.env.NODE_ENV !== 'production',
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/chapter-5/.env.local:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | EXTEND_ESLINT=true
--------------------------------------------------------------------------------
/chapter-5/.env.production:
--------------------------------------------------------------------------------
1 | GENERATE_SOURCEMAP=false
--------------------------------------------------------------------------------
/chapter-5/.eslintignore:
--------------------------------------------------------------------------------
1 | cypress
--------------------------------------------------------------------------------
/chapter-5/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't check auto-generated stuff into git
2 | coverage
3 | build
4 | node_modules
5 | stats.json
6 | .pnp
7 | .pnp.js
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | # boilerplate internals
20 | generated-cra-app
21 | .cra-template-rb
22 | template
23 |
24 | .eslintcache
--------------------------------------------------------------------------------
/chapter-5/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact = true
2 |
--------------------------------------------------------------------------------
/chapter-5/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/dubnium
2 |
--------------------------------------------------------------------------------
/chapter-5/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 | yarn.lock
--------------------------------------------------------------------------------
/chapter-5/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "arrowParens": "avoid"
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-5/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "processors": ["stylelint-processor-styled-components"],
3 | "extends": [
4 | "stylelint-config-recommended",
5 | "stylelint-config-styled-components"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/chapter-5/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "esbenp.prettier-vscode",
5 | "msjsdiag.debugger-for-chrome",
6 | "vscode-icons-team.vscode-icons",
7 | "Orta.vscode-jest",
8 | "eg2.vscode-npm-script",
9 | "jpoissonnier.vscode-styled-components"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/chapter-5/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Chrome",
6 | "type": "chrome",
7 | "request": "launch",
8 | "url": "http://localhost:3000",
9 | "webRoot": "${workspaceFolder}/src",
10 | "sourceMapPathOverrides": {
11 | "webpack:///src/*": "${webRoot}/*"
12 | }
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/chapter-5/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/chapter-5/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-5/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-5/cypress/screenshots/All Integration Specs/my-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-5/cypress/screenshots/All Integration Specs/my-image.png
--------------------------------------------------------------------------------
/chapter-5/internals/generators/component/index.test.tsx.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 |
4 | import { {{ properCase ComponentName }} } from '..';
5 |
6 | describe('<{{ properCase ComponentName }} />', () => {
7 | it('should match snapshot', () => {
8 | const loadingIndicator = render(<{{ properCase ComponentName }} />);
9 | expect(loadingIndicator.container.firstChild).toMatchSnapshot();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/chapter-5/internals/generators/component/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-5/internals/generators/container/appendRootState.hbs:
--------------------------------------------------------------------------------
1 | {{ camelCase ComponentName }}?: {{ properCase ComponentName }}State;
2 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-5/internals/generators/container/importContainerState.hbs:
--------------------------------------------------------------------------------
1 | import { {{ properCase ComponentName }}State } from 'app/containers/{{ properCase ComponentName }}/types';
2 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-5/internals/generators/container/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-5/internals/generators/container/saga.ts.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select, takeLatest } from 'redux-saga/effects';
2 | // import { actions } from './slice';
3 |
4 | // export function* doSomething() {}
5 |
6 | export function* {{ camelCase ComponentName }}Saga() {
7 | // yield takeLatest(actions.someAction.type, doSomething);
8 | }
9 |
--------------------------------------------------------------------------------
/chapter-5/internals/generators/container/selectors.ts.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from '@reduxjs/toolkit';
2 |
3 | import { RootState } from 'types';
4 | import { initialState } from './slice';
5 |
6 | const selectDomain = (state: RootState) => state.{{ camelCase ComponentName }} || initialState;
7 |
8 | export const select{{ properCase ComponentName }} = createSelector(
9 | [selectDomain],
10 | {{ camelCase ComponentName }}State => {{ camelCase ComponentName }}State,
11 | );
12 |
--------------------------------------------------------------------------------
/chapter-5/internals/generators/container/types.ts.hbs:
--------------------------------------------------------------------------------
1 | /* --- STATE --- */
2 | export interface {{ properCase ComponentName }}State {}
3 |
4 | export type ContainerState = {{ properCase ComponentName }}State;
--------------------------------------------------------------------------------
/chapter-5/internals/testing/loadable.mock.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function ExportedFunc() {
4 | return My lazy-loaded component
;
5 | }
6 | export default ExportedFunc;
7 |
--------------------------------------------------------------------------------
/chapter-5/internals/ts-node.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "noEmit": true,
7 | "allowSyntheticDefaultImports": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-5/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-5/public/favicon.ico
--------------------------------------------------------------------------------
/chapter-5/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-5/public/logo192.png
--------------------------------------------------------------------------------
/chapter-5/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-5/public/logo512.png
--------------------------------------------------------------------------------
/chapter-5/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/chapter-5/src/app/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 |
4 | import { App } from '../index';
5 |
6 | const renderer = createRenderer();
7 |
8 | describe('', () => {
9 | it('should render and match the snapshot', () => {
10 | renderer.render();
11 | const renderedOutput = renderer.getRenderOutput();
12 | expect(renderedOutput).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-5/src/app/layouts/dashboard-layout/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Grid } from '@material-ui/core';
3 |
4 | import DashboardSidebarNavigation from './dashboard-sidebar-navigation';
5 |
6 | type Props = {
7 | children: React.ReactNode;
8 | };
9 |
10 | const Dashboard = ({ children }: Props) => {
11 | return (
12 |
18 | {children}
19 |
20 | );
21 | };
22 |
23 | export default Dashboard;
24 |
--------------------------------------------------------------------------------
/chapter-5/src/app/views/dashboard/dashboard-default-content.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const DashboardDefaultContent = () => (
4 |
5 |
Dashboard Default Content
6 |
7 | );
8 |
9 | export default DashboardDefaultContent;
10 |
--------------------------------------------------------------------------------
/chapter-5/src/app/views/dashboard/settings-and-privacy.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const SettingsAndPrivacy = () => (
4 |
5 |
Settings and Privacy Content
6 |
7 | );
8 |
9 | export default SettingsAndPrivacy;
10 |
--------------------------------------------------------------------------------
/chapter-5/src/app/views/pages/AboutPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const AboutPage = () => {
4 | return (
5 |
6 |
This is About Page
7 |
8 | );
9 | };
10 |
11 | export default AboutPage;
12 |
--------------------------------------------------------------------------------
/chapter-5/src/app/views/pages/HomePage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | const Main = () => {
4 | return (
5 |
6 |
Main Page
7 |
8 | );
9 | };
10 |
11 | export default Main;
12 |
--------------------------------------------------------------------------------
/chapter-5/src/app/views/pages/NotFoundPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFoundPage = () => {
4 | return (
5 |
6 |
404 Page Not Found
7 |
8 | );
9 | };
10 |
11 | export default NotFoundPage;
12 |
--------------------------------------------------------------------------------
/chapter-5/src/locales/__tests__/i18n.test.ts:
--------------------------------------------------------------------------------
1 | import { i18n } from '../i18n';
2 |
3 | describe('i18n', () => {
4 | it('should initate i18n', async () => {
5 | const t = await i18n;
6 | expect(t).toBeDefined();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/chapter-5/src/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "welcome"
3 | }
4 |
--------------------------------------------------------------------------------
/chapter-5/src/locales/types.ts:
--------------------------------------------------------------------------------
1 | export type ConvertedToObjectType = {
2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType;
3 | };
4 |
--------------------------------------------------------------------------------
/chapter-5/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
4 | ///
5 |
--------------------------------------------------------------------------------
/chapter-5/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';
6 |
7 | import 'react-app-polyfill/ie11';
8 | import 'react-app-polyfill/stable';
9 |
10 | import 'jest-styled-components';
11 |
--------------------------------------------------------------------------------
/chapter-5/src/styles/__tests__/media.test.ts:
--------------------------------------------------------------------------------
1 | import { media, sizes } from '../media';
2 | import { css } from 'styled-components/macro';
3 |
4 | describe('media', () => {
5 | it('should return media query in css', () => {
6 | const mediaQuery = media.small`color:red;`.join('');
7 | const cssVersion = css`
8 | @media (min-width: ${sizes.small}px) {
9 | color: red;
10 | }
11 | `.join('');
12 | expect(mediaQuery).toEqual(cssVersion);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-5/src/types/RootState.ts:
--------------------------------------------------------------------------------
1 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
2 |
3 | /*
4 | Because the redux-injectors injects your reducers asynchronously somewhere in your code
5 | You have to declare them here manually
6 | */
7 | export interface RootState {
8 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-5/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import { RootState } from './RootState';
2 |
3 | export type { RootState };
4 |
--------------------------------------------------------------------------------
/chapter-5/src/utils/@reduxjs/toolkit.tsx:
--------------------------------------------------------------------------------
1 | import { RootStateKeyType } from '../types/injector-typings';
2 | import {
3 | createSlice as createSliceOriginal,
4 | SliceCaseReducers,
5 | CreateSliceOptions,
6 | } from '@reduxjs/toolkit';
7 |
8 | /* Wrap createSlice with stricter Name options */
9 |
10 | /* istanbul ignore next */
11 | export const createSlice = <
12 | State,
13 | CaseReducers extends SliceCaseReducers,
14 | Name extends RootStateKeyType
15 | >(
16 | options: CreateSliceOptions,
17 | ) => {
18 | return createSliceOriginal(options);
19 | };
20 |
--------------------------------------------------------------------------------
/chapter-5/src/utils/redux-injectors.ts:
--------------------------------------------------------------------------------
1 | import {
2 | useInjectReducer as useReducer,
3 | useInjectSaga as useSaga,
4 | } from 'redux-injectors';
5 | import {
6 | InjectReducerParams,
7 | InjectSagaParams,
8 | RootStateKeyType,
9 | } from './types/injector-typings';
10 |
11 | /* Wrap redux-injectors with stricter types */
12 |
13 | export function useInjectReducer(
14 | params: InjectReducerParams,
15 | ) {
16 | return useReducer(params);
17 | }
18 |
19 | export function useInjectSaga(params: InjectSagaParams) {
20 | return useSaga(params);
21 | }
22 |
--------------------------------------------------------------------------------
/chapter-6/.babel-plugin-macrosrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styledComponents: {
3 | displayName: process.env.NODE_ENV !== 'production',
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/chapter-6/.env.local:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | EXTEND_ESLINT=true
--------------------------------------------------------------------------------
/chapter-6/.env.production:
--------------------------------------------------------------------------------
1 | GENERATE_SOURCEMAP=false
--------------------------------------------------------------------------------
/chapter-6/.eslintignore:
--------------------------------------------------------------------------------
1 | cypress
--------------------------------------------------------------------------------
/chapter-6/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't check auto-generated stuff into git
2 | coverage
3 | build
4 | node_modules
5 | stats.json
6 | .pnp
7 | .pnp.js
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | # boilerplate internals
20 | generated-cra-app
21 | .cra-template-rb
22 | template
23 |
24 | .eslintcache
--------------------------------------------------------------------------------
/chapter-6/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact = true
2 |
--------------------------------------------------------------------------------
/chapter-6/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/dubnium
2 |
--------------------------------------------------------------------------------
/chapter-6/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 | yarn.lock
--------------------------------------------------------------------------------
/chapter-6/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "arrowParens": "avoid"
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-6/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "processors": ["stylelint-processor-styled-components"],
3 | "extends": [
4 | "stylelint-config-recommended",
5 | "stylelint-config-styled-components"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/chapter-6/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/chapter-6/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-6/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-6/cypress/screenshots/All Integration Specs/my-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-6/cypress/screenshots/All Integration Specs/my-image.png
--------------------------------------------------------------------------------
/chapter-6/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "sales": [
3 | {
4 | "id": "sgacus86fov",
5 | "name": "This week",
6 | "data": [30, 40, 25, 50, 49, 21, 70, 51]
7 | },
8 | {
9 | "id": "saftyaf56",
10 | "name": "Last week",
11 | "data": [23, 12, 54, 61, 32, 56, 81, 19]
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/chapter-6/internals/generators/component/index.test.tsx.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 |
4 | import { {{ properCase ComponentName }} } from '..';
5 |
6 | describe('<{{ properCase ComponentName }} />', () => {
7 | it('should match snapshot', () => {
8 | const loadingIndicator = render(<{{ properCase ComponentName }} />);
9 | expect(loadingIndicator.container.firstChild).toMatchSnapshot();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/chapter-6/internals/generators/component/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-6/internals/generators/container/appendRootState.hbs:
--------------------------------------------------------------------------------
1 | {{ camelCase ComponentName }}?: {{ properCase ComponentName }}State;
2 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-6/internals/generators/container/importContainerState.hbs:
--------------------------------------------------------------------------------
1 | import { {{ properCase ComponentName }}State } from 'app/containers/{{ properCase ComponentName }}/types';
2 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-6/internals/generators/container/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-6/internals/generators/container/saga.ts.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select, takeLatest } from 'redux-saga/effects';
2 | // import { actions } from './slice';
3 |
4 | // export function* doSomething() {}
5 |
6 | export function* {{ camelCase ComponentName }}Saga() {
7 | // yield takeLatest(actions.someAction.type, doSomething);
8 | }
9 |
--------------------------------------------------------------------------------
/chapter-6/internals/generators/container/selectors.ts.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from '@reduxjs/toolkit';
2 |
3 | import { RootState } from 'types';
4 | import { initialState } from './slice';
5 |
6 | const selectDomain = (state: RootState) => state.{{ camelCase ComponentName }} || initialState;
7 |
8 | export const select{{ properCase ComponentName }} = createSelector(
9 | [selectDomain],
10 | {{ camelCase ComponentName }}State => {{ camelCase ComponentName }}State,
11 | );
12 |
--------------------------------------------------------------------------------
/chapter-6/internals/generators/container/types.ts.hbs:
--------------------------------------------------------------------------------
1 | /* --- STATE --- */
2 | export interface {{ properCase ComponentName }}State {}
3 |
4 | export type ContainerState = {{ properCase ComponentName }}State;
--------------------------------------------------------------------------------
/chapter-6/internals/testing/loadable.mock.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function ExportedFunc() {
4 | return My lazy-loaded component
;
5 | }
6 | export default ExportedFunc;
7 |
--------------------------------------------------------------------------------
/chapter-6/internals/ts-node.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "noEmit": true,
7 | "allowSyntheticDefaultImports": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-6/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-6/public/favicon.ico
--------------------------------------------------------------------------------
/chapter-6/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-6/public/logo192.png
--------------------------------------------------------------------------------
/chapter-6/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-6/public/logo512.png
--------------------------------------------------------------------------------
/chapter-6/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/chapter-6/src/api/axios.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | /*create an instance of axios with a default base URI when sending HTTP requests*/
4 | /*JSON Server has CORS Policy by default*/
5 | const api = axios.create({
6 | baseURL: 'http://localhost:5000/',
7 | });
8 |
9 | export default api;
10 |
11 | export const EndPoints = {
12 | sales: 'sales',
13 | };
14 |
--------------------------------------------------------------------------------
/chapter-6/src/app/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 |
4 | import { App } from '../index';
5 |
6 | const renderer = createRenderer();
7 |
8 | describe('', () => {
9 | it('should render and match the snapshot', () => {
10 | renderer.render();
11 | const renderedOutput = renderer.getRenderOutput();
12 | expect(renderedOutput).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-6/src/app/components/page.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, HTMLProps, ReactNode } from 'react';
2 | import { Helmet } from 'react-helmet-async';
3 |
4 | type Props = {
5 | children?: ReactNode;
6 | title?: string;
7 | } & HTMLProps;
8 |
9 | const Page = forwardRef(
10 | ({ children, title = '', ...rest }, ref) => {
11 | return (
12 |
13 |
14 | {title}
15 |
16 | {children}
17 |
18 | );
19 | },
20 | );
21 |
22 | export default Page;
23 |
--------------------------------------------------------------------------------
/chapter-6/src/app/views/dashboard/settings-and-privacy.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const SettingsAndPrivacy = () => (
4 |
5 |
Settings and Privacy Content
6 |
7 | );
8 |
9 | export default SettingsAndPrivacy;
10 |
--------------------------------------------------------------------------------
/chapter-6/src/app/views/pages/AboutPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const AboutPage = () => {
4 | return (
5 |
6 |
This is About Page
7 |
8 | );
9 | };
10 |
11 | export default AboutPage;
12 |
--------------------------------------------------------------------------------
/chapter-6/src/app/views/pages/HomePage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | const Main = () => {
4 | return (
5 |
6 |
Main Page
7 |
8 | );
9 | };
10 |
11 | export default Main;
12 |
--------------------------------------------------------------------------------
/chapter-6/src/app/views/pages/NotFoundPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFoundPage = () => {
4 | return (
5 |
6 |
404 Page Not Found
7 |
8 | );
9 | };
10 |
11 | export default NotFoundPage;
12 |
--------------------------------------------------------------------------------
/chapter-6/src/locales/__tests__/i18n.test.ts:
--------------------------------------------------------------------------------
1 | import { i18n } from '../i18n';
2 |
3 | describe('i18n', () => {
4 | it('should initate i18n', async () => {
5 | const t = await i18n;
6 | expect(t).toBeDefined();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/chapter-6/src/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "welcome"
3 | }
4 |
--------------------------------------------------------------------------------
/chapter-6/src/locales/types.ts:
--------------------------------------------------------------------------------
1 | export type ConvertedToObjectType = {
2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType;
3 | };
4 |
--------------------------------------------------------------------------------
/chapter-6/src/models/sale-type.ts:
--------------------------------------------------------------------------------
1 | export type SaleType = {
2 | name: string;
3 | data: number[];
4 | };
5 |
--------------------------------------------------------------------------------
/chapter-6/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
4 | ///
5 |
--------------------------------------------------------------------------------
/chapter-6/src/services/saleService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { SaleType } from 'models/sale-type';
3 |
4 | export async function getSalesAxios() {
5 | return await api.get(EndPoints.sales);
6 | }
7 |
--------------------------------------------------------------------------------
/chapter-6/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';
6 |
7 | import 'react-app-polyfill/ie11';
8 | import 'react-app-polyfill/stable';
9 |
10 | import 'jest-styled-components';
11 |
--------------------------------------------------------------------------------
/chapter-6/src/styles/__tests__/media.test.ts:
--------------------------------------------------------------------------------
1 | import { media, sizes } from '../media';
2 | import { css } from 'styled-components/macro';
3 |
4 | describe('media', () => {
5 | it('should return media query in css', () => {
6 | const mediaQuery = media.small`color:red;`.join('');
7 | const cssVersion = css`
8 | @media (min-width: ${sizes.small}px) {
9 | color: red;
10 | }
11 | `.join('');
12 | expect(mediaQuery).toEqual(cssVersion);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-6/src/types/RootState.ts:
--------------------------------------------------------------------------------
1 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
2 |
3 | /*
4 | Because the redux-injectors injects your reducers asynchronously somewhere in your code
5 | You have to declare them here manually
6 | */
7 | export interface RootState {
8 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-6/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import { RootState } from './RootState';
2 |
3 | export type { RootState };
4 |
--------------------------------------------------------------------------------
/chapter-6/src/utils/@reduxjs/toolkit.tsx:
--------------------------------------------------------------------------------
1 | import { RootStateKeyType } from '../types/injector-typings';
2 | import {
3 | createSlice as createSliceOriginal,
4 | SliceCaseReducers,
5 | CreateSliceOptions,
6 | } from '@reduxjs/toolkit';
7 |
8 | /* Wrap createSlice with stricter Name options */
9 |
10 | /* istanbul ignore next */
11 | export const createSlice = <
12 | State,
13 | CaseReducers extends SliceCaseReducers,
14 | Name extends RootStateKeyType
15 | >(
16 | options: CreateSliceOptions,
17 | ) => {
18 | return createSliceOriginal(options);
19 | };
20 |
--------------------------------------------------------------------------------
/chapter-6/src/utils/redux-injectors.ts:
--------------------------------------------------------------------------------
1 | import {
2 | useInjectReducer as useReducer,
3 | useInjectSaga as useSaga,
4 | } from 'redux-injectors';
5 | import {
6 | InjectReducerParams,
7 | InjectSagaParams,
8 | RootStateKeyType,
9 | } from './types/injector-typings';
10 |
11 | /* Wrap redux-injectors with stricter types */
12 |
13 | export function useInjectReducer(
14 | params: InjectReducerParams,
15 | ) {
16 | return useReducer(params);
17 | }
18 |
19 | export function useInjectSaga(params: InjectSagaParams) {
20 | return useSaga(params);
21 | }
22 |
--------------------------------------------------------------------------------
/chapter-7/.babel-plugin-macrosrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styledComponents: {
3 | displayName: process.env.NODE_ENV !== 'production',
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/chapter-7/.env.local:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | EXTEND_ESLINT=true
--------------------------------------------------------------------------------
/chapter-7/.env.production:
--------------------------------------------------------------------------------
1 | GENERATE_SOURCEMAP=false
--------------------------------------------------------------------------------
/chapter-7/.eslintignore:
--------------------------------------------------------------------------------
1 | cypress
--------------------------------------------------------------------------------
/chapter-7/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't check auto-generated stuff into git
2 | coverage
3 | build
4 | node_modules
5 | stats.json
6 | .pnp
7 | .pnp.js
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | # boilerplate internals
20 | generated-cra-app
21 | .cra-template-rb
22 | template
23 |
24 | .eslintcache
--------------------------------------------------------------------------------
/chapter-7/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact = true
2 |
--------------------------------------------------------------------------------
/chapter-7/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/dubnium
2 |
--------------------------------------------------------------------------------
/chapter-7/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 | yarn.lock
--------------------------------------------------------------------------------
/chapter-7/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "arrowParens": "avoid"
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-7/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "processors": ["stylelint-processor-styled-components"],
3 | "extends": [
4 | "stylelint-config-recommended",
5 | "stylelint-config-styled-components"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/chapter-7/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/chapter-7/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-7/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-7/cypress/screenshots/All Integration Specs/my-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-7/cypress/screenshots/All Integration Specs/my-image.png
--------------------------------------------------------------------------------
/chapter-7/internals/generators/component/index.test.tsx.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 |
4 | import { {{ properCase ComponentName }} } from '..';
5 |
6 | describe('<{{ properCase ComponentName }} />', () => {
7 | it('should match snapshot', () => {
8 | const loadingIndicator = render(<{{ properCase ComponentName }} />);
9 | expect(loadingIndicator.container.firstChild).toMatchSnapshot();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/chapter-7/internals/generators/component/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-7/internals/generators/container/appendRootState.hbs:
--------------------------------------------------------------------------------
1 | {{ camelCase ComponentName }}?: {{ properCase ComponentName }}State;
2 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-7/internals/generators/container/importContainerState.hbs:
--------------------------------------------------------------------------------
1 | import { {{ properCase ComponentName }}State } from 'app/containers/{{ properCase ComponentName }}/types';
2 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-7/internals/generators/container/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-7/internals/generators/container/saga.ts.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select, takeLatest } from 'redux-saga/effects';
2 | // import { actions } from './slice';
3 |
4 | // export function* doSomething() {}
5 |
6 | export function* {{ camelCase ComponentName }}Saga() {
7 | // yield takeLatest(actions.someAction.type, doSomething);
8 | }
9 |
--------------------------------------------------------------------------------
/chapter-7/internals/generators/container/selectors.ts.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from '@reduxjs/toolkit';
2 |
3 | import { RootState } from 'types';
4 | import { initialState } from './slice';
5 |
6 | const selectDomain = (state: RootState) => state.{{ camelCase ComponentName }} || initialState;
7 |
8 | export const select{{ properCase ComponentName }} = createSelector(
9 | [selectDomain],
10 | {{ camelCase ComponentName }}State => {{ camelCase ComponentName }}State,
11 | );
12 |
--------------------------------------------------------------------------------
/chapter-7/internals/generators/container/types.ts.hbs:
--------------------------------------------------------------------------------
1 | /* --- STATE --- */
2 | export interface {{ properCase ComponentName }}State {}
3 |
4 | export type ContainerState = {{ properCase ComponentName }}State;
--------------------------------------------------------------------------------
/chapter-7/internals/testing/loadable.mock.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function ExportedFunc() {
4 | return My lazy-loaded component
;
5 | }
6 | export default ExportedFunc;
7 |
--------------------------------------------------------------------------------
/chapter-7/internals/ts-node.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "noEmit": true,
7 | "allowSyntheticDefaultImports": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-7/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-7/public/favicon.ico
--------------------------------------------------------------------------------
/chapter-7/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-7/public/logo192.png
--------------------------------------------------------------------------------
/chapter-7/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-7/public/logo512.png
--------------------------------------------------------------------------------
/chapter-7/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/chapter-7/src/api/axios.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | /*create an instance of axios with a default base URI when sending HTTP requests*/
4 | /*JSON Server has CORS Policy by default*/
5 | const api = axios.create({
6 | baseURL: 'http://localhost:5000/',
7 | });
8 |
9 | export default api;
10 |
11 | export const EndPoints = {
12 | sales: 'sales',
13 | products: 'products',
14 | };
15 |
--------------------------------------------------------------------------------
/chapter-7/src/app/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 |
4 | import { App } from '../index';
5 |
6 | const renderer = createRenderer();
7 |
8 | describe('', () => {
9 | it('should render and match the snapshot', () => {
10 | renderer.render();
11 | const renderedOutput = renderer.getRenderOutput();
12 | expect(renderedOutput).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-7/src/app/components/page.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, HTMLProps, ReactNode } from 'react';
2 | import { Helmet } from 'react-helmet-async';
3 |
4 | type Props = {
5 | children?: ReactNode;
6 | title?: string;
7 | } & HTMLProps;
8 |
9 | const Page = forwardRef(
10 | ({ children, title = '', ...rest }, ref) => {
11 | return (
12 |
13 |
14 | {title}
15 |
16 | {children}
17 |
18 | );
19 | },
20 | );
21 |
22 | export default Page;
23 |
--------------------------------------------------------------------------------
/chapter-7/src/app/views/dashboard/product/ProductCreateView/schema/yupProductValidation.ts:
--------------------------------------------------------------------------------
1 | import * as Yup from 'yup';
2 |
3 | export const yupProductValidation = Yup.object().shape({
4 | category: Yup.string().max(255),
5 | description: Yup.string().max(5000),
6 | images: Yup.array(),
7 | includesTaxes: Yup.bool().required(),
8 | isTaxable: Yup.bool().required(),
9 | name: Yup.string().max(255).required(),
10 | price: Yup.number().min(0).required(),
11 | productCode: Yup.string().max(255),
12 | productSku: Yup.string().max(255),
13 | salePrice: Yup.number().min(0),
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-7/src/app/views/pages/AboutPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const AboutPage = () => {
4 | return (
5 |
6 |
This is About Page
7 |
8 | );
9 | };
10 |
11 | export default AboutPage;
12 |
--------------------------------------------------------------------------------
/chapter-7/src/app/views/pages/HomePage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | const Main = () => {
4 | return (
5 |
6 |
Main Page
7 |
8 | );
9 | };
10 |
11 | export default Main;
12 |
--------------------------------------------------------------------------------
/chapter-7/src/app/views/pages/NotFoundPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFoundPage = () => {
4 | return (
5 |
6 |
404 Page Not Found
7 |
8 | );
9 | };
10 |
11 | export default NotFoundPage;
12 |
--------------------------------------------------------------------------------
/chapter-7/src/locales/__tests__/i18n.test.ts:
--------------------------------------------------------------------------------
1 | import { i18n } from '../i18n';
2 |
3 | describe('i18n', () => {
4 | it('should initate i18n', async () => {
5 | const t = await i18n;
6 | expect(t).toBeDefined();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/chapter-7/src/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "welcome"
3 | }
4 |
--------------------------------------------------------------------------------
/chapter-7/src/locales/types.ts:
--------------------------------------------------------------------------------
1 | export type ConvertedToObjectType = {
2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType;
3 | };
4 |
--------------------------------------------------------------------------------
/chapter-7/src/models/sale-type.ts:
--------------------------------------------------------------------------------
1 | export type SaleType = {
2 | name: string;
3 | data: number[];
4 | };
5 |
--------------------------------------------------------------------------------
/chapter-7/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
4 | ///
5 |
--------------------------------------------------------------------------------
/chapter-7/src/services/productService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { ProductType } from 'models/product-type';
3 |
4 | export async function getProductsAxios() {
5 | return await api.get(EndPoints.products);
6 | }
7 |
8 | export async function postProductAxios(product: ProductType) {
9 | return await api.post(EndPoints.products, product);
10 | }
11 |
--------------------------------------------------------------------------------
/chapter-7/src/services/saleService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { SaleType } from 'models/sale-type';
3 |
4 | export async function getSalesAxios() {
5 | return await api.get(EndPoints.sales);
6 | }
7 |
--------------------------------------------------------------------------------
/chapter-7/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';
6 |
7 | import 'react-app-polyfill/ie11';
8 | import 'react-app-polyfill/stable';
9 |
10 | import 'jest-styled-components';
11 |
--------------------------------------------------------------------------------
/chapter-7/src/styles/__tests__/media.test.ts:
--------------------------------------------------------------------------------
1 | import { media, sizes } from '../media';
2 | import { css } from 'styled-components/macro';
3 |
4 | describe('media', () => {
5 | it('should return media query in css', () => {
6 | const mediaQuery = media.small`color:red;`.join('');
7 | const cssVersion = css`
8 | @media (min-width: ${sizes.small}px) {
9 | color: red;
10 | }
11 | `.join('');
12 | expect(mediaQuery).toEqual(cssVersion);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-7/src/types/RootState.ts:
--------------------------------------------------------------------------------
1 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
2 |
3 | /*
4 | Because the redux-injectors injects your reducers asynchronously somewhere in your code
5 | You have to declare them here manually
6 | */
7 | export interface RootState {
8 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-7/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import { RootState } from './RootState';
2 |
3 | export type { RootState };
4 |
--------------------------------------------------------------------------------
/chapter-7/src/utils/@reduxjs/toolkit.tsx:
--------------------------------------------------------------------------------
1 | import { RootStateKeyType } from '../types/injector-typings';
2 | import {
3 | createSlice as createSliceOriginal,
4 | SliceCaseReducers,
5 | CreateSliceOptions,
6 | } from '@reduxjs/toolkit';
7 |
8 | /* Wrap createSlice with stricter Name options */
9 |
10 | /* istanbul ignore next */
11 | export const createSlice = <
12 | State,
13 | CaseReducers extends SliceCaseReducers,
14 | Name extends RootStateKeyType
15 | >(
16 | options: CreateSliceOptions,
17 | ) => {
18 | return createSliceOriginal(options);
19 | };
20 |
--------------------------------------------------------------------------------
/chapter-7/src/utils/bytes-to-size.ts:
--------------------------------------------------------------------------------
1 | const bytesToSize = (bytes: number, decimals: number = 2) => {
2 | if (bytes === 0) return '0 Bytes';
3 |
4 | const k = 1024;
5 | const dm = decimals < 0 ? 0 : decimals;
6 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
7 | const i = Math.floor(Math.log(bytes) / Math.log(k));
8 |
9 | return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
10 | };
11 |
12 | export default bytesToSize;
13 |
--------------------------------------------------------------------------------
/chapter-7/src/utils/redux-injectors.ts:
--------------------------------------------------------------------------------
1 | import {
2 | useInjectReducer as useReducer,
3 | useInjectSaga as useSaga,
4 | } from 'redux-injectors';
5 | import {
6 | InjectReducerParams,
7 | InjectSagaParams,
8 | RootStateKeyType,
9 | } from './types/injector-typings';
10 |
11 | /* Wrap redux-injectors with stricter types */
12 |
13 | export function useInjectReducer(
14 | params: InjectReducerParams,
15 | ) {
16 | return useReducer(params);
17 | }
18 |
19 | export function useInjectSaga(params: InjectSagaParams) {
20 | return useSaga(params);
21 | }
22 |
--------------------------------------------------------------------------------
/chapter-8/README.md:
--------------------------------------------------------------------------------
1 | # No coding in this chapter
--------------------------------------------------------------------------------
/chapter-9/.babel-plugin-macrosrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styledComponents: {
3 | displayName: process.env.NODE_ENV !== 'production',
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/chapter-9/.env.local:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | EXTEND_ESLINT=true
--------------------------------------------------------------------------------
/chapter-9/.env.production:
--------------------------------------------------------------------------------
1 | GENERATE_SOURCEMAP=false
--------------------------------------------------------------------------------
/chapter-9/.eslintignore:
--------------------------------------------------------------------------------
1 | cypress
--------------------------------------------------------------------------------
/chapter-9/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't check auto-generated stuff into git
2 | coverage
3 | build
4 | node_modules
5 | stats.json
6 | .pnp
7 | .pnp.js
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | # boilerplate internals
20 | generated-cra-app
21 | .cra-template-rb
22 | template
23 |
24 | .eslintcache
--------------------------------------------------------------------------------
/chapter-9/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact = true
2 |
--------------------------------------------------------------------------------
/chapter-9/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/dubnium
2 |
--------------------------------------------------------------------------------
/chapter-9/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 | yarn.lock
--------------------------------------------------------------------------------
/chapter-9/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "arrowParens": "avoid"
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-9/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "processors": ["stylelint-processor-styled-components"],
3 | "extends": [
4 | "stylelint-config-recommended",
5 | "stylelint-config-styled-components"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/chapter-9/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/chapter-9/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-9/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
6 |
--------------------------------------------------------------------------------
/chapter-9/cypress/screenshots/All Integration Specs/my-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-9/cypress/screenshots/All Integration Specs/my-image.png
--------------------------------------------------------------------------------
/chapter-9/internals/generators/component/index.test.tsx.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 |
4 | import { {{ properCase ComponentName }} } from '..';
5 |
6 | describe('<{{ properCase ComponentName }} />', () => {
7 | it('should match snapshot', () => {
8 | const loadingIndicator = render(<{{ properCase ComponentName }} />);
9 | expect(loadingIndicator.container.firstChild).toMatchSnapshot();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/chapter-9/internals/generators/component/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-9/internals/generators/container/appendRootState.hbs:
--------------------------------------------------------------------------------
1 | {{ camelCase ComponentName }}?: {{ properCase ComponentName }}State;
2 | // [INSERT NEW REDUCER KEY ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-9/internals/generators/container/importContainerState.hbs:
--------------------------------------------------------------------------------
1 | import { {{ properCase ComponentName }}State } from 'app/containers/{{ properCase ComponentName }}/types';
2 | // [IMPORT NEW CONTAINERSTATE ABOVE] < Needed for generating containers seamlessly
3 |
--------------------------------------------------------------------------------
/chapter-9/internals/generators/container/loadable.ts.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for {{ properCase ComponentName }}
4 | *
5 | */
6 |
7 | import { lazyLoad } from 'utils/loadable';
8 |
9 | export const {{ properCase ComponentName }} = lazyLoad(() => import('./index'), module => module.{{ properCase ComponentName }});
--------------------------------------------------------------------------------
/chapter-9/internals/generators/container/saga.ts.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select, takeLatest } from 'redux-saga/effects';
2 | // import { actions } from './slice';
3 |
4 | // export function* doSomething() {}
5 |
6 | export function* {{ camelCase ComponentName }}Saga() {
7 | // yield takeLatest(actions.someAction.type, doSomething);
8 | }
9 |
--------------------------------------------------------------------------------
/chapter-9/internals/generators/container/selectors.ts.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from '@reduxjs/toolkit';
2 |
3 | import { RootState } from 'types';
4 | import { initialState } from './slice';
5 |
6 | const selectDomain = (state: RootState) => state.{{ camelCase ComponentName }} || initialState;
7 |
8 | export const select{{ properCase ComponentName }} = createSelector(
9 | [selectDomain],
10 | {{ camelCase ComponentName }}State => {{ camelCase ComponentName }}State,
11 | );
12 |
--------------------------------------------------------------------------------
/chapter-9/internals/generators/container/types.ts.hbs:
--------------------------------------------------------------------------------
1 | /* --- STATE --- */
2 | export interface {{ properCase ComponentName }}State {}
3 |
4 | export type ContainerState = {{ properCase ComponentName }}State;
--------------------------------------------------------------------------------
/chapter-9/internals/testing/loadable.mock.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function ExportedFunc() {
4 | return My lazy-loaded component
;
5 | }
6 | export default ExportedFunc;
7 |
--------------------------------------------------------------------------------
/chapter-9/internals/ts-node.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "noEmit": true,
7 | "allowSyntheticDefaultImports": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/chapter-9/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-9/public/favicon.ico
--------------------------------------------------------------------------------
/chapter-9/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-9/public/logo192.png
--------------------------------------------------------------------------------
/chapter-9/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webmasterdevlin/practical-enterprise-react/1d530ed2e45647d4de2312c67656eda0b55cda66/chapter-9/public/logo512.png
--------------------------------------------------------------------------------
/chapter-9/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/chapter-9/src/api/axios.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | /*create an instance of axios with a default base URI when sending HTTP requests*/
4 | /*JSON Server has CORS Policy by default*/
5 | const api = axios.create({
6 | baseURL: 'http://localhost:5000/',
7 | });
8 |
9 | export default api;
10 |
11 | export const EndPoints = {
12 | sales: 'sales',
13 | products: 'products',
14 | events: 'events',
15 | };
16 |
--------------------------------------------------------------------------------
/chapter-9/src/app/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 |
4 | import { App } from '../index';
5 |
6 | const renderer = createRenderer();
7 |
8 | describe('', () => {
9 | it('should render and match the snapshot', () => {
10 | renderer.render();
11 | const renderedOutput = renderer.getRenderOutput();
12 | expect(renderedOutput).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-9/src/app/components/page.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, HTMLProps, ReactNode } from 'react';
2 | import { Helmet } from 'react-helmet-async';
3 |
4 | type Props = {
5 | children?: ReactNode;
6 | title?: string;
7 | } & HTMLProps;
8 |
9 | const Page = forwardRef(
10 | ({ children, title = '', ...rest }, ref) => {
11 | return (
12 |
13 |
14 | {title}
15 |
16 | {children}
17 |
18 | );
19 | },
20 | );
21 |
22 | export default Page;
23 |
--------------------------------------------------------------------------------
/chapter-9/src/app/views/dashboard/product/ProductCreateView/schema/yupProductValidation.ts:
--------------------------------------------------------------------------------
1 | import * as Yup from 'yup';
2 |
3 | export const yupProductValidation = Yup.object().shape({
4 | category: Yup.string().max(255),
5 | description: Yup.string().max(5000),
6 | images: Yup.array(),
7 | includesTaxes: Yup.bool().required(),
8 | isTaxable: Yup.bool().required(),
9 | name: Yup.string().max(255).required(),
10 | price: Yup.number().min(0).required(),
11 | productCode: Yup.string().max(255),
12 | productSku: Yup.string().max(255),
13 | salePrice: Yup.number().min(0),
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-9/src/app/views/pages/AboutPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const AboutPage = () => {
4 | return (
5 |
6 |
This is About Page
7 |
8 | );
9 | };
10 |
11 | export default AboutPage;
12 |
--------------------------------------------------------------------------------
/chapter-9/src/app/views/pages/HomePage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | const Main = () => {
4 | return (
5 |
6 |
Main Page
7 |
8 | );
9 | };
10 |
11 | export default Main;
12 |
--------------------------------------------------------------------------------
/chapter-9/src/app/views/pages/NotFoundPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFoundPage = () => {
4 | return (
5 |
6 |
404 Page Not Found
7 |
8 | );
9 | };
10 |
11 | export default NotFoundPage;
12 |
--------------------------------------------------------------------------------
/chapter-9/src/locales/__tests__/i18n.test.ts:
--------------------------------------------------------------------------------
1 | import { i18n } from '../i18n';
2 |
3 | describe('i18n', () => {
4 | it('should initate i18n', async () => {
5 | const t = await i18n;
6 | expect(t).toBeDefined();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/chapter-9/src/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "welcome"
3 | }
4 |
--------------------------------------------------------------------------------
/chapter-9/src/locales/types.ts:
--------------------------------------------------------------------------------
1 | export type ConvertedToObjectType = {
2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType;
3 | };
4 |
--------------------------------------------------------------------------------
/chapter-9/src/models/calendar-type.ts:
--------------------------------------------------------------------------------
1 | export type EventType = {
2 | id: string;
3 | allDay: boolean;
4 | color?: string;
5 | description: string;
6 | end: Date;
7 | start: Date;
8 | title: string;
9 | };
10 |
11 | export type ViewType =
12 | | 'dayGridMonth'
13 | | 'timeGridWeek'
14 | | 'timeGridDay'
15 | | 'listWeek';
16 |
--------------------------------------------------------------------------------
/chapter-9/src/models/sale-type.ts:
--------------------------------------------------------------------------------
1 | export type SaleType = {
2 | name: string;
3 | data: number[];
4 | };
5 |
--------------------------------------------------------------------------------
/chapter-9/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245
4 | ///
5 |
--------------------------------------------------------------------------------
/chapter-9/src/services/productService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { ProductType } from 'models/product-type';
3 |
4 | export async function getProductsAxios() {
5 | return await api.get(EndPoints.products);
6 | }
7 |
8 | export async function postProductAxios(product: ProductType) {
9 | return await api.post(EndPoints.products, product);
10 | }
11 |
--------------------------------------------------------------------------------
/chapter-9/src/services/saleService.ts:
--------------------------------------------------------------------------------
1 | import api, { EndPoints } from 'api/axios';
2 | import { SaleType } from 'models/sale-type';
3 |
4 | export async function getSalesAxios() {
5 | return await api.get(EndPoints.sales);
6 | }
7 |
--------------------------------------------------------------------------------
/chapter-9/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';
6 |
7 | import 'react-app-polyfill/ie11';
8 | import 'react-app-polyfill/stable';
9 |
10 | import 'jest-styled-components';
11 |
--------------------------------------------------------------------------------
/chapter-9/src/store/reducers.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Combine all reducers in this file and export the combined reducers.
3 | */
4 |
5 | import { combineReducers } from '@reduxjs/toolkit';
6 | import calendarReducer from 'features/calendar/calendarSlice';
7 |
8 | const injectedReducers = {
9 | calendar: calendarReducer,
10 | };
11 |
12 | const rootReducer = combineReducers({
13 | ...injectedReducers,
14 | });
15 |
16 | export type RootState = ReturnType;
17 |
18 | export const createReducer = () => rootReducer;
19 |
--------------------------------------------------------------------------------
/chapter-9/src/styles/__tests__/media.test.ts:
--------------------------------------------------------------------------------
1 | import { media, sizes } from '../media';
2 | import { css } from 'styled-components/macro';
3 |
4 | describe('media', () => {
5 | it('should return media query in css', () => {
6 | const mediaQuery = media.small`color:red;`.join('');
7 | const cssVersion = css`
8 | @media (min-width: ${sizes.small}px) {
9 | color: red;
10 | }
11 | `.join('');
12 | expect(mediaQuery).toEqual(cssVersion);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/chapter-9/src/utils/bytes-to-size.ts:
--------------------------------------------------------------------------------
1 | const bytesToSize = (bytes: number, decimals: number = 2) => {
2 | if (bytes === 0) return '0 Bytes';
3 |
4 | const k = 1024;
5 | const dm = decimals < 0 ? 0 : decimals;
6 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
7 | const i = Math.floor(Math.log(bytes) / Math.log(k));
8 |
9 | return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
10 | };
11 |
12 | export default bytesToSize;
13 |
--------------------------------------------------------------------------------