├── .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 | 
6 | [](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 You need to enable JavaScript to run this 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 | You need to enable JavaScript to run this app.
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 |
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 |
43 | LOGIN
44 |
45 |
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 |
86 | Log in
87 |
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 |
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 |
21 | Go Back
22 |
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 |
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 |
19 |
20 |
21 | REACT TS
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
38 |
39 |
40 |
47 |
48 |
49 |
50 |
51 |
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 |
23 |
24 | Go To Login Page
25 |
26 |
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 |
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 |
76 |
77 |
78 |
88 |
94 | option?.children.toLowerCase().indexOf(input.toLowerCase()) >=
95 | 0
96 | }
97 | >
98 | {allBrands.map(brand => {
99 | return {brand} ;
100 | })}
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | Submit
110 |
111 | history.goBack()}>Back
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 |
63 | Go Back
64 |
65 |
66 |
67 |
68 | Edit
69 | {' '}
70 |
71 | Delete
72 |
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 |
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 |
114 |
115 | New product
116 |
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 |
28 |
29 |
30 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
62 | value
63 | ? Promise.resolve()
64 | : Promise.reject('Should accept agreement'),
65 | },
66 | ]}
67 | >
68 | I agree with terms and conditions.
69 |
70 |
71 |
72 |
77 | Submit
78 |
79 |
80 |
81 |
82 |
83 |
84 | );
85 | };
86 |
--------------------------------------------------------------------------------
/src/components/StaticPages/Demo.option.1.tsx:
--------------------------------------------------------------------------------
1 | import { PlayCircleOutlined } from '@ant-design/icons';
2 | import { Button, Modal } from 'antd';
3 | import React, { useState } from 'react';
4 |
5 | export const DemoOption1 = () => {
6 | const [visible, setVisible] = useState(false);
7 | const showModal = e => {
8 | setVisible(true);
9 | };
10 | const handleCancel = e => {
11 | setVisible(false);
12 | };
13 | return (
14 |
15 |
16 |
17 |
18 |
Demo Option 1
19 |
20 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Error
21 | nostrum est minima. Dicta, ipsam explicabo ipsum velit eligendi
22 | distinctio in!
23 |
24 |
25 |
26 |
showModal(e)}>
27 |
28 |
29 |
30 |
handleCancel(e)}
34 | footer={null}
35 | >
36 | VIDEO
44 |
45 |
46 |
47 |
48 | );
49 | };
50 |
--------------------------------------------------------------------------------
/src/components/StaticPages/Demo.option.2.tsx:
--------------------------------------------------------------------------------
1 | import { PlayCircleOutlined } from '@ant-design/icons';
2 | import { Button, Modal } from 'antd';
3 | import React, { useState } from 'react';
4 |
5 | export const DemoOption2 = () => {
6 | const [visible, setVisible] = useState(false);
7 | const showModal = e => {
8 | setVisible(true);
9 | };
10 | const handleCancel = e => {
11 | setVisible(false);
12 | };
13 | return (
14 |
15 |
16 |
17 |
18 |
Demo Option 2
19 |
20 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Error
21 | nostrum est minima. Dicta, ipsam explicabo ipsum velit eligendi
22 | distinctio in!
23 |
24 |
25 |
26 |
showModal(e)}>
27 |
28 |
29 |
30 |
handleCancel(e)}
34 | footer={null}
35 | >
36 | VIDEO
44 |
45 |
46 |
47 |
48 | );
49 | };
50 |
--------------------------------------------------------------------------------
/src/components/StaticPages/Feature.option.1.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const FeatureOption1 = () => {
4 | return (
5 |
6 |
7 |
8 |
Feature Page - Option 1
9 |
10 |
11 |
12 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum
13 | earum animi possimus, iusto consectetur vel accusantium ipsam beatae
14 | minus unde ipsum, cumque, perspiciatis rerum laborum! Accusamus
15 | magnam sequi, a in ab quam temporibus excepturi debitis soluta
16 | inventore error nihil minima consectetur aliquid, ullam quos. A quae
17 | iusto nulla culpa! Amet optio accusamus laborum similique incidunt
18 | totam nemo expedita odit saepe quisquam numquam nesciunt beatae
19 | repellat illo doloremque inventore quibusdam ut earum consequuntur
20 | enim sunt, sint dolore! Maxime provident maiores eos velit. Adipisci
21 | at asperiores ullam praesentium. Illum, temporibus libero. Cumque
22 | officia consectetur, dolores id natus dolorem recusandae sunt eaque
23 | quis.
24 |
25 |
26 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Ea omnis
27 | blanditiis minima possimus quasi esse facere excepturi mollitia
28 | corrupti dicta praesentium sunt, saepe placeat aliquam, commodi
29 | magnam cumque temporibus corporis beatae? Doloribus voluptate
30 | dolorem saepe voluptatum culpa odit fugiat, sunt atque hic animi
31 | sequi cupiditate ad. Ratione omnis eos nesciunt.
32 |
33 |
34 |
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/StaticPages/Feature.option.2.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const FeatureOption2 = () => {
4 | return (
5 |
6 |
7 |
8 |
Service Page - Option 2
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. Magni
19 | rerum aperiam aspernatur debitis expedita eligendi facere non!
20 | Quibusdam accusamus accusantium maiores nostrum amet, neque quod
21 | quae eos hic aliquid veniam mollitia delectus cum qui aliquam
22 | laboriosam voluptate natus dolores est? Molestias, quidem fugiat
23 | placeat expedita dignissimos unde illum inventore recusandae.
24 |
25 |
26 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorem,
27 | recusandae?
28 |
29 |
30 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Sed ducimus
31 | voluptates odit cum illo suscipit voluptatem mollitia in laudantium
32 | natus. Tempore molestiae repudiandae beatae, numquam repellat,
33 | reprehenderit vero omnis consequuntur reiciendis voluptatibus quia
34 | placeat debitis quod iure ab praesentium doloremque ipsum aperiam
35 | eveniet soluta quisquam est quaerat error. Ad qui nulla, inventore
36 | similique animi numquam est soluta cupiditate suscipit! Ex id
37 | tempore pariatur aspernatur amet laborum et nisi! Architecto quidem
38 | ullam, tempore voluptate facere aliquam dicta error totam impedit,
39 | distinctio dolores, consectetur rerum pariatur molestiae
40 | perspiciatis! Obcaecati enim id sapiente impedit velit earum,
41 | adipisci dignissimos alias est quas odit nulla ad nam illum
42 | inventore veniam molestias quis quam consequatur! Alias similique
43 | quas possimus repellendus accusamus doloremque tempore quidem ipsum
44 | praesentium laborum laudantium nesciunt illum, sapiente minima esse
45 | amet libero temporibus cum optio et molestiae magnam neque facere
46 | earum? Maxime dolores autem ad, officia sint repellendus accusamus
47 | officiis iusto illo labore animi odio incidunt exercitationem
48 | blanditiis quod sequi, numquam voluptate adipisci, mollitia enim
49 | rem! Omnis eveniet odit mollitia maiores minus ratione deleniti
50 | pariatur quas fuga, ipsam doloremque voluptates fugiat nisi velit
51 | excepturi, ullam in laudantium facere impedit quidem dolorum ipsa
52 | exercitationem dolores! Excepturi earum vel natus rem culpa nihil,
53 | repudiandae velit.
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
--------------------------------------------------------------------------------
/src/constants/alerts.ts:
--------------------------------------------------------------------------------
1 | export enum AlertTypes {
2 | SUCCESS = 'success',
3 | ERROR = 'error',
4 | INFO = 'info',
5 | WARNING = 'warning',
6 | }
7 |
--------------------------------------------------------------------------------
/src/constants/paths.ts:
--------------------------------------------------------------------------------
1 | export const PATH = {
2 | HOME: '/',
3 | LOGIN: '/login',
4 | REGISTER: '/signup',
5 | CONTACT: '/contact',
6 | ABOUT: '/about',
7 | DEMO1: '/demo1',
8 | DEMO2: '/demo2',
9 | FEATURE1: '/feature1',
10 | FEATURE2: '/feature2',
11 | PROFILE: '/profile',
12 | PRODUCTS: '/products',
13 | PRODUCT_SHOW: '/products/:id',
14 | PRODUCT_NEW: '/products/new',
15 | PRODUCT_EDIT: '/products/:id/edit',
16 | };
17 |
--------------------------------------------------------------------------------
/src/constants/products.ts:
--------------------------------------------------------------------------------
1 | export enum PhoneBrand {
2 | APPLE,
3 | SAMSUNG,
4 | XIAOMI,
5 | HUEWEI,
6 | OTHERS,
7 | }
8 |
--------------------------------------------------------------------------------
/src/constants/urls.ts:
--------------------------------------------------------------------------------
1 | export const URL = {
2 | baseAPIUrl:
3 | process.env.REACT_APP_API_BASE_URL ||
4 | 'https://reactts-simple-server.herokuapp.com',
5 | };
6 |
--------------------------------------------------------------------------------
/src/hooks/usePrevious.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useEffect } from 'react';
2 |
3 | export function usePrevious(value) {
4 | const ref = useRef();
5 | useEffect(() => {
6 | ref.current = value;
7 | });
8 | return ref.current;
9 | }
10 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import 'src/assets/scss/index.scss';
4 | import 'antd/dist/antd.css';
5 | import { App } from './App/App';
6 | import reportWebVitals from './reportWebVitals';
7 | import { Provider } from 'react-redux';
8 | import { store } from './store';
9 |
10 | ReactDOM.render(
11 |
12 |
13 |
14 |
15 | ,
16 | document.getElementById('root'),
17 | );
18 |
19 | // If you want to start measuring performance in your app, pass a function
20 | // to log results (for example: reportWebVitals(console.log))
21 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
22 | reportWebVitals();
23 |
--------------------------------------------------------------------------------
/src/pages/AuthPages/LoginPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Login } from 'src/components/Auth/Login';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _LoginPage = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | const LoginPage = React.memo(_LoginPage);
14 | export default LoginPage;
15 |
--------------------------------------------------------------------------------
/src/pages/AuthPages/ProfilePage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Profile } from 'src/components/Auth/Profile';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _ProfilePage = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | const ProfilePage = React.memo(_ProfilePage);
14 | export default ProfilePage;
15 |
--------------------------------------------------------------------------------
/src/pages/AuthPages/RegisterPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Register } from 'src/components/Auth/Register';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _RegisterPage = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | const RegisterPage = React.memo(_RegisterPage);
14 | export default RegisterPage;
15 |
--------------------------------------------------------------------------------
/src/pages/ErrorPages/404Pages.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NotFound } from 'src/components/Error/404';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _NotFoundPage = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 | const NotFoundPage = React.memo(_NotFoundPage);
13 | export { NotFoundPage };
14 | export default NotFoundPage;
15 |
--------------------------------------------------------------------------------
/src/pages/HomePages/HomePage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Home } from 'src/components/Home';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _HomePage = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 | const HomePage = React.memo(_HomePage);
13 | export default HomePage;
14 |
--------------------------------------------------------------------------------
/src/pages/ProductPages/ProductEditPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ProductForm } from 'src/components/Products/ProductForm';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _ProductNewPage = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 | const ProductNewPage = React.memo(_ProductNewPage);
13 | export default ProductNewPage;
14 |
--------------------------------------------------------------------------------
/src/pages/ProductPages/ProductItemPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ProductItem } from 'src/components/Products/ProductItem';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _ProductItemPage = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 | const ProductItemPage = React.memo(_ProductItemPage);
13 | export default ProductItemPage;
14 |
--------------------------------------------------------------------------------
/src/pages/ProductPages/ProductListPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ProductList } from 'src/components/Products/ProductList';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _ProductListPage = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 | const ProductListPage = React.memo(_ProductListPage);
13 | export default ProductListPage;
14 |
--------------------------------------------------------------------------------
/src/pages/ProductPages/ProductNewPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ProductForm } from 'src/components/Products/ProductForm';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _ProductNewPage = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 | const ProductNewPage = React.memo(_ProductNewPage);
13 | export default ProductNewPage;
14 |
--------------------------------------------------------------------------------
/src/pages/StaticPages/AboutPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { About } from 'src/components/StaticPages/About';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _AboutPage = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | const AboutPage = React.memo(_AboutPage);
14 | export default AboutPage;
15 |
--------------------------------------------------------------------------------
/src/pages/StaticPages/ContactPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Contact } from 'src/components/StaticPages/Contact';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _ContactPage = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | const ContactPage = React.memo(_ContactPage);
14 | export default ContactPage;
15 |
--------------------------------------------------------------------------------
/src/pages/StaticPages/Demo1Page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DemoOption1 } from 'src/components/StaticPages/Demo.option.1';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _Demo1 = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | const Demo1 = React.memo(_Demo1);
14 | export default Demo1;
15 |
--------------------------------------------------------------------------------
/src/pages/StaticPages/Demo2Page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DemoOption2 } from 'src/components/StaticPages/Demo.option.2';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _Demo2 = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | const Demo2 = React.memo(_Demo2);
14 | export default Demo2;
15 |
--------------------------------------------------------------------------------
/src/pages/StaticPages/Feature1Page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FeatureOption1 } from 'src/components/StaticPages/Feature.option.1';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _Feature1 = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | const Feature1Page = React.memo(_Feature1);
14 | export default Feature1Page;
15 |
--------------------------------------------------------------------------------
/src/pages/StaticPages/Feature2Page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FeatureOption2 } from 'src/components/StaticPages/Feature.option.2';
3 | import { PageLayout } from 'src/pages/layouts/PageLayout';
4 |
5 | const _Feature2 = () => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | const Feature2Page = React.memo(_Feature2);
14 | export default Feature2Page;
15 |
--------------------------------------------------------------------------------
/src/pages/layouts/MainLayout.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import { AppHeader } from 'src/components/Header';
3 | import { AppFooter } from 'src/components/Footer';
4 | import { Layout } from 'antd';
5 | import { AppAlert } from 'src/components/Alert';
6 | const { Header, Content, Footer } = Layout;
7 |
8 | interface Props {
9 | children: ReactNode;
10 | }
11 | export const MainLayout = (props: Props) => {
12 | const { children } = props;
13 | return (
14 |
15 |
18 |
19 |
20 | {children}
21 |
22 |
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/src/pages/layouts/PageLayout.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import { Layout } from 'antd';
3 | import { AppAlert } from 'src/components/Alert';
4 | const { Content } = Layout;
5 |
6 | interface Props {
7 | children: ReactNode;
8 | }
9 | export const PageLayout = (props: Props) => {
10 | const { children } = props;
11 | return (
12 |
13 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/src/routes/PrivateRoute.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Route,
4 | RouteProps,
5 | Redirect,
6 | RouteComponentProps,
7 | } from 'react-router-dom';
8 | import { connect } from 'react-redux';
9 | import { PATH } from 'src/constants/paths';
10 |
11 | interface ReduxProps {
12 | isAuthenticated: boolean;
13 | }
14 | interface Props extends ReduxProps, RouteProps {
15 | component: React.ComponentType;
16 | }
17 |
18 | const mapStateToProps = state => ({
19 | isAuthenticated: state.auth.isAuthenticated,
20 | });
21 |
22 | const mapDispatchToProps = {};
23 |
24 | const connector = connect(mapStateToProps, mapDispatchToProps);
25 |
26 | const _PrivateRoute = (props: Props) => {
27 | const { isAuthenticated, component: Component, ...rest } = props;
28 | return (
29 | {
32 | if (!isAuthenticated && !localStorage.getItem('user')) {
33 | return ;
34 | }
35 | return ;
36 | }}
37 | />
38 | );
39 | };
40 |
41 | export const PrivateRoute = connector(_PrivateRoute);
42 |
--------------------------------------------------------------------------------
/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { lazy, Suspense } from 'react';
2 | import { BrowserRouter } from 'react-router-dom';
3 | import { PATH } from 'src/constants/paths';
4 | import { Loading } from 'src/components/Loading';
5 | import { Route, Switch } from 'react-router-dom';
6 | import { PrivateRoute } from './PrivateRoute';
7 | import { MainLayout } from 'src/pages/layouts/MainLayout';
8 | import { Helmet, HelmetProvider } from 'react-helmet-async';
9 |
10 | // ---> Static pages
11 | const HomePage = lazy(() => import('src/pages/HomePages/HomePage'));
12 | const ContactPage = lazy(() => import('src/pages/StaticPages/ContactPage'));
13 | const AboutPage = lazy(() => import('src/pages/StaticPages/AboutPage'));
14 | const Demo1Page = lazy(() => import('src/pages/StaticPages/Demo1Page'));
15 | const Demo2Page = lazy(() => import('src/pages/StaticPages/Demo2Page'));
16 | const Feature1Page = lazy(() => import('src/pages/StaticPages/Feature1Page'));
17 | const Feature2Page = lazy(() => import('src/pages/StaticPages/Feature2Page'));
18 |
19 | // ---> Auth pages
20 | const LoginPage = lazy(() => import('src/pages/AuthPages/LoginPage'));
21 | const RegisterPage = lazy(() => import('src/pages/AuthPages/RegisterPage'));
22 | const ProfilePage = lazy(() => import('src/pages/AuthPages/ProfilePage'));
23 |
24 | // ---> Products pages
25 | const ProductListPage = lazy(
26 | () => import('src/pages/ProductPages/ProductListPage'),
27 | );
28 | const ProductItemPage = lazy(
29 | () => import('src/pages/ProductPages/ProductItemPage'),
30 | );
31 | const ProductNewPage = lazy(
32 | () => import('src/pages/ProductPages/ProductNewPage'),
33 | );
34 | const ProductEditPage = lazy(
35 | () => import('src/pages/ProductPages/ProductEditPage'),
36 | );
37 |
38 | // ---> Error pages
39 | const NotFoundPage = lazy(() => import('src/pages/ErrorPages/404Pages'));
40 |
41 | const helmetContext = {};
42 |
43 | export const Routes = () => {
44 | return (
45 |
46 |
47 |
48 |
49 | React TS Boilerplate
50 |
54 |
55 |
56 |
57 | }>
58 |
59 | {/* Static pages routes */}
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | {/* Auth routes */}
69 |
70 |
71 |
72 |
73 | {/* Products routes */}
74 |
79 |
84 |
89 |
94 |
95 | {/* Error routes */}
96 |
97 |
98 |
99 |
100 |
101 |
102 | );
103 | };
104 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import "srctesting-library/jest-dom/extend-expect"
6 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { RootReducer } from './reducers';
4 | const composeEnhancers =
5 | typeof window === 'object' &&
6 | process.env.NODE_ENV === 'development' &&
7 | (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
8 | ? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
9 | : compose;
10 | const enhancer = composeEnhancers(applyMiddleware(thunk));
11 | export const store = createStore(RootReducer, enhancer);
12 |
--------------------------------------------------------------------------------
/src/store/reducers.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { authReducer } from 'src/components/Auth/Auth.reducers';
3 | import { productReducer } from 'src/components/Products/Product.reducers';
4 | import { alertReducer } from 'src/components/Alert/Alert.reducers';
5 |
6 | export const RootReducer = combineReducers({
7 | auth: authReducer,
8 | products: productReducer,
9 | alerts: alertReducer,
10 | });
11 |
--------------------------------------------------------------------------------
/src/utils/helper.js:
--------------------------------------------------------------------------------
1 | // Put yours code helpter in this folder
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "noImplicitAny": false,
19 | "baseUrl": "."
20 | },
21 | "include": ["src"]
22 | }
23 |
--------------------------------------------------------------------------------