├── .editorconfig ├── .env.example ├── .eslint.another.js ├── .eslintignore ├── .eslintrc ├── .github ├── mergify.yml └── workflows │ ├── build.yml │ └── lint.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .travis.yml ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── build ├── asset-manifest.json ├── favicon.ico ├── images │ ├── image-default.jpg │ ├── iphone-11-pro-max.jpg │ ├── iphone-11-pro.jpg │ ├── iphone-11.jpg │ ├── iphone-8-plus.jpg │ ├── iphone-se.jpg │ ├── iphone-x.jpg │ ├── iphone-xr.jpg │ └── iphone-xs-max.jpg ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── robots.txt └── static │ ├── css │ ├── 9.b00201c5.chunk.css │ ├── 9.b00201c5.chunk.css.map │ ├── main.7f760b14.chunk.css │ └── main.7f760b14.chunk.css.map │ ├── js │ ├── 0.c4acbe49.chunk.js │ ├── 0.c4acbe49.chunk.js.LICENSE.txt │ ├── 0.c4acbe49.chunk.js.map │ ├── 1.02189ba9.chunk.js │ ├── 1.02189ba9.chunk.js.map │ ├── 10.ea9accaa.chunk.js │ ├── 10.ea9accaa.chunk.js.map │ ├── 11.00591301.chunk.js │ ├── 11.00591301.chunk.js.map │ ├── 12.17fdb4a4.chunk.js │ ├── 12.17fdb4a4.chunk.js.map │ ├── 13.d248867a.chunk.js │ ├── 13.d248867a.chunk.js.map │ ├── 14.c7595904.chunk.js │ ├── 14.c7595904.chunk.js.map │ ├── 15.fc221629.chunk.js │ ├── 15.fc221629.chunk.js.map │ ├── 16.42efe802.chunk.js │ ├── 16.42efe802.chunk.js.map │ ├── 17.158f0397.chunk.js │ ├── 17.158f0397.chunk.js.map │ ├── 18.4506f6b4.chunk.js │ ├── 18.4506f6b4.chunk.js.map │ ├── 19.55b1ae06.chunk.js │ ├── 19.55b1ae06.chunk.js.map │ ├── 2.0c14bcc1.chunk.js │ ├── 2.0c14bcc1.chunk.js.map │ ├── 20.305f5474.chunk.js │ ├── 20.305f5474.chunk.js.map │ ├── 21.fc6f3247.chunk.js │ ├── 21.fc6f3247.chunk.js.map │ ├── 22.d5cf7005.chunk.js │ ├── 22.d5cf7005.chunk.js.map │ ├── 23.e961ffed.chunk.js │ ├── 23.e961ffed.chunk.js.map │ ├── 24.942bca39.chunk.js │ ├── 24.942bca39.chunk.js.map │ ├── 25.b09a67e1.chunk.js │ ├── 25.b09a67e1.chunk.js.map │ ├── 26.fb5a996d.chunk.js │ ├── 26.fb5a996d.chunk.js.map │ ├── 3.c93c435d.chunk.js │ ├── 3.c93c435d.chunk.js.map │ ├── 4.6229faaa.chunk.js │ ├── 4.6229faaa.chunk.js.map │ ├── 5.68865e81.chunk.js │ ├── 5.68865e81.chunk.js.map │ ├── 6.03eeb66d.chunk.js │ ├── 6.03eeb66d.chunk.js.map │ ├── 9.b37c6a40.chunk.js │ ├── 9.b37c6a40.chunk.js.LICENSE.txt │ ├── 9.b37c6a40.chunk.js.map │ ├── main.a4aaf2ea.chunk.js │ ├── main.a4aaf2ea.chunk.js.map │ ├── runtime-main.adee2a3d.js │ └── runtime-main.adee2a3d.js.map │ └── media │ └── home2.aaaacc9b.jpeg ├── package.json ├── public ├── favicon.ico ├── images │ ├── image-default.jpg │ ├── iphone-11-pro-max.jpg │ ├── iphone-11-pro.jpg │ ├── iphone-11.jpg │ ├── iphone-8-plus.jpg │ ├── iphone-se.jpg │ ├── iphone-x.jpg │ ├── iphone-xr.jpg │ └── iphone-xs-max.jpg ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── renovate.json ├── server ├── db.json ├── package.json ├── routes.json ├── server.js └── yarn.lock ├── src ├── @types │ ├── actions.d.ts │ ├── alert.d.ts │ ├── api.d.ts │ ├── files.d.ts │ ├── product.d.ts │ ├── reducer.d.ts │ └── user.d.ts ├── App │ └── App.tsx ├── assets │ ├── images │ │ ├── home.jpeg │ │ ├── home.svg │ │ ├── home2.jpeg │ │ ├── list.svg │ │ ├── modern-design.jpg │ │ ├── open-menu.svg │ │ ├── products │ │ │ └── apple │ │ │ │ ├── iphone-11-pro-max.jpg │ │ │ │ ├── iphone-11-pro.jpg │ │ │ │ ├── iphone-11.jpg │ │ │ │ ├── iphone-8-plus.jpg │ │ │ │ ├── iphone-se.jpg │ │ │ │ ├── iphone-x.jpg │ │ │ │ ├── iphone-xr.jpg │ │ │ │ └── iphone-xs-max.jpg │ │ └── spinner.gif │ └── scss │ │ ├── _config.scss │ │ ├── _fonts.scss │ │ ├── _footer.scss │ │ ├── _header.scss │ │ ├── _home.scss │ │ ├── _login.scss │ │ ├── _responsive.scss │ │ ├── _staticPages.scss │ │ └── index.scss ├── components │ ├── Alert │ │ ├── Alert.actions.ts │ │ ├── Alert.constants.ts │ │ ├── Alert.reducers.ts │ │ ├── Alert.thunks.ts │ │ └── index.tsx │ ├── Auth │ │ ├── Auth.actions.ts │ │ ├── Auth.constants.ts │ │ ├── Auth.reducers.ts │ │ ├── Auth.thunks.ts │ │ ├── Login.tsx │ │ ├── Profile.tsx │ │ └── Register.tsx │ ├── Error │ │ └── 404.tsx │ ├── Footer │ │ └── index.tsx │ ├── Header │ │ ├── LeftMenu.tsx │ │ ├── RightMenu.tsx │ │ └── index.tsx │ ├── Home │ │ ├── AuthLinks.tsx │ │ ├── GuestLinks.tsx │ │ └── index.tsx │ ├── Loading │ │ └── index.tsx │ ├── Products │ │ ├── Product.actions.ts │ │ ├── Product.constants.ts │ │ ├── Product.reducers.ts │ │ ├── Product.thunks.ts │ │ ├── ProductForm.tsx │ │ ├── ProductItem.tsx │ │ └── ProductList.tsx │ └── StaticPages │ │ ├── About.tsx │ │ ├── Contact.tsx │ │ ├── Demo.option.1.tsx │ │ ├── Demo.option.2.tsx │ │ ├── Feature.option.1.tsx │ │ └── Feature.option.2.tsx ├── constants │ ├── alerts.ts │ ├── paths.ts │ ├── products.ts │ └── urls.ts ├── hooks │ └── usePrevious.tsx ├── index.tsx ├── pages │ ├── AuthPages │ │ ├── LoginPage.tsx │ │ ├── ProfilePage.tsx │ │ └── RegisterPage.tsx │ ├── ErrorPages │ │ └── 404Pages.tsx │ ├── HomePages │ │ └── HomePage.tsx │ ├── ProductPages │ │ ├── ProductEditPage.tsx │ │ ├── ProductItemPage.tsx │ │ ├── ProductListPage.tsx │ │ └── ProductNewPage.tsx │ ├── StaticPages │ │ ├── AboutPage.tsx │ │ ├── ContactPage.tsx │ │ ├── Demo1Page.tsx │ │ ├── Demo2Page.tsx │ │ ├── Feature1Page.tsx │ │ └── Feature2Page.tsx │ └── layouts │ │ ├── MainLayout.tsx │ │ └── PageLayout.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── routes │ ├── PrivateRoute.tsx │ └── index.tsx ├── setupTests.ts ├── store │ ├── index.ts │ └── reducers.ts └── utils │ └── helper.js ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | EXTEND_ESLINT=true -------------------------------------------------------------------------------- /.eslint.another.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | extends: [ 4 | 'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react 5 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 6 | 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 7 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 8 | ], 9 | parserOptions: { 10 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 11 | sourceType: 'module', // Allows for the use of imports 12 | ecmaFeatures: { 13 | jsx: true, // Allows for the parsing of JSX 14 | }, 15 | }, 16 | rules: { 17 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 18 | // e.g. "@typescript-eslint/explicit-function-return-type": "off", 19 | }, 20 | settings: { 21 | react: { 22 | version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use 23 | }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /src/serviceWorker.ts 2 | /src/setupTests.ts -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["react-app", "prettier"], 3 | "plugins": ["react", "prettier"], 4 | "rules": { 5 | "prettier/prettier": [ 6 | "warn", 7 | { 8 | "arrowParens": "avoid", 9 | "trailingComma": "all", 10 | "endOfLine": "lf", 11 | "tabWidth": 2, 12 | "printWidth": 80, 13 | "useTabs": false 14 | } 15 | ], 16 | "no-console": "warn" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: Automated merge for Renovate pull requests 3 | conditions: 4 | - author=renovate[bot] 5 | - status-success=build (14.x) 6 | - status-success=lint (14.x) 7 | - status-success=Travis CI - Pull Request 8 | actions: 9 | merge: 10 | method: merge 11 | label: 12 | add: ['chore(deps):', '🛠 Improvement'] 13 | 14 | - name: Automated dependabot merge 15 | conditions: 16 | - author~=^dependabot(|-preview)\[bot\]$ 17 | - status-success=build (14.x) 18 | - status-success=lint (14.x) 19 | - status-success=Travis CI - Pull Request 20 | actions: 21 | merge: 22 | method: merge 23 | label: 24 | add: ['chore(deps):', '🛠 Improvement'] 25 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build workflow 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x] 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v2 20 | 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | 26 | - name: Install dependencies 27 | run: yarn install 28 | 29 | - name: Build local 30 | run: yarn build 31 | 32 | # - name: Deploy to netlify 33 | # uses: netlify/actions/cli@master 34 | # env: 35 | # NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 36 | # NETLIFY_SITE_ID: ${{ secrets.DEV_NETLIFY_SITE_ID }} 37 | # with: 38 | # args: deploy --dir=build --prod 39 | # secrets: '["NETLIFY_AUTH_TOKEN", "NETLIFY_SITE_ID"]' 40 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v2 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - name: Install dependencies 24 | run: yarn install 25 | - run: yarn lint 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | node_modules 3 | scripts/flow/*/.flowconfig 4 | .flowconfig 5 | *~ 6 | *.pyc 7 | .grunt 8 | _SpecRunner.html 9 | __benchmarks__ 10 | remote-repo/ 11 | coverage/ 12 | .module-cache 13 | fixtures/dom/public/react-dom.js 14 | fixtures/dom/public/react.js 15 | test/the-files-to-test.generated.js 16 | *.log* 17 | chrome-user-data 18 | *.sublime-project 19 | *.sublime-workspace 20 | .idea 21 | *.iml 22 | *.swp 23 | *.swo 24 | 25 | packages/react-devtools-core/dist 26 | packages/react-devtools-extensions/chrome/build 27 | packages/react-devtools-extensions/chrome/*.crx 28 | packages/react-devtools-extensions/chrome/*.pem 29 | packages/react-devtools-extensions/firefox/build 30 | packages/react-devtools-extensions/firefox/*.xpi 31 | packages/react-devtools-extensions/firefox/*.pem 32 | packages/react-devtools-extensions/shared/build 33 | packages/react-devtools-extensions/.tempUserDataDir 34 | packages/react-devtools-inline/dist 35 | packages/react-devtools-shell/dist 36 | packages/react-devtools-scheduling-profiler/dist 37 | 38 | 39 | *.rbc 40 | capybara-*.html 41 | .rspec 42 | /db/*.sqlite3 43 | /db/*.sqlite3-journal 44 | /db/*.sqlite3-[0-9]* 45 | /public/system 46 | /coverage/ 47 | /spec/tmp 48 | *.orig 49 | rerun.txt 50 | pickle-email-*.html 51 | 52 | # Ignore all logfiles and tempfiles. 53 | /log/* 54 | /tmp/* 55 | !/log/.keep 56 | !/tmp/.keep 57 | 58 | # TODO Comment out this rule if you are OK with secrets being uploaded to the repo 59 | config/initializers/secret_token.rb 60 | config/master.key 61 | 62 | # Only include if you have production secrets in this file, which is no longer a Rails default 63 | # config/secrets.yml 64 | 65 | # dotenv, dotenv-rails 66 | # TODO Comment out these rules if environment variables can be committed 67 | .env 68 | 69 | 70 | ## Environment normalization: 71 | /.bundle 72 | /vendor/bundle 73 | 74 | # these should all be checked in to normalize the environment: 75 | # Gemfile.lock, .ruby-version, .ruby-gemset 76 | 77 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 78 | .rvmrc 79 | 80 | # if using bower-rails ignore default bower_components path bower.json files 81 | /vendor/assets/bower_components 82 | *.bowerrc 83 | bower.json 84 | 85 | # Ignore pow environment settings 86 | .powenv 87 | 88 | # Ignore Byebug command history file. 89 | .byebug_history 90 | 91 | # Ignore node_modules 92 | node_modules/ 93 | 94 | # Ignore precompiled javascript packs 95 | /public/packs 96 | /public/packs-test 97 | /public/assets 98 | 99 | # Ignore yarn files 100 | /yarn-error.log 101 | yarn-debug.log* 102 | .yarn-integrity 103 | 104 | # Ignore uploaded files in development 105 | /storage/* 106 | !/storage/.keep 107 | /public/uploads 108 | 109 | 110 | # Logs 111 | logs 112 | *.log 113 | npm-debug.log* 114 | yarn-debug.log* 115 | yarn-error.log* 116 | lerna-debug.log* 117 | 118 | # Diagnostic reports (https://nodejs.org/api/report.html) 119 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 120 | 121 | # Runtime data 122 | pids 123 | *.pid 124 | *.seed 125 | *.pid.lock 126 | 127 | # Directory for instrumented libs generated by jscoverage/JSCover 128 | lib-cov 129 | 130 | # Coverage directory used by tools like istanbul 131 | coverage 132 | *.lcov 133 | 134 | # nyc test coverage 135 | .nyc_output 136 | 137 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 138 | .grunt 139 | 140 | # Bower dependency directory (https://bower.io/) 141 | bower_components 142 | 143 | # node-waf configuration 144 | .lock-wscript 145 | 146 | # Compiled binary addons (https://nodejs.org/api/addons.html) 147 | 148 | # Dependency directories 149 | node_modules/ 150 | jspm_packages/ 151 | 152 | # Snowpack dependency directory (https://snowpack.dev/) 153 | web_modules/ 154 | 155 | # TypeScript cache 156 | *.tsbuildinfo 157 | 158 | # Optional npm cache directory 159 | .npm 160 | 161 | # Optional eslint cache 162 | .eslintcache 163 | 164 | # Microbundle cache 165 | .rpt2_cache/ 166 | .rts2_cache_cjs/ 167 | .rts2_cache_es/ 168 | .rts2_cache_umd/ 169 | 170 | # Optional REPL history 171 | .node_repl_history 172 | 173 | # Output of 'npm pack' 174 | *.tgz 175 | 176 | # Yarn Integrity file 177 | .yarn-integrity 178 | 179 | # dotenv environment variables file 180 | .env 181 | .env.test 182 | 183 | # parcel-bundler cache (https://parceljs.org/) 184 | .cache 185 | .parcel-cache 186 | 187 | # Next.js build output 188 | .next 189 | out 190 | 191 | # Nuxt.js build / generate output 192 | .nuxt 193 | dist 194 | dist/ 195 | # Gatsby files 196 | .cache/ 197 | # Comment in the public line in if your project uses Gatsby and not Next.js 198 | # https://nextjs.org/blog/next-9-1#public-directory-support 199 | # public 200 | 201 | # vuepress build output 202 | .vuepress/dist 203 | 204 | # Serverless directories 205 | .serverless/ 206 | 207 | # FuseBox cache 208 | .fusebox/ 209 | 210 | # DynamoDB Local files 211 | .dynamodb/ 212 | 213 | # TernJS port file 214 | .tern-port 215 | 216 | # Stores VSCode versions used for testing VSCode extensions 217 | .vscode-test 218 | 219 | # yarn v2 220 | .yarn/cache 221 | .yarn/unplugged 222 | .yarn/build-state.yml 223 | .yarn/install-state.gz 224 | .pnp.* 225 | 226 | .boilerplate/ 227 | # Local Netlify folder 228 | .netlify -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | package-lock.json 3 | src/serviceWorker.ts 4 | src/setupTests.ts -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "semi": true, 4 | "trailingComma": "all", 5 | "endOfLine": "lf", 6 | "tabWidth": 2, 7 | "printWidth": 80, 8 | "useTabs": false, 9 | "singleQuote": true 10 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | branches: 4 | only: 5 | - main 6 | 7 | node_js: 8 | - 12 9 | 10 | cache: 11 | directories: 12 | - node_modules 13 | 14 | notification: 15 | email: false 16 | 17 | script: 18 | - yarn build 19 | - yarn lint 20 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "akamud.vscode-theme-onedark", 4 | "esbenp.prettier-vscode", 5 | "alefragnani.Bookmarks", 6 | "CoenraadS.bracket-pair-colorizer", 7 | "cssho.vscode-svgviewer", 8 | "dbaeumer.vscode-eslint", 9 | "dsznajder.es7-react-js-snippets", 10 | "eamodio.gitlens", 11 | "EditorConfig.EditorConfig", 12 | "formulahendry.auto-close-tag", 13 | "formulahendry.auto-rename-tag", 14 | "jpoissonnier.vscode-styled-components", 15 | "PKief.material-icon-theme", 16 | "streetsidesoftware.code-spell-checker", 17 | "ritwickdey.LiveServer", 18 | "riazxrazor.html-to-jsx" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true 6 | }, 7 | "prettier.singleQuote": true, 8 | "prettier.jsxSingleQuote": true 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tien Duy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeScript React Boilerplate 2 | 3 | Template Typescript React Project 4 | 5 | ![Travis (.com)](https://img.shields.io/travis/com/tienduy-nguyen/typescript-react-boilerplate) 6 | [![Netlify Status](https://api.netlify.com/api/v1/badges/2c01fcbb-9811-4e0e-b86c-e1b79e4f2c46/deploy-status)](https://app.netlify.com/sites/reactts-boilerplate/deploys) 7 | 8 | 9 | Demo deploy at: [reactts-boilerplate.netlify.app](https://reactts-boilerplate.netlify.app/) 10 | 11 | 12 | 13 | **This version is deprecated, It will be updated & changed soon. Learn more about [React boiler plate](https://github.com/react-boilerplate/react-boilerplate)** 14 | 15 | 16 | --- 17 | 18 | - [TypeScript React Boilerplate](#typescript-react-boilerplate) 19 | - [Installation](#installation) 20 | - [Technologies](#technologies) 21 | - [Project structure](#project-structure) 22 | - [src folder](#src-folder) 23 | - [Project Routes](#project-routes) 24 | - [Public routes](#public-routes) 25 | - [Private routes (need auth)](#private-routes-need-auth) 26 | ## Installation 27 | 28 | To use this template 29 | - Clone this project 30 | - Rename project as you want 31 | - Install dependencies from `package.json` to your machine 32 | 33 | ```bash 34 | $ yarn 35 | # or 36 | $ npm install 37 | ``` 38 | 39 | - Run or build project 40 | ```bash 41 | $ yarn start 42 | $ yarn build 43 | # or npm run start / npm run build 44 | ``` 45 | - Start coding 46 | 47 | Login 48 | ``` 49 | username: tester 50 | password: 123456 51 | ``` 52 | 53 | 54 | ## Technologies 55 | - Integrate ESlint, Prettier 56 | - Styled-Component and CSS Module for CSS 57 | - Using TypeScript 58 | - Using Redux, React thunks 59 | - Functional programming with React hooks 60 | - Lazy load page 61 | - Using ant design 62 | - Using json-server to create fake server backend 63 | - CI-CD with Travisci & Github actions 64 | 65 | 66 | ## Project structure 67 | 68 |
69 | Click me to expand 70 | 71 | ```tree 72 | . 73 | ├── LICENSE 74 | ├── package.json 75 | ├── public 76 | │ ├── favicon.ico 77 | │ ├── images 78 | │ ├── index.html 79 | │ ├── logo192.png 80 | │ ├── logo512.png 81 | │ ├── manifest.json 82 | │ └── robots.txt 83 | ├── README.md 84 | ├── server 85 | │ ├── db 86 | │ │ └── db.json 87 | │ └── routes.json 88 | ├── src 89 | │ ├── App 90 | │ │ └── App.tsx 91 | │ ├── assets 92 | │ │ ├── images 93 | │ │ └── scss 94 | │ │ ├── _config.scss 95 | │ │ ├── _fonts.scss 96 | │ │ ├── _footer.scss 97 | │ │ ├── _header.scss 98 | │ │ ├── _home.scss 99 | │ │ ├── index.scss 100 | │ │ ├── _login.scss 101 | │ │ ├── _responsive.scss 102 | │ │ └── _staticPages.scss 103 | │ ├── components 104 | │ │ ├── Auth 105 | │ │ │ ├── Auth.actions.ts 106 | │ │ │ ├── Auth.constants.ts 107 | │ │ │ ├── Auth.reducers.ts 108 | │ │ │ ├── Auth.thunks.ts 109 | │ │ │ ├── Login.tsx 110 | │ │ │ ├── Profile.tsx 111 | │ │ │ └── Register.tsx 112 | │ │ ├── Error 113 | │ │ │ └── 404.tsx 114 | │ │ ├── Footer 115 | │ │ │ └── index.tsx 116 | │ │ ├── Header 117 | │ │ │ ├── index.tsx 118 | │ │ │ ├── LeftMenu.tsx 119 | │ │ │ └── RightMenu.tsx 120 | │ │ ├── Home 121 | │ │ │ ├── AuthLinks.tsx 122 | │ │ │ ├── GuestLinks.tsx 123 | │ │ │ └── index.tsx 124 | │ │ ├── Loading 125 | │ │ │ ├── index.tsx 126 | │ │ │ └── Loading.styles.ts 127 | │ │ ├── Products 128 | │ │ │ ├── Product.actions.ts 129 | │ │ │ ├── Product.constants.ts 130 | │ │ │ ├── ProductForm.tsx 131 | │ │ │ ├── ProductItem.tsx 132 | │ │ │ ├── ProductList.tsx 133 | │ │ │ ├── Product.reducers.ts 134 | │ │ │ └── Product.thunks.ts 135 | │ │ └── StaticPages 136 | │ │ ├── About.tsx 137 | │ │ ├── Contact.tsx 138 | │ │ ├── Demo.option.1.tsx 139 | │ │ ├── Demo.option.2.tsx 140 | │ │ ├── Feature.option.1.tsx 141 | │ │ └── Feature.option.2.tsx 142 | │ ├── constants 143 | │ │ ├── paths.ts 144 | │ │ ├── products.ts 145 | │ │ └── urls.ts 146 | │ ├── hooks 147 | │ │ └── usePrevious.tsx 148 | │ ├── index.tsx 149 | │ ├── pages 150 | │ │ ├── AuthPages 151 | │ │ │ ├── LoginPage.tsx 152 | │ │ │ ├── ProfilePage.tsx 153 | │ │ │ └── RegisterPage.tsx 154 | │ │ ├── ErrorPages 155 | │ │ │ └── 404Pages.tsx 156 | │ │ ├── HomePages 157 | │ │ │ └── HomePage.tsx 158 | │ │ ├── layouts 159 | │ │ │ └── MainLayout.tsx 160 | │ │ ├── ProductPages 161 | │ │ │ ├── ProductEditPage.tsx 162 | │ │ │ ├── ProductItemPage.tsx 163 | │ │ │ ├── ProductListPage.tsx 164 | │ │ │ └── ProductNewPage.tsx 165 | │ │ └── StaticPages 166 | │ │ ├── AboutPage.tsx 167 | │ │ ├── ContactPage.tsx 168 | │ │ ├── Demo1Page.tsx 169 | │ │ ├── Demo2Page.tsx 170 | │ │ ├── Feature1Page.tsx 171 | │ │ └── Feature2Page.tsx 172 | │ ├── react-app-env.d.ts 173 | │ ├── reportWebVitals.ts 174 | │ ├── routes 175 | │ │ ├── index.tsx 176 | │ │ └── PrivateRoute.tsx 177 | │ ├── setupTests.ts 178 | │ ├── store 179 | │ │ ├── index.ts 180 | │ │ └── reducers.ts 181 | │ ├── @types 182 | │ │ ├── actions.d.ts 183 | │ │ ├── alert.d.ts 184 | │ │ ├── api.d.ts 185 | │ │ ├── files.d.ts 186 | │ │ ├── product.d.ts 187 | │ │ ├── reducer.d.ts 188 | │ │ └── user.d.ts 189 | │ └── utils 190 | │ └── helper.js 191 | ├── tsconfig.json 192 | └── yarn.lock 193 | 194 | ``` 195 | 196 |
197 | 198 | --- 199 | 200 | ### src folder 201 | - **@types**: Declare modules, interface, type for TypeScript 202 | - `action.d.ts`: Action Interface for Redux 203 | - `api.d.ts`: Response interface for api 204 | - `files.d.ts`: Declare modules for images, videos, css formats... 205 | - `reducer.d.ts`: return type of reducer 206 | - `product.d.ts`, `user.d.ts`: return interface of item in project 207 | - **api**: services, contains functions get, post .. api (axios e.g) 208 | - **App**: component App 209 | - **assets**: images, videos, files, … 210 | - **components**: contains folders components 211 | - **constants**: constant, enum 212 | - **helpers**: functions helpers 213 | - **hooks**: contains hooks using 214 | - **pages**: pages of project 215 | - **routes**: private routes and public routes of project 216 | - **store**: store of Redux and root reducers 217 | 218 | ## Project Routes 219 | ### Public routes 220 | - **Home**: '/': Show landing page before login 221 | - **Feature - Option 1**: '/feature1' 222 | - **Feature - Option 2**: '/feature2' 223 | - **Demo - Option 1**: '/demo1' 224 | - **Demo - Option 2**: '/demo1' 225 | - **About**: '/about' 226 | - **Contact**: '/about' 227 | - **Login**: '/login' 228 | - **Register**: '/signup' 229 | - **404**: Page not found 230 | 231 | ### Private routes (need auth) 232 | - **Profile**: '/profile' 233 | - **Products**: '/' or '/products': Show list of products 234 | - **Show Product**: '/products/:id 235 | - **Create Product**: '/products/new 236 | - **Edit Product**: '/products/:id/edit 237 | - **Update Product**: '/products/:id 238 | - **Delete Product**: button click 239 | -------------------------------------------------------------------------------- /build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "static/js/0.c4acbe49.chunk.js": "/static/js/0.c4acbe49.chunk.js", 4 | "static/js/0.c4acbe49.chunk.js.map": "/static/js/0.c4acbe49.chunk.js.map", 5 | "static/js/1.02189ba9.chunk.js": "/static/js/1.02189ba9.chunk.js", 6 | "static/js/1.02189ba9.chunk.js.map": "/static/js/1.02189ba9.chunk.js.map", 7 | "static/js/2.0c14bcc1.chunk.js": "/static/js/2.0c14bcc1.chunk.js", 8 | "static/js/2.0c14bcc1.chunk.js.map": "/static/js/2.0c14bcc1.chunk.js.map", 9 | "static/js/3.c93c435d.chunk.js": "/static/js/3.c93c435d.chunk.js", 10 | "static/js/3.c93c435d.chunk.js.map": "/static/js/3.c93c435d.chunk.js.map", 11 | "static/js/4.6229faaa.chunk.js": "/static/js/4.6229faaa.chunk.js", 12 | "static/js/4.6229faaa.chunk.js.map": "/static/js/4.6229faaa.chunk.js.map", 13 | "static/js/5.68865e81.chunk.js": "/static/js/5.68865e81.chunk.js", 14 | "static/js/5.68865e81.chunk.js.map": "/static/js/5.68865e81.chunk.js.map", 15 | "static/js/6.03eeb66d.chunk.js": "/static/js/6.03eeb66d.chunk.js", 16 | "static/js/6.03eeb66d.chunk.js.map": "/static/js/6.03eeb66d.chunk.js.map", 17 | "main.css": "/static/css/main.7f760b14.chunk.css", 18 | "main.js": "/static/js/main.a4aaf2ea.chunk.js", 19 | "main.js.map": "/static/js/main.a4aaf2ea.chunk.js.map", 20 | "runtime-main.js": "/static/js/runtime-main.adee2a3d.js", 21 | "runtime-main.js.map": "/static/js/runtime-main.adee2a3d.js.map", 22 | "static/css/9.b00201c5.chunk.css": "/static/css/9.b00201c5.chunk.css", 23 | "static/js/9.b37c6a40.chunk.js": "/static/js/9.b37c6a40.chunk.js", 24 | "static/js/9.b37c6a40.chunk.js.map": "/static/js/9.b37c6a40.chunk.js.map", 25 | "static/js/10.ea9accaa.chunk.js": "/static/js/10.ea9accaa.chunk.js", 26 | "static/js/10.ea9accaa.chunk.js.map": "/static/js/10.ea9accaa.chunk.js.map", 27 | "static/js/11.00591301.chunk.js": "/static/js/11.00591301.chunk.js", 28 | "static/js/11.00591301.chunk.js.map": "/static/js/11.00591301.chunk.js.map", 29 | "static/js/12.17fdb4a4.chunk.js": "/static/js/12.17fdb4a4.chunk.js", 30 | "static/js/12.17fdb4a4.chunk.js.map": "/static/js/12.17fdb4a4.chunk.js.map", 31 | "static/js/13.d248867a.chunk.js": "/static/js/13.d248867a.chunk.js", 32 | "static/js/13.d248867a.chunk.js.map": "/static/js/13.d248867a.chunk.js.map", 33 | "static/js/14.c7595904.chunk.js": "/static/js/14.c7595904.chunk.js", 34 | "static/js/14.c7595904.chunk.js.map": "/static/js/14.c7595904.chunk.js.map", 35 | "static/js/15.fc221629.chunk.js": "/static/js/15.fc221629.chunk.js", 36 | "static/js/15.fc221629.chunk.js.map": "/static/js/15.fc221629.chunk.js.map", 37 | "static/js/16.42efe802.chunk.js": "/static/js/16.42efe802.chunk.js", 38 | "static/js/16.42efe802.chunk.js.map": "/static/js/16.42efe802.chunk.js.map", 39 | "static/js/17.158f0397.chunk.js": "/static/js/17.158f0397.chunk.js", 40 | "static/js/17.158f0397.chunk.js.map": "/static/js/17.158f0397.chunk.js.map", 41 | "static/js/18.4506f6b4.chunk.js": "/static/js/18.4506f6b4.chunk.js", 42 | "static/js/18.4506f6b4.chunk.js.map": "/static/js/18.4506f6b4.chunk.js.map", 43 | "static/js/19.55b1ae06.chunk.js": "/static/js/19.55b1ae06.chunk.js", 44 | "static/js/19.55b1ae06.chunk.js.map": "/static/js/19.55b1ae06.chunk.js.map", 45 | "static/js/20.305f5474.chunk.js": "/static/js/20.305f5474.chunk.js", 46 | "static/js/20.305f5474.chunk.js.map": "/static/js/20.305f5474.chunk.js.map", 47 | "static/js/21.fc6f3247.chunk.js": "/static/js/21.fc6f3247.chunk.js", 48 | "static/js/21.fc6f3247.chunk.js.map": "/static/js/21.fc6f3247.chunk.js.map", 49 | "static/js/22.d5cf7005.chunk.js": "/static/js/22.d5cf7005.chunk.js", 50 | "static/js/22.d5cf7005.chunk.js.map": "/static/js/22.d5cf7005.chunk.js.map", 51 | "static/js/23.e961ffed.chunk.js": "/static/js/23.e961ffed.chunk.js", 52 | "static/js/23.e961ffed.chunk.js.map": "/static/js/23.e961ffed.chunk.js.map", 53 | "static/js/24.942bca39.chunk.js": "/static/js/24.942bca39.chunk.js", 54 | "static/js/24.942bca39.chunk.js.map": "/static/js/24.942bca39.chunk.js.map", 55 | "static/js/25.b09a67e1.chunk.js": "/static/js/25.b09a67e1.chunk.js", 56 | "static/js/25.b09a67e1.chunk.js.map": "/static/js/25.b09a67e1.chunk.js.map", 57 | "static/js/26.fb5a996d.chunk.js": "/static/js/26.fb5a996d.chunk.js", 58 | "static/js/26.fb5a996d.chunk.js.map": "/static/js/26.fb5a996d.chunk.js.map", 59 | "index.html": "/index.html", 60 | "static/css/9.b00201c5.chunk.css.map": "/static/css/9.b00201c5.chunk.css.map", 61 | "static/css/main.7f760b14.chunk.css.map": "/static/css/main.7f760b14.chunk.css.map", 62 | "static/js/0.c4acbe49.chunk.js.LICENSE.txt": "/static/js/0.c4acbe49.chunk.js.LICENSE.txt", 63 | "static/js/9.b37c6a40.chunk.js.LICENSE.txt": "/static/js/9.b37c6a40.chunk.js.LICENSE.txt", 64 | "static/media/index.scss": "/static/media/home2.aaaacc9b.jpeg" 65 | }, 66 | "entrypoints": [ 67 | "static/js/runtime-main.adee2a3d.js", 68 | "static/css/9.b00201c5.chunk.css", 69 | "static/js/9.b37c6a40.chunk.js", 70 | "static/css/main.7f760b14.chunk.css", 71 | "static/js/main.a4aaf2ea.chunk.js" 72 | ] 73 | } -------------------------------------------------------------------------------- /build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/build/favicon.ico -------------------------------------------------------------------------------- /build/images/image-default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/build/images/image-default.jpg -------------------------------------------------------------------------------- /build/images/iphone-11-pro-max.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/build/images/iphone-11-pro-max.jpg -------------------------------------------------------------------------------- /build/images/iphone-11-pro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/build/images/iphone-11-pro.jpg -------------------------------------------------------------------------------- /build/images/iphone-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/build/images/iphone-11.jpg -------------------------------------------------------------------------------- /build/images/iphone-8-plus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/build/images/iphone-8-plus.jpg -------------------------------------------------------------------------------- /build/images/iphone-se.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/build/images/iphone-se.jpg -------------------------------------------------------------------------------- /build/images/iphone-x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/build/images/iphone-x.jpg -------------------------------------------------------------------------------- /build/images/iphone-xr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/build/images/iphone-xr.jpg -------------------------------------------------------------------------------- /build/images/iphone-xs-max.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/build/images/iphone-xs-max.jpg -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | React App
-------------------------------------------------------------------------------- /build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/build/logo192.png -------------------------------------------------------------------------------- /build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/build/logo512.png -------------------------------------------------------------------------------- /build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /build/static/css/main.7f760b14.chunk.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap);.m-1{margin:1rem}.my-1{margin:1rem 0}.mx-1{margin:0 1rem}.ml-1{margin-left:1rem}.mr-1{margin-right:1rem}.mt-1{margin-top:1rem}.mb-1{margin-bottom:1rem}.p-1{padding:1rem}.py-1{padding:1rem 0}.px-1{padding:0 1rem}.mp-1{padding-left:1rem}.pr-1{padding-right:1rem}.pt-1{padding-top:1rem}.pb-1{padding-bottom:1rem}.m-2{margin:2rem}.my-2{margin:2rem 0}.mx-2{margin:0 2rem}.ml-2{margin-left:2rem}.mr-2{margin-right:2rem}.mt-2{margin-top:2rem}.mb-2{margin-bottom:2rem}.p-2{padding:2rem}.py-2{padding:2rem 0}.px-2{padding:0 2rem}.mp-2{padding-left:2rem}.pr-2{padding-right:2rem}.pt-2{padding-top:2rem}.pb-2{padding-bottom:2rem}.m-3{margin:3rem}.my-3{margin:3rem 0}.mx-3{margin:0 3rem}.ml-3{margin-left:3rem}.mr-3{margin-right:3rem}.mt-3{margin-top:3rem}.mb-3{margin-bottom:3rem}.p-3{padding:3rem}.py-3{padding:3rem 0}.px-3{padding:0 3rem}.mp-3{padding-left:3rem}.pr-3{padding-right:3rem}.pt-3{padding-top:3rem}.pb-3{padding-bottom:3rem}.m-4{margin:4rem}.my-4{margin:4rem 0}.mx-4{margin:0 4rem}.ml-4{margin-left:4rem}.mr-4{margin-right:4rem}.mt-4{margin-top:4rem}.mb-4{margin-bottom:4rem}.p-4{padding:4rem}.py-4{padding:4rem 0}.px-4{padding:0 4rem}.mp-4{padding-left:4rem}.pr-4{padding-right:4rem}.pt-4{padding-top:4rem}.pb-4{padding-bottom:4rem}.m-5{margin:5rem}.my-5{margin:5rem 0}.mx-5{margin:0 5rem}.ml-5{margin-left:5rem}.mr-5{margin-right:5rem}.mt-5{margin-top:5rem}.mb-5{margin-bottom:5rem}.p-5{padding:5rem}.py-5{padding:5rem 0}.px-5{padding:0 5rem}.mp-5{padding-left:5rem}.pr-5{padding-right:5rem}.pt-5{padding-top:5rem}.pb-5{padding-bottom:5rem}body{font-family:"Roboto",sans-serif;font-weight:300}.ant-layout-header{position:relative;left:0;right:0;z-index:9}.main-layout .ant-layout-header{background:#fff;padding:0;height:60px;line-height:1.6}.navbar-section{border-bottom:1px solid #e8e8e8;overflow:auto;box-shadow:0 0 2px #f3f1f1}.navbar{display:flex;align-items:center;padding:0}.navbar .ant-menu-item{padding:0 2px}.navbar .ant-menu-submenu-title{padding:4px}.navbar .ant-menu-item a,.navbar .ant-menu-submenu-title a{padding:10px 0}.navbar .ant-menu-horizontal{border-bottom:none}.navbar-left-menu{float:left}.navbar-right-menu{float:right}.navbar-logo{width:150px;float:left}.navbar-logo a{display:inline-block;font-size:1.2rem;padding:16px 20px;text-transform:uppercase}.navbar-menu{width:calc(100% - 150px);float:left}.navbar .navbar-btnBars{float:right;height:32px;padding:6px;margin-top:8px;display:none;background:none}.navbar .navbar-btnBars-span{display:block;width:20px;height:2px;background:#1890ff;position:relative}.navbar .navbar-btnBars-span:after,.navbar .navbar-btnBars-span:before{content:attr(x);width:20px;position:absolute;top:-6px;left:0;height:2px;background:#1890ff}.navbar .navbar-btnBars-span:after{top:auto;bottom:-6px}.navbar .navbar-btnBars-span>span{display:block}.ant-drawer-body{padding:0}.ant-drawer-body .ant-menu-horizontal>.ant-menu-item,.ant-drawer-body .ant-menu-horizontal>.ant-menu-submenu{display:inline-block;width:100%}.ant-drawer-body .ant-menu-horizontal{border-bottom:none}.ant-drawer-body .ant-menu-horizontal>.ant-menu-item:hover{border-bottom-color:transparent}@media (max-width:767px){.navbar .navbar-btnBars{display:inline-block}.navbar .navbar-left-menu,.navbar .navbar-right-menu{display:none}.navbar .navbar-logo a{margin-left:-20px;padding:10px 20px}.navbar .ant-menu-item,.navbar .ant-menu-submenu-title{padding:1px 20px}}.ant-layout-footer{background:#111!important;color:#fff!important;padding:20px 0!important;text-align:center;height:150px}.footer .footer-logo{font-size:1.2rem;text-transform:uppercase;margin:0 0 1rem;font-weight:500}.footer .footer-logo a{color:#fff}.footer .footer-logo a:hover{color:#1890ff}.footer-inner{color:#fff}.ant-back-top svg{color:#1890ff;font-size:1.6rem}.ant-back-top svg:hover{opacity:.7}.footer .footer-socials{list-style:none;display:flex;align-items:center;justify-content:center;padding:0;font-size:24px;margin-bottom:.5rem}.footer .footer-socials li{margin:0 10px}.footer .footer-socials a{color:#fff}.footer .footer-socials a:hover{color:#1890ff}.homepage{position:relative;background:url(/static/media/home2.aaaacc9b.jpeg) no-repeat 50%/cover;height:100vh}.homepage-inner{color:#fff;height:100%;display:flex;flex-direction:column;width:80%;margin:auto;align-items:center;justify-content:center;text-align:center}.homepage h1{color:#fff;font-size:250%;text-transform:uppercase;font-weight:700;letter-spacing:2px}.homepage p{font-size:large;font-weight:400}.homepage .home-content{background:rgba(0,0,0,.3);padding:2rem 8rem;border-radius:4px}.homepage .home-content .home-text-light{color:#b3b3b3}.homepage .home-content .home-text-light span{color:#fff}.home-overlay{background:rgba(0,0,0,.2);height:100vh}.main-body-section{margin:2rem auto 1rem}.block .ant-form{max-width:640px;margin:auto}.block .ant-form textarea{min-height:120px;height:120px}.block-title{text-align:center;max-width:500px;position:relative;padding:0 0 20px;margin:auto auto 40px}.block-title:after{transform:translateX(-50%);content:"";background:#1890ff;position:absolute;bottom:0;left:50%;height:3px;width:50px}.block-title h2{font-size:28px;margin:0}.demo-content{margin:auto;text-align:center}.demo-button-play:before{content:"";background:rgba(0,0,0,.05);position:absolute;top:0;bottom:0;left:0;right:0}.hide-on-mobile{display:block}.show-on-mobile{display:none}@media only screen and (max-width:991px){.pricingBlock .ant-row>div:nth-child(2) .ant-list-item{transform:scale(1.14)}}@media only screen and (max-width:767px){.hide-on-mobile{display:none}.show-on-mobile{display:block}}.login-form-wrap{margin:80px auto;max-width:300px;height:80vh}.login-form-title{text-align:center}.login-form-title img{height:44px;margin-right:16}.login-form{max-width:300px}.login-form-forgot{float:right}.ant-col-rtl .login-form-forgot{float:left}.login-form-button{width:100%;margin-bottom:.5rem}.login-form-register-link-wrapper{float:right}.site-form-item-icon svg{color:rgba(0,0,0,.25)}*{box-sizing:border-box;margin:0;padding:0}body{font-size:1rem;line-height:1.6;background-color:#fff}a{text-decoration:none;color:#1890ff}ul{list-style:none}img{width:100%}.container-fluid{max-width:100%}.container,.container-fluid{margin:0 auto;padding:0 15px}.container{max-width:1200px}.ant-layout{background:#fff}.ant-layout-content{min-height:calc(100vh - 210px)!important;overflow:auto}.ant-layout-content,.ant-layout.main-layout{background-color:#fff}p{font-size:16px;line-height:1.6} 2 | /*# sourceMappingURL=main.7f760b14.chunk.css.map */ -------------------------------------------------------------------------------- /build/static/js/0.c4acbe49.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | Copyright (c) 2017 Jed Watson. 3 | Licensed under the MIT License (MIT), see 4 | http://jedwatson.github.io/classnames 5 | */ 6 | -------------------------------------------------------------------------------- /build/static/js/19.55b1ae06.chunk.js: -------------------------------------------------------------------------------- 1 | (this["webpackJsonptypescript-react-boilerplate"]=this["webpackJsonptypescript-react-boilerplate"]||[]).push([[19],{101:function(e,t,c){"use strict";c.d(t,"a",(function(){return T}));var a=c(1),n=c(0),s=c(111),r=c(21),i=c(253),j=c(181),l=i.a.SubMenu,o=i.a.ItemGroup,b=j.a.useBreakpoint,d=function(){var e=b().md;return Object(a.jsxs)(i.a,{mode:e?"horizontal":"inline",children:[Object(a.jsx)(i.a.Item,{children:Object(a.jsx)(r.c,{className:"navbar-item",to:"/",children:"Home"})},"key-home"),Object(a.jsxs)(l,{title:"Services",children:[Object(a.jsxs)(o,{title:"Features",children:[Object(a.jsx)(i.a.Item,{children:Object(a.jsx)(r.c,{to:"/feature1",children:"Option 1"})},"setting:1"),Object(a.jsx)(i.a.Item,{children:Object(a.jsx)(r.c,{to:"/feature2",children:"Option 2"})},"setting:2")]}),Object(a.jsxs)(o,{title:"Demo",children:[Object(a.jsx)(i.a.Item,{children:Object(a.jsx)(r.c,{to:"/demo1",children:"Option 1"})},"setting:3"),Object(a.jsx)(i.a.Item,{children:Object(a.jsx)(r.c,{to:"/demo2",children:"Option 2"})},"setting:4")]})]},"sub1"),Object(a.jsx)(i.a.Item,{children:Object(a.jsx)(r.c,{className:"navbar-item",to:"/about",children:"About"})},"key-about"),Object(a.jsx)(i.a.Item,{children:Object(a.jsx)(r.c,{className:"navbar-item",to:"/contact",children:"Contact"})},"key-contact")]})},h=c(443),O=c(26),m=c(17),x=c(4),u=j.a.useBreakpoint,v={logout:O.c},f=Object(m.b)((function(e){return{isAuthenticated:e.auth.isAuthenticated,user:e.auth.user}}),v)((function(e){var t=e.isAuthenticated,c=e.logout,n=e.user,s=u().md,j=Object(a.jsxs)(i.a,{mode:s?"horizontal":"inline",children:[Object(a.jsx)(i.a.Item,{children:Object(a.jsx)(r.c,{className:"navbar-item primary",to:x.a.LOGIN,children:"Sign In"})},"menukey-login"),Object(a.jsx)(i.a.Item,{children:Object(a.jsx)(r.c,{className:"navbar-item",to:x.a.REGISTER,children:"Register"})},"menukey-signup")]}),l=Object(a.jsxs)(i.a,{mode:s?"horizontal":"inline",children:[Object(a.jsx)(i.a.Item,{children:Object(a.jsxs)(r.c,{className:"navbar-item primary",to:x.a.PROFILE,children:["Hi ",Object(a.jsx)("strong",{children:n.username})]})},"menukey-profile"),Object(a.jsx)(i.a.Item,{children:Object(a.jsx)(r.c,{className:"navbar-item primary",to:x.a.HOME,onClick:function(){return c()},children:Object(a.jsxs)("span",{children:[Object(a.jsx)(h.a,{}),"Log Out"]})})},"menukey-login")]});return Object(a.jsx)(a.Fragment,{children:t?l:j})})),p=c(146),g=c(430),N=function(){var e=Object(n.useState)(!1),t=Object(s.a)(e,2),c=t[0],i=t[1];return Object(a.jsx)("div",{className:"navbar-section",children:Object(a.jsx)("div",{className:"container",children:Object(a.jsxs)("nav",{className:"navbar",children:[Object(a.jsx)("div",{className:"navbar-logo",children:Object(a.jsx)(r.c,{to:"/",className:"navbar-item",activeClassName:"is-active",children:"REACT TS"})}),Object(a.jsxs)("div",{className:"navbar-menu",children:[Object(a.jsx)("div",{className:"navbar-left-menu",children:Object(a.jsx)(d,{})}),Object(a.jsx)("div",{className:"navbar-right-menu",children:Object(a.jsx)(f,{})})]}),Object(a.jsxs)("div",{children:[Object(a.jsx)(p.a,{className:"navbar-btnBars",type:"primary",onClick:function(){i(!0)},children:Object(a.jsx)("span",{className:"navbar-btnBars-span"})}),Object(a.jsxs)(g.a,{title:"Drawer navbar",placement:"right",closable:!0,onClose:function(){i(!1)},visible:c,children:[Object(a.jsx)(d,{}),Object(a.jsx)(f,{})]})]})]})})})},y=c(444),k=c(445),I=c(446),w=c(447),C=c(441),E=function(){return Object(a.jsx)("div",{className:"footer",children:Object(a.jsx)("div",{className:"container",children:Object(a.jsxs)("div",{className:"footer-inner",children:[Object(a.jsx)("div",{className:"footer-logo",children:Object(a.jsx)("a",{href:"/",children:"REACT TS"})}),Object(a.jsxs)("ul",{className:"footer-socials",children:[Object(a.jsx)("li",{children:Object(a.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://github.com/tienduy-nguyen",children:Object(a.jsx)(y.a,{})})}),Object(a.jsx)("li",{children:Object(a.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.twitter.com/tienduy_nguyen",children:Object(a.jsx)(k.a,{})})}),Object(a.jsx)("li",{children:Object(a.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.linkedin.com/",children:Object(a.jsx)(I.a,{})})})]}),Object(a.jsx)("div",{className:"copyright",children:"Copyright \xa9 2020 REACT TS"}),Object(a.jsx)(C.a,{children:Object(a.jsx)("div",{className:"go-top",children:Object(a.jsx)(w.a,{})})})]})})})},S=c(416),R=S.a.Header,A=S.a.Content,B=S.a.Footer,T=function(e){var t=e.children;return Object(a.jsxs)(S.a,{className:"main-layout",children:[Object(a.jsx)(R,{children:Object(a.jsx)(N,{})}),Object(a.jsx)(A,{className:"layout-children",children:t}),Object(a.jsx)(B,{children:Object(a.jsx)(E,{})})]})}},211:function(e,t,c){"use strict";c.d(t,"a",(function(){return b}));var a=c(1),n=c(0),s={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M448 804a64 64 0 10128 0 64 64 0 10-128 0zm32-168h64c4.4 0 8-3.6 8-8V164c0-4.4-3.6-8-8-8h-64c-4.4 0-8 3.6-8 8v464c0 4.4 3.6 8 8 8z"}}]},name:"exclamation",theme:"outlined"},r=c(109),i=function(e,t){return n.createElement(r.a,Object.assign({},e,{ref:t,icon:s}))};i.displayName="ExclamationOutlined";var j=n.forwardRef(i),l=c(146),o=c(3),b=function(){var e=Object(o.g)();return Object(a.jsx)("div",{className:"main-body-section",children:Object(a.jsx)("div",{className:"container",children:Object(a.jsxs)("div",{className:"block-title",style:{color:"#fa8c16"},children:[Object(a.jsxs)("h2",{style:{color:"#fa8c16"},children:[Object(a.jsx)(j,{}),"Page Not Found"]}),Object(a.jsx)("p",{className:"large",children:"Sorry, this page does not exist"}),Object(a.jsx)(l.a,{type:"primary",onClick:function(){e.goBack()},children:"Go Back"})]})})})}},420:function(e,t,c){"use strict";c.r(t),c.d(t,"NotFoundPage",(function(){return l}));var a=c(1),n=c(0),s=c.n(n),r=c(211),i=c(101),j=function(){return Object(a.jsx)(i.a,{children:Object(a.jsx)(r.a,{})})},l=s.a.memo(j);t.default=l}}]); 2 | //# sourceMappingURL=19.55b1ae06.chunk.js.map -------------------------------------------------------------------------------- /build/static/js/20.305f5474.chunk.js: -------------------------------------------------------------------------------- 1 | (this["webpackJsonptypescript-react-boilerplate"]=this["webpackJsonptypescript-react-boilerplate"]||[]).push([[20],{101:function(e,t,c){"use strict";c.d(t,"a",(function(){return R}));var s=c(1),a=c(0),n=c(111),r=c(21),i=c(253),j=c(181),l=i.a.SubMenu,b=i.a.ItemGroup,o=j.a.useBreakpoint,d=function(){var e=o().md;return Object(s.jsxs)(i.a,{mode:e?"horizontal":"inline",children:[Object(s.jsx)(i.a.Item,{children:Object(s.jsx)(r.c,{className:"navbar-item",to:"/",children:"Home"})},"key-home"),Object(s.jsxs)(l,{title:"Services",children:[Object(s.jsxs)(b,{title:"Features",children:[Object(s.jsx)(i.a.Item,{children:Object(s.jsx)(r.c,{to:"/feature1",children:"Option 1"})},"setting:1"),Object(s.jsx)(i.a.Item,{children:Object(s.jsx)(r.c,{to:"/feature2",children:"Option 2"})},"setting:2")]}),Object(s.jsxs)(b,{title:"Demo",children:[Object(s.jsx)(i.a.Item,{children:Object(s.jsx)(r.c,{to:"/demo1",children:"Option 1"})},"setting:3"),Object(s.jsx)(i.a.Item,{children:Object(s.jsx)(r.c,{to:"/demo2",children:"Option 2"})},"setting:4")]})]},"sub1"),Object(s.jsx)(i.a.Item,{children:Object(s.jsx)(r.c,{className:"navbar-item",to:"/about",children:"About"})},"key-about"),Object(s.jsx)(i.a.Item,{children:Object(s.jsx)(r.c,{className:"navbar-item",to:"/contact",children:"Contact"})},"key-contact")]})},O=c(443),h=c(26),x=c(17),m=c(4),u=j.a.useBreakpoint,p={logout:h.c},v=Object(x.b)((function(e){return{isAuthenticated:e.auth.isAuthenticated,user:e.auth.user}}),p)((function(e){var t=e.isAuthenticated,c=e.logout,a=e.user,n=u().md,j=Object(s.jsxs)(i.a,{mode:n?"horizontal":"inline",children:[Object(s.jsx)(i.a.Item,{children:Object(s.jsx)(r.c,{className:"navbar-item primary",to:m.a.LOGIN,children:"Sign In"})},"menukey-login"),Object(s.jsx)(i.a.Item,{children:Object(s.jsx)(r.c,{className:"navbar-item",to:m.a.REGISTER,children:"Register"})},"menukey-signup")]}),l=Object(s.jsxs)(i.a,{mode:n?"horizontal":"inline",children:[Object(s.jsx)(i.a.Item,{children:Object(s.jsxs)(r.c,{className:"navbar-item primary",to:m.a.PROFILE,children:["Hi ",Object(s.jsx)("strong",{children:a.username})]})},"menukey-profile"),Object(s.jsx)(i.a.Item,{children:Object(s.jsx)(r.c,{className:"navbar-item primary",to:m.a.HOME,onClick:function(){return c()},children:Object(s.jsxs)("span",{children:[Object(s.jsx)(O.a,{}),"Log Out"]})})},"menukey-login")]});return Object(s.jsx)(s.Fragment,{children:t?l:j})})),f=c(146),g=c(430),N=function(){var e=Object(a.useState)(!1),t=Object(n.a)(e,2),c=t[0],i=t[1];return Object(s.jsx)("div",{className:"navbar-section",children:Object(s.jsx)("div",{className:"container",children:Object(s.jsxs)("nav",{className:"navbar",children:[Object(s.jsx)("div",{className:"navbar-logo",children:Object(s.jsx)(r.c,{to:"/",className:"navbar-item",activeClassName:"is-active",children:"REACT TS"})}),Object(s.jsxs)("div",{className:"navbar-menu",children:[Object(s.jsx)("div",{className:"navbar-left-menu",children:Object(s.jsx)(d,{})}),Object(s.jsx)("div",{className:"navbar-right-menu",children:Object(s.jsx)(v,{})})]}),Object(s.jsxs)("div",{children:[Object(s.jsx)(f.a,{className:"navbar-btnBars",type:"primary",onClick:function(){i(!0)},children:Object(s.jsx)("span",{className:"navbar-btnBars-span"})}),Object(s.jsxs)(g.a,{title:"Drawer navbar",placement:"right",closable:!0,onClose:function(){i(!1)},visible:c,children:[Object(s.jsx)(d,{}),Object(s.jsx)(v,{})]})]})]})})})},y=c(444),k=c(445),I=c(446),w=c(447),C=c(441),E=function(){return Object(s.jsx)("div",{className:"footer",children:Object(s.jsx)("div",{className:"container",children:Object(s.jsxs)("div",{className:"footer-inner",children:[Object(s.jsx)("div",{className:"footer-logo",children:Object(s.jsx)("a",{href:"/",children:"REACT TS"})}),Object(s.jsxs)("ul",{className:"footer-socials",children:[Object(s.jsx)("li",{children:Object(s.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://github.com/tienduy-nguyen",children:Object(s.jsx)(y.a,{})})}),Object(s.jsx)("li",{children:Object(s.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.twitter.com/tienduy_nguyen",children:Object(s.jsx)(k.a,{})})}),Object(s.jsx)("li",{children:Object(s.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.linkedin.com/",children:Object(s.jsx)(I.a,{})})})]}),Object(s.jsx)("div",{className:"copyright",children:"Copyright \xa9 2020 REACT TS"}),Object(s.jsx)(C.a,{children:Object(s.jsx)("div",{className:"go-top",children:Object(s.jsx)(w.a,{})})})]})})})},A=c(416),T=A.a.Header,S=A.a.Content,H=A.a.Footer,R=function(e){var t=e.children;return Object(s.jsxs)(A.a,{className:"main-layout",children:[Object(s.jsx)(T,{children:Object(s.jsx)(N,{})}),Object(s.jsx)(S,{className:"layout-children",children:t}),Object(s.jsx)(H,{children:Object(s.jsx)(E,{})})]})}},440:function(e,t,c){"use strict";c.r(t);var s=c(1),a=c(0),n=c.n(a),r=c(17),i=c(423),j=c(227),l=c(228),b=c(429),o=c(449),d=c(413),O=c(450),h=c(3),x=c(4),m=i.a.Meta,u=Object(r.b)((function(e){return{user:e.auth.user}}),{})((function(e){var t=e.user,c=Object(h.g)();return Object(s.jsxs)("div",{className:"mt-1",children:[Object(s.jsx)(j.a,{children:Object(s.jsx)(l.a,{span:8,offset:10,children:Object(s.jsxs)(i.a,{hoverable:!0,style:{width:350},cover:Object(s.jsx)("img",{alt:"example",src:"https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png"}),actions:[Object(s.jsx)(o.a,{onClick:function(){c.push(x.a.HOME)}},"setting"),Object(s.jsx)(d.a,{},"edit"),Object(s.jsx)(O.a,{},"ellipsis")],children:[Object(s.jsx)(m,{avatar:Object(s.jsx)(b.a,{src:"https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"}),title:t.username,description:"User profile"}),Object(s.jsxs)("div",{className:"additional",children:[Object(s.jsx)("p",{}),Object(s.jsxs)(j.a,{children:[Object(s.jsx)(l.a,{offset:4,children:Object(s.jsx)("small",{children:"Email: "})}),Object(s.jsx)(l.a,{offset:4,children:Object(s.jsx)("strong",{children:t.email})})]}),Object(s.jsxs)(j.a,{children:[Object(s.jsx)(l.a,{offset:4,children:Object(s.jsx)("small",{children:"Password: "})}),Object(s.jsx)(l.a,{offset:2,children:Object(s.jsx)("strong",{children:t.password})})]})]})]})})}),","]})})),p=c(101),v=function(){return Object(s.jsx)(p.a,{children:Object(s.jsx)(u,{})})},f=n.a.memo(v);t.default=f}}]); 2 | //# sourceMappingURL=20.305f5474.chunk.js.map -------------------------------------------------------------------------------- /build/static/js/21.fc6f3247.chunk.js: -------------------------------------------------------------------------------- 1 | (this["webpackJsonptypescript-react-boilerplate"]=this["webpackJsonptypescript-react-boilerplate"]||[]).push([[21],{101:function(e,t,i){"use strict";i.d(t,"a",(function(){return T}));var a=i(1),s=i(0),c=i(111),n=i(21),r=i(253),l=i(181),j=r.a.SubMenu,o=r.a.ItemGroup,u=l.a.useBreakpoint,m=function(){var e=u().md;return Object(a.jsxs)(r.a,{mode:e?"horizontal":"inline",children:[Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{className:"navbar-item",to:"/",children:"Home"})},"key-home"),Object(a.jsxs)(j,{title:"Services",children:[Object(a.jsxs)(o,{title:"Features",children:[Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{to:"/feature1",children:"Option 1"})},"setting:1"),Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{to:"/feature2",children:"Option 2"})},"setting:2")]}),Object(a.jsxs)(o,{title:"Demo",children:[Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{to:"/demo1",children:"Option 1"})},"setting:3"),Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{to:"/demo2",children:"Option 2"})},"setting:4")]})]},"sub1"),Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{className:"navbar-item",to:"/about",children:"About"})},"key-about"),Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{className:"navbar-item",to:"/contact",children:"Contact"})},"key-contact")]})},d=i(443),b=i(26),h=i(17),O=i(4),x=l.a.useBreakpoint,p={logout:b.c},v=Object(h.b)((function(e){return{isAuthenticated:e.auth.isAuthenticated,user:e.auth.user}}),p)((function(e){var t=e.isAuthenticated,i=e.logout,s=e.user,c=x().md,l=Object(a.jsxs)(r.a,{mode:c?"horizontal":"inline",children:[Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{className:"navbar-item primary",to:O.a.LOGIN,children:"Sign In"})},"menukey-login"),Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{className:"navbar-item",to:O.a.REGISTER,children:"Register"})},"menukey-signup")]}),j=Object(a.jsxs)(r.a,{mode:c?"horizontal":"inline",children:[Object(a.jsx)(r.a.Item,{children:Object(a.jsxs)(n.c,{className:"navbar-item primary",to:O.a.PROFILE,children:["Hi ",Object(a.jsx)("strong",{children:s.username})]})},"menukey-profile"),Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{className:"navbar-item primary",to:O.a.HOME,onClick:function(){return i()},children:Object(a.jsxs)("span",{children:[Object(a.jsx)(d.a,{}),"Log Out"]})})},"menukey-login")]});return Object(a.jsx)(a.Fragment,{children:t?j:l})})),f=i(146),g=i(430),N=function(){var e=Object(s.useState)(!1),t=Object(c.a)(e,2),i=t[0],r=t[1];return Object(a.jsx)("div",{className:"navbar-section",children:Object(a.jsx)("div",{className:"container",children:Object(a.jsxs)("nav",{className:"navbar",children:[Object(a.jsx)("div",{className:"navbar-logo",children:Object(a.jsx)(n.c,{to:"/",className:"navbar-item",activeClassName:"is-active",children:"REACT TS"})}),Object(a.jsxs)("div",{className:"navbar-menu",children:[Object(a.jsx)("div",{className:"navbar-left-menu",children:Object(a.jsx)(m,{})}),Object(a.jsx)("div",{className:"navbar-right-menu",children:Object(a.jsx)(v,{})})]}),Object(a.jsxs)("div",{children:[Object(a.jsx)(f.a,{className:"navbar-btnBars",type:"primary",onClick:function(){r(!0)},children:Object(a.jsx)("span",{className:"navbar-btnBars-span"})}),Object(a.jsxs)(g.a,{title:"Drawer navbar",placement:"right",closable:!0,onClose:function(){r(!1)},visible:i,children:[Object(a.jsx)(m,{}),Object(a.jsx)(v,{})]})]})]})})})},q=i(444),y=i(445),k=i(446),I=i(447),C=i(441),w=function(){return Object(a.jsx)("div",{className:"footer",children:Object(a.jsx)("div",{className:"container",children:Object(a.jsxs)("div",{className:"footer-inner",children:[Object(a.jsx)("div",{className:"footer-logo",children:Object(a.jsx)("a",{href:"/",children:"REACT TS"})}),Object(a.jsxs)("ul",{className:"footer-socials",children:[Object(a.jsx)("li",{children:Object(a.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://github.com/tienduy-nguyen",children:Object(a.jsx)(q.a,{})})}),Object(a.jsx)("li",{children:Object(a.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.twitter.com/tienduy_nguyen",children:Object(a.jsx)(y.a,{})})}),Object(a.jsx)("li",{children:Object(a.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.linkedin.com/",children:Object(a.jsx)(k.a,{})})})]}),Object(a.jsx)("div",{className:"copyright",children:"Copyright \xa9 2020 REACT TS"}),Object(a.jsx)(C.a,{children:Object(a.jsx)("div",{className:"go-top",children:Object(a.jsx)(I.a,{})})})]})})})},A=i(416),E=A.a.Header,S=A.a.Content,R=A.a.Footer,T=function(e){var t=e.children;return Object(a.jsxs)(A.a,{className:"main-layout",children:[Object(a.jsx)(E,{children:Object(a.jsx)(N,{})}),Object(a.jsx)(S,{className:"layout-children",children:t}),Object(a.jsx)(R,{children:Object(a.jsx)(w,{})})]})}},434:function(e,t,i){"use strict";i.r(t);var a=i(1),s=i(0),c=i.n(s),n=function(){return Object(a.jsx)("div",{className:"main-body-section",children:Object(a.jsxs)("div",{className:"container",children:[Object(a.jsx)("div",{className:"block-title",children:Object(a.jsx)("h2",{children:"About Us"})}),Object(a.jsxs)("div",{className:"about-content",children:[Object(a.jsx)("p",{children:"Lorem, ipsum dolor sit amet consectetur adipisicing elit. Cumque cupiditate omnis eius totam aperiam? Illo eius repudiandae nostrum iste dignissimos ad molestias facere atque, itaque placeat dolores dolorum doloremque? Sint."}),Object(a.jsx)("p",{children:"Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit impedit sapiente tempore veritatis neque minima laudantium eius magni aut aliquid esse sequi, hic delectus nihil temporibus quas nobis porro exercitationem? Repellendus ipsa animi enim id sint ullam beatae eius repudiandae consequuntur nisi, eum quia, ad iure similique, tenetur cum voluptatibus sit natus rem eos iusto quam eveniet et quidem. Est distinctio minima sequi quas provident hic nulla maiores quam quaerat. Corrupti unde illum suscipit autem iure maxime dolorem magni ipsam! Ad officia veritatis aperiam nemo. Voluptas at nostrum molestias incidunt necessitatibus, sapiente numquam facere explicabo quos, eum cumque repellat quo."}),Object(a.jsx)("p",{children:"Lorem ipsum dolor sit amet consectetur, adipisicing elit. Eveniet sunt ullam nesciunt beatae deleniti similique aliquam excepturi, minus nobis minima ex atque, quos veniam architecto doloribus maxime sapiente. Vel voluptatem pariatur delectus dolor quidem eveniet repellat facere tempora nisi. Ut!"})]})]})})},r=i(101),l=function(){return Object(a.jsx)(r.a,{children:Object(a.jsx)(n,{})})},j=c.a.memo(l);t.default=j}}]); 2 | //# sourceMappingURL=21.fc6f3247.chunk.js.map -------------------------------------------------------------------------------- /build/static/js/22.d5cf7005.chunk.js: -------------------------------------------------------------------------------- 1 | (this["webpackJsonptypescript-react-boilerplate"]=this["webpackJsonptypescript-react-boilerplate"]||[]).push([[22],{101:function(e,t,c){"use strict";c.d(t,"a",(function(){return D}));var n=c(1),i=c(0),s=c(111),a=c(21),r=c(253),j=c(181),l=r.a.SubMenu,o=r.a.ItemGroup,b=j.a.useBreakpoint,d=function(){var e=b().md;return Object(n.jsxs)(r.a,{mode:e?"horizontal":"inline",children:[Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{className:"navbar-item",to:"/",children:"Home"})},"key-home"),Object(n.jsxs)(l,{title:"Services",children:[Object(n.jsxs)(o,{title:"Features",children:[Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{to:"/feature1",children:"Option 1"})},"setting:1"),Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{to:"/feature2",children:"Option 2"})},"setting:2")]}),Object(n.jsxs)(o,{title:"Demo",children:[Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{to:"/demo1",children:"Option 1"})},"setting:3"),Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{to:"/demo2",children:"Option 2"})},"setting:4")]})]},"sub1"),Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{className:"navbar-item",to:"/about",children:"About"})},"key-about"),Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{className:"navbar-item",to:"/contact",children:"Contact"})},"key-contact")]})},h=c(443),O=c(26),m=c(17),x=c(4),u=j.a.useBreakpoint,p={logout:O.c},v=Object(m.b)((function(e){return{isAuthenticated:e.auth.isAuthenticated,user:e.auth.user}}),p)((function(e){var t=e.isAuthenticated,c=e.logout,i=e.user,s=u().md,j=Object(n.jsxs)(r.a,{mode:s?"horizontal":"inline",children:[Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{className:"navbar-item primary",to:x.a.LOGIN,children:"Sign In"})},"menukey-login"),Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{className:"navbar-item",to:x.a.REGISTER,children:"Register"})},"menukey-signup")]}),l=Object(n.jsxs)(r.a,{mode:s?"horizontal":"inline",children:[Object(n.jsx)(r.a.Item,{children:Object(n.jsxs)(a.c,{className:"navbar-item primary",to:x.a.PROFILE,children:["Hi ",Object(n.jsx)("strong",{children:i.username})]})},"menukey-profile"),Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{className:"navbar-item primary",to:x.a.HOME,onClick:function(){return c()},children:Object(n.jsxs)("span",{children:[Object(n.jsx)(h.a,{}),"Log Out"]})})},"menukey-login")]});return Object(n.jsx)(n.Fragment,{children:t?l:j})})),f=c(146),N=c(430),g=function(){var e=Object(i.useState)(!1),t=Object(s.a)(e,2),c=t[0],r=t[1];return Object(n.jsx)("div",{className:"navbar-section",children:Object(n.jsx)("div",{className:"container",children:Object(n.jsxs)("nav",{className:"navbar",children:[Object(n.jsx)("div",{className:"navbar-logo",children:Object(n.jsx)(a.c,{to:"/",className:"navbar-item",activeClassName:"is-active",children:"REACT TS"})}),Object(n.jsxs)("div",{className:"navbar-menu",children:[Object(n.jsx)("div",{className:"navbar-left-menu",children:Object(n.jsx)(d,{})}),Object(n.jsx)("div",{className:"navbar-right-menu",children:Object(n.jsx)(v,{})})]}),Object(n.jsxs)("div",{children:[Object(n.jsx)(f.a,{className:"navbar-btnBars",type:"primary",onClick:function(){r(!0)},children:Object(n.jsx)("span",{className:"navbar-btnBars-span"})}),Object(n.jsxs)(N.a,{title:"Drawer navbar",placement:"right",closable:!0,onClose:function(){r(!1)},visible:c,children:[Object(n.jsx)(d,{}),Object(n.jsx)(v,{})]})]})]})})})},y=c(444),k=c(445),w=c(446),I=c(447),C=c(441),S=function(){return Object(n.jsx)("div",{className:"footer",children:Object(n.jsx)("div",{className:"container",children:Object(n.jsxs)("div",{className:"footer-inner",children:[Object(n.jsx)("div",{className:"footer-logo",children:Object(n.jsx)("a",{href:"/",children:"REACT TS"})}),Object(n.jsxs)("ul",{className:"footer-socials",children:[Object(n.jsx)("li",{children:Object(n.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://github.com/tienduy-nguyen",children:Object(n.jsx)(y.a,{})})}),Object(n.jsx)("li",{children:Object(n.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.twitter.com/tienduy_nguyen",children:Object(n.jsx)(k.a,{})})}),Object(n.jsx)("li",{children:Object(n.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.linkedin.com/",children:Object(n.jsx)(w.a,{})})})]}),Object(n.jsx)("div",{className:"copyright",children:"Copyright \xa9 2020 REACT TS"}),Object(n.jsx)(C.a,{children:Object(n.jsx)("div",{className:"go-top",children:Object(n.jsx)(I.a,{})})})]})})})},E=c(416),A=E.a.Header,R=E.a.Content,T=E.a.Footer,D=function(e){var t=e.children;return Object(n.jsxs)(E.a,{className:"main-layout",children:[Object(n.jsx)(A,{children:Object(n.jsx)(g,{})}),Object(n.jsx)(R,{className:"layout-children",children:t}),Object(n.jsx)(T,{children:Object(n.jsx)(S,{})})]})}},435:function(e,t,c){"use strict";c.r(t);var n=c(1),i=c(0),s=c.n(i),a=c(111),r=c(448),j=c(146),l=c(426),o=function(){var e=Object(i.useState)(!1),t=Object(a.a)(e,2),c=t[0],s=t[1];return Object(n.jsx)("div",{className:"main-body-section ",children:Object(n.jsx)("div",{className:"container-fluid",children:Object(n.jsxs)("div",{className:"demo-content",children:[Object(n.jsxs)("div",{className:"block-title",children:[Object(n.jsx)("h2",{children:"Demo Option 1"}),Object(n.jsx)("p",{children:"Lorem ipsum dolor sit amet consectetur adipisicing elit. Error nostrum est minima. Dicta, ipsam explicabo ipsum velit eligendi distinctio in!"})]}),Object(n.jsx)("div",{className:"demo-button-play",children:Object(n.jsx)(j.a,{size:"large",onClick:function(e){s(!0)},children:Object(n.jsx)(r.a,{})})}),Object(n.jsx)(l.a,{title:"Demo Option 1",visible:c,onCancel:function(e){s(!1)},footer:null,children:Object(n.jsx)("iframe",{title:"Demo Option 1",width:"100%",height:"350",src:"https://www.youtube.com/embed/QpcP30GY2-8",allowFullScreen:!0,allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"})})]})})})},b=c(101),d=function(){return Object(n.jsx)(b.a,{children:Object(n.jsx)(o,{})})},h=s.a.memo(d);t.default=h}}]); 2 | //# sourceMappingURL=22.d5cf7005.chunk.js.map -------------------------------------------------------------------------------- /build/static/js/23.e961ffed.chunk.js: -------------------------------------------------------------------------------- 1 | (this["webpackJsonptypescript-react-boilerplate"]=this["webpackJsonptypescript-react-boilerplate"]||[]).push([[23],{101:function(e,t,c){"use strict";c.d(t,"a",(function(){return D}));var n=c(1),i=c(0),s=c(111),a=c(21),r=c(253),j=c(181),l=r.a.SubMenu,o=r.a.ItemGroup,b=j.a.useBreakpoint,d=function(){var e=b().md;return Object(n.jsxs)(r.a,{mode:e?"horizontal":"inline",children:[Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{className:"navbar-item",to:"/",children:"Home"})},"key-home"),Object(n.jsxs)(l,{title:"Services",children:[Object(n.jsxs)(o,{title:"Features",children:[Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{to:"/feature1",children:"Option 1"})},"setting:1"),Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{to:"/feature2",children:"Option 2"})},"setting:2")]}),Object(n.jsxs)(o,{title:"Demo",children:[Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{to:"/demo1",children:"Option 1"})},"setting:3"),Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{to:"/demo2",children:"Option 2"})},"setting:4")]})]},"sub1"),Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{className:"navbar-item",to:"/about",children:"About"})},"key-about"),Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{className:"navbar-item",to:"/contact",children:"Contact"})},"key-contact")]})},h=c(443),O=c(26),m=c(17),x=c(4),u=j.a.useBreakpoint,p={logout:O.c},v=Object(m.b)((function(e){return{isAuthenticated:e.auth.isAuthenticated,user:e.auth.user}}),p)((function(e){var t=e.isAuthenticated,c=e.logout,i=e.user,s=u().md,j=Object(n.jsxs)(r.a,{mode:s?"horizontal":"inline",children:[Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{className:"navbar-item primary",to:x.a.LOGIN,children:"Sign In"})},"menukey-login"),Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{className:"navbar-item",to:x.a.REGISTER,children:"Register"})},"menukey-signup")]}),l=Object(n.jsxs)(r.a,{mode:s?"horizontal":"inline",children:[Object(n.jsx)(r.a.Item,{children:Object(n.jsxs)(a.c,{className:"navbar-item primary",to:x.a.PROFILE,children:["Hi ",Object(n.jsx)("strong",{children:i.username})]})},"menukey-profile"),Object(n.jsx)(r.a.Item,{children:Object(n.jsx)(a.c,{className:"navbar-item primary",to:x.a.HOME,onClick:function(){return c()},children:Object(n.jsxs)("span",{children:[Object(n.jsx)(h.a,{}),"Log Out"]})})},"menukey-login")]});return Object(n.jsx)(n.Fragment,{children:t?l:j})})),f=c(146),N=c(430),g=function(){var e=Object(i.useState)(!1),t=Object(s.a)(e,2),c=t[0],r=t[1];return Object(n.jsx)("div",{className:"navbar-section",children:Object(n.jsx)("div",{className:"container",children:Object(n.jsxs)("nav",{className:"navbar",children:[Object(n.jsx)("div",{className:"navbar-logo",children:Object(n.jsx)(a.c,{to:"/",className:"navbar-item",activeClassName:"is-active",children:"REACT TS"})}),Object(n.jsxs)("div",{className:"navbar-menu",children:[Object(n.jsx)("div",{className:"navbar-left-menu",children:Object(n.jsx)(d,{})}),Object(n.jsx)("div",{className:"navbar-right-menu",children:Object(n.jsx)(v,{})})]}),Object(n.jsxs)("div",{children:[Object(n.jsx)(f.a,{className:"navbar-btnBars",type:"primary",onClick:function(){r(!0)},children:Object(n.jsx)("span",{className:"navbar-btnBars-span"})}),Object(n.jsxs)(N.a,{title:"Drawer navbar",placement:"right",closable:!0,onClose:function(){r(!1)},visible:c,children:[Object(n.jsx)(d,{}),Object(n.jsx)(v,{})]})]})]})})})},y=c(444),k=c(445),w=c(446),I=c(447),C=c(441),S=function(){return Object(n.jsx)("div",{className:"footer",children:Object(n.jsx)("div",{className:"container",children:Object(n.jsxs)("div",{className:"footer-inner",children:[Object(n.jsx)("div",{className:"footer-logo",children:Object(n.jsx)("a",{href:"/",children:"REACT TS"})}),Object(n.jsxs)("ul",{className:"footer-socials",children:[Object(n.jsx)("li",{children:Object(n.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://github.com/tienduy-nguyen",children:Object(n.jsx)(y.a,{})})}),Object(n.jsx)("li",{children:Object(n.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.twitter.com/tienduy_nguyen",children:Object(n.jsx)(k.a,{})})}),Object(n.jsx)("li",{children:Object(n.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.linkedin.com/",children:Object(n.jsx)(w.a,{})})})]}),Object(n.jsx)("div",{className:"copyright",children:"Copyright \xa9 2020 REACT TS"}),Object(n.jsx)(C.a,{children:Object(n.jsx)("div",{className:"go-top",children:Object(n.jsx)(I.a,{})})})]})})})},A=c(416),E=A.a.Header,R=A.a.Content,T=A.a.Footer,D=function(e){var t=e.children;return Object(n.jsxs)(A.a,{className:"main-layout",children:[Object(n.jsx)(E,{children:Object(n.jsx)(g,{})}),Object(n.jsx)(R,{className:"layout-children",children:t}),Object(n.jsx)(T,{children:Object(n.jsx)(S,{})})]})}},436:function(e,t,c){"use strict";c.r(t);var n=c(1),i=c(0),s=c.n(i),a=c(111),r=c(448),j=c(146),l=c(426),o=function(){var e=Object(i.useState)(!1),t=Object(a.a)(e,2),c=t[0],s=t[1];return Object(n.jsx)("div",{className:"main-body-section ",children:Object(n.jsx)("div",{className:"container",children:Object(n.jsxs)("div",{className:"demo-content",children:[Object(n.jsxs)("div",{className:"block-title",children:[Object(n.jsx)("h2",{children:"Demo Option 2"}),Object(n.jsx)("p",{children:"Lorem ipsum dolor sit amet consectetur adipisicing elit. Error nostrum est minima. Dicta, ipsam explicabo ipsum velit eligendi distinctio in!"})]}),Object(n.jsx)("div",{className:"demo-button-play",children:Object(n.jsx)(j.a,{size:"large",onClick:function(e){s(!0)},children:Object(n.jsx)(r.a,{})})}),Object(n.jsx)(l.a,{title:"Demo Option 2",visible:c,onCancel:function(e){s(!1)},footer:null,children:Object(n.jsx)("iframe",{title:"Demo Option 2",width:"100%",height:"350",src:"https://www.youtube.com/embed/FA8tl0fsYdI",allowFullScreen:!0,allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"})})]})})})},b=c(101),d=function(){return Object(n.jsx)(b.a,{children:Object(n.jsx)(o,{})})},h=s.a.memo(d);t.default=h}}]); 2 | //# sourceMappingURL=23.e961ffed.chunk.js.map -------------------------------------------------------------------------------- /build/static/js/24.942bca39.chunk.js: -------------------------------------------------------------------------------- 1 | (this["webpackJsonptypescript-react-boilerplate"]=this["webpackJsonptypescript-react-boilerplate"]||[]).push([[24],{101:function(e,t,i){"use strict";i.d(t,"a",(function(){return T}));var c=i(1),a=i(0),s=i(111),n=i(21),r=i(253),l=i(181),o=r.a.SubMenu,j=r.a.ItemGroup,u=l.a.useBreakpoint,m=function(){var e=u().md;return Object(c.jsxs)(r.a,{mode:e?"horizontal":"inline",children:[Object(c.jsx)(r.a.Item,{children:Object(c.jsx)(n.c,{className:"navbar-item",to:"/",children:"Home"})},"key-home"),Object(c.jsxs)(o,{title:"Services",children:[Object(c.jsxs)(j,{title:"Features",children:[Object(c.jsx)(r.a.Item,{children:Object(c.jsx)(n.c,{to:"/feature1",children:"Option 1"})},"setting:1"),Object(c.jsx)(r.a.Item,{children:Object(c.jsx)(n.c,{to:"/feature2",children:"Option 2"})},"setting:2")]}),Object(c.jsxs)(j,{title:"Demo",children:[Object(c.jsx)(r.a.Item,{children:Object(c.jsx)(n.c,{to:"/demo1",children:"Option 1"})},"setting:3"),Object(c.jsx)(r.a.Item,{children:Object(c.jsx)(n.c,{to:"/demo2",children:"Option 2"})},"setting:4")]})]},"sub1"),Object(c.jsx)(r.a.Item,{children:Object(c.jsx)(n.c,{className:"navbar-item",to:"/about",children:"About"})},"key-about"),Object(c.jsx)(r.a.Item,{children:Object(c.jsx)(n.c,{className:"navbar-item",to:"/contact",children:"Contact"})},"key-contact")]})},b=i(443),d=i(26),h=i(17),O=i(4),x=l.a.useBreakpoint,p={logout:d.c},v=Object(h.b)((function(e){return{isAuthenticated:e.auth.isAuthenticated,user:e.auth.user}}),p)((function(e){var t=e.isAuthenticated,i=e.logout,a=e.user,s=x().md,l=Object(c.jsxs)(r.a,{mode:s?"horizontal":"inline",children:[Object(c.jsx)(r.a.Item,{children:Object(c.jsx)(n.c,{className:"navbar-item primary",to:O.a.LOGIN,children:"Sign In"})},"menukey-login"),Object(c.jsx)(r.a.Item,{children:Object(c.jsx)(n.c,{className:"navbar-item",to:O.a.REGISTER,children:"Register"})},"menukey-signup")]}),o=Object(c.jsxs)(r.a,{mode:s?"horizontal":"inline",children:[Object(c.jsx)(r.a.Item,{children:Object(c.jsxs)(n.c,{className:"navbar-item primary",to:O.a.PROFILE,children:["Hi ",Object(c.jsx)("strong",{children:a.username})]})},"menukey-profile"),Object(c.jsx)(r.a.Item,{children:Object(c.jsx)(n.c,{className:"navbar-item primary",to:O.a.HOME,onClick:function(){return i()},children:Object(c.jsxs)("span",{children:[Object(c.jsx)(b.a,{}),"Log Out"]})})},"menukey-login")]});return Object(c.jsx)(c.Fragment,{children:t?o:l})})),f=i(146),g=i(430),N=function(){var e=Object(a.useState)(!1),t=Object(s.a)(e,2),i=t[0],r=t[1];return Object(c.jsx)("div",{className:"navbar-section",children:Object(c.jsx)("div",{className:"container",children:Object(c.jsxs)("nav",{className:"navbar",children:[Object(c.jsx)("div",{className:"navbar-logo",children:Object(c.jsx)(n.c,{to:"/",className:"navbar-item",activeClassName:"is-active",children:"REACT TS"})}),Object(c.jsxs)("div",{className:"navbar-menu",children:[Object(c.jsx)("div",{className:"navbar-left-menu",children:Object(c.jsx)(m,{})}),Object(c.jsx)("div",{className:"navbar-right-menu",children:Object(c.jsx)(v,{})})]}),Object(c.jsxs)("div",{children:[Object(c.jsx)(f.a,{className:"navbar-btnBars",type:"primary",onClick:function(){r(!0)},children:Object(c.jsx)("span",{className:"navbar-btnBars-span"})}),Object(c.jsxs)(g.a,{title:"Drawer navbar",placement:"right",closable:!0,onClose:function(){r(!1)},visible:i,children:[Object(c.jsx)(m,{}),Object(c.jsx)(v,{})]})]})]})})})},y=i(444),q=i(445),k=i(446),I=i(447),A=i(441),C=function(){return Object(c.jsx)("div",{className:"footer",children:Object(c.jsx)("div",{className:"container",children:Object(c.jsxs)("div",{className:"footer-inner",children:[Object(c.jsx)("div",{className:"footer-logo",children:Object(c.jsx)("a",{href:"/",children:"REACT TS"})}),Object(c.jsxs)("ul",{className:"footer-socials",children:[Object(c.jsx)("li",{children:Object(c.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://github.com/tienduy-nguyen",children:Object(c.jsx)(y.a,{})})}),Object(c.jsx)("li",{children:Object(c.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.twitter.com/tienduy_nguyen",children:Object(c.jsx)(q.a,{})})}),Object(c.jsx)("li",{children:Object(c.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.linkedin.com/",children:Object(c.jsx)(k.a,{})})})]}),Object(c.jsx)("div",{className:"copyright",children:"Copyright \xa9 2020 REACT TS"}),Object(c.jsx)(A.a,{children:Object(c.jsx)("div",{className:"go-top",children:Object(c.jsx)(I.a,{})})})]})})})},w=i(416),E=w.a.Header,R=w.a.Content,S=w.a.Footer,T=function(e){var t=e.children;return Object(c.jsxs)(w.a,{className:"main-layout",children:[Object(c.jsx)(E,{children:Object(c.jsx)(N,{})}),Object(c.jsx)(R,{className:"layout-children",children:t}),Object(c.jsx)(S,{children:Object(c.jsx)(C,{})})]})}},437:function(e,t,i){"use strict";i.r(t);var c=i(1),a=i(0),s=i.n(a),n=function(){return Object(c.jsx)("div",{className:"main-body-section",children:Object(c.jsxs)("div",{className:"container",children:[Object(c.jsx)("div",{className:"block-title",children:Object(c.jsx)("h2",{children:"Feature Page - Option 1"})}),Object(c.jsxs)("div",{className:"feature-content",children:[Object(c.jsx)("p",{children:"Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum earum animi possimus, iusto consectetur vel accusantium ipsam beatae minus unde ipsum, cumque, perspiciatis rerum laborum! Accusamus magnam sequi, a in ab quam temporibus excepturi debitis soluta inventore error nihil minima consectetur aliquid, ullam quos. A quae iusto nulla culpa! Amet optio accusamus laborum similique incidunt totam nemo expedita odit saepe quisquam numquam nesciunt beatae repellat illo doloremque inventore quibusdam ut earum consequuntur enim sunt, sint dolore! Maxime provident maiores eos velit. Adipisci at asperiores ullam praesentium. Illum, temporibus libero. Cumque officia consectetur, dolores id natus dolorem recusandae sunt eaque quis."}),Object(c.jsx)("p",{children:"Lorem ipsum dolor sit amet consectetur adipisicing elit. Ea omnis blanditiis minima possimus quasi esse facere excepturi mollitia corrupti dicta praesentium sunt, saepe placeat aliquam, commodi magnam cumque temporibus corporis beatae? Doloribus voluptate dolorem saepe voluptatum culpa odit fugiat, sunt atque hic animi sequi cupiditate ad. Ratione omnis eos nesciunt."})]})]})})},r=i(101),l=function(){return Object(c.jsx)(r.a,{children:Object(c.jsx)(n,{})})},o=s.a.memo(l);t.default=o}}]); 2 | //# sourceMappingURL=24.942bca39.chunk.js.map -------------------------------------------------------------------------------- /build/static/js/25.b09a67e1.chunk.js: -------------------------------------------------------------------------------- 1 | (this["webpackJsonptypescript-react-boilerplate"]=this["webpackJsonptypescript-react-boilerplate"]||[]).push([[25],{101:function(e,i,t){"use strict";t.d(i,"a",(function(){return L}));var a=t(1),s=t(0),c=t(111),n=t(21),r=t(253),l=t(181),o=r.a.SubMenu,u=r.a.ItemGroup,m=l.a.useBreakpoint,d=function(){var e=m().md;return Object(a.jsxs)(r.a,{mode:e?"horizontal":"inline",children:[Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{className:"navbar-item",to:"/",children:"Home"})},"key-home"),Object(a.jsxs)(o,{title:"Services",children:[Object(a.jsxs)(u,{title:"Features",children:[Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{to:"/feature1",children:"Option 1"})},"setting:1"),Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{to:"/feature2",children:"Option 2"})},"setting:2")]}),Object(a.jsxs)(u,{title:"Demo",children:[Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{to:"/demo1",children:"Option 1"})},"setting:3"),Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{to:"/demo2",children:"Option 2"})},"setting:4")]})]},"sub1"),Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{className:"navbar-item",to:"/about",children:"About"})},"key-about"),Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{className:"navbar-item",to:"/contact",children:"Contact"})},"key-contact")]})},j=t(443),b=t(26),p=t(17),h=t(4),O=l.a.useBreakpoint,x={logout:b.c},v=Object(p.b)((function(e){return{isAuthenticated:e.auth.isAuthenticated,user:e.auth.user}}),x)((function(e){var i=e.isAuthenticated,t=e.logout,s=e.user,c=O().md,l=Object(a.jsxs)(r.a,{mode:c?"horizontal":"inline",children:[Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{className:"navbar-item primary",to:h.a.LOGIN,children:"Sign In"})},"menukey-login"),Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{className:"navbar-item",to:h.a.REGISTER,children:"Register"})},"menukey-signup")]}),o=Object(a.jsxs)(r.a,{mode:c?"horizontal":"inline",children:[Object(a.jsx)(r.a.Item,{children:Object(a.jsxs)(n.c,{className:"navbar-item primary",to:h.a.PROFILE,children:["Hi ",Object(a.jsx)("strong",{children:s.username})]})},"menukey-profile"),Object(a.jsx)(r.a.Item,{children:Object(a.jsx)(n.c,{className:"navbar-item primary",to:h.a.HOME,onClick:function(){return t()},children:Object(a.jsxs)("span",{children:[Object(a.jsx)(j.a,{}),"Log Out"]})})},"menukey-login")]});return Object(a.jsx)(a.Fragment,{children:i?o:l})})),f=t(146),g=t(430),q=function(){var e=Object(s.useState)(!1),i=Object(c.a)(e,2),t=i[0],r=i[1];return Object(a.jsx)("div",{className:"navbar-section",children:Object(a.jsx)("div",{className:"container",children:Object(a.jsxs)("nav",{className:"navbar",children:[Object(a.jsx)("div",{className:"navbar-logo",children:Object(a.jsx)(n.c,{to:"/",className:"navbar-item",activeClassName:"is-active",children:"REACT TS"})}),Object(a.jsxs)("div",{className:"navbar-menu",children:[Object(a.jsx)("div",{className:"navbar-left-menu",children:Object(a.jsx)(d,{})}),Object(a.jsx)("div",{className:"navbar-right-menu",children:Object(a.jsx)(v,{})})]}),Object(a.jsxs)("div",{children:[Object(a.jsx)(f.a,{className:"navbar-btnBars",type:"primary",onClick:function(){r(!0)},children:Object(a.jsx)("span",{className:"navbar-btnBars-span"})}),Object(a.jsxs)(g.a,{title:"Drawer navbar",placement:"right",closable:!0,onClose:function(){r(!1)},visible:t,children:[Object(a.jsx)(d,{}),Object(a.jsx)(v,{})]})]})]})})})},N=t(444),y=t(445),k=t(446),I=t(447),C=t(441),S=function(){return Object(a.jsx)("div",{className:"footer",children:Object(a.jsx)("div",{className:"container",children:Object(a.jsxs)("div",{className:"footer-inner",children:[Object(a.jsx)("div",{className:"footer-logo",children:Object(a.jsx)("a",{href:"/",children:"REACT TS"})}),Object(a.jsxs)("ul",{className:"footer-socials",children:[Object(a.jsx)("li",{children:Object(a.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://github.com/tienduy-nguyen",children:Object(a.jsx)(N.a,{})})}),Object(a.jsx)("li",{children:Object(a.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.twitter.com/tienduy_nguyen",children:Object(a.jsx)(y.a,{})})}),Object(a.jsx)("li",{children:Object(a.jsx)("a",{target:"_blank",rel:"noreferrer",href:"https://www.linkedin.com/",children:Object(a.jsx)(k.a,{})})})]}),Object(a.jsx)("div",{className:"copyright",children:"Copyright \xa9 2020 REACT TS"}),Object(a.jsx)(C.a,{children:Object(a.jsx)("div",{className:"go-top",children:Object(a.jsx)(I.a,{})})})]})})})},w=t(416),A=w.a.Header,E=w.a.Content,T=w.a.Footer,L=function(e){var i=e.children;return Object(a.jsxs)(w.a,{className:"main-layout",children:[Object(a.jsx)(A,{children:Object(a.jsx)(q,{})}),Object(a.jsx)(E,{className:"layout-children",children:i}),Object(a.jsx)(T,{children:Object(a.jsx)(S,{})})]})}},438:function(e,i,t){"use strict";t.r(i);var a=t(1),s=t(0),c=t.n(s),n=function(){return Object(a.jsx)("div",{className:"main-body-section",children:Object(a.jsxs)("div",{className:"container",children:[Object(a.jsx)("div",{className:"block-title",children:Object(a.jsx)("h2",{children:"Service Page - Option 2"})}),Object(a.jsxs)("div",{className:"feature-content",children:[Object(a.jsx)("p",{children:"Lorem, ipsum dolor sit amet consectetur adipisicing elit. Cumque cupiditate omnis eius totam aperiam? Illo eius repudiandae nostrum iste dignissimos ad molestias facere atque, itaque placeat dolores dolorum doloremque? Sint."}),Object(a.jsx)("p",{children:"Lorem ipsum, dolor sit amet consectetur adipisicing elit. Magni rerum aperiam aspernatur debitis expedita eligendi facere non! Quibusdam accusamus accusantium maiores nostrum amet, neque quod quae eos hic aliquid veniam mollitia delectus cum qui aliquam laboriosam voluptate natus dolores est? Molestias, quidem fugiat placeat expedita dignissimos unde illum inventore recusandae."}),Object(a.jsx)("p",{children:"Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorem, recusandae?"}),Object(a.jsx)("p",{children:"Lorem ipsum dolor sit amet consectetur adipisicing elit. Sed ducimus voluptates odit cum illo suscipit voluptatem mollitia in laudantium natus. Tempore molestiae repudiandae beatae, numquam repellat, reprehenderit vero omnis consequuntur reiciendis voluptatibus quia placeat debitis quod iure ab praesentium doloremque ipsum aperiam eveniet soluta quisquam est quaerat error. Ad qui nulla, inventore similique animi numquam est soluta cupiditate suscipit! Ex id tempore pariatur aspernatur amet laborum et nisi! Architecto quidem ullam, tempore voluptate facere aliquam dicta error totam impedit, distinctio dolores, consectetur rerum pariatur molestiae perspiciatis! Obcaecati enim id sapiente impedit velit earum, adipisci dignissimos alias est quas odit nulla ad nam illum inventore veniam molestias quis quam consequatur! Alias similique quas possimus repellendus accusamus doloremque tempore quidem ipsum praesentium laborum laudantium nesciunt illum, sapiente minima esse amet libero temporibus cum optio et molestiae magnam neque facere earum? Maxime dolores autem ad, officia sint repellendus accusamus officiis iusto illo labore animi odio incidunt exercitationem blanditiis quod sequi, numquam voluptate adipisci, mollitia enim rem! Omnis eveniet odit mollitia maiores minus ratione deleniti pariatur quas fuga, ipsam doloremque voluptates fugiat nisi velit excepturi, ullam in laudantium facere impedit quidem dolorum ipsa exercitationem dolores! Excepturi earum vel natus rem culpa nihil, repudiandae velit."})]})]})})},r=t(101),l=function(){return Object(a.jsx)(r.a,{children:Object(a.jsx)(n,{})})},o=c.a.memo(l);i.default=o}}]); 2 | //# sourceMappingURL=25.b09a67e1.chunk.js.map -------------------------------------------------------------------------------- /build/static/js/26.fb5a996d.chunk.js: -------------------------------------------------------------------------------- 1 | (this["webpackJsonptypescript-react-boilerplate"]=this["webpackJsonptypescript-react-boilerplate"]||[]).push([[26],{421:function(t,e,n){"use strict";n.r(e),n.d(e,"getCLS",(function(){return v})),n.d(e,"getFCP",(function(){return g})),n.d(e,"getFID",(function(){return h})),n.d(e,"getLCP",(function(){return S})),n.d(e,"getTTFB",(function(){return F}));var i,a,r=function(){return"".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)},o=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:-1;return{name:t,value:e,delta:0,entries:[],id:r(),isFinal:!1}},u=function(t,e){try{if(PerformanceObserver.supportedEntryTypes.includes(t)){var n=new PerformanceObserver((function(t){return t.getEntries().map(e)}));return n.observe({type:t,buffered:!0}),n}}catch(t){}},c=!1,s=!1,p=function(t){c=!t.persisted},d=function(){addEventListener("pagehide",p),addEventListener("beforeunload",(function(){}))},f=function(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1];s||(d(),s=!0),addEventListener("visibilitychange",(function(e){var n=e.timeStamp;"hidden"===document.visibilityState&&t({timeStamp:n,isUnloading:c})}),{capture:!0,once:e})},l=function(t,e,n,i){var a;return function(){n&&e.isFinal&&n.disconnect(),e.value>=0&&(i||e.isFinal||"hidden"===document.visibilityState)&&(e.delta=e.value-(a||0),(e.delta||e.isFinal||void 0===a)&&(t(e),a=e.value))}},v=function(t){var e,n=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=o("CLS",0),a=function(t){t.hadRecentInput||(i.value+=t.value,i.entries.push(t),e())},r=u("layout-shift",a);r&&(e=l(t,i,r,n),f((function(t){var n=t.isUnloading;r.takeRecords().map(a),n&&(i.isFinal=!0),e()})))},m=function(){return void 0===i&&(i="hidden"===document.visibilityState?0:1/0,f((function(t){var e=t.timeStamp;return i=e}),!0)),{get timeStamp(){return i}}},g=function(t){var e,n=o("FCP"),i=m(),a=u("paint",(function(t){"first-contentful-paint"===t.name&&t.startTime1&&void 0!==arguments[1]&&arguments[1],i=o("LCP"),a=m(),r=function(t){var n=t.startTime;n1&&void 0!==arguments[1]?arguments[1]:-1;return{name:t,value:n,delta:0,entries:[],id:e(),isFinal:!1}},a=function(t,n){try{if(PerformanceObserver.supportedEntryTypes.includes(t)){var e=new PerformanceObserver((function(t){return t.getEntries().map(n)}));return e.observe({type:t,buffered:!0}),e}}catch(t){}},r=!1,o=!1,s=function(t){r=!t.persisted},u=function(){addEventListener(\"pagehide\",s),addEventListener(\"beforeunload\",(function(){}))},c=function(t){var n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];o||(u(),o=!0),addEventListener(\"visibilitychange\",(function(n){var e=n.timeStamp;\"hidden\"===document.visibilityState&&t({timeStamp:e,isUnloading:r})}),{capture:!0,once:n})},l=function(t,n,e,i){var a;return function(){e&&n.isFinal&&e.disconnect(),n.value>=0&&(i||n.isFinal||\"hidden\"===document.visibilityState)&&(n.delta=n.value-(a||0),(n.delta||n.isFinal||void 0===a)&&(t(n),a=n.value))}},p=function(t){var n,e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=i(\"CLS\",0),o=function(t){t.hadRecentInput||(r.value+=t.value,r.entries.push(t),n())},s=a(\"layout-shift\",o);s&&(n=l(t,r,s,e),c((function(t){var e=t.isUnloading;s.takeRecords().map(o),e&&(r.isFinal=!0),n()})))},d=function(){return void 0===t&&(t=\"hidden\"===document.visibilityState?0:1/0,c((function(n){var e=n.timeStamp;return t=e}),!0)),{get timeStamp(){return t}}},v=function(t){var n,e=i(\"FCP\"),r=d(),o=a(\"paint\",(function(t){\"first-contentful-paint\"===t.name&&t.startTime1&&void 0!==arguments[1]&&arguments[1],r=i(\"LCP\"),o=d(),s=function(t){var e=t.startTime;e0.2%", 60 | "not dead", 61 | "not op_mini all" 62 | ], 63 | "development": [ 64 | "last 1 chrome version", 65 | "last 1 firefox version", 66 | "last 1 safari version" 67 | ] 68 | } 69 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/public/favicon.ico -------------------------------------------------------------------------------- /public/images/image-default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/public/images/image-default.jpg -------------------------------------------------------------------------------- /public/images/iphone-11-pro-max.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/public/images/iphone-11-pro-max.jpg -------------------------------------------------------------------------------- /public/images/iphone-11-pro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/public/images/iphone-11-pro.jpg -------------------------------------------------------------------------------- /public/images/iphone-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/public/images/iphone-11.jpg -------------------------------------------------------------------------------- /public/images/iphone-8-plus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/public/images/iphone-8-plus.jpg -------------------------------------------------------------------------------- /public/images/iphone-se.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/public/images/iphone-se.jpg -------------------------------------------------------------------------------- /public/images/iphone-x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/public/images/iphone-x.jpg -------------------------------------------------------------------------------- /public/images/iphone-xr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/public/images/iphone-xr.jpg -------------------------------------------------------------------------------- /public/images/iphone-xs-max.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/public/images/iphone-xs-max.jpg -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 25 | 26 | 27 | 28 | React TS Boilerplate 29 | 30 | 31 | 32 | 33 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": 3 | ["config:base", 4 | "schedule:earlyMondays" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /server/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": [ 3 | { 4 | "id": "idtest", 5 | "username": "tester", 6 | "email": "tester@gmail.com", 7 | "password": "123456" 8 | }, 9 | { 10 | "id": "d18eca47-e1fb-4259-b2b7-9cf2ead4ba29", 11 | "username": "tienduy", 12 | "email": "tienduy@gmail.com", 13 | "password": "123456" 14 | }, 15 | { 16 | "id": "d3dda531-1893-4ffa-972e-c3d55541f7d5", 17 | "username": "alan", 18 | "email": "alan@gmail.com", 19 | "password": "123456" 20 | }, 21 | { 22 | "id": "38550566-e1c6-40c2-a616-8ea74189f677", 23 | "username": "felix", 24 | "email": "felix@gmail.com", 25 | "password": "123456" 26 | } 27 | ], 28 | "products": [ 29 | { 30 | "id": "iphone-8-plus", 31 | "name": "Iphone 8 Plus", 32 | "image_url": "/images/iphone-8-plus.jpg", 33 | "brand": "APPLE" 34 | }, 35 | { 36 | "id": "iphone-11-pro-max", 37 | "name": "Iphone 11 Pro Max", 38 | "image_url": "/images/iphone-11-pro-max.jpg", 39 | "brand": "APPLE" 40 | }, 41 | { 42 | "id": "iphone-11", 43 | "name": "Iphone 11", 44 | "image_url": "/images/iphone-11.jpg", 45 | "brand": "APPLE" 46 | }, 47 | { 48 | "id": "iphone-se", 49 | "name": "Iphone SE", 50 | "image_url": "/images/iphone-se.jpg", 51 | "brand": "APPLE" 52 | }, 53 | { 54 | "id": "iphone-x", 55 | "name": "Iphone X", 56 | "image_url": "/images/iphone-x.jpg", 57 | "brand": "APPLE" 58 | }, 59 | { 60 | "id": "iphone-xr", 61 | "name": "Iphone XR", 62 | "image_url": "/images/iphone-xr.jpg", 63 | "brand": "APPLE" 64 | }, 65 | { 66 | "id": "iphone-xs-max", 67 | "name": "Iphone XS Max", 68 | "image_url": "/images/iphone-xs-max.jpg", 69 | "brand": "APPLE" 70 | }, 71 | { 72 | "name": "Iphone 7s plus", 73 | "brand": "APPLE", 74 | "image_url": "https://images-na.ssl-images-amazon.com/images/I/71Q5RFc7NVL._AC_SL1500_.jpg", 75 | "id": "00528308-fb0f-4d60-85b3-473ebd1aed76" 76 | }, 77 | { 78 | "name": "Iphone 12 Pro", 79 | "brand": "APPLE", 80 | "image_url": "https://images.frandroid.com/wp-content/uploads/2020/10/iphone-12-max-frandroid-2020.png", 81 | "id": "5695c3c0-10b3-40d7-b39b-f8f807323dc4" 82 | }, 83 | { 84 | "name": "Iphone 12 Pro Max", 85 | "brand": "APPLE", 86 | "image_url": "https://www.apple.com/newsroom/images/product/iphone/standard/Apple_announce-iphone12pro_10132020.jpg.landing-big_2x.jpg", 87 | "id": "f7882d67-2207-4dfd-919e-c60a6477cdbd" 88 | }, 89 | { 90 | "name": "Iphone 11 Pro max gold", 91 | "brand": "APPLE", 92 | "image_url": "https://leronza.com/wp-content/uploads/2020/10/iPhone-12-Pro-24k-Gold-copy.png", 93 | "id": "4c0ae0b4-9a15-4be7-96cf-6bf47366805c" 94 | }, 95 | { 96 | "name": "Iphone 12 Pro Gold", 97 | "brand": "APPLE", 98 | "image_url": "https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/iphone-12-pro-gold-hero?wid=470&hei=556&fmt=jpeg&qlt=95&op_usm=0.5,0.5&.v=1604021659000", 99 | "id": "f1d30887-14f9-4238-8ecd-c106d5f529e8" 100 | }, 101 | { 102 | "name": "test", 103 | "brand": "OTHERS", 104 | "image_url": "/images/image-default.jpg", 105 | "id": "72fc536d-6992-4d26-a9ff-1294bb38cd5a" 106 | } 107 | ] 108 | } -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-server-deploy", 3 | "version": "1.0.0", 4 | "description": "Simple json database with json-server", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "keywords": [ 10 | "json-server,heroku, node, REST API" 11 | ], 12 | "author": "Tien Duy", 13 | "license": "MIT", 14 | "dependencies": { 15 | "json-server": "^0.16.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /server/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": "/$1" 3 | } -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const jsonServer = require('json-server'); 2 | const server = jsonServer.create(); 3 | const router = jsonServer.router('db.json'); 4 | const middleware = jsonServer.defaults(); 5 | const port = process.env.PORT || 5000; 6 | 7 | // Set default middlewares (logger, static, cors and no-cache) 8 | server.use(middleware); 9 | 10 | server.use('/api', router); 11 | 12 | server.listen(port, () => { 13 | //eslint-disable-next-line 14 | console.log(`Server started on port ${port}`); 15 | }); 16 | -------------------------------------------------------------------------------- /src/@types/actions.d.ts: -------------------------------------------------------------------------------- 1 | interface ActionRedux { 2 | type: string; 3 | payload?: any; 4 | } 5 | -------------------------------------------------------------------------------- /src/@types/alert.d.ts: -------------------------------------------------------------------------------- 1 | //eslint-disable-next-line 2 | 3 | interface IAlert { 4 | id: string; 5 | msg: string; 6 | type: AlertTypes; 7 | } 8 | -------------------------------------------------------------------------------- /src/@types/api.d.ts: -------------------------------------------------------------------------------- 1 | interface Res { 2 | data: any; 3 | message: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/@types/files.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | declare namespace NodeJS { 6 | interface ProcessEnv { 7 | readonly NODE_ENV: 'development' | 'production' | 'test'; 8 | readonly PUBLIC_URL: string; 9 | } 10 | } 11 | 12 | declare module '*.bmp' { 13 | const src: string; 14 | export default src; 15 | } 16 | 17 | declare module '*.gif' { 18 | const src: string; 19 | export default src; 20 | } 21 | 22 | declare module '*.jpg' { 23 | const src: string; 24 | export default src; 25 | } 26 | 27 | declare module '*.jpeg' { 28 | const src: string; 29 | export default src; 30 | } 31 | 32 | declare module '*.png' { 33 | const src: string; 34 | export default src; 35 | } 36 | 37 | declare module '*.webp' { 38 | const src: string; 39 | export default src; 40 | } 41 | 42 | declare module '*.svg' { 43 | import * as React from 'react'; 44 | 45 | export const ReactComponent: React.FunctionComponent< 46 | React.SVGProps & { title?: string } 47 | >; 48 | 49 | const src: string; 50 | export default src; 51 | } 52 | 53 | declare module '*.module.css' { 54 | const classes: { readonly [key: string]: string }; 55 | export default classes; 56 | } 57 | 58 | declare module '*.module.scss' { 59 | const classes: { readonly [key: string]: string }; 60 | export default classes; 61 | } 62 | 63 | declare module '*.module.sass' { 64 | const classes: { readonly [key: string]: string }; 65 | export default classes; 66 | } 67 | -------------------------------------------------------------------------------- /src/@types/product.d.ts: -------------------------------------------------------------------------------- 1 | interface Product { 2 | id: string; 3 | name: string; 4 | image_url: string; 5 | brand: string; 6 | } 7 | interface ProductError { 8 | msg: string; 9 | status: number; 10 | } 11 | 12 | interface ProductForm { 13 | name: string; 14 | image_url: string; 15 | brand: string; 16 | } 17 | interface ProductUrlParams { 18 | id: string; 19 | } 20 | -------------------------------------------------------------------------------- /src/@types/reducer.d.ts: -------------------------------------------------------------------------------- 1 | import { RootReducer } from 'src/reducer'; 2 | 3 | declare global { 4 | type AppState = ReturnType; 5 | } 6 | -------------------------------------------------------------------------------- /src/@types/user.d.ts: -------------------------------------------------------------------------------- 1 | interface ReqLogin { 2 | username: string; 3 | password: string; 4 | email?: string; 5 | } 6 | interface ResLoginApi extends Res { 7 | data: { 8 | id: string; 9 | username: string; 10 | email: string; 11 | password: string; 12 | }; 13 | } 14 | 15 | interface IUser { 16 | id: string; 17 | username: string; 18 | email?: string; 19 | password: string; 20 | accessToken?: string; 21 | } 22 | 23 | interface DispatchAuth { 24 | type: string; 25 | payload?: any; 26 | } 27 | -------------------------------------------------------------------------------- /src/App/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { connect, ConnectedProps } from 'react-redux'; 3 | import { loadUser, logout } from 'src/components/Auth/Auth.thunks'; 4 | import { Routes } from 'src/routes'; 5 | 6 | const mapStateToProps = () => ({}); 7 | const mapDispatchToProps = { 8 | loadUser, 9 | logout, 10 | }; 11 | const connector = connect(mapStateToProps, mapDispatchToProps); 12 | interface Props extends ConnectedProps {} 13 | 14 | const _App = (props: Props) => { 15 | useEffect(() => { 16 | const { loadUser, logout } = props; 17 | // check for token in LS 18 | if (localStorage.user) { 19 | loadUser(); 20 | } 21 | 22 | // log user out from all tabs if they log out in one tab 23 | window.addEventListener('storage', () => { 24 | if (!localStorage.user) logout(); 25 | }); 26 | }, [props]); 27 | return ; 28 | }; 29 | 30 | export const App = connector(_App); 31 | -------------------------------------------------------------------------------- /src/assets/images/home.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/src/assets/images/home.jpeg -------------------------------------------------------------------------------- /src/assets/images/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/images/home2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/src/assets/images/home2.jpeg -------------------------------------------------------------------------------- /src/assets/images/list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/assets/images/modern-design.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/src/assets/images/modern-design.jpg -------------------------------------------------------------------------------- /src/assets/images/open-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/images/products/apple/iphone-11-pro-max.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/src/assets/images/products/apple/iphone-11-pro-max.jpg -------------------------------------------------------------------------------- /src/assets/images/products/apple/iphone-11-pro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/src/assets/images/products/apple/iphone-11-pro.jpg -------------------------------------------------------------------------------- /src/assets/images/products/apple/iphone-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/src/assets/images/products/apple/iphone-11.jpg -------------------------------------------------------------------------------- /src/assets/images/products/apple/iphone-8-plus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/src/assets/images/products/apple/iphone-8-plus.jpg -------------------------------------------------------------------------------- /src/assets/images/products/apple/iphone-se.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/src/assets/images/products/apple/iphone-se.jpg -------------------------------------------------------------------------------- /src/assets/images/products/apple/iphone-x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/src/assets/images/products/apple/iphone-x.jpg -------------------------------------------------------------------------------- /src/assets/images/products/apple/iphone-xr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/src/assets/images/products/apple/iphone-xr.jpg -------------------------------------------------------------------------------- /src/assets/images/products/apple/iphone-xs-max.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/src/assets/images/products/apple/iphone-xs-max.jpg -------------------------------------------------------------------------------- /src/assets/images/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartedStory/react-typescript/4ea0c628dc09259fa86da5867fd0dc134d4ff242/src/assets/images/spinner.gif -------------------------------------------------------------------------------- /src/assets/scss/_config.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #1890ff; 2 | $dark-color: #111; 3 | $light-color: #f4f4f4; 4 | $white-color: #fff; 5 | $danger-color: #e23c39; 6 | $success-color: #70c040; 7 | $warning-color: #efaf41; 8 | 9 | $max-width: 1200px; 10 | $heightHeader: 60px; 11 | $heightFooter: 150px; 12 | 13 | //Margin & Padding 14 | $spaceamounts: (1, 2, 3, 4, 5); 15 | @each $space in $spaceamounts { 16 | // All around margin 17 | .m-#{$space} { 18 | margin: #{$space}rem; 19 | } 20 | // Vertical margin 21 | .my-#{$space} { 22 | margin: #{$space}rem 0; 23 | } 24 | // Horizontal margin 25 | .mx-#{$space} { 26 | margin: 0 #{$space}rem; 27 | } 28 | // Margin-left 29 | .ml-#{$space} { 30 | margin-left: #{$space}rem; 31 | } 32 | // Margin-right 33 | .mr-#{$space} { 34 | margin-right: #{$space}rem; 35 | } 36 | // Margin-top 37 | .mt-#{$space} { 38 | margin-top: #{$space}rem; 39 | } 40 | // Margin-right 41 | .mb-#{$space} { 42 | margin-bottom: #{$space}rem; 43 | } 44 | 45 | // All around padding 46 | .p-#{$space} { 47 | padding: #{$space}rem; 48 | } 49 | // Vertical padding 50 | .py-#{$space} { 51 | padding: #{$space}rem 0; 52 | } 53 | // Horizontal padding 54 | .px-#{$space} { 55 | padding: 0 #{$space}rem; 56 | } 57 | // padding-left 58 | .mp-#{$space} { 59 | padding-left: #{$space}rem; 60 | } 61 | // padding-right 62 | .pr-#{$space} { 63 | padding-right: #{$space}rem; 64 | } 65 | // padding-top 66 | .pt-#{$space} { 67 | padding-top: #{$space}rem; 68 | } 69 | // Margin-right 70 | .pb-#{$space} { 71 | padding-bottom: #{$space}rem; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/assets/scss/_fonts.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap'); 2 | 3 | body { 4 | font-family: 'Roboto', sans-serif; 5 | font-weight: 300; 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/scss/_footer.scss: -------------------------------------------------------------------------------- 1 | .ant-layout-footer { 2 | background: #111 !important; 3 | color: #fff !important; 4 | padding: 20px 0 !important; 5 | text-align: center; 6 | height: $heightFooter; 7 | // position: absolute; 8 | // left: 0; 9 | // bottom: 0; 10 | // right: 0; 11 | } 12 | .footer .footer-logo { 13 | font-size: 1.2rem; 14 | text-transform: uppercase; 15 | margin: 0 0 1rem; 16 | font-weight: 500; 17 | a { 18 | color: $white-color; 19 | &:hover { 20 | color: $primary-color; 21 | } 22 | } 23 | } 24 | .footer-inner { 25 | color: $white-color; 26 | } 27 | .ant-back-top { 28 | svg { 29 | color: $primary-color; 30 | font-size: 1.6rem; 31 | &:hover { 32 | opacity: 0.7; 33 | } 34 | } 35 | } 36 | 37 | .footer .footer-socials { 38 | list-style: none; 39 | display: flex; 40 | align-items: center; 41 | justify-content: center; 42 | padding: 0; 43 | font-size: 24px; 44 | margin-bottom: 0.5rem; 45 | 46 | li { 47 | margin: 0 10px; 48 | } 49 | a { 50 | color: $white-color; 51 | } 52 | a:hover { 53 | color: $primary-color; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/assets/scss/_header.scss: -------------------------------------------------------------------------------- 1 | .ant-layout-header { 2 | position: relative; 3 | left: 0; 4 | right: 0; 5 | z-index: 9; 6 | } 7 | .main-layout .ant-layout-header { 8 | background: #fff; 9 | padding: 0; 10 | height: $heightHeader; 11 | line-height: 1.6; 12 | } 13 | .navbar-section { 14 | border-bottom: solid 1px #e8e8e8; 15 | overflow: auto; 16 | box-shadow: 0 0 2px #f3f1f1; 17 | } 18 | .navbar { 19 | display: flex; 20 | align-items: center; 21 | padding: 0; 22 | 23 | .ant-menu-item { 24 | padding: 0px 2px; 25 | } 26 | .ant-menu-submenu-title { 27 | padding: 4px; 28 | } 29 | .ant-menu-item a, 30 | .ant-menu-submenu-title a { 31 | padding: 10px 0px; 32 | } 33 | .ant-menu-horizontal { 34 | border-bottom: none; 35 | } 36 | } 37 | .navbar-left-menu { 38 | float: left; 39 | } 40 | .navbar-right-menu { 41 | float: right; 42 | } 43 | // .navbar .ant-menu li.ant-menu-item, 44 | // .navbar .ant-menu li.ant-menu-item-selected { 45 | // border: 0 !important; 46 | // } 47 | 48 | // .navbar .ant-menu li.ant-menu-item-selected, 49 | // .navbar .ant-menu li.ant-menu-item:hover { 50 | // color: $primary-color; 51 | // background: none; 52 | // } 53 | 54 | .navbar-logo { 55 | width: 150px; 56 | float: left; 57 | a { 58 | display: inline-block; 59 | font-size: 1.2rem; 60 | padding: 16px 20px; 61 | text-transform: uppercase; 62 | } 63 | } 64 | .navbar-menu { 65 | width: calc(100% - 150px); 66 | float: left; 67 | } 68 | 69 | // Button bars 70 | .navbar .navbar-btnBars { 71 | float: right; 72 | height: 32px; 73 | padding: 6px; 74 | margin-top: 8px; 75 | display: none; 76 | background: none; 77 | } 78 | .navbar .navbar-btnBars-span { 79 | display: block; 80 | width: 20px; 81 | height: 2px; 82 | background: $primary-color; 83 | position: relative; 84 | &:after, 85 | &:before { 86 | content: attr(x); 87 | width: 20px; 88 | position: absolute; 89 | top: -6px; 90 | left: 0; 91 | height: 2px; 92 | background: $primary-color; 93 | } 94 | &:after { 95 | top: auto; 96 | bottom: -6px; 97 | } 98 | & > span { 99 | display: block; 100 | } 101 | } 102 | 103 | .ant-drawer-body { 104 | padding: 0; 105 | } 106 | .ant-drawer-body .ant-menu-horizontal > .ant-menu-item, 107 | .ant-drawer-body .ant-menu-horizontal > .ant-menu-submenu { 108 | display: inline-block; 109 | width: 100%; 110 | } 111 | 112 | .ant-drawer-body .ant-menu-horizontal { 113 | border-bottom: none; 114 | } 115 | 116 | .ant-drawer-body .ant-menu-horizontal > .ant-menu-item:hover { 117 | border-bottom-color: transparent; 118 | } 119 | 120 | @media (max-width: 767px) { 121 | .navbar .navbar-btnBars { 122 | display: inline-block; 123 | } 124 | 125 | .navbar .navbar-left-menu, 126 | .navbar .navbar-right-menu { 127 | display: none; 128 | } 129 | 130 | .navbar .navbar-logo a { 131 | margin-left: -20px; 132 | padding: 10px 20px; 133 | } 134 | 135 | .navbar .ant-menu-item, 136 | .navbar .ant-menu-submenu-title { 137 | padding: 1px 20px; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/assets/scss/_home.scss: -------------------------------------------------------------------------------- 1 | .homepage { 2 | position: relative; 3 | background: url('../images/home2.jpeg') no-repeat center center/cover; 4 | height: 100vh; 5 | 6 | &-inner { 7 | color: $white-color; 8 | height: 100%; 9 | display: flex; 10 | flex-direction: column; 11 | width: 80%; 12 | margin: auto; 13 | align-items: center; 14 | justify-content: center; 15 | text-align: center; 16 | } 17 | h1 { 18 | color: $white-color; 19 | font-size: 250%; 20 | text-transform: uppercase; 21 | font-weight: 700; 22 | letter-spacing: 2px; 23 | } 24 | p { 25 | font-size: large; 26 | font-weight: 400; 27 | } 28 | 29 | .home-content { 30 | background: rgba(0, 0, 0, 0.3); 31 | padding: 2rem 8rem; 32 | border-radius: 4px; 33 | .home-text-light { 34 | color: darken($white-color, 30%); 35 | span { 36 | color: $white-color; 37 | } 38 | } 39 | } 40 | } 41 | .home-overlay { 42 | background: rgba(0, 0, 0, 0.2); 43 | height: 100vh; 44 | } 45 | .alert-section { 46 | position: absolute; 47 | top: 63px; 48 | right: 0; 49 | } 50 | -------------------------------------------------------------------------------- /src/assets/scss/_login.scss: -------------------------------------------------------------------------------- 1 | .login-form-wrap { 2 | margin: 80px auto; 3 | max-width: 300px; 4 | height: 80vh; 5 | } 6 | .login-form-title { 7 | text-align: center; 8 | img { 9 | height: 44px; 10 | margin-right: 16; 11 | } 12 | } 13 | .login-form { 14 | max-width: 300px; 15 | } 16 | .login-form-forgot { 17 | float: right; 18 | } 19 | 20 | .ant-col-rtl .login-form-forgot { 21 | float: left; 22 | } 23 | .login-form-button { 24 | width: 100%; 25 | margin-bottom: 0.5rem; 26 | } 27 | .login-form-register-link-wrapper { 28 | float: right; 29 | } 30 | .site-form-item-icon { 31 | svg { 32 | color: rgba(0, 0, 0, 0.25); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/assets/scss/_responsive.scss: -------------------------------------------------------------------------------- 1 | /* 2 | responsive 3 | --- 4 | */ 5 | 6 | .hide-on-mobile { 7 | display: block; //display on tablet and desktop 8 | } 9 | .show-on-mobile { 10 | display: none; //hide on tablet & desktop 11 | } 12 | 13 | // tablet 14 | @media only screen and (max-width: 991px) { 15 | .pricingBlock .ant-row > div:nth-child(2) .ant-list-item { 16 | transform: scale(1.14); 17 | } 18 | } 19 | 20 | // mobile 21 | @media only screen and (max-width: 767px) { 22 | .hide-on-mobile { 23 | display: none; 24 | } 25 | .show-on-mobile { 26 | display: block; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/assets/scss/_staticPages.scss: -------------------------------------------------------------------------------- 1 | .main-body-section { 2 | margin: 2rem auto 1rem auto; 3 | } 4 | 5 | /* 6 | contact 7 | ------- 8 | */ 9 | 10 | .block .ant-form { 11 | max-width: 640px; 12 | margin: auto; 13 | } 14 | 15 | .block .ant-form textarea { 16 | min-height: 120px; 17 | height: 120px; 18 | } 19 | .block-title { 20 | text-align: center; 21 | margin: auto; 22 | max-width: 500px; 23 | position: relative; 24 | padding: 0 0 20px; 25 | margin-bottom: 40px; 26 | } 27 | 28 | .block-title:after { 29 | transform: translateX(-50%); 30 | content: ''; 31 | background: #1890ff; 32 | position: absolute; 33 | bottom: 0; 34 | left: 50%; 35 | height: 3px; 36 | width: 50px; 37 | } 38 | 39 | .block-title h2 { 40 | font-size: 28px; 41 | margin: 0; 42 | } 43 | 44 | /* 45 | demo 46 | ------- 47 | */ 48 | .demo-content { 49 | margin: auto; 50 | text-align: center; 51 | } 52 | 53 | .demo-button-play:before { 54 | content: ''; 55 | background: rgba(0, 0, 0, 0.05); 56 | position: absolute; 57 | top: 0; 58 | bottom: 0; 59 | left: 0; 60 | right: 0; 61 | } 62 | -------------------------------------------------------------------------------- /src/assets/scss/index.scss: -------------------------------------------------------------------------------- 1 | @import 'config'; 2 | @import 'fonts'; 3 | @import 'header'; 4 | @import 'footer'; 5 | @import 'home'; 6 | @import 'staticPages'; 7 | @import 'responsive'; 8 | @import 'login'; 9 | 10 | * { 11 | box-sizing: border-box; 12 | margin: 0; 13 | padding: 0; 14 | } 15 | 16 | body { 17 | font-size: 1rem; 18 | line-height: 1.6; 19 | background-color: #fff; 20 | } 21 | 22 | a { 23 | text-decoration: none; 24 | color: $primary-color; 25 | } 26 | ul { 27 | list-style: none; 28 | } 29 | img { 30 | width: 100%; 31 | } 32 | 33 | .container-fluid { 34 | margin: 0 auto; 35 | max-width: 100%; 36 | padding: 0 15px; 37 | } 38 | .container { 39 | margin: 0 auto; 40 | max-width: 1200px; 41 | padding: 0 15px; 42 | } 43 | 44 | .ant-layout { 45 | background: #fff; 46 | } 47 | .ant-layout-content { 48 | background-color: #fff; 49 | min-height: calc(100vh - #{$heightFooter} - #{$heightHeader}) !important; 50 | overflow: auto; 51 | } 52 | .ant-layout.main-layout { 53 | background-color: #fff; 54 | } 55 | 56 | p { 57 | font-size: 16px; 58 | line-height: 1.6; 59 | } 60 | -------------------------------------------------------------------------------- /src/components/Alert/Alert.actions.ts: -------------------------------------------------------------------------------- 1 | import * as types from './Alert.constants'; 2 | 3 | export const setAlertSuccess = (payload: IAlert) => ({ 4 | type: types.SET_ALERT, 5 | payload, 6 | }); 7 | 8 | export const removeAlertSuccess = (payload: string) => ({ 9 | type: types.REMOVE_ALERT, 10 | payload, 11 | }); 12 | -------------------------------------------------------------------------------- /src/components/Alert/Alert.constants.ts: -------------------------------------------------------------------------------- 1 | export const SET_ALERT = 'view/Alert/SET_ALERT'; 2 | export const REMOVE_ALERT = 'view/Alert/REMOVE_ALERT'; 3 | -------------------------------------------------------------------------------- /src/components/Alert/Alert.reducers.ts: -------------------------------------------------------------------------------- 1 | import * as types from './Alert.constants'; 2 | 3 | const initialState = [] as IAlert[]; 4 | 5 | export const alertReducer = (state = initialState, action: ActionRedux) => { 6 | const { type, payload } = action; 7 | switch (type) { 8 | case types.SET_ALERT: 9 | return [...state, payload]; 10 | case types.REMOVE_ALERT: 11 | return state.filter(alert => alert.id !== payload); 12 | default: 13 | return state; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/Alert/Alert.thunks.ts: -------------------------------------------------------------------------------- 1 | import * as actions from './Alert.actions'; 2 | import { v4 as uuid } from 'uuid'; 3 | import { AlertTypes } from 'src/constants/alerts'; 4 | 5 | interface PayloadAlert { 6 | msg: string; 7 | type: AlertTypes; 8 | } 9 | export const setAlert = (payload: PayloadAlert) => async dispatch => { 10 | const newAlert = { ...payload, id: uuid() }; 11 | dispatch(actions.setAlertSuccess(newAlert)); 12 | setTimeout(() => dispatch(actions.removeAlertSuccess(newAlert.id)), 4000); 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/Alert/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect, ConnectedProps } from 'react-redux'; 3 | import { Alert } from 'antd'; 4 | 5 | const mapStateToProps = (state: AppState) => ({ 6 | alerts: state.alerts, 7 | }); 8 | const mapDispatchToProps = {}; 9 | 10 | const connector = connect(mapStateToProps, mapDispatchToProps); 11 | interface Props extends ConnectedProps {} 12 | 13 | const _AppAlert = (props: Props) => { 14 | const { alerts } = props; 15 | return ( 16 | alerts !== null && 17 | alerts.length > 0 && 18 | alerts.map((alert, index) => { 19 | return ( 20 |
21 |
22 | 29 |
30 |
31 | ); 32 | }) 33 | ); 34 | }; 35 | 36 | export const AppAlert = connector(_AppAlert); 37 | -------------------------------------------------------------------------------- /src/components/Auth/Auth.actions.ts: -------------------------------------------------------------------------------- 1 | import * as types from './Auth.constants'; 2 | 3 | export const loginSuccess = (payload: IUser) => ({ 4 | type: types.LOGIN_SUCCESS, 5 | payload, 6 | }); 7 | 8 | export const loginFailed = () => ({ 9 | type: types.LOGIN_FAILED, 10 | }); 11 | 12 | export const userLoaded = payload => ({ 13 | type: types.USER_LOADED, 14 | payload, 15 | }); 16 | 17 | export const authError = () => ({ 18 | type: types.AUTH_ERROR, 19 | }); 20 | 21 | export const logoutSuccess = () => ({ 22 | type: types.LOGOUT, 23 | }); 24 | 25 | export const registerSuccess = payload => ({ 26 | type: types.REGISTER_SUCCESS, 27 | payload, 28 | }); 29 | 30 | export const registerFailed = () => ({ 31 | type: types.REGISTER_FAILED, 32 | }); 33 | -------------------------------------------------------------------------------- /src/components/Auth/Auth.constants.ts: -------------------------------------------------------------------------------- 1 | export const LOGIN_SUCCESS = 'views/auth/LOGIN_SUCCESS'; 2 | export const LOGIN_FAILED = 'views/auth/LOGIN_FAILED'; 3 | export const USER_LOADED = 'views/auth/USER_LOADED'; 4 | export const AUTH_ERROR = 'views/auth/AUTH_ERROR'; 5 | export const LOGOUT = 'views/auth/LOGOUT'; 6 | export const REGISTER_SUCCESS = 'views/auth/REGISTER_SUCCESS'; 7 | export const REGISTER_FAILED = 'views/auth/REGISTER_FAIL'; 8 | -------------------------------------------------------------------------------- /src/components/Auth/Auth.reducers.ts: -------------------------------------------------------------------------------- 1 | import * as types from './Auth.constants'; 2 | import produce from 'immer'; 3 | 4 | let userType: IUser = { 5 | id: '', 6 | username: '', 7 | email: undefined, 8 | password: '', 9 | accessToken: '', 10 | }; 11 | const initialState = { 12 | loading: true, 13 | isAuthenticated: false, 14 | token: null, 15 | user: userType, 16 | }; 17 | 18 | export const authReducer = (state = initialState, action: ActionRedux) => 19 | produce(state, draft => { 20 | switch (action.type) { 21 | case types.USER_LOADED: 22 | draft.isAuthenticated = true; 23 | draft.loading = false; 24 | draft.token = action.payload.id; 25 | draft.user = action.payload; 26 | break; 27 | case types.LOGIN_SUCCESS: 28 | case types.REGISTER_SUCCESS: 29 | localStorage.setItem('user', JSON.stringify(action.payload)); 30 | draft.isAuthenticated = true; 31 | draft.loading = false; 32 | draft.user = action.payload; 33 | break; 34 | case types.LOGIN_FAILED: 35 | case types.AUTH_ERROR: 36 | case types.REGISTER_FAILED: 37 | localStorage.removeItem('user'); 38 | draft.token = null; 39 | draft.isAuthenticated = false; 40 | draft.loading = false; 41 | break; 42 | case types.LOGOUT: 43 | localStorage.removeItem('user'); 44 | draft.token = null; 45 | draft.isAuthenticated = false; 46 | draft.loading = false; 47 | break; 48 | 49 | default: 50 | return state; 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /src/components/Auth/Auth.thunks.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { URL } from 'src/constants/urls'; 3 | import * as actions from './Auth.actions'; 4 | import { v4 as uuid } from 'uuid'; 5 | import { setAlert } from 'src/components/Alert/Alert.thunks'; 6 | import { AlertTypes } from 'src/constants/alerts'; 7 | 8 | export const loadUser = () => async dispatch => { 9 | const userJson = localStorage.getItem('user') || '{}'; 10 | const user = JSON.parse(userJson) as IUser; 11 | const id = user.id; 12 | if (!id) { 13 | dispatch(actions.authError()); 14 | dispatch(setAlert({ msg: 'Cant not load user!', type: AlertTypes.ERROR })); 15 | return; 16 | } 17 | try { 18 | const res = await axios.get(`${URL.baseAPIUrl}/api/users/${id}`); 19 | if (res) { 20 | return dispatch(actions.userLoaded(res.data)); 21 | } 22 | dispatch(actions.authError()); 23 | dispatch(setAlert({ msg: 'Get user error!', type: AlertTypes.ERROR })); 24 | return; 25 | } catch (error) { 26 | dispatch(actions.authError()); 27 | dispatch(setAlert({ msg: error.message, type: AlertTypes.ERROR })); 28 | return; 29 | } 30 | }; 31 | 32 | export const login = (payload: ReqLogin) => async dispatch => { 33 | const { username, password } = payload; 34 | try { 35 | const res = await axios.get(`${URL.baseAPIUrl}/api/users`); 36 | const allUsers = res.data; 37 | 38 | let user = allUsers.filter(x => x.username === username)[0]; 39 | if (user && user.password === password) { 40 | dispatch(actions.loginSuccess(user)); 41 | dispatch( 42 | setAlert({ 43 | msg: 'You are logged in!', 44 | type: AlertTypes.SUCCESS, 45 | }), 46 | ); 47 | dispatch(loadUser()); 48 | return; 49 | } 50 | dispatch( 51 | setAlert({ 52 | msg: 'Invalid credentials', 53 | type: AlertTypes.ERROR, 54 | }), 55 | ); 56 | return dispatch(actions.loginFailed()); 57 | } catch (error) { 58 | dispatch( 59 | setAlert({ 60 | msg: 'Invalid credentials', 61 | type: AlertTypes.ERROR, 62 | }), 63 | ); 64 | return dispatch(actions.loginFailed()); 65 | } 66 | }; 67 | 68 | export const register = (payload: ReqLogin) => async dispatch => { 69 | try { 70 | const id = uuid(); 71 | const accessToken = id; 72 | 73 | const newUser = { ...payload, id, accessToken }; 74 | await axios.post(`${URL.baseAPIUrl}/api/users`, newUser); 75 | dispatch(actions.registerSuccess(newUser)); 76 | dispatch( 77 | setAlert({ 78 | msg: 'Register successfully!', 79 | type: AlertTypes.SUCCESS, 80 | }), 81 | ); 82 | dispatch(loadUser()); 83 | } catch (error) { 84 | return dispatch(actions.registerFailed()); 85 | } 86 | }; 87 | export const logout = () => async dispatch => { 88 | dispatch(actions.logoutSuccess()); 89 | dispatch( 90 | setAlert({ 91 | msg: 'You are logged out!', 92 | type: AlertTypes.WARNING, 93 | }), 94 | ); 95 | }; 96 | -------------------------------------------------------------------------------- /src/components/Auth/Login.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Form, Input, Button, Checkbox, message } from 'antd'; 3 | import { UserOutlined, LockOutlined } from '@ant-design/icons'; 4 | import { connect, ConnectedProps } from 'react-redux'; 5 | import { Link, Redirect } from 'react-router-dom'; 6 | import { login } from './Auth.thunks'; 7 | import { PATH } from 'src/constants/paths'; 8 | 9 | const mapStateToProps = (state: AppState) => ({ 10 | isAuthenticated: state.auth.isAuthenticated, 11 | }); 12 | const mapDispatchToProps = { 13 | login, 14 | }; 15 | const connector = connect(mapStateToProps, mapDispatchToProps); 16 | interface Props extends ConnectedProps {} 17 | 18 | const _Login = (props: Props) => { 19 | // eslint-disable-next-line 20 | const [error, setError] = useState(''); 21 | const { login, isAuthenticated } = props; 22 | 23 | const onFinish = async formData => { 24 | try { 25 | await login(formData); 26 | } catch (error) { 27 | message.error(error.message); 28 | setError(error.payload.message); 29 | } 30 | }; 31 | 32 | if (isAuthenticated) { 33 | return ; 34 | } 35 | return ( 36 |
37 |
38 |

39 |  logo 43 | LOGIN 44 |

45 |
51 | 55 | } 57 | placeholder="Username" 58 | /> 59 | 60 | 61 | 65 | } 67 | type="password" 68 | placeholder="Password" 69 | /> 70 | 71 | 72 | 73 | Remember me 74 | 75 | 76 | 77 | Forgot password 78 | 79 | 80 | 81 | 88 |
89 | Or{' '} 90 | 91 | Register now! 92 | 93 |
94 |
95 |
96 |
97 |
98 | ); 99 | }; 100 | const Login = connector(_Login); 101 | export { Login }; 102 | -------------------------------------------------------------------------------- /src/components/Auth/Profile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect, ConnectedProps } from 'react-redux'; 3 | import { Card, Avatar, Row, Col } from 'antd'; 4 | import { 5 | EditOutlined, 6 | EllipsisOutlined, 7 | SettingOutlined, 8 | } from '@ant-design/icons'; 9 | import { useHistory } from 'react-router-dom'; 10 | import { PATH } from 'src/constants/paths'; 11 | const { Meta } = Card; 12 | 13 | const mapStateToProps = (state: AppState) => ({ 14 | user: state.auth.user, 15 | }); 16 | 17 | const mapDispatchToProps = {}; 18 | 19 | const connector = connect(mapStateToProps, mapDispatchToProps); 20 | interface Props extends ConnectedProps {} 21 | const _Profile = (props: Props) => { 22 | const { user } = props; 23 | const history = useHistory(); 24 | const onSetting = () => { 25 | history.push(PATH.HOME); 26 | }; 27 | return ( 28 |
29 | 30 | 31 | 39 | } 40 | actions={[ 41 | onSetting()} />, 42 | , 43 | , 44 | ]} 45 | > 46 | 49 | } 50 | title={user.username} 51 | description="User profile" 52 | > 53 |
54 |

55 | 56 | 57 | Email: 58 | 59 | 60 | 61 | {user.email} 62 | 63 | 64 | 65 | 66 | Password: 67 | 68 | 69 | 70 | {user.password} 71 | 72 | 73 |
74 |
75 | 76 |
77 | , 78 |
79 | ); 80 | }; 81 | 82 | export const Profile = connector(_Profile); 83 | -------------------------------------------------------------------------------- /src/components/Auth/Register.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Form, Input, Button } from 'antd'; 3 | import { UserOutlined, LockOutlined, MailOutlined } from '@ant-design/icons'; 4 | import { connect, ConnectedProps } from 'react-redux'; 5 | import { Link, Redirect } from 'react-router-dom'; 6 | import { register } from './Auth.thunks'; 7 | import { PATH } from 'src/constants/paths'; 8 | 9 | const mapStateToProps = (state: AppState) => ({ 10 | isAuthenticated: state.auth.isAuthenticated, 11 | }); 12 | const mapDispatchToProps = { 13 | register, 14 | }; 15 | const connector = connect(mapStateToProps, mapDispatchToProps); 16 | interface Props extends ConnectedProps {} 17 | 18 | const _Register = (props: Props) => { 19 | // eslint-disable-next-line 20 | const [error, setError] = useState(''); 21 | const { register, isAuthenticated } = props; 22 | 23 | const onFinish = async formData => { 24 | try { 25 | await register(formData); 26 | } catch (error) { 27 | setError(error.payload.message); 28 | } 29 | }; 30 | const onFinishFailed = errorInfo => { 31 | // eslint-disable-next-line 32 | console.log('Failed:', errorInfo); 33 | }; 34 | 35 | if (isAuthenticated) { 36 | return ; 37 | } 38 | return ( 39 |
40 |
41 |

42 |  logo 46 | SIGNUP 47 |

48 |
55 | 59 | } 61 | placeholder="Username" 62 | /> 63 | 64 | 68 | } 70 | placeholder="Email" 71 | type="email" 72 | /> 73 | 74 | 75 | 79 | } 81 | type="password" 82 | placeholder="Password" 83 | /> 84 | 85 | 86 | 93 |
94 | Or{' '} 95 | 96 | Log in now! 97 | 98 |
99 |
100 |
101 |
102 |
103 | ); 104 | }; 105 | const Register = connector(_Register); 106 | export { Register }; 107 | -------------------------------------------------------------------------------- /src/components/Error/404.tsx: -------------------------------------------------------------------------------- 1 | import { ExclamationOutlined } from '@ant-design/icons'; 2 | import { Button } from 'antd'; 3 | import React from 'react'; 4 | import { useHistory } from 'react-router-dom'; 5 | 6 | export const NotFound = () => { 7 | const history = useHistory(); 8 | const goBack = () => { 9 | history.goBack(); 10 | }; 11 | return ( 12 |
13 |
14 |
15 |

16 | 17 | Page Not Found 18 |

19 |

Sorry, this page does not exist

20 | 23 |
24 |
25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | GithubOutlined, 4 | LinkedinOutlined, 5 | TwitterOutlined, 6 | UpCircleOutlined, 7 | } from '@ant-design/icons'; 8 | import { BackTop } from 'antd'; 9 | 10 | export const AppFooter = () => { 11 | return ( 12 |
13 |
14 |
15 |
16 | REACT TS 17 |
18 | 47 |
Copyright © 2020 REACT TS
48 | 49 |
50 | 51 |
52 |
53 |
54 |
55 |
56 | ); 57 | }; 58 | -------------------------------------------------------------------------------- /src/components/Header/LeftMenu.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NavLink } from 'react-router-dom'; 3 | import { Menu, Grid } from 'antd'; 4 | 5 | const { SubMenu, ItemGroup: MenuItemGroup } = Menu; 6 | const { useBreakpoint } = Grid; 7 | 8 | export const LeftMenu = () => { 9 | const { md } = useBreakpoint(); 10 | return ( 11 | 12 | 13 | 14 | Home 15 | 16 | 17 | 18 | 19 | 20 | Option 1 21 | 22 | 23 | Option 2 24 | 25 | 26 | 27 | 28 | Option 1 29 | 30 | 31 | Option 2 32 | 33 | 34 | 35 | 36 | 37 | About 38 | 39 | 40 | 41 | 42 | Contact 43 | 44 | 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /src/components/Header/RightMenu.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Menu, Grid } from 'antd'; 3 | import { NavLink } from 'react-router-dom'; 4 | import { LoginOutlined } from '@ant-design/icons'; 5 | import { logout } from 'src/components/Auth/Auth.thunks'; 6 | import { connect, ConnectedProps } from 'react-redux'; 7 | import { PATH } from 'src/constants/paths'; 8 | 9 | const { useBreakpoint } = Grid; 10 | 11 | const mapStateToProps = (state: AppState) => ({ 12 | isAuthenticated: state.auth.isAuthenticated, 13 | user: state.auth.user as IUser, 14 | }); 15 | const mapDispatchToProps = { 16 | logout, 17 | }; 18 | 19 | const connector = connect(mapStateToProps, mapDispatchToProps); 20 | interface Props extends ConnectedProps {} 21 | 22 | const _RightMenu = (props: Props) => { 23 | const { isAuthenticated, logout, user } = props; 24 | const { md } = useBreakpoint(); 25 | const guestLinks = ( 26 | 27 | 28 | 29 | Sign In 30 | 31 | 32 | 33 | 34 | Register 35 | 36 | 37 | 38 | ); 39 | const authLinks = ( 40 | 41 | 42 | 43 | Hi {user.username} 44 | 45 | 46 | 47 | logout()} 51 | > 52 | 53 | 54 | Log Out 55 | 56 | 57 | 58 | 59 | ); 60 | return <>{isAuthenticated ? authLinks : guestLinks}; 61 | }; 62 | 63 | const RightMenu = connector(_RightMenu); 64 | export { RightMenu }; 65 | -------------------------------------------------------------------------------- /src/components/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { NavLink } from 'react-router-dom'; 3 | import { LeftMenu } from './LeftMenu'; 4 | import { RightMenu } from './RightMenu'; 5 | import { Drawer, Button } from 'antd'; 6 | 7 | export const AppHeader = () => { 8 | const [visible, setVisible] = useState(false); 9 | const showDrawer = () => { 10 | setVisible(true); 11 | }; 12 | const drawerOnClose = () => { 13 | setVisible(false); 14 | }; 15 | return ( 16 |
17 |
18 | 52 |
53 |
54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /src/components/Home/AuthLinks.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ProductList } from 'src/components/Products/ProductList'; 3 | 4 | export const AuthLinks = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/components/Home/GuestLinks.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from 'antd'; 2 | import React from 'react'; 3 | import { Link } from 'react-router-dom'; 4 | import { PATH } from 'src/constants/paths'; 5 | 6 | export const GuestLinks = () => { 7 | return ( 8 |
9 |
10 |
11 |
12 |

React Typescript Template

13 |

Please login with account & password below.

14 |

15 | Account: 16 | tester 17 |

18 |

19 | Password: 123456 20 |

21 |
22 | 27 |
28 |
29 |
30 |
31 |
32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/components/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect, ConnectedProps } from 'react-redux'; 3 | import { GuestLinks } from './GuestLinks'; 4 | import { AuthLinks } from './AuthLinks'; 5 | 6 | const mapStateToProps = (state: AppState) => ({ 7 | loading: state.auth.loading, 8 | isAuthenticated: state.auth.isAuthenticated, 9 | }); 10 | const mapDispatchToProps = {}; 11 | 12 | const connector = connect(mapStateToProps, mapDispatchToProps); 13 | interface Props extends ConnectedProps {} 14 | 15 | const _Home = (props: Props) => { 16 | const { loading, isAuthenticated } = props; 17 | return <>{!loading && isAuthenticated ? : }; 18 | }; 19 | 20 | const Home = connector(_Home); 21 | export { Home }; 22 | -------------------------------------------------------------------------------- /src/components/Loading/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Spin } from 'antd'; 3 | import { LoadingOutlined } from '@ant-design/icons'; 4 | 5 | const antIcon = ; 6 | 7 | export const Loading = () => { 8 | return ( 9 |
10 |
11 | 12 |
13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/Products/Product.actions.ts: -------------------------------------------------------------------------------- 1 | import * as types from './Product.constants'; 2 | 3 | export const getProductsSuccess = (payload: Product[]) => ({ 4 | type: types.GET_PRODUCTS, 5 | payload, 6 | }); 7 | 8 | export const getProductSuccess = (payload: Product) => ({ 9 | type: types.GET_PRODUCT, 10 | payload, 11 | }); 12 | 13 | export const createProductSuccess = (payload: Product) => ({ 14 | type: types.CREATE_PRODUCT, 15 | payload, 16 | }); 17 | 18 | export const editProductSuccess = (payload: Product) => ({ 19 | type: types.EDIT_PRODUCT, 20 | payload, 21 | }); 22 | 23 | export const updateProductSuccess = (payload: Product) => ({ 24 | type: types.UPDATE_PRODUCT, 25 | payload, 26 | }); 27 | 28 | export const deleteProductSuccess = (payload: string) => ({ 29 | type: types.DELETE_PRODUCT, 30 | payload, 31 | }); 32 | 33 | export const productError = (payload: ProductError) => ({ 34 | type: types.PRODUCT_ERROR, 35 | payload, 36 | }); 37 | 38 | export const clearProductSuccess = () => ({ 39 | type: types.CLEAR_PRODUCT, 40 | }); 41 | -------------------------------------------------------------------------------- /src/components/Products/Product.constants.ts: -------------------------------------------------------------------------------- 1 | export const GET_PRODUCTS = 'views/Product/GET_PRODUCTS'; 2 | export const GET_PRODUCT = 'views/Product/GET_PRODUCT'; 3 | export const PRODUCT_ERROR = 'views/Product/PRODUCT_ERROR'; 4 | export const CREATE_PRODUCT = 'views/Product/CREATE_PRODUCT'; 5 | export const EDIT_PRODUCT = 'views/Product/EDIT_PRODUCT'; 6 | export const UPDATE_PRODUCT = 'views/Product/UPDATE_PRODUCT'; 7 | export const DELETE_PRODUCT = 'views/Product/DELETE_PRODUCT'; 8 | export const CLEAR_PRODUCT = 'views/Product/CLEAR_PRODUCT'; 9 | -------------------------------------------------------------------------------- /src/components/Products/Product.reducers.ts: -------------------------------------------------------------------------------- 1 | import * as types from './Product.constants'; 2 | import produce from 'immer'; 3 | 4 | const products = [] as Product[]; 5 | 6 | const initialState = { 7 | loading: true, 8 | error: {}, 9 | product: null, 10 | products: products, 11 | }; 12 | 13 | export const productReducer = (state = initialState, action) => 14 | produce(state, draft => { 15 | const { type, payload } = action; 16 | switch (type) { 17 | case types.GET_PRODUCTS: 18 | draft.products = payload; 19 | draft.loading = false; 20 | break; 21 | case types.GET_PRODUCT: 22 | draft.product = payload; 23 | draft.loading = false; 24 | break; 25 | case types.CREATE_PRODUCT: 26 | draft.products = [payload, ...state.products]; 27 | draft.loading = false; 28 | break; 29 | case types.EDIT_PRODUCT: 30 | draft.loading = false; 31 | draft.product = payload; 32 | break; 33 | case types.UPDATE_PRODUCT: 34 | draft.products = state.products.map(x => 35 | x.id === payload.id ? { ...payload } : x, 36 | ); 37 | draft.loading = false; 38 | break; 39 | case types.DELETE_PRODUCT: 40 | draft.products = state.products.filter(p => p.id !== payload); 41 | draft.loading = false; 42 | break; 43 | case types.PRODUCT_ERROR: 44 | draft.error = payload; 45 | draft.loading = false; 46 | draft.products = []; 47 | draft.product = null; 48 | break; 49 | 50 | case types.CLEAR_PRODUCT: 51 | draft.product = null; 52 | draft.loading = false; 53 | break; 54 | default: 55 | return state; 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /src/components/Products/Product.thunks.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { URL } from 'src/constants/urls'; 3 | import * as actions from './Product.actions'; 4 | import { v4 as uuid } from 'uuid'; 5 | import { setAlert } from 'src/components/Alert/Alert.thunks'; 6 | import { AlertTypes } from 'src/constants/alerts'; 7 | 8 | const dispatchError = (dispatch, error) => { 9 | const payload = { 10 | msg: error.response?.statusText, 11 | status: error.response?.status, 12 | }; 13 | dispatch(actions.productError(payload)); 14 | dispatch( 15 | setAlert({ 16 | msg: error.response?.statusText, 17 | type: AlertTypes.ERROR, 18 | }), 19 | ); 20 | }; 21 | 22 | export const getProducts = () => async dispatch => { 23 | try { 24 | const res = await axios.get(`${URL.baseAPIUrl}/api/products`); 25 | const products = res.data; 26 | dispatch(actions.getProductsSuccess(products)); 27 | } catch (error) { 28 | dispatchError(dispatch, error); 29 | } 30 | }; 31 | 32 | export const getProduct = id => async dispatch => { 33 | try { 34 | const res = await axios.get(`${URL.baseAPIUrl}/api/products/${id}`); 35 | const product = res.data as Product; 36 | dispatch(actions.getProductSuccess(product)); 37 | } catch (error) { 38 | dispatchError(dispatch, error); 39 | } 40 | }; 41 | export const clearProduct = () => dispatch => { 42 | dispatch(actions.clearProductSuccess()); 43 | }; 44 | 45 | export const createProduct = (formData: ProductForm) => async dispatch => { 46 | try { 47 | const img_default = '/images/image-default.jpg'; 48 | const newProduct = { 49 | ...formData, 50 | id: uuid(), 51 | image_url: formData.image_url || img_default, 52 | }; 53 | await axios.post(`${URL.baseAPIUrl}/api/products`, newProduct); 54 | dispatch(actions.createProductSuccess(newProduct)); 55 | dispatch( 56 | setAlert({ 57 | msg: 'Create product successfully', 58 | type: AlertTypes.SUCCESS, 59 | }), 60 | ); 61 | dispatch(getProducts()); 62 | } catch (error) { 63 | dispatchError(dispatch, error); 64 | } 65 | }; 66 | 67 | export const deleteProduct = (id: string) => async dispatch => { 68 | try { 69 | await axios.delete(`${URL.baseAPIUrl}/api/products/${id}`); 70 | dispatch(actions.deleteProductSuccess(id)); 71 | dispatch( 72 | setAlert({ 73 | msg: 'Delete product successfully', 74 | type: AlertTypes.SUCCESS, 75 | }), 76 | ); 77 | dispatch(getProducts()); 78 | } catch (error) { 79 | dispatchError(dispatch, error); 80 | } 81 | }; 82 | 83 | export const editProduct = (id: string) => async dispatch => { 84 | try { 85 | const res = await axios.get(`${URL.baseAPIUrl}/api/products/${id}`); 86 | const product = res.data as Product; 87 | dispatch(actions.editProductSuccess(product)); 88 | } catch (error) { 89 | dispatchError(dispatch, error); 90 | } 91 | }; 92 | 93 | export const updateProduct = (product: Product) => async dispatch => { 94 | try { 95 | await axios.put(`${URL.baseAPIUrl}/api/products/${product.id}`, product); 96 | dispatch(actions.updateProductSuccess(product)); 97 | dispatch( 98 | setAlert({ 99 | msg: 'Update product successfully', 100 | type: AlertTypes.SUCCESS, 101 | }), 102 | ); 103 | dispatch(getProducts()); 104 | } catch (error) { 105 | dispatchError(dispatch, error); 106 | } 107 | }; 108 | -------------------------------------------------------------------------------- /src/components/Products/ProductForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { connect, ConnectedProps } from 'react-redux'; 3 | import { Form, Input, Button, Select } from 'antd'; 4 | import { PhoneBrand } from 'src/constants/products'; 5 | import { useHistory, useParams } from 'react-router-dom'; 6 | 7 | import { createProduct, editProduct, updateProduct } from './Product.thunks'; 8 | import { PATH } from 'src/constants/paths'; 9 | const { Option } = Select; 10 | 11 | const mapStateToProps = (state: AppState) => ({ 12 | products: state.products.products, 13 | product: state.products.product, 14 | }); 15 | const mapDispatchToProps = { createProduct, editProduct, updateProduct }; 16 | const connector = connect(mapStateToProps, mapDispatchToProps); 17 | interface Props extends ConnectedProps { 18 | edit: boolean; 19 | } 20 | export const _ProductForm = (props: Props) => { 21 | const { createProduct, edit, product, updateProduct, editProduct } = props; 22 | const history = useHistory(); 23 | const params: ProductUrlParams = useParams(); 24 | const layout = { 25 | labelCol: { span: 4 }, 26 | wrapperCol: { span: 16 }, 27 | }; 28 | 29 | const onFinish = values => { 30 | if (!edit) { 31 | createProduct(values); 32 | } else { 33 | const changedProduct = { ...product, ...values }; 34 | updateProduct(changedProduct); 35 | } 36 | 37 | history.push(PATH.HOME); 38 | }; 39 | 40 | const allBrands = Object.values(PhoneBrand).filter( 41 | x => typeof x !== 'number', 42 | ); 43 | const initialValues = { ...product }; 44 | 45 | useEffect(() => { 46 | if (edit) { 47 | const { id } = params; 48 | editProduct(id); 49 | } 50 | }, [edit, editProduct, params]); 51 | 52 | return ( 53 |
54 |
55 |
56 |
57 |

{edit ? 'Update product' : 'Create product'}

58 |
59 |
66 | 76 | 77 | 78 | 88 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 111 | 112 | 113 |
114 |
115 |
116 |
117 | ); 118 | }; 119 | 120 | export const ProductForm = connector(_ProductForm); 121 | -------------------------------------------------------------------------------- /src/components/Products/ProductItem.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { connect, ConnectedProps } from 'react-redux'; 3 | import { getProduct, deleteProduct } from './Product.thunks'; 4 | import { useParams } from 'react-router-dom'; 5 | import { Row, Col, Image, Button } from 'antd'; 6 | import { NotFound } from 'src/components/Error/404'; 7 | import { useHistory } from 'react-router-dom'; 8 | import { 9 | ArrowLeftOutlined, 10 | DeleteOutlined, 11 | EditOutlined, 12 | } from '@ant-design/icons'; 13 | import { PATH } from 'src/constants/paths'; 14 | 15 | const mapStateToProps = (state: AppState) => ({ 16 | product: state.products.product, 17 | }); 18 | const mapDispatchToProps = { 19 | getProduct, 20 | deleteProduct, 21 | }; 22 | 23 | const connector = connect(mapStateToProps, mapDispatchToProps); 24 | interface Props extends ConnectedProps {} 25 | 26 | export const _ProductItem = (props: Props) => { 27 | const { product, getProduct, deleteProduct } = props; 28 | 29 | const history = useHistory(); 30 | const goBack = () => { 31 | history.goBack(); 32 | }; 33 | const goEdit = () => { 34 | history.push(`${PATH.PRODUCTS}/${product.id}/edit`); 35 | }; 36 | const onDelete = () => { 37 | deleteProduct(product.id); 38 | history.push(PATH.HOME); 39 | }; 40 | 41 | let productComponent = item => { 42 | if (item) { 43 | return ( 44 |
45 |
46 | 47 | 48 | 49 | 50 | 51 |

52 | Name:{' '} 53 | {item.name} 54 |

55 |

56 | Brand: {item.brand} 57 |

58 | 59 |
60 | 61 | 62 | 65 | 66 | 67 | {' '} 70 | 73 | 74 | 75 |
76 |
77 | ); 78 | } else { 79 | return ; 80 | } 81 | }; 82 | 83 | const params: ProductUrlParams = useParams(); 84 | useEffect(() => { 85 | const { id } = params; 86 | getProduct(id); 87 | }, [params, getProduct]); 88 | 89 | return productComponent(product); 90 | }; 91 | export const ProductItem = connector(_ProductItem); 92 | -------------------------------------------------------------------------------- /src/components/Products/ProductList.tsx: -------------------------------------------------------------------------------- 1 | import { PlusOutlined } from '@ant-design/icons'; 2 | import { Button, Col, Row, Table, Image } from 'antd'; 3 | import React, { useEffect } from 'react'; 4 | import { connect, ConnectedProps } from 'react-redux'; 5 | import { Link, useHistory } from 'react-router-dom'; 6 | import { PATH } from 'src/constants/paths'; 7 | import { getProducts, clearProduct } from './Product.thunks'; 8 | 9 | const mapStateToProps = (state: AppState) => ({ 10 | loading: state.products.loading, 11 | products: state.products.products, 12 | }); 13 | 14 | const mapDispatchToProps = { 15 | getProducts, 16 | clearProduct, 17 | }; 18 | 19 | const connector = connect(mapStateToProps, mapDispatchToProps); 20 | interface Props extends ConnectedProps {} 21 | 22 | export const _ProductList = (props: Props) => { 23 | const history = useHistory(); 24 | 25 | const { products, getProducts, clearProduct } = props; 26 | 27 | const columns: any = [ 28 | { 29 | title: 'Preview', 30 | dataIndex: 'image_url', 31 | render: (image_url, row) => renderImgProduct(image_url, row), 32 | }, 33 | { 34 | title: 'Name', 35 | dataIndex: 'name', 36 | sorter: (a, b) => { 37 | return a.name.localeCompare(b.name); 38 | }, 39 | sortDirections: ['descend', 'ascend'], 40 | render: (name, row) => showProduct(name, row), 41 | }, 42 | { 43 | title: 'Brand', 44 | dataIndex: 'brand', 45 | sorter: (a, b) => { 46 | return a.brand.localeCompare(b.brand); 47 | }, 48 | defaultSortOrder: 'descend', 49 | }, 50 | ]; 51 | const showProduct = (name, row) => { 52 | return ( 53 | 57 | {name} 58 | 59 | ); 60 | }; 61 | const renderImgProduct = (image_url, row) => { 62 | if (row.id) { 63 | return ( 64 | 65 | Image_sp 66 | 67 | ); 68 | } 69 | }; 70 | 71 | const addNewProduct = () => { 72 | clearProduct(); 73 | history.push(PATH.PRODUCT_NEW); 74 | }; 75 | 76 | let data = [{}]; 77 | useEffect(() => { 78 | getProducts(); 79 | }, [getProducts]); 80 | 81 | // eslint-disable-next-line array-callback-return 82 | products.forEach((product: Product, index: number) => { 83 | if (index === 0) { 84 | data = [ 85 | { 86 | key: index, 87 | id: product.id, 88 | name: product.name, 89 | brand: product.brand, 90 | image_url: product.image_url, 91 | }, 92 | ]; 93 | } else { 94 | data.push({ 95 | key: index, 96 | id: product.id, 97 | name: product.name, 98 | brand: product.brand, 99 | image_url: product.image_url, 100 | }); 101 | } 102 | }); 103 | 104 | return ( 105 |
106 |
107 |
108 |

All Smartphones available

109 |
110 |
111 | 112 | 113 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | ); 129 | }; 130 | 131 | export const ProductList = connector(_ProductList); 132 | -------------------------------------------------------------------------------- /src/components/StaticPages/About.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const About = () => { 4 | return ( 5 |
6 |
7 |
8 |

About Us

9 |
10 |
11 |

12 | Lorem, ipsum dolor sit amet consectetur adipisicing elit. Cumque 13 | cupiditate omnis eius totam aperiam? Illo eius repudiandae nostrum 14 | iste dignissimos ad molestias facere atque, itaque placeat dolores 15 | dolorum doloremque? Sint. 16 |

17 |

18 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit 19 | impedit sapiente tempore veritatis neque minima laudantium eius 20 | magni aut aliquid esse sequi, hic delectus nihil temporibus quas 21 | nobis porro exercitationem? Repellendus ipsa animi enim id sint 22 | ullam beatae eius repudiandae consequuntur nisi, eum quia, ad iure 23 | similique, tenetur cum voluptatibus sit natus rem eos iusto quam 24 | eveniet et quidem. Est distinctio minima sequi quas provident hic 25 | nulla maiores quam quaerat. Corrupti unde illum suscipit autem iure 26 | maxime dolorem magni ipsam! Ad officia veritatis aperiam nemo. 27 | Voluptas at nostrum molestias incidunt necessitatibus, sapiente 28 | numquam facere explicabo quos, eum cumque repellat quo. 29 |

30 |

31 | Lorem ipsum dolor sit amet consectetur, adipisicing elit. Eveniet 32 | sunt ullam nesciunt beatae deleniti similique aliquam excepturi, 33 | minus nobis minima ex atque, quos veniam architecto doloribus maxime 34 | sapiente. Vel voluptatem pariatur delectus dolor quidem eveniet 35 | repellat facere tempora nisi. Ut! 36 |

37 |
38 |
39 |
40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /src/components/StaticPages/Contact.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form, Input, Button, Checkbox } from 'antd'; 3 | const { TextArea } = Input; 4 | 5 | export const Contact = () => { 6 | return ( 7 |
8 |
9 |
10 |
11 |

Get in Touch

12 |

Dolore nam rerum obcaecati fugit odio nobis Molestiae rerum

13 |
14 |
19 | 28 | 29 | 30 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |