├── .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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /chapter-4/starter-boilerplate/src/app/containers/GithubRepoForm/assets/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 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 | <h1 12 | class="c0" 13 | /> 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('<Logo />', () => { 6 | it('should match snapshot', () => { 7 | const logo = render(<Logo />); 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('<Nav />', () => { 7 | it('should match the snapshot', () => { 8 | const logo = render( 9 | <MemoryRouter> 10 | <Nav /> 11 | </MemoryRouter>, 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[`<NavBar /> should match snapshot 1`] = ` 4 | <NavBar__Wrapper> 5 | <PageWrapper> 6 | <Logo /> 7 | <Nav /> 8 | </PageWrapper> 9 | </NavBar__Wrapper> 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('<NavBar />', () => { 8 | it('should match snapshot', () => { 9 | shallowRenderer.render(<NavBar />); 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 | <svg class="icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path fill-rule="evenodd" clip-rule="evenodd" d="M7 6H17V18H7V6ZM5 20V4H19V20H5ZM15 9H8V10H15V9ZM8 11H15V12H8V11ZM12 13H8V14H12V13Z" fill="currentColor"/> 3 | </svg> 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: <LoadingIndicator />, 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<T> = { 2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType<T[P]>; 3 | }; 4 | -------------------------------------------------------------------------------- /chapter-4/starter-boilerplate/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="react-scripts" /> 2 | 3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245 4 | /// <reference types="styled-components/cssprop" /> 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 | <div> 5 | My lazy-loaded component 6 | </div> 7 | `; 8 | 9 | exports[`loadable should render fallback if given one 1`] = ` 10 | <div> 11 | Loading 12 | </div> 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 <div>My lazy-loaded component</div>; 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('<App />', () => { 9 | it('should render and match the snapshot', () => { 10 | renderer.render(<App />); 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 | <Grid 13 | container 14 | direction="row" 15 | justify="flex-start" 16 | alignItems="flex-start" 17 | > 18 | <DashboardSidebarNavigation /> {children} 19 | </Grid> 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 | <div> 5 | <h1>Dashboard Default Content</h1> 6 | </div> 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 | <div> 5 | <h1>Settings and Privacy Content</h1> 6 | </div> 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 | <div> 6 | <h1>This is About Page</h1> 7 | </div> 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 | <div> 6 | <h1>Main Page</h1> 7 | </div> 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 | <div> 6 | <h1>404 Page Not Found</h1> 7 | </div> 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<T> = { 2 | [P in keyof T]: T[P] extends string ? string : ConvertedToObjectType<T[P]>; 3 | }; 4 | -------------------------------------------------------------------------------- /chapter-5/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="react-scripts" /> 2 | 3 | // To solve the issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31245 4 | /// <reference types="styled-components/cssprop" /> 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<State>, 14 | Name extends RootStateKeyType 15 | >( 16 | options: CreateSliceOptions<State, CaseReducers, Name>, 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<Key extends RootStateKeyType>( 14 | params: InjectReducerParams<Key>, 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 <div>My lazy-loaded component</div>; 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('<App />', () => { 9 | it('should render and match the snapshot', () => { 10 | renderer.render(<App />); 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<HTMLDivElement>; 8 | 9 | const Page = forwardRef<HTMLDivElement, Props>( 10 | ({ children, title = '', ...rest }, ref) => { 11 | return ( 12 | <div ref={ref as any} {...rest}> 13 | <Helmet> 14 | <title>{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 | --------------------------------------------------------------------------------