├── .editorconfig
├── .env.example
├── .eslintrc.js
├── .gitattributes
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .yarnclean
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── Dockerfile.dev
├── LICENSE.md
├── PULL_REQUEST_TEMPLATE.md
├── Procfile
├── README.md
├── app
├── .htaccess
├── .nginx.conf
├── app.js
├── assets
│ ├── header
│ │ ├── avator.jpeg
│ │ ├── en_US.svg
│ │ ├── language.svg
│ │ ├── notice.svg
│ │ └── zh_CN.svg
│ ├── i18n
│ │ ├── en.svg
│ │ └── ne.svg
│ ├── images
│ │ ├── helps-bg.jpg
│ │ ├── icons
│ │ │ ├── android-chrome-192x192.png
│ │ │ ├── android-chrome-512x512.png
│ │ │ ├── apple-touch-icon.png
│ │ │ ├── favicon-16x16.png
│ │ │ ├── favicon-32x32.png
│ │ │ ├── favicon.ico
│ │ │ ├── icon-512x512.png
│ │ │ └── site.webmanifest
│ │ ├── logo
│ │ │ ├── long-logo.svg
│ │ │ └── short-logo.svg
│ │ ├── navbar.png
│ │ └── reporting.gif
│ ├── menu
│ │ ├── account.svg
│ │ ├── dashboard.svg
│ │ ├── documentation.svg
│ │ ├── guide.svg
│ │ └── permission.svg
│ └── sass
│ │ ├── _variable.scss
│ │ ├── components
│ │ ├── _buttons.scss
│ │ ├── _card.scss
│ │ ├── _font.scss
│ │ ├── _form.scss
│ │ ├── _helpers.scss
│ │ ├── _pagination.scss
│ │ ├── _spacing.scss
│ │ ├── _table.scss
│ │ └── _typography.scss
│ │ ├── layout
│ │ ├── _footer.scss
│ │ └── _navbar.scss
│ │ ├── main.scss
│ │ ├── mixins
│ │ ├── _box.scss
│ │ ├── _button.scss
│ │ ├── _display.scss
│ │ └── _media.scss
│ │ └── pages
│ │ ├── _home.scss
│ │ └── _user-account.scss
├── common
│ ├── helpers
│ │ └── index.js
│ ├── hooks
│ │ ├── passwordStrengthHook.js
│ │ └── usePrevious.js
│ ├── language
│ │ └── index.js
│ ├── layout
│ │ └── Navbar.js
│ ├── messages
│ │ └── index.js
│ ├── rules
│ │ └── index.js
│ ├── saga
│ │ └── index.js
│ └── validator
│ │ └── index.js
├── components
│ ├── DraftEditor
│ │ ├── ToolWrapper.js
│ │ ├── helper.js
│ │ └── index.js
│ ├── ErrorFallback
│ │ ├── index.js
│ │ └── messages.js
│ ├── Footer
│ │ ├── index.js
│ │ └── messages.js
│ ├── FormButtonWrapper
│ │ └── index.js
│ ├── FormCheckboxWrapper
│ │ └── index.js
│ ├── FormInputWrapper
│ │ └── index.js
│ ├── FormWrapper
│ │ └── index.js
│ ├── Header
│ │ ├── index.js
│ │ └── messages.js
│ ├── Img
│ │ └── index.js
│ ├── Layout
│ │ ├── Menu
│ │ │ ├── index.js
│ │ │ └── list.js
│ │ ├── index.js
│ │ └── index.less
│ ├── LoadingIndicator
│ │ ├── Circle.js
│ │ ├── Wrapper.js
│ │ └── index.js
│ ├── OtpModal
│ │ ├── index.js
│ │ ├── messages.js
│ │ └── otpInput.js
│ ├── PageHeaderWrapper
│ │ └── index.js
│ ├── SearchInput
│ │ ├── index.js
│ │ └── messages.js
│ ├── SelectInputWrapper
│ │ ├── index.js
│ │ └── messages.js
│ ├── Toggle
│ │ └── index.js
│ ├── ToggleOption
│ │ └── index.js
│ └── ToolTipButtonWrapper
│ │ └── index.js
├── configure-store.js
├── containers
│ ├── AlertMessage
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── index.js
│ │ ├── reducer.js
│ │ ├── saga.js
│ │ └── selectors.js
│ ├── App
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── index.js
│ │ ├── index.less
│ │ ├── reducer.js
│ │ ├── saga.js
│ │ └── selectors.js
│ ├── Dashboard
│ │ ├── Loadable.js
│ │ ├── actions.js
│ │ ├── charts
│ │ │ └── deviceChart.js
│ │ ├── constants.js
│ │ ├── index.js
│ │ ├── messages.js
│ │ ├── reducer.js
│ │ ├── saga.js
│ │ └── selectors.js
│ ├── EmailTemplate
│ │ ├── Loadable.js
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── createEmailTemplateModal.js
│ │ ├── editEmailTemplateModal.js
│ │ ├── emailTemplateTable.js
│ │ ├── hooks
│ │ │ └── useGetEmailTemplateForm.js
│ │ ├── index.js
│ │ ├── messages.js
│ │ ├── reducer.js
│ │ ├── saga.js
│ │ └── selectors.js
│ ├── ForgotPassword
│ │ ├── Loadable.js
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── index.js
│ │ ├── messages.js
│ │ ├── reducer.js
│ │ ├── saga.js
│ │ └── selectors.js
│ ├── HomePage
│ │ ├── Loadable.js
│ │ ├── actions.js
│ │ ├── banner.js
│ │ ├── constants.js
│ │ ├── contributors.js
│ │ ├── index.js
│ │ ├── messages.js
│ │ ├── reducer.js
│ │ ├── saga.js
│ │ ├── selector.js
│ │ └── truthyHelp.js
│ ├── LanguageProvider
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── index.js
│ │ ├── reducer.js
│ │ └── selectors.js
│ ├── LocaleToggle
│ │ ├── Wrapper.js
│ │ ├── index.js
│ │ └── messages.js
│ ├── LoginPage
│ │ ├── Loadable.js
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── index.js
│ │ ├── loginForm.js
│ │ ├── messages.js
│ │ ├── reducer.js
│ │ ├── saga.js
│ │ └── selectors.js
│ ├── NotFoundPage
│ │ ├── Loadable.js
│ │ ├── index.js
│ │ └── messages.js
│ ├── Permission
│ │ ├── Loadable.js
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── createPermissionModal.js
│ │ ├── editPermissionModal.js
│ │ ├── hooks
│ │ │ └── useGetPermissionForm.js
│ │ ├── index.js
│ │ ├── messages.js
│ │ ├── permissionTable.js
│ │ ├── reducer.js
│ │ ├── saga.js
│ │ └── selectors.js
│ ├── PermissionDeniedPage
│ │ ├── Loadable.js
│ │ ├── index.js
│ │ └── messages.js
│ ├── PrivateRoute
│ │ └── index.js
│ ├── Profile
│ │ ├── Loadable.js
│ │ ├── index.js
│ │ └── messages.js
│ ├── PublicRoute
│ │ └── index.js
│ ├── RegisterPage
│ │ ├── Loadable.js
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── index.js
│ │ ├── index.less
│ │ ├── messages.js
│ │ ├── reducer.js
│ │ ├── registerForm.js
│ │ ├── saga.js
│ │ └── selectors.js
│ ├── ResetPassword
│ │ ├── Loadable.js
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── index.js
│ │ ├── messages.js
│ │ ├── reducer.js
│ │ ├── saga.js
│ │ └── selectors.js
│ ├── Role
│ │ ├── Loadable.js
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── createRoleModal.js
│ │ ├── editRoleModal.js
│ │ ├── hooks
│ │ │ └── useGetRoleForm.js
│ │ ├── index.js
│ │ ├── messages.js
│ │ ├── modifyPermissionModal.js
│ │ ├── reducer.js
│ │ ├── roleTable.js
│ │ ├── saga.js
│ │ └── selectors.js
│ ├── SnackMessage
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── index.js
│ │ ├── reducer.js
│ │ ├── saga.js
│ │ └── selectors.js
│ ├── UserAccount
│ │ ├── Loadable.js
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── index.js
│ │ ├── loginActivity.js
│ │ ├── messages.js
│ │ ├── profileForm.js
│ │ ├── reducer.js
│ │ ├── saga.js
│ │ ├── securityTab.js
│ │ └── selectors.js
│ ├── Users
│ │ ├── Loadable.js
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── createUserModal.js
│ │ ├── editUserModal.js
│ │ ├── hooks
│ │ │ └── useGetUserForm.js
│ │ ├── index.js
│ │ ├── messages.js
│ │ ├── reducer.js
│ │ ├── saga.js
│ │ ├── selectors.js
│ │ └── userTable.js
│ └── VerifyAccount
│ │ ├── Loadable.js
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── index.js
│ │ ├── messages.js
│ │ ├── reducer.js
│ │ ├── saga.js
│ │ └── selectors.js
├── global-styles.js
├── helpers
│ ├── Validation.js
│ ├── ValidatonModel.js
│ └── messages.js
├── hooks
│ ├── localstorage.js
│ └── useCookie.js
├── i18n.js
├── i18n
│ ├── en.json
│ └── ne.json
├── index.html
├── logo.svg
├── reducers
│ └── index.js
├── reportWebVitals.js
├── routes
│ ├── index.js
│ └── messages.js
├── services
│ ├── cookie.service.js
│ └── persist.service.js
├── setupTests.js
├── store
│ └── index.js
└── utils
│ ├── api.js
│ ├── checkStore.js
│ ├── colors.js
│ ├── common.js
│ ├── constants.js
│ ├── history.js
│ ├── injectReducer.js
│ ├── injectSaga.js
│ ├── loadable.js
│ ├── pagination.js
│ ├── permission.js
│ ├── reducerInjectors.js
│ ├── request.bak.js
│ ├── request.js
│ ├── rwd.js
│ └── sagaInjectors.js
├── babel.config.js
├── bin
└── cli.js
├── internals
├── scripts
│ ├── analyze.js
│ ├── clean.js
│ ├── extract-intl.js
│ ├── helpers
│ │ ├── checkmark.js
│ │ ├── get-npm-config.js
│ │ ├── progress.js
│ │ └── xmark.js
│ ├── npmcheckversion.js
│ └── setup.js
└── webpack
│ ├── webpack.base.babel.js
│ ├── webpack.dev.babel.js
│ └── webpack.prod.babel.js
├── jsconfig.json
├── nginx.conf
├── package.json
├── server
├── argv.js
├── index.js
├── logger.js
├── middlewares
│ ├── addDevMiddlewares.js
│ ├── addProdMiddlewares.js
│ └── frontendMiddleware.js
└── port.js
├── vercel.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | insert_final_newline = true
9 | indent_style = space
10 | indent_size = 2
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | NODE_ENV=development
2 | REACT_APP_URI=http://localhost:3000
3 | REACT_APP_API_BASE_URI=http://localhost:7777
4 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes
2 |
3 | # Handle line endings automatically for files detected as text
4 | # and leave all files detected as binary untouched.
5 | * text=auto
6 |
7 | #
8 | # The above will handle all files NOT found below
9 | #
10 |
11 | #
12 | ## These files are text and should be normalized (Convert crlf => lf)
13 | #
14 |
15 | # source code
16 | *.php text
17 | *.css text
18 | *.sass text
19 | *.scss text
20 | *.less text
21 | *.styl text
22 | *.js text eol=lf
23 | *.coffee text
24 | *.json text
25 | *.htm text
26 | *.html text
27 | *.xml text
28 | *.svg text
29 | *.txt text
30 | *.ini text
31 | *.inc text
32 | *.pl text
33 | *.rb text
34 | *.py text
35 | *.scm text
36 | *.sql text
37 | *.sh text
38 | *.bat text
39 |
40 | # templates
41 | *.ejs text
42 | *.hbt text
43 | *.jade text
44 | *.haml text
45 | *.hbs text
46 | *.dot text
47 | *.tmpl text
48 | *.phtml text
49 |
50 | # server config
51 | .htaccess text
52 | .nginx.conf text
53 |
54 | # git config
55 | .gitattributes text
56 | .gitignore text
57 | .gitconfig text
58 |
59 | # code analysis config
60 | .jshintrc text
61 | .jscsrc text
62 | .jshintignore text
63 | .csslintrc text
64 |
65 | # misc config
66 | *.yaml text
67 | *.yml text
68 | .editorconfig text
69 |
70 | # build config
71 | *.npmignore text
72 | *.bowerrc text
73 |
74 | # Heroku
75 | Procfile text
76 | .slugignore text
77 |
78 | # Documentation
79 | *.md text
80 | LICENSE text
81 | AUTHORS text
82 |
83 |
84 | #
85 | ## These files are binary and should be left untouched
86 | #
87 |
88 | # (binary is a macro for -text -diff)
89 | *.png binary
90 | *.jpg binary
91 | *.jpeg binary
92 | *.gif binary
93 | *.ico binary
94 | *.mov binary
95 | *.mp4 binary
96 | *.mp3 binary
97 | *.flv binary
98 | *.fla binary
99 | *.swf binary
100 | *.gz binary
101 | *.zip binary
102 | *.7z binary
103 | *.ttf binary
104 | *.eot binary
105 | *.woff binary
106 | *.pyc binary
107 | *.pdf binary
108 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Call function '...'
16 | 2. Pass value '...'
17 | 3. See error
18 |
19 | **Expected behavior**
20 | A clear and concise description of what you expected to happen.
21 |
22 | **Screenshots**
23 | If applicable, add screenshots to help explain your problem.
24 |
25 | **Additional context**
26 | Add any other context about the problem here.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 | .env
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 | .idea
26 | .idea/*
27 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/dubnium
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | internals/generators/
4 | internals/scripts/
5 | package-lock.json
6 | yarn.lock
7 | package.json
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all"
8 | }
9 |
--------------------------------------------------------------------------------
/.yarnclean:
--------------------------------------------------------------------------------
1 | # test directories
2 | __tests__
3 | test
4 | tests
5 | powered-test
6 |
7 | # asset directories
8 | docs
9 | doc
10 | website
11 | images
12 | assets
13 |
14 | # examples
15 | example
16 | examples
17 |
18 | # code coverage directories
19 | coverage
20 | .nyc_output
21 |
22 | # build scripts
23 | Makefile
24 | Gulpfile.js
25 | Gruntfile.js
26 |
27 | # configs
28 | appveyor.yml
29 | circle.yml
30 | codeship-services.yml
31 | codeship-steps.yml
32 | wercker.yml
33 | .tern-project
34 | .gitattributes
35 | .editorconfig
36 | .*ignore
37 | .eslintrc
38 | .jshintrc
39 | .flowconfig
40 | .documentup.json
41 | .yarn-metadata.json
42 | .travis.yml
43 |
44 | # misc
45 | *.md
46 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14 as build-deps
2 |
3 | WORKDIR /app
4 |
5 | COPY . .
6 |
7 | RUN yarn install
8 | RUN yarn build
9 |
10 |
11 | FROM nginx:1.21-alpine
12 | COPY --from=build-deps /app/build /usr/share/nginx/html
13 | COPY nginx.conf /etc/nginx/nginx.conf
14 |
15 | CMD ["nginx", "-g", "daemon off;"]
--------------------------------------------------------------------------------
/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:alpine
2 |
3 | WORKDIR '/app'
4 |
5 | COPY package.json .
6 | RUN npm install
7 |
8 | COPY . .
9 |
10 | CMD ["npm", "run", "start"]
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021 Truthy
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 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
4 |
5 | Fixes # (issue)
6 |
7 | ## Type of change
8 |
9 | Please delete options that are not relevant.
10 |
11 | - [ ] Bug fix (non-breaking change which fixes an issue)
12 | - [ ] New feature (non-breaking change which adds functionality)
13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
14 | - [ ] This change requires a documentation update
15 |
16 | # How Has This Been Tested?
17 |
18 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
19 |
20 | - [ ] Test A
21 | - [ ] Test B
22 |
23 | # Checklist:
24 |
25 | - [ ] My code follows the style guidelines of this project
26 | - [ ] I have performed a self-review of my own code
27 | - [ ] I have commented my code, particularly in hard-to-understand areas
28 | - [ ] I have made corresponding changes to the documentation
29 | - [ ] My changes generate no new warnings
30 | - [ ] I have added tests that prove my fix is effective or that my feature works
31 | - [ ] New and existing unit tests pass locally with my changes
32 | - [ ] Any dependent changes have been merged and published in downstream modules
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm run start
2 |
--------------------------------------------------------------------------------
/app/.htaccess:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #######################################################################
5 | # GENERAL #
6 | #######################################################################
7 |
8 | # Make apache follow sym links to files
9 | Options +FollowSymLinks
10 | # If somebody opens a folder, hide all files from the resulting folder list
11 | IndexIgnore */*
12 |
13 |
14 | #######################################################################
15 | # REWRITING #
16 | #######################################################################
17 |
18 | # Enable rewriting
19 | RewriteEngine On
20 |
21 | # If its not HTTPS
22 | RewriteCond %{HTTPS} off
23 |
24 | # Comment out the RewriteCond above, and uncomment the RewriteCond below if you're using a load balancer (e.g. CloudFlare) for SSL
25 | # RewriteCond %{HTTP:X-Forwarded-Proto} !https
26 |
27 | # Redirect to the same URL with https://, ignoring all further rules if this one is in effect
28 | RewriteRule ^(.*) https://%{HTTP_HOST}/$1 [R,L]
29 |
30 | # If we get to here, it means we are on https://
31 |
32 | # If the file with the specified name in the browser doesn't exist
33 | RewriteCond %{REQUEST_FILENAME} !-f
34 |
35 | # and the directory with the specified name in the browser doesn't exist
36 | RewriteCond %{REQUEST_FILENAME} !-d
37 |
38 | # and we are not opening the root already (otherwise we get a redirect loop)
39 | RewriteCond %{REQUEST_FILENAME} !\/$
40 |
41 | # Rewrite all requests to the root
42 | RewriteRule ^(.*) /
43 |
44 |
45 |
46 |
47 | # Do not cache sw.js, required for offline-first updates.
48 |
49 | Header set Cache-Control "private, no-cache, no-store, proxy-revalidate, no-transform"
50 | Header set Pragma "no-cache"
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/app.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | /* eslint-disable import/no-import-module-exports */
3 | /**
4 | * app.js
5 | *
6 | * This is the entry file for the application, only setup and boilerplate
7 | * code.
8 | */
9 |
10 | // Needed for redux-saga es6 generator support
11 | import 'react-app-polyfill/ie11';
12 | import 'react-app-polyfill/stable';
13 | import React from 'react';
14 | import ReactDOM from 'react-dom';
15 | import { Provider } from 'react-redux';
16 | import 'antd/dist/antd.css';
17 | import 'assets/sass/main.scss';
18 | import App from 'containers/App';
19 | import LanguageProvider from 'containers/LanguageProvider';
20 | import '!file-loader?name=[name].[ext]!./assets/images/icons/favicon.ico';
21 | import 'file-loader?name=.htaccess!./.htaccess';
22 | import { saveState } from 'services/persist.service';
23 | import { throttle } from 'lodash';
24 | import reportWebVitals from 'reportWebVitals';
25 | import { store } from './store';
26 | import { translationMessages } from './i18n';
27 |
28 | const MOUNT_NODE = document.getElementById('app');
29 |
30 | store.subscribe(
31 | throttle(() => {
32 | saveState({
33 | language: store.getState().language,
34 | // global: store.getState().global,
35 | });
36 | }, 1000),
37 | );
38 |
39 | const render = (messages) => {
40 | ReactDOM.render(
41 |
42 |
43 |
44 |
45 | ,
46 | MOUNT_NODE,
47 | );
48 | };
49 |
50 | if (module.hot) {
51 | // Hot reloadable React components and translation json files
52 | // modules.hot.accept does not accept dynamic dependencies,
53 | // have to be constants at compile-time
54 | module.hot.accept(['./i18n', 'containers/App'], () => {
55 | ReactDOM.unmountComponentAtNode(MOUNT_NODE);
56 | render(translationMessages);
57 | });
58 | }
59 |
60 | render(translationMessages);
61 |
62 | if (process.env.NODE_ENV === 'development') {
63 | // eslint-disable-next-line no-console
64 | reportWebVitals(console.log);
65 | }
66 |
--------------------------------------------------------------------------------
/app/assets/header/avator.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/header/avator.jpeg
--------------------------------------------------------------------------------
/app/assets/header/notice.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/header/zh_CN.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/i18n/ne.svg:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/app/assets/images/helps-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/images/helps-bg.jpg
--------------------------------------------------------------------------------
/app/assets/images/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/images/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/app/assets/images/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/images/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/app/assets/images/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/images/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/app/assets/images/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/images/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/app/assets/images/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/images/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/app/assets/images/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/images/icons/favicon.ico
--------------------------------------------------------------------------------
/app/assets/images/icons/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/images/icons/icon-512x512.png
--------------------------------------------------------------------------------
/app/assets/images/icons/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/app/assets/images/navbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/images/navbar.png
--------------------------------------------------------------------------------
/app/assets/images/reporting.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/images/reporting.gif
--------------------------------------------------------------------------------
/app/assets/menu/account.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/menu/dashboard.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/menu/documentation.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/menu/guide.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/menu/permission.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/sass/_variable.scss:
--------------------------------------------------------------------------------
1 |
2 | // ************* COLORS PALLETS **************** //
3 |
4 |
5 | // Blue Pallets Color
6 |
7 | $primary:#39af9f;
8 | $secondary:#3fceba;
9 | $white:#ffffff;
10 | $bg-color:#ececec;
11 | $border-color:#d4d5d6;
12 |
13 |
14 |
15 | // Red paalets
16 | $danger:#611a15;
17 | $danger-bg: #fdecea;
18 | $danger-icon:#f44336;
19 |
20 |
21 | // Green pallets
22 |
23 | $success: #1e4620;
24 | $success-bg: #edf7ed;
25 | $success-icon:#4caf50;
26 |
27 |
28 | $text-color: #000000d9;
29 |
30 |
31 |
32 | // font sizes
33 | $font-md: 1.375rem;
34 | $font-sm: 1.25rem;
35 | $font-regular: 1rem;
36 | $font-xs: 0.875rem;
37 | $font-xxs: 0.625rem;
38 |
39 |
40 | // font-weight
41 | $light:300;
42 | $regular:400;
43 | $semi-bold:600;
44 | $bold: 700;
45 |
46 |
47 | // font-path
48 | $open-sans: 'Open Sans', sans-serif;
49 |
50 |
51 | // media queries
52 | $screen-sm-max: 575px; // Small tablets and large smartphones (landscape view)
53 | $screen-md-max: 767px; // Small tablets (portrait view)
54 | $screen-lg-max: 991px; // Tablets and small desktops
55 | $screen-xl-max: 1200px; // Large tablets and desktops
--------------------------------------------------------------------------------
/app/assets/sass/components/_buttons.scss:
--------------------------------------------------------------------------------
1 | .ant-btn-link {
2 | color: $primary;
3 | &:hover,
4 | &:active,
5 | &:focus {
6 | color: $primary;
7 | background: transparent;
8 | border-color: transparent;
9 | }
10 | }
11 | .ant-btn-primary {
12 | @include bg-border-color($primary, $primary);
13 | background-position: center;
14 | transition: background 0.8s;
15 | height: 38px;
16 | text-transform: capitalize;
17 | border-radius: 5px;
18 | &.active,
19 | &:hover,
20 | &:focus {
21 | @include bg-border-color($primary, $primary);
22 | }
23 | &:hover {
24 | background: $primary radial-gradient(circle, transparent 1%, $primary 1%)
25 | center/15000%;
26 | }
27 | &.active {
28 | background-color: rgba($primary, 0.5);
29 | background-size: 100%;
30 | transition: background 0s;
31 | }
32 | }
33 |
34 | .otp-wrapper {
35 | display: flex;
36 | justify-content: space-around;
37 | margin: 20px 0;
38 | .danger {
39 | border: 1px solid $danger;
40 | }
41 | input {
42 | transition: box-shadow 0.5s;
43 | &.shake-input {
44 | animation: shake 0.1s ease-in-out 0s 2;
45 | box-shadow: 2px 3px 6px rgba($danger, 0.15);
46 | }
47 | }
48 | }
49 |
50 | .ant-modal-footer,
51 | .ant-upload {
52 | .ant-btn {
53 | background-position: center;
54 | transition: all ease 0.3s;
55 | height: 38px;
56 | text-transform: capitalize;
57 | border-radius: 5px;
58 |
59 | &:hover,
60 | &:active,
61 | &:focus {
62 | color: $primary;
63 | background: #fff;
64 | border-color: $primary;
65 | }
66 | &.active {
67 | background-size: 100%;
68 | transition: background 0s;
69 | }
70 | }
71 | }
72 |
73 | @keyframes shake {
74 | 0% {
75 | margin-left: 0rem;
76 | }
77 | 25% {
78 | margin-left: 0.5rem;
79 | }
80 | 75% {
81 | margin-left: -0.5rem;
82 | }
83 | 100% {
84 | margin-left: 0rem;
85 | }
86 | }
87 |
88 | .ant-switch-checked {
89 | background-color: $primary;
90 | }
91 |
92 | .mr-2 {
93 | margin-right: 12px;
94 | }
95 |
--------------------------------------------------------------------------------
/app/assets/sass/components/_card.scss:
--------------------------------------------------------------------------------
1 | .ant-alert{
2 | &-error{
3 | @include bg-border-color($danger-bg, $danger-bg);
4 | margin-bottom: 10px;
5 | border-radius: 5px;
6 | .ant-alert-with-description{
7 | padding: 8px 15px;
8 | }
9 | .ant-alert-message{
10 | margin-bottom: 0;
11 | color: $danger !important;
12 | line-height: 19px;
13 | }
14 | .ant-alert-icon{
15 | font-size: 20px;
16 | margin-right: 10px;
17 | color: $danger-icon;
18 | }
19 |
20 | }
21 | &-success{
22 | @include bg-border-color($success-bg, $success-bg);
23 | margin-bottom: 10px;
24 | border-radius: 5px;
25 | .ant-alert-with-description{
26 | padding: 8px 15px;
27 | }
28 | .ant-alert-message{
29 | margin-bottom: 0;
30 | color: $success !important;
31 | line-height: 19px;
32 | }
33 | .ant-alert-icon{
34 | font-size: 20px;
35 | margin-right: 10px;
36 | color: $success-icon;
37 | }
38 |
39 | }
40 | &-description{
41 | margin-top: 5px;
42 | }
43 |
44 | }
45 |
46 |
47 | .ant-modal{
48 | &-content{
49 | border-radius: 5px;
50 | }
51 | &-header{
52 | border-radius: 5px 5px 0 0;
53 | }
54 | &-title{
55 | font-weight: 500;
56 | }
57 | }
--------------------------------------------------------------------------------
/app/assets/sass/components/_font.scss:
--------------------------------------------------------------------------------
1 | // @import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600;700&display=swap');
2 |
3 | @import url('https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500;600;700&display=swap');
--------------------------------------------------------------------------------
/app/assets/sass/components/_form.scss:
--------------------------------------------------------------------------------
1 | .ant-input-affix-wrapper, .ant-input{
2 | border-radius: 5px;
3 | min-height: 42px;
4 | padding:0px;
5 | overflow: hidden;
6 | &:focus{
7 | box-shadow: 0 0 0 2px rgba($primary, 0.2);
8 | border-color: $primary;
9 | }
10 | }
11 |
12 | .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled){
13 | &:hover{
14 | border-color: $primary;
15 | }
16 | }
17 |
18 | // #create-user{
19 | // @extend .ant-input-affix-wrapper;
20 | // }
21 |
22 | .ant-input-affix-wrapper > input.ant-input{
23 | padding-left: 30px;
24 | }
25 |
26 | .ant-form-item-has-success.ant-form-item-has-feedback .ant-form-item-children-icon{
27 | color: $primary;
28 | }
29 |
30 | .ant-input-suffix{
31 | position: absolute;
32 | right: 0;
33 | top: 50%;
34 | transform: translate(-50%, -50%);
35 | }
36 | .ant-input-prefix{
37 | position: absolute;
38 | left: 15px;
39 | top: 50%;
40 | z-index: 2;
41 | transform: translate(-50%, -50%);
42 | }
43 |
44 | .ant-checkbox-checked {
45 | .ant-checkbox-inner{
46 | @include bg-border-color($primary, $primary)
47 | }
48 | }
49 |
50 | .ant-form-item-label{
51 | text-align: left;
52 | }
53 |
54 | .form-ant-items{
55 | .ant-input{
56 | padding-left: 15px !important;
57 | }
58 | }
59 | .ant-input{
60 | &:placeholder-shown{
61 | text-transform: capitalize;
62 | }
63 | }
64 |
65 | .ant-form-item-label{
66 | text-transform: capitalize;
67 | }
68 |
69 | .ant-progress-steps-item-active{
70 | background: $primary;
71 | }
72 |
73 | .ant-progress-steps-outer{
74 | margin-bottom: 15px;
75 | }
--------------------------------------------------------------------------------
/app/assets/sass/components/_helpers.scss:
--------------------------------------------------------------------------------
1 | .mh-100{
2 | min-height: 100vh;
3 | }
4 |
5 | .vh-center{
6 | @include d-center;
7 | }
8 |
9 | .spin{
10 | i{
11 | background-color: $primary;
12 |
13 | }
14 |
15 | }
16 | .d-flex{
17 | display: flex;
18 | }
19 |
20 | .w-100{
21 | width: 100%;
22 | }
23 |
24 | .m-auto{
25 | margin: auto;
26 | }
27 |
28 |
29 | .ml-auto{
30 | margin-left: auto;
31 | }
32 |
33 | a{
34 | color: $primary;
35 | &:hover{
36 | color: $primary;
37 | text-decoration: underline;
38 | }
39 | }
--------------------------------------------------------------------------------
/app/assets/sass/components/_pagination.scss:
--------------------------------------------------------------------------------
1 | .ant-pagination-item-active{
2 | border-color: $primary;
3 | }
--------------------------------------------------------------------------------
/app/assets/sass/components/_spacing.scss:
--------------------------------------------------------------------------------
1 | h1,h2,h3,h4,h5,h6, p{
2 | margin-bottom: 0;
3 | }
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/assets/sass/components/_table.scss:
--------------------------------------------------------------------------------
1 | .truthy-table {
2 | background-color: $white;
3 | padding: 20px;
4 | border-radius: 8px;
5 | .ant-table-title{
6 | padding-left: 0;
7 | padding-top: 0;
8 | }
9 | .ant-table-thead{
10 |
11 | }
12 | }
13 |
14 |
15 | .ant-table-container{
16 | border: 1px solid $border-color !important;
17 | border-bottom: 0 !important;
18 | }
19 | .ant-table-thead > tr > th{
20 | background: $white;
21 | border-bottom: 0 !important;
22 | border-left: 1px solid $border-color !important;
23 | // color: $secondary-color;
24 | // font-weight: $bold;
25 | }
26 | .ant-table-thead > tr > th::before{
27 | display: none;
28 | }
29 | .ant-table-thead > tr > th:first-child{
30 | border-left: 0 !important;
31 | }
32 | .ant-table-tbody > tr > td{
33 | border-bottom: 1px solid $border-color !important;
34 | border-left: 1px solid $border-color !important;
35 | // color: $secondary-color;
36 | // text-align: right;
37 | }
38 | .ant-table-tbody > tr > td:first-child{
39 | border-left: 0 !important;
40 | }
41 | .ant-table-tbody > tr.ant-table-row:hover > td{
42 | background: transparent;
43 | }
44 |
45 | .ant-table-thead > tr > th, .ant-table-tbody > tr > td, .ant-table tfoot > tr > th, .ant-table tfoot > tr > td{
46 | padding: 10px 15px;
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/app/assets/sass/components/_typography.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/sass/components/_typography.scss
--------------------------------------------------------------------------------
/app/assets/sass/layout/_footer.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/sass/layout/_footer.scss
--------------------------------------------------------------------------------
/app/assets/sass/layout/_navbar.scss:
--------------------------------------------------------------------------------
1 | .ant-menu-vertical .ant-menu-item::after,
2 | .ant-menu-vertical-left .ant-menu-item::after,
3 | .ant-menu-vertical-right .ant-menu-item::after,
4 | .ant-menu-inline .ant-menu-item::after {
5 | border-right: 3px solid $primary;
6 | }
7 | .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
8 | background-color: #f1fffd;
9 | }
10 | .ant-menu-item-selected {
11 | color: $primary;
12 | }
13 |
14 | .ant-menu-light .ant-menu-item:hover,
15 | .ant-menu-light .ant-menu-item-active,
16 | .ant-menu-light .ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open,
17 | .ant-menu-light .ant-menu-submenu-active,
18 | .ant-menu-light .ant-menu-submenu-title:hover {
19 | color: $primary;
20 | }
21 |
22 | .ant-menu-item:active,
23 | .ant-menu-submenu-title:active {
24 | background-color: #f1fffd;
25 | }
26 |
27 | .home-page {
28 | .navbar{
29 | width: 100%;
30 | .logos{
31 | width: 250px;
32 | @include d-y-center;
33 | flex-grow: 1;
34 | font-weight: 900;
35 | color: #637381;
36 | font-size: 24px;
37 | text-shadow: #e0e0e0 1px 1px 0;
38 | }
39 | .header-main{
40 | @include d-y-center;
41 | list-style: none;
42 | margin: 0;
43 | li{
44 | margin-left: 50px;
45 | a{
46 | font-size: 16px;
47 | color: #637381;
48 | &.active{
49 | color: $primary;
50 |
51 | }
52 | &:hover{
53 | text-decoration: none;
54 | color: $primary;
55 | }
56 | }
57 | }
58 |
59 | }
60 | }
61 | .ant-layout-header {
62 | background: #ececec;
63 | // background: url('./../../../assets//images/navbar.png');
64 | }
65 | .ant-menu-horizontal{
66 | border: 0;
67 | }
68 | .ant-menu-item::after{
69 | display: none;
70 | }
71 | .ant-menu{
72 | background: #ececec;
73 | }
74 | }
75 |
76 |
77 |
--------------------------------------------------------------------------------
/app/assets/sass/main.scss:
--------------------------------------------------------------------------------
1 | @import 'variable';
2 |
3 | @import 'mixins/display';
4 | @import 'mixins/button';
5 | @import 'mixins/box';
6 | @import 'mixins/media';
7 |
8 | @import 'components/helpers';
9 | @import 'components/spacing';
10 | @import 'components/buttons';
11 | @import 'components/card';
12 | @import 'components/form';
13 | @import 'components/table';
14 | @import 'components/typography';
15 | @import 'components/pagination';
16 |
17 | @import 'layout/navbar';
18 | @import 'layout/footer';
19 |
20 | @import 'pages/home';
21 | @import 'pages/user-account';
22 |
--------------------------------------------------------------------------------
/app/assets/sass/mixins/_box.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/assets/sass/mixins/_box.scss
--------------------------------------------------------------------------------
/app/assets/sass/mixins/_button.scss:
--------------------------------------------------------------------------------
1 | @mixin transition($transition...) {
2 | -moz-transition: $transition;
3 | -o-transition: $transition;
4 | -webkit-transition: $transition;
5 | transition: $transition;
6 | }
7 |
8 |
9 | @mixin button-bg($bg) {
10 | background: $bg;
11 | @include transition(all 0.4s ease);
12 | &:hover {
13 | background:darken($bg,8%);
14 |
15 | border: none;
16 | }
17 | &:active {
18 | background:darken($bg,25%);
19 | }
20 | }
21 |
22 | @mixin bg-border-color($bg, $border){
23 | background-color: $bg;
24 | border-color: $border;
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/app/assets/sass/mixins/_display.scss:
--------------------------------------------------------------------------------
1 | @mixin d-center{
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | }
6 |
7 |
8 | @mixin d-y-center{
9 | display: flex;
10 | align-items: center;
11 | }
12 |
13 | @mixin d-x-center{
14 | display: flex;
15 | justify-content: center;
16 | }
--------------------------------------------------------------------------------
/app/assets/sass/mixins/_media.scss:
--------------------------------------------------------------------------------
1 |
2 | // Custom devices
3 | @mixin rwd($screen) {
4 | @media (max-width: $screen+'px') {
5 | @content;
6 | }
7 | }
8 |
9 | // Extra large devices
10 | @mixin xl {
11 | @media (max-width: #{$screen-xl-max}) {
12 | @content;
13 | }
14 | }
15 |
16 | // Large devices
17 | @mixin lg {
18 | @media (max-width: #{$screen-lg-max}) {
19 | @content;
20 | }
21 | }
22 |
23 | // Medium devices
24 | @mixin md {
25 | @media (max-width: #{$screen-md-max}) {
26 | @content;
27 | }
28 | }
29 |
30 | @mixin sm {
31 | @media (max-width: #{$screen-sm-max}) {
32 | @content;
33 | }
34 | }
--------------------------------------------------------------------------------
/app/assets/sass/pages/_user-account.scss:
--------------------------------------------------------------------------------
1 | .user-acccount, .change-password-form{
2 | .ant-input-affix-wrapper > input.ant-input{
3 | padding-left: 10px;
4 | }
5 | }
6 |
7 | .card{
8 | background-color: $white;
9 | padding: 20px;
10 | border-radius: 8px;
11 | }
12 |
13 | .profile{
14 | .user-image{
15 | border-radius: 8px;
16 | border: 1px solid #f0f0f0;
17 | background-color: #efefef;
18 | height: 100px;
19 | width: 100px;
20 | margin-bottom: 10px;
21 | img{
22 | height: 100%;
23 | width: 100%;
24 | object-fit: contain;
25 | }
26 | }
27 | .ant-tabs-ink-bar{
28 | background: $primary;
29 | }
30 | // .ant-tabs-nav-list{
31 | // padding-right: 20px;
32 | // .ant-tabs-tab-active{
33 | // background-color: rgba($primary, 0.14);
34 | // border-radius: 5px;
35 | // }
36 | // }
37 | }
38 |
39 | .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn{
40 | color: $primary;
41 | }
42 |
43 | .ant-tabs-tab:hover {
44 | color: $primary;
45 | }
46 |
47 |
48 | .profile-details-card{
49 | .ant-card-body{
50 | margin: 0;
51 |
52 | }
53 | .ant-page-header{
54 | padding: 0;
55 | margin-bottom: 15px;
56 | }
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/app/common/helpers/index.js:
--------------------------------------------------------------------------------
1 | const buildQueryString = (keywords, pageNumber, limit) => {
2 | const queryObj = {
3 | page: pageNumber > 0 ? pageNumber : 1,
4 | limit: limit > 0 ? limit : 10,
5 | };
6 | if (keywords && keywords.trim().length > 0) {
7 | queryObj.keywords = keywords;
8 | }
9 | return Object.keys(queryObj)
10 | .map((key) => `${key}=${queryObj[key]}`)
11 | .join('&');
12 | };
13 |
14 | export { buildQueryString };
15 |
--------------------------------------------------------------------------------
/app/common/hooks/passwordStrengthHook.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | const usePasswordStrengthCheckHook = (password) => {
4 | const [lowerCheck, setLowerCheck] = useState(0);
5 | const [upperCheck, setUpperCheck] = useState(0);
6 | const [numChecker, setNumChecker] = useState(0);
7 | const [charCheck, setCharChecker] = useState(0);
8 |
9 | useEffect(() => {
10 | if (/^(?=.*[a-z]).+$/.test(password)) {
11 | setLowerCheck(1);
12 | } else {
13 | setLowerCheck(0);
14 | }
15 |
16 | if (/^(?=.*[A-Z]).+$/.test(password)) {
17 | setUpperCheck(1);
18 | } else {
19 | setUpperCheck(0);
20 | }
21 |
22 | if (/\d/.test(password)) {
23 | setNumChecker(1);
24 | } else {
25 | setNumChecker(0);
26 | }
27 |
28 | if (/^(?=.*[-+_!@#$%^&*.,?]).+$/.test(password)) {
29 | setCharChecker(1);
30 | } else {
31 | setCharChecker(0);
32 | }
33 | }, [password]);
34 |
35 | return [lowerCheck, upperCheck, numChecker, charCheck];
36 | };
37 |
38 | export default usePasswordStrengthCheckHook;
39 |
--------------------------------------------------------------------------------
/app/common/hooks/usePrevious.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | /**
4 | * Tracks previous state of a value.
5 | *
6 | * @param value Props, state or any other calculated value.
7 | * @returns Value from the previous render of the enclosing component.
8 | *
9 | * @example
10 | * function Component() {
11 | * const [count, setCount] = useState(0);
12 | * const prevCount = usePrevious(count);
13 | * // ...
14 | * return `Now: ${count}, before: ${prevCount}`;
15 | * }
16 | */
17 | export default function usePrevious(value) {
18 | const ref = useRef();
19 | useEffect(() => {
20 | ref.current = value;
21 | });
22 | return ref.current;
23 | }
24 |
--------------------------------------------------------------------------------
/app/common/language/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import EnSvg from 'assets/i18n/en.svg';
3 | import NeSvg from 'assets/i18n/ne.svg';
4 |
5 | export const appLocales = [
6 | { label: 'en', flag:
},
7 | { label: 'ne', flag:
},
8 | ];
9 |
--------------------------------------------------------------------------------
/app/common/layout/Navbar.js:
--------------------------------------------------------------------------------
1 | import { Header } from 'antd/lib/layout/layout';
2 | import React from 'react';
3 | import { FormattedMessage } from 'react-intl';
4 | import { NavLink } from 'react-router-dom';
5 | import messages from 'containers/HomePage/messages';
6 | import LocaleToggle from 'containers/LocaleToggle';
7 |
8 | function Navbar() {
9 | return (
10 |
11 |
12 |
13 |
Truthy
14 |
15 | -
16 |
17 |
18 |
19 |
20 | -
21 |
22 |
23 |
24 |
25 | -
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | export default Navbar;
36 |
--------------------------------------------------------------------------------
/app/common/rules/index.js:
--------------------------------------------------------------------------------
1 | import { FormattedMessage } from 'react-intl';
2 | import commonMessage from 'common/messages';
3 | import React from 'react';
4 | import { checkIfStrongPassword } from 'common/validator';
5 |
6 | export const rules = {
7 | email: [
8 | {
9 | type: 'email',
10 | message: ,
11 | },
12 | {
13 | required: true,
14 | whitespace: true,
15 | message: ,
16 | },
17 | ],
18 | password: [
19 | {
20 | required: true,
21 | whitespace: true,
22 | message: ,
23 | },
24 | {
25 | validator: checkIfStrongPassword,
26 | },
27 | ],
28 | username: [
29 | {
30 | required: true,
31 | whitespace: true,
32 | message: ,
33 | },
34 | ],
35 | name: [
36 | {
37 | required: true,
38 | whitespace: true,
39 | message: ,
40 | },
41 | ],
42 | };
43 |
--------------------------------------------------------------------------------
/app/common/saga/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { put } from 'redux-saga/effects';
3 | import { FormattedMessage } from 'react-intl';
4 | import { enqueueAlertAction } from 'containers/AlertMessage/actions';
5 | import { enqueueSnackMessageAction } from 'containers/SnackMessage/actions';
6 |
7 | /**
8 | * show formatted error alert
9 | * @param type
10 | * @param message
11 | * @returns {IterableIterator<*>}
12 | */
13 | export function* showFormattedAlert(type, message) {
14 | return yield put(
15 | enqueueAlertAction({
16 | message: ,
17 | type,
18 | }),
19 | );
20 | }
21 |
22 | /**
23 | * show formatted snack message
24 | * @param payload: {type: string, message: string, translate: boolean, id: string}
25 | * @returns {IterableIterator<*>}
26 | */
27 | export function* showMessage(payload) {
28 | return yield put(enqueueSnackMessageAction(payload));
29 | }
30 |
31 | /**
32 | * show error alert
33 | * @param type
34 | * @param message
35 | * @returns {IterableIterator<*>}
36 | */
37 | export function* showAlert(type, message) {
38 | return yield put(
39 | enqueueAlertAction({
40 | message,
41 | type,
42 | }),
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/app/common/validator/index.js:
--------------------------------------------------------------------------------
1 | import messages from 'common/messages';
2 | import { FormattedMessage } from 'react-intl';
3 | import React from 'react';
4 |
5 | export const checkIfStrongPassword = (rule, value, callback) => {
6 | const re =
7 | /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])(?!.*\s).{6,20}$/;
8 | if (re.test(value)) {
9 | callback();
10 | } else {
11 | callback();
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/app/components/DraftEditor/ToolWrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const ToolWrapper = styled.div`
4 | border: 1px solid #f1f1f1;
5 | padding: 5px;
6 | min-width: 25px;
7 | height: 20px;
8 | border-radius: 2px;
9 | margin: 0 4px;
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 | cursor: pointer;
14 | background: white;
15 | `;
16 |
17 | export default ToolWrapper;
18 |
--------------------------------------------------------------------------------
/app/components/ErrorFallback/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import messages from 'components/ErrorFallback/messages';
4 | import { injectIntl } from 'react-intl';
5 |
6 | const ErrorFallback = (props) => {
7 | const { error, resetErrorBoundary, intl } = props;
8 | return (
9 |
10 |
{intl.formatMessage(messages.somethingWrong)}
11 |
{error.message}
12 |
15 |
16 | );
17 | };
18 |
19 | ErrorFallback.propTypes = {
20 | error: PropTypes.object,
21 | resetErrorBoundary: PropTypes.func,
22 | intl: PropTypes.object.isRequired,
23 | };
24 |
25 | export default injectIntl(ErrorFallback);
26 |
--------------------------------------------------------------------------------
/app/components/ErrorFallback/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * ErrorFallback Messages
3 | *
4 | * This contains all the text for the ErrorFallback component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export const scope = 'components.ErrorFallback';
9 |
10 | export default defineMessages({
11 | somethingWrong: {
12 | id: `${scope}.somethingWrong`,
13 | defaultMessage: 'Something went wrong:',
14 | },
15 | tryAgain: {
16 | id: `${scope}.tryAgain`,
17 | defaultMessage: 'Try again',
18 | },
19 | });
20 |
--------------------------------------------------------------------------------
/app/components/Footer/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Footer Messages
3 | *
4 | * This contains all the text for the Footer component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export const scope = 'components.Footer';
9 |
10 | export default defineMessages({
11 | licenseMessage: {
12 | id: `${scope}.license.message`,
13 | defaultMessage: 'This project is licensed under the MIT license.',
14 | },
15 | appName: {
16 | id: `${scope}.appName`,
17 | defaultMessage: 'Truthy',
18 | },
19 | copyright: {
20 | id: `${scope}.copyright`,
21 | defaultMessage: 'Copyright',
22 | },
23 | settings: {
24 | id: `${scope}.settings`,
25 | defaultMessage: 'Settings',
26 | },
27 | download: {
28 | id: `${scope}.download`,
29 | defaultMessage: 'Download',
30 | },
31 | star: {
32 | id: `${scope}.star`,
33 | defaultMessage: 'Star',
34 | },
35 | language: {
36 | id: `${scope}.language`,
37 | defaultMessage: 'Language',
38 | },
39 | });
40 |
--------------------------------------------------------------------------------
/app/components/FormButtonWrapper/index.js:
--------------------------------------------------------------------------------
1 | import { injectIntl } from 'react-intl';
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 | import { Button, Form } from 'antd';
5 |
6 | /**
7 | *
8 | * FormButtonWrapper
9 | *
10 | */
11 |
12 | const FormButtonWrapper = (props) => {
13 | const {
14 | label,
15 | icon,
16 | intl,
17 | variant,
18 | show = true,
19 | className,
20 | disabled,
21 | form,
22 | } = props;
23 | return (
24 | <>
25 | {show ? (
26 |
27 | {() => (
28 |
41 | )}
42 |
43 | ) : (
44 | ''
45 | )}
46 | >
47 | );
48 | };
49 |
50 | FormButtonWrapper.propTypes = {
51 | form: PropTypes.object,
52 | icon: PropTypes.object,
53 | intl: PropTypes.object,
54 | disabled: PropTypes.bool,
55 | show: PropTypes.bool,
56 | variant: PropTypes.string.isRequired,
57 | className: PropTypes.string,
58 | label: PropTypes.object.isRequired,
59 | };
60 |
61 | export default injectIntl(FormButtonWrapper);
62 |
--------------------------------------------------------------------------------
/app/components/FormCheckboxWrapper/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { injectIntl } from 'react-intl';
4 | import { Checkbox, Form } from 'antd';
5 |
6 | const FormCheckBoxWrapper = (props) => {
7 | const {
8 | layout = {},
9 | label,
10 | rules = [],
11 | intl,
12 | children = null,
13 | name,
14 | id,
15 | classname = '',
16 | value,
17 | disabled = false,
18 | changeHandler = () => {},
19 | } = props;
20 |
21 | return (
22 |
29 |
37 | {children}
38 |
39 |
40 | );
41 | };
42 |
43 | FormCheckBoxWrapper.propTypes = {
44 | children: PropTypes.node,
45 | rules: PropTypes.array,
46 | disabled: PropTypes.bool,
47 | layout: PropTypes.object,
48 | label: PropTypes.object,
49 | name: PropTypes.string,
50 | id: PropTypes.string,
51 | classname: PropTypes.string,
52 | changeHandler: PropTypes.func,
53 | value: PropTypes.bool,
54 | placeholder: PropTypes.object,
55 | intl: PropTypes.object,
56 | };
57 |
58 | export default injectIntl(FormCheckBoxWrapper);
59 |
--------------------------------------------------------------------------------
/app/components/FormWrapper/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Form, Row } from 'antd';
4 |
5 | const FormWrapper = ({
6 | layout,
7 | formInstance,
8 | children,
9 | device,
10 | name,
11 | classname,
12 | values,
13 | responsive = false,
14 | ...props
15 | }) => (
16 |
27 | );
28 |
29 | FormWrapper.propTypes = {
30 | formInstance: PropTypes.object,
31 | children: PropTypes.node,
32 | device: PropTypes.string,
33 | classname: PropTypes.string,
34 | name: PropTypes.string.isRequired,
35 | values: PropTypes.object,
36 | layout: PropTypes.object,
37 | responsive: PropTypes.bool,
38 | };
39 |
40 | export default FormWrapper;
41 |
--------------------------------------------------------------------------------
/app/components/Header/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Header Messages
3 | *
4 | * This contains all the text for the Header component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export const scope = 'components.Header';
9 |
10 | export default defineMessages({
11 | profile: {
12 | id: `${scope}.profile`,
13 | defaultMessage: 'My Profile',
14 | },
15 | setting: {
16 | id: `${scope}.setting`,
17 | defaultMessage: 'Settings',
18 | },
19 | logout: {
20 | id: `${scope}.logout`,
21 | defaultMessage: 'Logout',
22 | },
23 |
24 | login: {
25 | id: `${scope}.login`,
26 | defaultMessage: 'Login',
27 | },
28 | });
29 |
--------------------------------------------------------------------------------
/app/components/Img/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Img.js
4 | *
5 | * Renders an image, enforcing the usage of the alt="" tag
6 | */
7 |
8 | import React from 'react';
9 | import PropTypes from 'prop-types';
10 |
11 | function Img(props) {
12 | return
;
13 | }
14 |
15 | // We require the use of src and alt, only enforced by react in dev mode
16 | Img.propTypes = {
17 | src: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
18 | alt: PropTypes.string.isRequired,
19 | className: PropTypes.string,
20 | };
21 |
22 | export default Img;
23 |
--------------------------------------------------------------------------------
/app/components/Layout/Menu/list.js:
--------------------------------------------------------------------------------
1 | import { FormattedMessage } from 'react-intl';
2 | import messages from 'routes/messages';
3 | import React from 'react';
4 | import {
5 | DashboardOutlined,
6 | UserOutlined,
7 | LockOutlined,
8 | UsergroupAddOutlined,
9 | MailOutlined,
10 | } from '@ant-design/icons';
11 |
12 | export const menuList = [
13 | {
14 | name: ,
15 | icon: ,
16 | key: 'dashboard-page',
17 | path: '/dashboard',
18 | method: 'get',
19 | resource: 'dashboard',
20 | defaultPermission: true,
21 | },
22 | {
23 | name: ,
24 | icon: ,
25 | key: 'user-page',
26 | path: '/users',
27 | method: 'get',
28 | resource: 'user',
29 | defaultPermission: false,
30 | },
31 | {
32 | name: ,
33 | icon: ,
34 | key: 'role-page',
35 | path: '/roles',
36 | method: 'get',
37 | resource: 'role',
38 | defaultPermission: false,
39 | },
40 | {
41 | name: ,
42 | icon: ,
43 | key: 'permission-page',
44 | path: '/permissions',
45 | method: 'get',
46 | resource: 'permission',
47 | defaultPermission: false,
48 | },
49 | {
50 | name: ,
51 | icon: ,
52 | key: 'email-template',
53 | path: '/email-templates',
54 | method: 'get',
55 | resource: 'emailTemplates',
56 | defaultPermission: false,
57 | },
58 | ];
59 |
--------------------------------------------------------------------------------
/app/components/LoadingIndicator/Circle.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled, { keyframes } from 'styled-components';
4 |
5 | const circleFadeDelay = keyframes`
6 | 0%,
7 | 39%,
8 | 100% {
9 | opacity: 0;
10 | }
11 | 40% {
12 | opacity: 1;
13 | }
14 | `;
15 |
16 | const Circle = (props) => {
17 | const CirclePrimitive = styled.div`
18 | width: 100%;
19 | height: 100%;
20 | position: absolute;
21 | left: 0;
22 | top: 0;
23 | ${props.rotate &&
24 | `
25 | -webkit-transform: rotate(${props.rotate}deg);
26 | -ms-transform: rotate(${props.rotate}deg);
27 | transform: rotate(${props.rotate}deg);
28 | `} &:before {
29 | content: '';
30 | display: block;
31 | margin: 0 auto;
32 | width: 15%;
33 | height: 15%;
34 | background-color: #999;
35 | border-radius: 100%;
36 | animation: ${circleFadeDelay} 1.2s infinite ease-in-out both;
37 | ${props.delay &&
38 | `
39 | -webkit-animation-delay: ${props.delay}s;
40 | animation-delay: ${props.delay}s;
41 | `};
42 | }
43 | `;
44 | return ;
45 | };
46 |
47 | Circle.propTypes = {
48 | delay: PropTypes.number,
49 | rotate: PropTypes.number,
50 | };
51 |
52 | export default Circle;
53 |
--------------------------------------------------------------------------------
/app/components/LoadingIndicator/Wrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Wrapper = styled.div`
4 | margin: 2em auto;
5 | width: 40px;
6 | height: 40px;
7 | position: relative;
8 | `;
9 |
10 | export default Wrapper;
11 |
--------------------------------------------------------------------------------
/app/components/LoadingIndicator/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Spin } from 'antd';
4 | import Wrapper from 'components/LoadingIndicator/Wrapper';
5 |
6 | const LoadingIndicator = () => (
7 |
8 |
9 |
10 | );
11 |
12 | export default LoadingIndicator;
13 |
--------------------------------------------------------------------------------
/app/components/OtpModal/messages.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/components/OtpModal/messages.js
--------------------------------------------------------------------------------
/app/components/OtpModal/otpInput.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useLayoutEffect, useRef } from 'react';
2 | import usePrevious from 'common/hooks/usePrevious';
3 | import PropTypes from 'prop-types';
4 |
5 | const OTPInputComponent = (props) => {
6 | const { focus, autoFocus, ...rest } = props;
7 | const inputRef = useRef(null);
8 | const prevFocus = usePrevious(!!focus);
9 | useLayoutEffect(() => {
10 | if (inputRef.current) {
11 | if (focus && autoFocus) {
12 | inputRef.current.focus();
13 | }
14 | if (focus && autoFocus && focus !== prevFocus) {
15 | inputRef.current.focus();
16 | inputRef.current.select();
17 | }
18 | }
19 | }, [autoFocus, focus, prevFocus]);
20 |
21 | return ;
22 | };
23 |
24 | OTPInputComponent.propTypes = {
25 | focus: PropTypes.bool,
26 | autoFocus: PropTypes.bool,
27 | };
28 |
29 | const OTPInput = memo(OTPInputComponent);
30 |
31 | export default OTPInput;
32 |
--------------------------------------------------------------------------------
/app/components/PageHeaderWrapper/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { useIntl } from 'react-intl';
4 | import { PageHeader } from 'antd';
5 | import history from 'utils/history';
6 |
7 | const PageHeaderWrapper = (props) => {
8 | const intl = useIntl();
9 | const { ghost = false, children, title, subtitle, extra, avatar } = props;
10 |
11 | return (
12 | history.back()}
15 | title={intl.formatMessage(title)}
16 | subTitle={subtitle ? intl.formatMessage(subtitle) : null}
17 | extra={extra}
18 | avatar={avatar}
19 | // extra={[]}
20 | >
21 | {children}
22 |
23 | );
24 | };
25 |
26 | PageHeaderWrapper.propTypes = {
27 | ghost: PropTypes.bool,
28 | title: PropTypes.object.isRequired,
29 | subtitle: PropTypes.object,
30 | extra: PropTypes.array,
31 | avatar: PropTypes.object,
32 | children: PropTypes.node,
33 | };
34 |
35 | export default PageHeaderWrapper;
36 |
--------------------------------------------------------------------------------
/app/components/SearchInput/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import messages from 'components/SearchInput/messages';
4 | import { injectIntl } from 'react-intl';
5 | import { Input } from 'antd';
6 | const { Search } = Input;
7 |
8 | const SearchInput = (props) => {
9 | const { onSearch, intl, isLoading } = props;
10 |
11 | return (
12 |
19 | );
20 | };
21 |
22 | SearchInput.propTypes = {
23 | isLoading: PropTypes.bool,
24 | onSearch: PropTypes.func.isRequired,
25 | intl: PropTypes.object.isRequired,
26 | };
27 |
28 | export default injectIntl(SearchInput);
29 |
--------------------------------------------------------------------------------
/app/components/SearchInput/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * SearchInput
3 | *
4 | * This contains all the text for the SearchInput component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export const scope = 'components.SearchInput';
9 |
10 | export default defineMessages({
11 | searchPlaceHolder: {
12 | id: `${scope}.searchPlaceHolder`,
13 | defaultMessage: 'Search',
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/app/components/SelectInputWrapper/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { useIntl } from 'react-intl';
4 | import { Form, Select } from 'antd';
5 |
6 | const SelectInputWrapper = (props) => {
7 | const intl = useIntl();
8 | const {
9 | children,
10 | rules = [],
11 | label,
12 | name,
13 | id,
14 | classname = '',
15 | value,
16 | disabled = false,
17 | required = true,
18 | changeHandler = () => {},
19 | error,
20 | } = props;
21 |
22 | return (
23 |
31 |
40 |
41 | );
42 | };
43 |
44 | SelectInputWrapper.propTypes = {
45 | name: PropTypes.string.isRequired,
46 | value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
47 | disabled: PropTypes.bool,
48 | required: PropTypes.bool,
49 | error: PropTypes.string,
50 | children: PropTypes.node,
51 | rules: PropTypes.array,
52 | label: PropTypes.object.isRequired,
53 | id: PropTypes.string,
54 | classname: PropTypes.string,
55 | changeHandler: PropTypes.func,
56 | };
57 |
58 | export default SelectInputWrapper;
59 |
--------------------------------------------------------------------------------
/app/components/SelectInputWrapper/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Select input Messages
3 | *
4 | * This contains all the text for the LoginForm component.
5 | */
6 |
7 | import { defineMessages } from 'react-intl';
8 |
9 | export const scope = 'components.selectInput';
10 |
11 | export default defineMessages({
12 | select: {
13 | id: `${scope}.select`,
14 | defaultMessage: 'Select',
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/app/components/Toggle/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * LocaleToggle
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import PropTypes from 'prop-types';
9 | import { Select } from 'antd';
10 | import { injectIntl } from 'react-intl';
11 |
12 | const { Option } = Select;
13 |
14 | const Toggle = (props) => {
15 | const { values, value, intl, onToggle, messages } = props;
16 | let content = ;
17 | if (values) {
18 | content = values.map((optVal) => (
19 |
22 | ));
23 | }
24 |
25 | return (
26 |
29 | );
30 | };
31 |
32 | Toggle.propTypes = {
33 | onToggle: PropTypes.func,
34 | intl: PropTypes.object,
35 | values: PropTypes.array,
36 | value: PropTypes.string,
37 | messages: PropTypes.object,
38 | };
39 |
40 | export default injectIntl(Toggle);
41 |
--------------------------------------------------------------------------------
/app/components/ToggleOption/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * ToggleOption
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import PropTypes from 'prop-types';
9 | import { injectIntl } from 'react-intl';
10 | import { Select } from 'antd';
11 | const { Option } = Select;
12 |
13 | const ToggleOption = ({ value, message, intl }) => (
14 |
15 | );
16 |
17 | ToggleOption.propTypes = {
18 | value: PropTypes.string.isRequired,
19 | message: PropTypes.object,
20 | intl: PropTypes.object,
21 | };
22 |
23 | export default injectIntl(ToggleOption);
24 |
--------------------------------------------------------------------------------
/app/components/ToolTipButtonWrapper/index.js:
--------------------------------------------------------------------------------
1 | import { Button, Tooltip } from 'antd';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import { useIntl } from 'react-intl';
5 |
6 | const ToolTipButtonWrapper = ({
7 | title,
8 | clickEvent,
9 | children,
10 | className = '',
11 | danger = false,
12 | color = '#39af9f',
13 | }) => {
14 | const intl = useIntl();
15 | return (
16 |
17 |
25 |
26 | );
27 | };
28 |
29 | ToolTipButtonWrapper.propTypes = {
30 | title: PropTypes.object,
31 | danger: PropTypes.bool,
32 | clickEvent: PropTypes.func,
33 | children: PropTypes.node,
34 | className: PropTypes.string,
35 | color: PropTypes.string,
36 | };
37 |
38 | export default ToolTipButtonWrapper;
39 |
--------------------------------------------------------------------------------
/app/containers/AlertMessage/actions.js:
--------------------------------------------------------------------------------
1 | import {
2 | ALERT_UNMOUNT,
3 | AUTO_DISMISS_ALERT,
4 | CLEAR_ALERT,
5 | SHOW_ALERT_MESSAGE,
6 | } from 'containers/AlertMessage/constants';
7 |
8 | /**
9 | * Enqueue alert on the screen
10 | * @param {object} alert payload
11 | *
12 | */
13 |
14 | export function enqueueAlertAction(alert) {
15 | return {
16 | type: SHOW_ALERT_MESSAGE,
17 | alert,
18 | };
19 | }
20 |
21 | /**
22 | * Remove alert from the screen
23 | *
24 | * @return {object} An action object with a type of CLEAR_ALERT
25 | */
26 | export function clearAlertAction() {
27 | return {
28 | type: CLEAR_ALERT,
29 | };
30 | }
31 |
32 | export function autoDismissAlertAction() {
33 | return {
34 | type: AUTO_DISMISS_ALERT,
35 | };
36 | }
37 |
38 | export function alertUnmountAction() {
39 | return {
40 | type: ALERT_UNMOUNT,
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/app/containers/AlertMessage/constants.js:
--------------------------------------------------------------------------------
1 | export const CLEAR_ALERT = 'containers/AlertMessage/CLEAR_ALERT';
2 | export const SHOW_ALERT_MESSAGE = 'containers/AlertMessage/SHOW_ALERT_MESSAGE';
3 | export const AUTO_DISMISS_ALERT = 'containers/AlertMessage/AUTO_DISMISS_ALERT';
4 | export const ALERT_UNMOUNT = 'containers/AlertMessage/ALERT_UNMOUNT';
5 |
--------------------------------------------------------------------------------
/app/containers/AlertMessage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Alert Message
4 | *
5 | */
6 |
7 | import React, { useEffect } from 'react';
8 | import { useDispatch, useSelector } from 'react-redux';
9 | import { useInjectReducer } from 'utils/injectReducer';
10 | import reducer from 'containers/AlertMessage/reducer';
11 | import { useInjectSaga } from 'utils/injectSaga';
12 | import saga from 'containers/AlertMessage/saga';
13 | import commonMessage from 'common/messages';
14 | import { FormattedMessage } from 'react-intl';
15 | import { createStructuredSelector } from 'reselect';
16 | import {
17 | makeAlertMessageSelector,
18 | makeAlertMessageTypeSelector,
19 | makeAlertShowSelector,
20 | makeIdSelector,
21 | } from 'containers/AlertMessage/selectors';
22 | import { Alert } from 'antd';
23 | import { autoDismissAlertAction } from 'containers/AlertMessage/actions';
24 |
25 | const key = 'alertMessage';
26 |
27 | const stateSelector = createStructuredSelector({
28 | message: makeAlertMessageSelector(),
29 | show: makeAlertShowSelector(),
30 | type: makeAlertMessageTypeSelector(),
31 | id: makeIdSelector(),
32 | });
33 |
34 | export default function AlertMessage() {
35 | const dispatch = useDispatch();
36 |
37 | const autoDismiss = () => dispatch(autoDismissAlertAction());
38 |
39 | useInjectReducer({ key, reducer });
40 |
41 | useInjectSaga({ key, saga });
42 |
43 | const { message, type, id, show } = useSelector(stateSelector);
44 |
45 | useEffect(() => {
46 | if (message !== '') {
47 | autoDismiss();
48 | }
49 | }, [message]);
50 |
51 | if (!show) {
52 | return <>>;
53 | }
54 |
55 | return (
56 | : ''}
59 | description={message}
60 | type={type}
61 | showIcon
62 | closable
63 | />
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/app/containers/AlertMessage/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Alert Message reducer
4 | *
5 | */
6 | import produce, { setAutoFreeze } from 'immer';
7 | import uuid from 'react-uuid';
8 | import {
9 | SHOW_ALERT_MESSAGE,
10 | CLEAR_ALERT,
11 | ALERT_UNMOUNT,
12 | } from 'containers/AlertMessage/constants';
13 |
14 | export const initialState = {
15 | show: false,
16 | message: '',
17 | type: '',
18 | id: '',
19 | };
20 |
21 | setAutoFreeze(false);
22 | /* eslint-disable default-case, no-param-reassign */
23 | const alertReducer = produce((draft, action) => {
24 | switch (action.type) {
25 | case SHOW_ALERT_MESSAGE:
26 | draft.show = true;
27 | draft.message = action.alert.message;
28 | draft.type = action.alert.type;
29 | draft.id = uuid();
30 | break;
31 | case ALERT_UNMOUNT:
32 | case CLEAR_ALERT:
33 | draft.show = false;
34 | draft.message = '';
35 | draft.type = '';
36 | draft.id = '';
37 | break;
38 | default:
39 | }
40 | }, initialState);
41 |
42 | export default alertReducer;
43 |
--------------------------------------------------------------------------------
/app/containers/AlertMessage/saga.js:
--------------------------------------------------------------------------------
1 | import { delay, put, takeLatest } from 'redux-saga/effects';
2 | import { AUTO_DISMISS_ALERT } from 'containers/AlertMessage/constants';
3 | import { clearAlertAction } from 'containers/AlertMessage/actions';
4 |
5 | export function* dismissAlertAction() {
6 | yield delay(5000);
7 | yield put(clearAlertAction());
8 | }
9 |
10 | export default function* snackBarSaga() {
11 | yield takeLatest(AUTO_DISMISS_ALERT, dismissAlertAction);
12 | }
13 |
--------------------------------------------------------------------------------
/app/containers/AlertMessage/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import { initialState } from 'containers/AlertMessage/reducer';
3 |
4 | const selectAlertMessage = (state) => state.alertMessage || initialState;
5 |
6 | const makeAlertMessageSelector = () =>
7 | createSelector(selectAlertMessage, (substate) => substate.message);
8 |
9 | const makeAlertMessageTypeSelector = () =>
10 | createSelector(selectAlertMessage, (substate) => substate.type);
11 |
12 | const makeAlertShowSelector = () =>
13 | createSelector(selectAlertMessage, (substate) => substate.show);
14 |
15 | const makeIdSelector = () =>
16 | createSelector(selectAlertMessage, (substate) => substate.id);
17 |
18 | export {
19 | makeIdSelector,
20 | makeAlertShowSelector,
21 | makeAlertMessageSelector,
22 | makeAlertMessageTypeSelector,
23 | };
24 |
--------------------------------------------------------------------------------
/app/containers/App/constants.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @type {string}
4 | */
5 | export const LOGGED_IN = 'containers/App/LOGGED_IN';
6 | export const GET_PROFILE_REQUEST = 'containers/App/GET_PROFILE_REQUEST';
7 | export const GET_PROFILE_SUCCESS = 'containers/App/GET_PROFILE_SUCCESS';
8 | export const GET_PROFILE_ERROR = 'containers/App/GET_PROFILE_ERROR';
9 | export const IS_LOGGED = 'containers/App/IS_LOGGED';
10 | export const IS_LOGGED_SUCCESS = 'containers/App/IS_LOGGED_SUCCESS';
11 | export const IS_LOGGED_ERROR = 'containers/App/IS_LOGGED_ERROR';
12 | export const CHANGE_FIELD = 'containers/App/CHANGE_FIELD';
13 | export const QUERY_NOTIFICATIONS = 'containers/App/QUERY_NOTIFICATIONS';
14 | export const LOGOUT = 'containers/App/LOGOUT';
15 | export const LOGOUT_SUCCESS = 'containers/App/LOGOUT_SUCCESS';
16 | export const LOGOUT_ERROR = 'containers/App/LOGOUT_ERROR';
17 | export const HIDE_HEADER = 'containers/App/HIDE_HEADER';
18 | export const ASYNC_START = 'containers/App/ASYNC_START';
19 | export const ASYNC_END = 'containers/App/ASYNC_END';
20 | export const REFRESH_TOKEN = 'containers/App/REFRESH_TOKEN';
21 | export const TOGGLE_COLLAPSE = 'containers/App/TOGGLE_COLLAPSE';
22 | export const CHANGE_DEVICE = 'containers/App/CHANGE_DEVICE';
23 | export const OTP_VERIFIED = 'containers/App/OTP_VERIFIED';
24 | export const OTP_UNVERIFIED = 'containers/App/OTP_UNVERIFIED';
25 | export const CHANGE_OTP_VALUE = 'containers/App/CHANGE_OTP_VALUE';
26 | export const AUTHENTICATE_OTP = 'containers/App/AUTHENTICATE_OTP';
27 | export const OTP_ERROR = 'containers/App/OTP_ERROR';
28 |
--------------------------------------------------------------------------------
/app/containers/App/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * App.js
4 | *
5 | * This component is the skeleton around the actual pages, and should only
6 | * contain code that should be seen on all pages. (e.g. navigation bar)
7 | *
8 | */
9 |
10 | import React, { useEffect } from 'react';
11 | import { useDispatch } from 'react-redux';
12 | import { BrowserRouter } from 'react-router-dom';
13 | import { Helmet } from 'react-helmet';
14 | import RenderRouter from 'routes';
15 | import { getProfileAction } from 'containers/App/actions';
16 | import { useInjectReducer } from 'utils/injectReducer';
17 | import reducer from 'containers/App/reducer';
18 | import { useInjectSaga } from 'utils/injectSaga';
19 | import saga from 'containers/App/saga';
20 | import { Layout } from 'antd';
21 | import 'containers/App/index.less';
22 | import GlobalStyle from 'global-styles';
23 | import SnackMessage from 'containers/SnackMessage';
24 |
25 | const key = 'global';
26 | export default function App() {
27 | const dispatch = useDispatch();
28 | const getLoggedInUserProfile = () => dispatch(getProfileAction());
29 | useInjectReducer({ key, reducer });
30 | useInjectSaga({ key, saga });
31 |
32 | useEffect(() => {
33 | getLoggedInUserProfile();
34 | }, []);
35 | return (
36 | <>
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | >
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/app/containers/App/index.less:
--------------------------------------------------------------------------------
1 | .content-page {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | background: #f0f2f5;
6 | &-form {
7 | width: 300px;
8 | padding: 50px 40px 10px;
9 | border-radius: 10px;
10 | h2 {
11 | text-align: center;
12 | }
13 | &_button {
14 | width: 100%;
15 | }
16 | }
17 | }
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/containers/App/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import { initialState } from 'containers/App/reducer';
3 |
4 | const selectGlobal = (state) => state.global || initialState;
5 |
6 | const selectRouter = (state) => state.router;
7 |
8 | const makeLoggedInUserSelector = () =>
9 | createSelector(selectGlobal, (globalState) => globalState.user);
10 |
11 | const makeIsLoggedSelector = () =>
12 | createSelector(selectGlobal, (globalState) => globalState.isLogged);
13 |
14 | const makeSelectLocation = () =>
15 | createSelector(selectRouter, (routerState) => routerState.location);
16 |
17 | const makeHideHeaderSelector = () =>
18 | createSelector(selectGlobal, (globalState) => globalState.hideHeader);
19 |
20 | const makeIsLoadingSelector = () =>
21 | createSelector(selectGlobal, (globalState) => globalState.isLoading);
22 |
23 | const makeDeviceSelector = () =>
24 | createSelector(selectGlobal, (globalState) => globalState.device);
25 |
26 | const makeCollapsedSelector = () =>
27 | createSelector(selectGlobal, (globalState) => globalState.collapsed);
28 |
29 | const makeOtpVerificationSelector = () =>
30 | createSelector(selectGlobal, (globalState) => globalState.otpVerified);
31 |
32 | const makeOtpValueSelector = () =>
33 | createSelector(selectGlobal, (globalState) => globalState.otp);
34 |
35 | const makeOtpErrorSelector = () =>
36 | createSelector(selectGlobal, (globalState) => globalState.otpError);
37 |
38 | export {
39 | makeOtpErrorSelector,
40 | makeOtpValueSelector,
41 | makeOtpVerificationSelector,
42 | makeCollapsedSelector,
43 | makeDeviceSelector,
44 | makeIsLoadingSelector,
45 | makeHideHeaderSelector,
46 | makeLoggedInUserSelector,
47 | makeIsLoggedSelector,
48 | makeSelectLocation,
49 | };
50 |
--------------------------------------------------------------------------------
/app/containers/Dashboard/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for Dashboard
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import loadable from 'utils/loadable';
9 | import LoadingIndicator from 'components/LoadingIndicator';
10 |
11 | export default loadable(() => import('./index'), {
12 | fallback: ,
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/Dashboard/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Dashboard actions
4 | *
5 | */
6 |
7 | import {
8 | QUERY_USER_STATS,
9 | SET_USER_STATS,
10 | ASYNC_START,
11 | ASYNC_END,
12 | SET_DEVICE_TYPE,
13 | QUERY_DEVICE_STATS,
14 | SET_DEVICE_CHART,
15 | } from 'containers/Dashboard/constants';
16 |
17 | export function queryUserStatsAction() {
18 | return {
19 | type: QUERY_USER_STATS,
20 | };
21 | }
22 |
23 | export function setUserStatsAction(stats) {
24 | return {
25 | type: SET_USER_STATS,
26 | stats,
27 | };
28 | }
29 |
30 | export function asyncStartAction() {
31 | return {
32 | type: ASYNC_START,
33 | };
34 | }
35 |
36 | export function asyncEndAction() {
37 | return {
38 | type: ASYNC_END,
39 | };
40 | }
41 |
42 | export function setDeviceTypeAction(deviceType) {
43 | return {
44 | type: SET_DEVICE_TYPE,
45 | deviceType,
46 | };
47 | }
48 |
49 | export function setDeviceChartAction(deviceChart) {
50 | return {
51 | type: SET_DEVICE_CHART,
52 | deviceChart,
53 | };
54 | }
55 |
56 | export function queryDeviceAction() {
57 | return {
58 | type: QUERY_DEVICE_STATS,
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/app/containers/Dashboard/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Dashboard constants
4 | *
5 | */
6 | export const UMOUNT_DASHBOARD = 'app/Dashboard/UMOUNT_DASHBOARD';
7 | export const QUERY_USER_STATS = 'app/Dashboard/QUERY_USER_STATS';
8 | export const SET_USER_STATS = 'app/Dashboard/SET_USER_STATS';
9 | export const ASYNC_START = 'containers/Dashboard/ASYNC_START';
10 | export const ASYNC_END = 'containers/Dashboard/ASYNC_END';
11 | export const BROWSER = 'BROWSER';
12 | export const OS = 'OS';
13 | export const GET_DEVICE_STATS = 'containers/Dashboard/GET_DEVICE_STATS';
14 | export const SET_DEVICE_TYPE = 'containers/Dashboard/SET_DEVICE_TYPE';
15 | export const QUERY_DEVICE_STATS = 'containers/Dashboard/QUERY_DEVICE_STATS';
16 | export const SET_DEVICE_CHART = 'containers/Dashboard/SET_DEVICE_CHART';
17 |
--------------------------------------------------------------------------------
/app/containers/Dashboard/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Dashboard Messages
3 | *
4 | * This contains all the text for the LoginPage container.
5 | */
6 |
7 | import { defineMessages } from 'react-intl';
8 |
9 | export const scope = 'containers.Dashboard';
10 |
11 | export default defineMessages({
12 | totalUser: {
13 | id: `${scope}.totalUser`,
14 | defaultMessage:
15 | 'Total {count, plural, =0 {user} one { user} other { users}}',
16 | },
17 | activeUser: {
18 | id: `${scope}.activeUser`,
19 | defaultMessage:
20 | 'Active {count, plural, =0 {user} one { user} other { users}}',
21 | },
22 | inActiveUser: {
23 | id: `${scope}.inActiveUser`,
24 | defaultMessage:
25 | 'In-active {count, plural, =0 {user} one { user} other { users}}',
26 | },
27 | deviceChart: {
28 | id: `${scope}.deviceChart`,
29 | defaultMessage: 'Devices',
30 | },
31 | });
32 |
--------------------------------------------------------------------------------
/app/containers/Dashboard/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LoginPage reducer
4 | *
5 | */
6 | import produce from 'immer';
7 | import {
8 | SET_DEVICE_CHART,
9 | UMOUNT_DASHBOARD,
10 | SET_USER_STATS,
11 | BROWSER,
12 | SET_DEVICE_TYPE,
13 | } from 'containers/Dashboard/constants';
14 |
15 | export const initialState = {
16 | isLoading: false,
17 | userStats: {
18 | total: 0,
19 | active: 0,
20 | inactive: 0,
21 | },
22 | deviceChart: [],
23 | deviceType: BROWSER,
24 | };
25 |
26 | /* eslint-disable default-case, no-param-reassign */
27 | const DashboardReducer = produce((draft, action) => {
28 | switch (action.type) {
29 | case SET_USER_STATS:
30 | draft.userStats = action.stats;
31 | break;
32 | case SET_DEVICE_TYPE:
33 | draft.deviceType = action.deviceType;
34 | break;
35 | case SET_DEVICE_CHART:
36 | draft.deviceChart = action.deviceChart;
37 | break;
38 | case UMOUNT_DASHBOARD:
39 | draft.userStats = {
40 | total: 0,
41 | active: 0,
42 | inactive: 0,
43 | };
44 | draft.isLoading = false;
45 | break;
46 | }
47 | }, initialState);
48 |
49 | export default DashboardReducer;
50 |
--------------------------------------------------------------------------------
/app/containers/Dashboard/saga.js:
--------------------------------------------------------------------------------
1 | import {
2 | asyncEndAction,
3 | asyncStartAction,
4 | setDeviceChartAction,
5 | setUserStatsAction,
6 | } from 'containers/Dashboard/actions';
7 | import {
8 | BROWSER,
9 | QUERY_DEVICE_STATS,
10 | QUERY_USER_STATS,
11 | } from 'containers/Dashboard/constants';
12 | import { makeDeviceTypeSelector } from 'containers/Dashboard/selectors';
13 | import { call, put, select, takeLatest } from 'redux-saga/effects';
14 | import ApiEndpoint from 'utils/api';
15 | import { GET } from 'utils/constants';
16 | import request from 'utils/request';
17 |
18 | export function* handleQueryUserStats() {
19 | yield put(asyncStartAction());
20 | const requestUrl = '/dashboard/users';
21 | const payload = ApiEndpoint.makeApiPayload(requestUrl, GET);
22 | try {
23 | const response = yield call(request, payload);
24 | return yield put(setUserStatsAction(response));
25 | } catch (error) {
26 | return yield put(asyncEndAction());
27 | }
28 | }
29 |
30 | export function* handleQueryDeviceStats() {
31 | yield put(asyncStartAction());
32 | const deviceType = yield select(makeDeviceTypeSelector());
33 | const requestUrl = `/dashboard/${deviceType === BROWSER ? 'browser' : 'os'}`;
34 | const payload = ApiEndpoint.makeApiPayload(requestUrl, GET);
35 | try {
36 | const response = yield call(request, payload);
37 | return yield put(setDeviceChartAction(response));
38 | } catch (error) {
39 | return yield put(asyncEndAction());
40 | }
41 | }
42 |
43 | export default function* DashboardSaga() {
44 | yield takeLatest(QUERY_USER_STATS, handleQueryUserStats);
45 | yield takeLatest(QUERY_DEVICE_STATS, handleQueryDeviceStats);
46 | }
47 |
--------------------------------------------------------------------------------
/app/containers/Dashboard/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import { initialState } from 'containers/Dashboard/reducer';
3 |
4 | const selectDashboardDomain = (state) => state.dashboard || initialState;
5 |
6 | const makeIsLoadingSelector = () =>
7 | createSelector(selectDashboardDomain, (substate) => substate.isLoading);
8 |
9 | const makeUserStatsSelector = () =>
10 | createSelector(selectDashboardDomain, (substate) => substate.userStats);
11 |
12 | const makeDeviceChartSelector = () =>
13 | createSelector(selectDashboardDomain, (substate) => substate.deviceChart);
14 |
15 | const makeDeviceTypeSelector = () =>
16 | createSelector(selectDashboardDomain, (substate) => substate.deviceType);
17 |
18 | export {
19 | makeIsLoadingSelector,
20 | makeUserStatsSelector,
21 | makeDeviceChartSelector,
22 | makeDeviceTypeSelector,
23 | };
24 |
--------------------------------------------------------------------------------
/app/containers/EmailTemplate/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for Email Template Module
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import loadable from 'utils/loadable';
9 | import LoadingIndicator from 'components/LoadingIndicator';
10 |
11 | export default loadable(() => import('./index'), {
12 | fallback: ,
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/EmailTemplate/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * EmailTemplate constants
4 | *
5 | */
6 |
7 | export const ADD_VALIDATION_ERROR =
8 | 'containers/EmailTemplate/ADD_VALIDATION_ERROR';
9 | export const ASYNC_START = 'containers/EmailTemplate/ASYNC_START';
10 | export const ASYNC_END = 'containers/EmailTemplate/ASYNC_END';
11 | export const SET_PAGE_SIZE = 'containers/EmailTemplate/SET_PAGE_SIZE';
12 | export const SET_FORM_METHOD = 'containers/EmailTemplate/SET_FORM_METHOD';
13 | export const SET_INITIAL_VALUES = 'containers/EmailTemplate/SET_INITIAL_VALUES';
14 | export const SET_ID = 'containers/EmailTemplate/SET_ID';
15 | export const INITIATE_CLEAN = 'containers/EmailTemplate/INITIATE_CLEAN';
16 | export const SET_FORM_VALUES = 'containers/EmailTemplate/SET_FORM_VALUES';
17 | export const SET_KEYWORD = 'containers/EmailTemplate/SET_KEYWORD';
18 | export const QUERY_TEMPLATE = 'containers/EmailTemplate/QUERY_TEMPLATE';
19 | export const SET_PAGE_NUMBER = 'containers/EmailTemplate/SET_PAGE_NUMBER';
20 | export const DELETE_ITEM_BY_ID = 'containers/EmailTemplate/DELETE_ITEM_BY_ID';
21 | export const SUBMIT_FORM = 'containers/EmailTemplate/SUBMIT_FORM';
22 | export const GET_TEMPLATE_BY_ID = 'containers/EmailTemplate/GET_TEMPLATE_BY_ID';
23 | export const CLEAR_FORM = 'containers/EmailTemplate/CLEAR_FORM';
24 | export const ASSIGN_TEMPLATE = 'containers/EmailTemplate/ASSIGN_TEMPLATE';
25 |
--------------------------------------------------------------------------------
/app/containers/ForgotPassword/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for ForgotPassword
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import loadable from 'utils/loadable';
9 | import LoadingIndicator from 'components/LoadingIndicator';
10 |
11 | export default loadable(() => import('./index'), {
12 | fallback: ,
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/ForgotPassword/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * ForgotPasswordPage actions
4 | *
5 | */
6 |
7 | import {
8 | ADD_VALIDATION_ERROR,
9 | FORGOT_PASSWORD,
10 | SET_FORM_VALUES,
11 | ASYNC_START,
12 | ASYNC_END,
13 | } from 'containers/ForgotPassword/constants';
14 |
15 | export function forgotPasswordAction() {
16 | return {
17 | type: FORGOT_PASSWORD,
18 | };
19 | }
20 |
21 | export function enterValidationErrorAction(errors) {
22 | return {
23 | type: ADD_VALIDATION_ERROR,
24 | errors,
25 | };
26 | }
27 |
28 | export function setFormValuesAction(formValues) {
29 | return {
30 | type: SET_FORM_VALUES,
31 | formValues,
32 | };
33 | }
34 |
35 | export function asyncStartAction() {
36 | return {
37 | type: ASYNC_START,
38 | };
39 | }
40 |
41 | export function asyncEndAction() {
42 | return {
43 | type: ASYNC_END,
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/app/containers/ForgotPassword/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * ForgotPasswordPage constants
4 | *
5 | */
6 |
7 | export const FORGOT_PASSWORD = 'app/ForgotPasswordPage/FORGOT_PASSWORD';
8 | export const ADD_VALIDATION_ERROR =
9 | 'app/ForgotPasswordPage/ADD_VALIDATION_ERROR';
10 | export const SET_FORM_VALUES = 'app/ForgotPasswordPage/SET_FORM_VALUES';
11 | export const ASYNC_END = 'app/ForgotPasswordPage/ASYNC_END';
12 | export const ASYNC_START = 'app/ForgotPasswordPage/ASYNC_START';
13 |
--------------------------------------------------------------------------------
/app/containers/ForgotPassword/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Forgot Password Messages
3 | *
4 | * This contains all the text for the Forgot Password container.
5 | */
6 |
7 | import { defineMessages } from 'react-intl';
8 |
9 | export const scope = 'containers.ForgotPassword';
10 |
11 | export default defineMessages({
12 | mailSent: {
13 | id: `${scope}.mailSent`,
14 | defaultMessage:
15 | 'If your email exists in our system you will be sent a link to reset password!',
16 | },
17 | mailSentError: {
18 | id: `${scope}.mailSentError`,
19 | defaultMessage: 'Error during process!',
20 | },
21 | helmetForgotPwdTitle: {
22 | id: `${scope}.helmetForgotPwdTitle`,
23 | defaultMessage: 'Forgot Password Page',
24 | },
25 | forgotPassword: {
26 | id: `${scope}.forgotPassword`,
27 | defaultMessage: 'Forgot Password',
28 | },
29 | forgotPasswordBtn: {
30 | id: `${scope}.forgotPasswordBtn`,
31 | defaultMessage: 'Send Reset Link',
32 | },
33 | back: {
34 | id: `${scope}.back`,
35 | defaultMessage: 'Go Back',
36 | },
37 | });
38 |
--------------------------------------------------------------------------------
/app/containers/ForgotPassword/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * ForgotPasswordPage reducer
4 | *
5 | */
6 | import produce from 'immer';
7 | import {
8 | ADD_VALIDATION_ERROR,
9 | SET_FORM_VALUES,
10 | } from 'containers/ForgotPassword/constants';
11 |
12 | export const initialState = {
13 | initialValues: { email: '' },
14 | formValues: {},
15 | isLoading: false,
16 | errors: [],
17 | };
18 |
19 | /* eslint-disable default-case, no-param-reassign */
20 | const forgotPasswordReducer = produce((draft, action) => {
21 | switch (action.type) {
22 | case SET_FORM_VALUES:
23 | draft.formValues = action.formValues;
24 | break;
25 | case ADD_VALIDATION_ERROR:
26 | draft.errors = action.errors;
27 | break;
28 | }
29 | }, initialState);
30 |
31 | export default forgotPasswordReducer;
32 |
--------------------------------------------------------------------------------
/app/containers/ForgotPassword/saga.js:
--------------------------------------------------------------------------------
1 | import { call, put, select, takeLatest } from 'redux-saga/effects';
2 | import { FORGOT_PASSWORD } from 'containers/ForgotPassword/constants';
3 | import { makeFormValuesSelector } from 'containers/ForgotPassword/selectors';
4 | import ApiEndpoint from 'utils/api';
5 | import request from 'utils/request';
6 | import {
7 | asyncEndAction,
8 | asyncStartAction,
9 | } from 'containers/ForgotPassword/actions';
10 | import messages from 'containers/ForgotPassword/messages';
11 | import { showAlert, showFormattedAlert } from 'common/saga';
12 | import { PUT } from 'utils/constants';
13 | import { enterValidationErrorAction } from 'containers/LoginPage/actions';
14 |
15 | export function* handleForgotPassword() {
16 | yield put(asyncStartAction());
17 | const formValues = yield select(makeFormValuesSelector());
18 | const requestUrl = `/auth/forgot-password`;
19 | const requestPayload = ApiEndpoint.makeApiPayload(
20 | requestUrl,
21 | PUT,
22 | formValues,
23 | );
24 | try {
25 | yield call(request, requestPayload);
26 | yield put(asyncEndAction());
27 | return yield showFormattedAlert('success', messages.mailSent);
28 | } catch (error) {
29 | yield put(asyncEndAction());
30 | if (error.data && error.data.statusCode === 422) {
31 | return yield put(enterValidationErrorAction(error.data.message));
32 | }
33 | return yield showAlert('error', error.data.message);
34 | }
35 | }
36 |
37 | export default function* forgotPasswordSaga() {
38 | yield takeLatest(FORGOT_PASSWORD, handleForgotPassword);
39 | }
40 |
--------------------------------------------------------------------------------
/app/containers/ForgotPassword/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import { initialState } from 'containers/ForgotPassword/reducer';
3 | const selectForgotPasswordPageDomain = (state) =>
4 | state.forgotPassword || initialState;
5 |
6 | const makeFormValuesSelector = () =>
7 | createSelector(
8 | selectForgotPasswordPageDomain,
9 | (substate) => substate.formValues,
10 | );
11 |
12 | const makeInitialValuesSelector = () =>
13 | createSelector(
14 | selectForgotPasswordPageDomain,
15 | (substate) => substate.initialValues,
16 | );
17 |
18 | const makeErrorsSelector = () =>
19 | createSelector(selectForgotPasswordPageDomain, (substate) => substate.errors);
20 |
21 | const makeIsLoadingSelector = () =>
22 | createSelector(
23 | selectForgotPasswordPageDomain,
24 | (substate) => substate.isLoading,
25 | );
26 |
27 | export {
28 | makeFormValuesSelector,
29 | makeErrorsSelector,
30 | makeIsLoadingSelector,
31 | makeInitialValuesSelector,
32 | };
33 |
--------------------------------------------------------------------------------
/app/containers/HomePage/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for HomePage
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import loadable from 'utils/loadable';
9 | import LoadingIndicator from 'components/LoadingIndicator';
10 |
11 | export default loadable(() => import('./index'), {
12 | fallback: ,
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/HomePage/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * HomePage actions
4 | *
5 | */
6 |
7 | import {
8 | IS_LOGGED,
9 | ASYNC_START,
10 | ASYNC_END,
11 | SET_CONTRIBUTORS,
12 | GET_CONTRIBUTORS,
13 | } from 'containers/HomePage/constants';
14 |
15 | export function isLoggedAction() {
16 | return {
17 | type: IS_LOGGED,
18 | };
19 | }
20 |
21 | export function asyncStartAction() {
22 | return {
23 | type: ASYNC_START,
24 | };
25 | }
26 |
27 | export function asyncEndAction() {
28 | return {
29 | type: ASYNC_END,
30 | };
31 | }
32 |
33 | export function setContributorAction(contributors) {
34 | return {
35 | type: SET_CONTRIBUTORS,
36 | contributors,
37 | };
38 | }
39 |
40 | export function getContributorAction() {
41 | return {
42 | type: GET_CONTRIBUTORS,
43 | };
44 | }
45 |
--------------------------------------------------------------------------------
/app/containers/HomePage/banner.js:
--------------------------------------------------------------------------------
1 | import { Button, Col, Row } from 'antd';
2 | import React from 'react';
3 | import { useIntl } from 'react-intl';
4 | import Welcome from 'assets/images/reporting.gif';
5 | import messages from 'containers/HomePage/messages';
6 |
7 | const Banner = () => {
8 | const intl = useIntl();
9 | return (
10 |
11 |
12 |
13 |
14 |
24 |
25 |
26 |
27 |

28 |
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default Banner;
37 |
--------------------------------------------------------------------------------
/app/containers/HomePage/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * HomePage constants
4 | *
5 | */
6 |
7 | export const IS_LOGGED = 'app/HomePage/IS_LOGGED';
8 | export const SET_CONTRIBUTORS = 'app/HomePage/SET_CONTRIBUTORS';
9 | export const GET_CONTRIBUTORS = 'app/HomePage/GET_CONTRIBUTORS';
10 | export const ASYNC_START = 'app/HomePage/ASYNC_START';
11 | export const ASYNC_END = 'app/HomePage/ASYNC_END';
12 |
--------------------------------------------------------------------------------
/app/containers/HomePage/index.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | /**
3 | *
4 | * HomePage
5 | *
6 | */
7 | import React, { useEffect } from 'react';
8 | import { useDispatch, useSelector } from 'react-redux';
9 | import { useInjectSaga } from 'utils/injectSaga';
10 | import { useInjectReducer } from 'utils/injectReducer';
11 | import { createStructuredSelector } from 'reselect';
12 | import Navbar from 'common/layout/Navbar';
13 | import TruthyHelps from 'containers/HomePage/truthyHelp';
14 | import Contributors from 'containers/HomePage/contributors';
15 | // import Footer from 'components/Footer';
16 | import reducer from 'containers/HomePage/reducer';
17 | import saga from 'containers/HomePage/saga';
18 | import Banner from 'containers/HomePage/banner';
19 | import {
20 | makeContributorsSelector,
21 | makeIsLoadingSelector,
22 | } from 'containers/HomePage/selector';
23 | import { getContributorAction } from 'containers/HomePage/actions';
24 |
25 | const key = 'homePage';
26 | const stateSelector = createStructuredSelector({
27 | contributors: makeContributorsSelector(),
28 | isLoading: makeIsLoadingSelector(),
29 | });
30 |
31 | export default function HomePage() {
32 | const dispatch = useDispatch();
33 |
34 | useInjectReducer({ key, reducer });
35 | useInjectSaga({ key, saga });
36 |
37 | const { contributors, isLoading } = useSelector(stateSelector);
38 |
39 | useEffect(() => {
40 | dispatch(getContributorAction());
41 | }, []);
42 |
43 | return (
44 |
45 |
46 |
47 | {/*

*/}
48 |
49 |
50 |
51 |
52 |
53 | {/*
*/}
54 | {/*
55 | Login
56 | */}
57 |
58 |
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/app/containers/HomePage/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * ForgotPasswordPage reducer
4 | *
5 | */
6 | import produce from 'immer';
7 | import {
8 | SET_CONTRIBUTORS,
9 | ASYNC_START,
10 | ASYNC_END,
11 | } from 'containers/HomePage/constants';
12 |
13 | export const initialState = {
14 | contributors: [],
15 | isLoading: false,
16 | errors: [],
17 | };
18 |
19 | /* eslint-disable default-case, no-param-reassign */
20 | const homePageReducer = produce((draft, action) => {
21 | switch (action.type) {
22 | case SET_CONTRIBUTORS:
23 | draft.contributors = action.contributors;
24 | break;
25 | case ASYNC_START:
26 | draft.isLoading = true;
27 | break;
28 | case ASYNC_END:
29 | draft.isLoading = false;
30 | break;
31 | }
32 | }, initialState);
33 |
34 | export default homePageReducer;
35 |
--------------------------------------------------------------------------------
/app/containers/HomePage/saga.js:
--------------------------------------------------------------------------------
1 | import { call, put, takeLatest } from 'redux-saga/effects';
2 | import { GET_CONTRIBUTORS } from 'containers/HomePage/constants';
3 | import {
4 | asyncEndAction,
5 | asyncStartAction,
6 | setContributorAction,
7 | } from 'containers/HomePage/actions';
8 | // import messages from 'containers/HomePage/messages';
9 | import { showAlert } from 'common/saga';
10 | import axios from 'axios';
11 |
12 | const contribUri =
13 | 'https://gobeam.github.io/truthy-contributors/contributors.json';
14 |
15 | async function getContrib() {
16 | try {
17 | const response = await axios.get(contribUri);
18 | return response;
19 | } catch (error) {
20 | return [];
21 | }
22 | }
23 |
24 | export function* handleGetContributors() {
25 | yield put(asyncStartAction());
26 | try {
27 | const contributors = yield call(getContrib);
28 | if (contributors?.data) {
29 | yield put(setContributorAction(contributors.data));
30 | }
31 |
32 | return yield put(asyncEndAction());
33 | } catch (error) {
34 | yield put(asyncEndAction());
35 | return yield showAlert('error', error.data.message);
36 | }
37 | }
38 |
39 | export default function* homePageSaga() {
40 | yield takeLatest(GET_CONTRIBUTORS, handleGetContributors);
41 | }
42 |
--------------------------------------------------------------------------------
/app/containers/HomePage/selector.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import { initialState } from 'containers/HomePage/reducer';
3 | const selectHomePageDomain = (state) => state.homePage || initialState;
4 |
5 | const makeContributorsSelector = () =>
6 | createSelector(selectHomePageDomain, (substate) => substate.contributors);
7 |
8 | const makeIsLoadingSelector = () =>
9 | createSelector(selectHomePageDomain, (substate) => substate.isLoading);
10 |
11 | export { makeIsLoadingSelector, makeContributorsSelector };
12 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider actions
4 | *
5 | */
6 |
7 | import { CHANGE_LOCALE } from './constants';
8 |
9 | /**
10 | * Change the language on the client side,
11 | *
12 | * @param {string} languageLocale The language locale
13 | *
14 | * @return {object} An action object with a type of CHANGE_LOCALE
15 | */
16 | export function changeLocaleAction(languageLocale) {
17 | return {
18 | type: CHANGE_LOCALE,
19 | locale: languageLocale,
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider constants
4 | *
5 | */
6 |
7 | export const CHANGE_LOCALE = 'app/LanguageToggle/CHANGE_LOCALE';
8 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider
4 | *
5 | * this component connects the redux state language locale to the
6 | * IntlProvider component and i18n messages (loaded from `app/translations`)
7 | */
8 |
9 | import React from 'react';
10 | import PropTypes from 'prop-types';
11 | import { useSelector } from 'react-redux';
12 | import { createSelector } from 'reselect';
13 | import { IntlProvider } from 'react-intl';
14 | import { makeSelectLocale } from 'containers/LanguageProvider/selectors';
15 |
16 | const stateSelector = createSelector(makeSelectLocale(), (locale) => ({
17 | locale,
18 | }));
19 |
20 | export default function LanguageProvider(props) {
21 | const { locale } = useSelector(stateSelector);
22 |
23 | return (
24 |
29 | {React.Children.only(props.children)}
30 |
31 | );
32 | }
33 |
34 | LanguageProvider.propTypes = {
35 | messages: PropTypes.object,
36 | children: PropTypes.element.isRequired,
37 | };
38 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider reducer
4 | *
5 | */
6 | import produce, { setAutoFreeze } from 'immer';
7 | import { DEFAULT_LOCALE } from 'i18n';
8 | import { CHANGE_LOCALE } from 'containers/LanguageProvider/constants';
9 |
10 | export const initialState = {
11 | locale: DEFAULT_LOCALE,
12 | };
13 |
14 | setAutoFreeze(false);
15 | /* eslint-disable default-case, no-param-reassign */
16 | const languageProviderReducer = produce((draft, action) => {
17 | switch (action.type) {
18 | case CHANGE_LOCALE:
19 | draft.locale = action.locale;
20 | break;
21 | default:
22 | }
23 | }, initialState);
24 |
25 | export default languageProviderReducer;
26 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import { initialState } from './reducer';
3 |
4 | /**
5 | * Direct selector to the languageToggle state domain
6 | */
7 | const selectLanguage = (state) => state.language || initialState;
8 |
9 | /**
10 | * Select the language locale
11 | */
12 |
13 | const makeSelectLocale = () =>
14 | createSelector(selectLanguage, (languageState) => languageState.locale);
15 |
16 | export { selectLanguage, makeSelectLocale };
17 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/Wrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Wrapper = styled.div`
4 | padding: 2px;
5 | `;
6 |
7 | export default Wrapper;
8 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageToggle
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import { useDispatch, useSelector } from 'react-redux';
9 | import { createStructuredSelector } from 'reselect';
10 | import { Dropdown, Menu } from 'antd';
11 | import { TranslationOutlined } from '@ant-design/icons';
12 | import { changeLocaleAction } from 'containers/LanguageProvider/actions';
13 | import { useCookie } from 'hooks/useCookie';
14 | import { makeSelectLocale } from 'containers/LanguageProvider/selectors';
15 | import { appLocales } from 'common/language';
16 |
17 | const stateSelector = createStructuredSelector({
18 | locale: makeSelectLocale(),
19 | });
20 |
21 | export function LocaleToggle() {
22 | const dispatch = useDispatch();
23 | // eslint-disable-next-line no-unused-vars
24 | const [cookie, updateCookie] = useCookie('lang', 'en');
25 | const { locale } = useSelector(stateSelector);
26 |
27 | const selectLocale = ({ key }) => {
28 | dispatch(changeLocaleAction(key));
29 | // @ts-ignore
30 | updateCookie(key, 10);
31 | };
32 |
33 | return (
34 |
39 | {appLocales.map((lang) => (
40 |
45 | {lang.flag} {lang.label}
46 |
47 | ))}
48 |
49 | }
50 | >
51 |
52 |
53 |
54 |
55 | );
56 | }
57 |
58 | export default LocaleToggle;
59 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * LocaleToggle Messages
3 | *
4 | * This contains all the text for the LanguageToggle component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export const scope = 'containers.LocaleToggle';
9 |
10 | export default defineMessages({
11 | en: {
12 | id: `${scope}.en`,
13 | defaultMessage: 'en',
14 | },
15 | de: {
16 | id: `${scope}.de`,
17 | defaultMessage: 'de',
18 | },
19 | ne: {
20 | id: `${scope}.ne`,
21 | defaultMessage: 'ne',
22 | },
23 | });
24 |
--------------------------------------------------------------------------------
/app/containers/LoginPage/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for LoginPage
4 | *
5 | */
6 |
7 | import loadable from 'utils/loadable';
8 | import LoadingIndicator from 'components/LoadingIndicator';
9 | import React from 'react';
10 |
11 | export default loadable(() => import('./index'), {
12 | fallback: ,
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/LoginPage/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LoginPage actions
4 | *
5 | */
6 |
7 | import {
8 | ADD_VALIDATION_ERROR,
9 | ASYNC_END,
10 | ASYNC_START,
11 | LOGIN_PROCESS,
12 | SET_FORM_VALUES,
13 | } from 'containers/LoginPage/constants';
14 |
15 | export function setFormValuesAction(formValues) {
16 | return {
17 | type: SET_FORM_VALUES,
18 | formValues,
19 | };
20 | }
21 |
22 | export function asyncStartAction() {
23 | return {
24 | type: ASYNC_START,
25 | };
26 | }
27 |
28 | export function asyncEndAction() {
29 | return {
30 | type: ASYNC_END,
31 | };
32 | }
33 |
34 | export function enterValidationErrorAction(errors) {
35 | return {
36 | type: ADD_VALIDATION_ERROR,
37 | errors,
38 | };
39 | }
40 |
41 | export function enterLoginAction() {
42 | return {
43 | type: LOGIN_PROCESS,
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/app/containers/LoginPage/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LoginPage constants
4 | *
5 | */
6 | export const ADD_VALIDATION_ERROR = 'containers/LoginPage/ADD_VALIDATION_ERROR';
7 | export const SET_FORM_VALUES = 'containers/LoginPage/SET_FORM_VALUES';
8 | export const ASYNC_START = 'containers/LoginPage/ASYNC_START';
9 | export const ASYNC_END = 'containers/LoginPage/ASYNC_END';
10 | export const LOGIN_PROCESS = 'containers/LoginPage/LOGIN_PROCESS';
11 | export const CHANGE_LOGIN = 'containers/LoginPage/CHANGE_LOGIN';
12 | export const ENTER_LOGIN_ERROR = 'containers/LoginPage/ENTER_LOGIN_ERROR';
13 | export const LOGIN = 'containers/LoginPage/LOGIN';
14 | export const LOGIN_SUCCESS = 'containers/LoginPage/LOGIN_SUCCESS';
15 | export const LOGIN_ERROR = 'containers/LoginPage/LOGIN_ERROR';
16 | export const IS_LOGGED = 'containers/LoginPage/IS_LOGGED';
17 | export const SUCCESS_REDIRECT = '/dashboard';
18 |
--------------------------------------------------------------------------------
/app/containers/LoginPage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * LoginPage
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import { useInjectSaga } from 'utils/injectSaga';
9 | import { useInjectReducer } from 'utils/injectReducer';
10 | import reducer from 'containers/LoginPage/reducer';
11 | import LoginForm from 'containers/LoginPage/loginForm';
12 | import saga from 'containers/LoginPage/saga';
13 | // import 'containers/LoginPage/index.less';
14 | import Helmet from 'react-helmet';
15 | import { FormattedMessage } from 'react-intl';
16 | import messages from 'containers/LoginPage/messages';
17 | import { Row, Col } from 'antd';
18 |
19 | const key = 'login';
20 |
21 | export default function LoginPage() {
22 | useInjectReducer({ key, reducer });
23 | useInjectSaga({ key, saga });
24 |
25 | return (
26 |
27 |
28 | {(title) => (
29 |
30 | {title}
31 |
32 | )}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/app/containers/LoginPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * LoginPage Messages
3 | *
4 | * This contains all the text for the LoginPage container.
5 | */
6 |
7 | import { defineMessages } from 'react-intl';
8 |
9 | export const scope = 'containers.LoginPage';
10 |
11 | export default defineMessages({
12 | back: {
13 | id: `${scope}.back`,
14 | defaultMessage: 'Go Back',
15 | },
16 | errorLogin: {
17 | id: `${scope}.errorLogin`,
18 | defaultMessage: 'Invalid credentials',
19 | },
20 | sessionOut: {
21 | id: `${scope}.sessionOut`,
22 | defaultMessage: 'Your session has expired',
23 | },
24 | serverError: {
25 | id: `${scope}.serverError`,
26 | defaultMessage: 'Please try again in a moment!',
27 | },
28 | loginToTheSystem: {
29 | id: `${scope}.loginToTheSystem`,
30 | defaultMessage: 'Log in',
31 | },
32 | loginSuccess: {
33 | id: `${scope}.loginSuccess`,
34 | defaultMessage: 'Successfully logged in!',
35 | },
36 | helmetLoginTitle: {
37 | id: `${scope}.HelmetLoginTitle`,
38 | defaultMessage: 'Login',
39 | },
40 | submit: {
41 | id: `${scope}.submit`,
42 | defaultMessage: 'Sign in',
43 | },
44 | lostPassword: {
45 | id: `${scope}.lostPassword`,
46 | defaultMessage: 'Lost password?',
47 | },
48 | inputLogin: {
49 | id: `${scope}.inputLogin`,
50 | defaultMessage: 'Log in',
51 | },
52 | register: {
53 | id: `${scope}.register`,
54 | defaultMessage: 'register now!',
55 | },
56 | rememberMe: {
57 | id: `${scope}.rememberMe`,
58 | defaultMessage: 'Remember me',
59 | },
60 | });
61 |
--------------------------------------------------------------------------------
/app/containers/LoginPage/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LoginPage reducer
4 | *
5 | */
6 | import produce from 'immer';
7 | import {
8 | ADD_VALIDATION_ERROR,
9 | ASYNC_END,
10 | ASYNC_START,
11 | CHANGE_LOGIN,
12 | ENTER_LOGIN_ERROR,
13 | LOGIN,
14 | LOGIN_ERROR,
15 | LOGIN_SUCCESS,
16 | SET_FORM_VALUES,
17 | } from 'containers/LoginPage/constants';
18 |
19 | const EmptyFields = {
20 | username: '',
21 | password: '',
22 | remember: false,
23 | };
24 | export const initialState = {
25 | initialValues: EmptyFields,
26 | formValues: {},
27 | errors: [],
28 | isLoading: false,
29 | };
30 |
31 | /* eslint-disable default-case, no-param-reassign */
32 | const loginPageReducer = produce((draft, action) => {
33 | switch (action.type) {
34 | case ADD_VALIDATION_ERROR:
35 | draft.errors = action.errors;
36 | draft.isLoading = false;
37 | break;
38 | case SET_FORM_VALUES:
39 | draft.formValues = action.formValues;
40 | break;
41 | case CHANGE_LOGIN:
42 | draft.login = action.login;
43 | draft.error = '';
44 | break;
45 | case ASYNC_START:
46 | draft.isLoading = true;
47 | break;
48 | case ASYNC_END:
49 | draft.isLoading = false;
50 | break;
51 | case ENTER_LOGIN_ERROR:
52 | draft.error = action.error;
53 | draft.isLoading = false;
54 | break;
55 | case LOGIN:
56 | draft.login = action.login;
57 | draft.password = action.password;
58 | draft.isLoading = true;
59 | break;
60 | case LOGIN_SUCCESS:
61 | draft.isLoading = false;
62 | break;
63 | case LOGIN_ERROR:
64 | draft.error = action.error;
65 | draft.isLoading = false;
66 | break;
67 | }
68 | }, initialState);
69 |
70 | export default loginPageReducer;
71 |
--------------------------------------------------------------------------------
/app/containers/LoginPage/saga.js:
--------------------------------------------------------------------------------
1 | import { call, put, select, takeEvery } from 'redux-saga/effects';
2 | import ApiEndpoint from 'utils/api';
3 | import request from 'utils/request';
4 | // import messages from 'containers/LoginPage/messages';
5 | import { makeFormValuesSelector } from 'containers/LoginPage/selectors';
6 | import { LOGIN_PROCESS } from 'containers/LoginPage/constants';
7 | import { getProfileAction } from 'containers/App/actions';
8 | import {
9 | asyncEndAction,
10 | asyncStartAction,
11 | enterValidationErrorAction,
12 | } from 'containers/LoginPage/actions';
13 | import { showAlert } from 'common/saga';
14 | import { POST } from 'utils/constants';
15 | import { clearSnackMessageAction } from 'containers/SnackMessage/actions';
16 |
17 | export function* attemptLogin() {
18 | yield put(asyncStartAction());
19 | const formValues = yield select(makeFormValuesSelector());
20 | const requestUrl = ApiEndpoint.getLoginPath();
21 | const requestPayload = ApiEndpoint.makeApiPayload(
22 | requestUrl,
23 | POST,
24 | formValues,
25 | );
26 | try {
27 | yield call(request, requestPayload);
28 | yield put(asyncEndAction());
29 | yield put(clearSnackMessageAction());
30 | return yield put(getProfileAction());
31 | // return yield showFormattedAlert('success', messages.loginSuccess);
32 | } catch (error) {
33 | yield put(asyncEndAction());
34 | if (error.data && error.data.statusCode === 422) {
35 | return yield put(enterValidationErrorAction(error.data.message));
36 | }
37 | return yield showAlert('error', error.data.message);
38 | }
39 | }
40 |
41 | export default function* loginPageSaga() {
42 | yield takeEvery(LOGIN_PROCESS, attemptLogin);
43 | }
44 |
--------------------------------------------------------------------------------
/app/containers/LoginPage/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import { initialState } from 'containers/LoginPage/reducer';
3 |
4 | /**
5 | * Direct selector to the loginPage state domain
6 | */
7 |
8 | const selectLoginPageDomain = (state) => state.login || initialState;
9 |
10 | /**
11 | * Other specific selectors
12 | */
13 |
14 | const makeInitialValuesSelector = () =>
15 | createSelector(selectLoginPageDomain, (substate) => substate.initialValues);
16 |
17 | const makeFormValuesSelector = () =>
18 | createSelector(selectLoginPageDomain, (substate) => substate.formValues);
19 |
20 | const makeErrorSelector = () =>
21 | createSelector(selectLoginPageDomain, (substate) => substate.errors);
22 |
23 | const makeIsLoadingSelector = () =>
24 | createSelector(selectLoginPageDomain, (substate) => substate.isLoading);
25 |
26 | /**
27 | * Default selector used by LoginPage
28 | */
29 |
30 | const makeSelectLoginPage = () =>
31 | createSelector(selectLoginPageDomain, (substate) => substate);
32 |
33 | export default makeSelectLoginPage;
34 |
35 | export {
36 | makeInitialValuesSelector,
37 | makeFormValuesSelector,
38 | makeErrorSelector,
39 | makeIsLoadingSelector,
40 | };
41 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Asynchronously loads the component for NotFoundPage
3 | */
4 |
5 | import React from 'react';
6 | import loadable from 'utils/loadable';
7 | import LoadingIndicator from 'components/LoadingIndicator';
8 |
9 | export default loadable(() => import('./index'), {
10 | fallback: ,
11 | });
12 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * NotFoundPage
3 | *
4 | * This is the page we show when the user visits a url that doesn't have a route
5 | */
6 | import React from 'react';
7 | import { FormattedMessage } from 'react-intl';
8 | import messages from 'containers/NotFoundPage/messages';
9 | import { Button, Result } from 'antd';
10 | import history from 'utils/history';
11 |
12 | /**
13 | * @return {boolean}
14 | */
15 |
16 | const NotFound = () => {
17 | const clickBack = () => history.push('/');
18 | return (
19 |
20 | }
23 | subTitle={}
24 | extra={
25 |
28 | }
29 | />
30 |
31 | );
32 | };
33 |
34 | export default NotFound;
35 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * NotFoundPage Messages
3 | *
4 | * This contains all the text for the NotFoundPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export const scope = 'containers.NotFoundPage';
9 |
10 | export default defineMessages({
11 | header: {
12 | id: `${scope}.header`,
13 | defaultMessage: 'Page not found.',
14 | },
15 | back: {
16 | id: `${scope}.back`,
17 | defaultMessage: 'Go Back Home',
18 | },
19 | message: {
20 | id: `${scope}.message`,
21 | defaultMessage:
22 | 'Oops! Looks like you followed a bad link. If you think this is a problem with us, please tell us.',
23 | },
24 | });
25 |
--------------------------------------------------------------------------------
/app/containers/Permission/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for Users
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import loadable from 'utils/loadable';
9 | import LoadingIndicator from 'components/LoadingIndicator';
10 |
11 | export default loadable(() => import('./index'), {
12 | fallback: ,
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/Permission/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Permission constants
4 | *
5 | */
6 |
7 | export const ADD_VALIDATION_ERROR =
8 | 'containers/Permission/ADD_VALIDATION_ERROR';
9 | export const ASYNC_START = 'containers/Permission/ASYNC_START';
10 | export const ASYNC_END = 'containers/Permission/ASYNC_END';
11 | export const QUERY_PERMISSION = 'containers/Permission/QUERY_PERMISSION';
12 | export const SET_ID = 'containers/Permission/SET_ID';
13 | export const SET_FORM_VALUES = 'containers/Permission/SET_FORM_VALUES';
14 | export const SET_KEYWORD = 'containers/Permission/SET_KEYWORD';
15 | export const SET_FORM_METHOD = 'containers/Permission/SET_FORM_METHOD';
16 | export const INITIATE_CLEAN = 'containers/Permission/INITIATE_CLEAN';
17 | export const SET_PAGE_SIZE = 'containers/Permission/SET_PAGE_SIZE';
18 | export const SET_INITIAL_VALUES = 'containers/Permission/SET_INITIAL_VALUES';
19 |
20 | export const SET_PAGE_NUMBER = 'containers/Permission/SET_PAGE_NUMBER';
21 | export const SYNC_PERMISSION = 'containers/Permission/SYNC_PERMISSION';
22 | export const DELETE_ITEM_BY_ID = 'containers/Permission/DELETE_ITEM_BY_ID';
23 | export const SUBMIT_FORM = 'containers/Permission/SUBMIT_FORM';
24 |
25 | export const GET_PERMISSION_BY_ID =
26 | 'containers/Permission/GET_PERMISSION_BY_ID';
27 | export const CLEAR_FORM = 'containers/Permission/CLEAR_FORM';
28 |
29 | export const ASSIGN_PERMISSION = 'containers/Permission/ASSIGN_PERMISSION';
30 |
--------------------------------------------------------------------------------
/app/containers/Permission/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import { initialState } from 'containers/Permission/reducer';
3 |
4 | /**
5 | * Direct selector to the Permission state domain
6 | */
7 |
8 | const selectPermissionDomain = (state) => state.permission || initialState;
9 |
10 | /**
11 | * Other specific selectors
12 | */
13 |
14 | const makeInitialValuesSelector = () =>
15 | createSelector(selectPermissionDomain, (substate) => substate.initialValues);
16 |
17 | const makeIsLoadingSelector = () =>
18 | createSelector(selectPermissionDomain, (substate) => substate.isLoading);
19 |
20 | const makeKeywordsSelector = () =>
21 | createSelector(selectPermissionDomain, (substate) => substate.keywords);
22 |
23 | const makeFormMethodSelector = () =>
24 | createSelector(selectPermissionDomain, (substate) => substate.formMethod);
25 |
26 | const makeUpdateIdSelector = () =>
27 | createSelector(selectPermissionDomain, (substate) => substate.id);
28 |
29 | const makePageNumberSelector = () =>
30 | createSelector(selectPermissionDomain, (substate) => substate.pageNumber);
31 |
32 | const makeLimitSelector = () =>
33 | createSelector(selectPermissionDomain, (substate) => substate.pageSize);
34 |
35 | const makePermissionsSelector = () =>
36 | createSelector(selectPermissionDomain, (substate) => substate.permissions);
37 |
38 | const makeErrorSelector = () =>
39 | createSelector(selectPermissionDomain, (substate) => substate.errors);
40 |
41 | const makeInitiateCleanFieldSelector = () =>
42 | createSelector(selectPermissionDomain, (substate) => substate.initiateClean);
43 |
44 | const makeFormValuesSelector = () =>
45 | createSelector(selectPermissionDomain, (substate) => substate.formValues);
46 |
47 | export {
48 | makeInitialValuesSelector,
49 | makeFormValuesSelector,
50 | makeInitiateCleanFieldSelector,
51 | makeLimitSelector,
52 | makeKeywordsSelector,
53 | makeUpdateIdSelector,
54 | makeFormMethodSelector,
55 | makeIsLoadingSelector,
56 | makeErrorSelector,
57 | makePageNumberSelector,
58 | makePermissionsSelector,
59 | };
60 |
--------------------------------------------------------------------------------
/app/containers/PermissionDeniedPage/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Asynchronously loads the component for PermissionDeniedPage
3 | */
4 |
5 | import React from 'react';
6 | import loadable from 'utils/loadable';
7 | import LoadingIndicator from 'components/LoadingIndicator';
8 |
9 | export default loadable(() => import('./index'), {
10 | fallback: ,
11 | });
12 |
--------------------------------------------------------------------------------
/app/containers/PermissionDeniedPage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * PermissionDeniedPage
3 | *
4 | * This is the page we show when the user visits a url that doesn't have permission to a route
5 | */
6 | import React from 'react';
7 | import { Button, Result } from 'antd';
8 | import { FormattedMessage } from 'react-intl';
9 | import messages from 'containers/PermissionDeniedPage/messages';
10 | import { useNavigate } from 'react-router-dom';
11 |
12 | /**
13 | * @return {boolean}
14 | */
15 |
16 | const PermissionDeniedPage = () => {
17 | const navigate = useNavigate();
18 | const clickBack = () => navigate('/login');
19 | return (
20 |
21 | }
24 | subTitle={}
25 | extra={
26 |
29 | }
30 | />
31 |
32 | );
33 | };
34 |
35 | export default PermissionDeniedPage;
36 |
--------------------------------------------------------------------------------
/app/containers/PermissionDeniedPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PermissionDeniedPage Messages
3 | *
4 | */
5 | import { defineMessages } from 'react-intl';
6 |
7 | export const scope = 'containers.PermissionDeniedPage';
8 |
9 | export default defineMessages({
10 | header: {
11 | id: `${scope}.header`,
12 | defaultMessage: 'Permission Denied',
13 | },
14 | back: {
15 | id: `${scope}.back`,
16 | defaultMessage: 'Go Back Home',
17 | },
18 | message: {
19 | id: `${scope}.message`,
20 | defaultMessage: "Oops! You don't have permission to visit this page.",
21 | },
22 | });
23 |
--------------------------------------------------------------------------------
/app/containers/PrivateRoute/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Private Route
4 | *
5 | */
6 |
7 | import LoadingIndicator from 'components/LoadingIndicator';
8 | import {
9 | makeIsLoggedSelector,
10 | makeLoggedInUserSelector,
11 | makeOtpErrorSelector,
12 | makeOtpVerificationSelector,
13 | } from 'containers/App/selectors';
14 | import PermissionDeniedPage from 'containers/PermissionDeniedPage';
15 | import PropTypes from 'prop-types';
16 | import React, { useEffect, useState } from 'react';
17 | import { useSelector } from 'react-redux';
18 | import { Navigate } from 'react-router-dom';
19 | import { createStructuredSelector } from 'reselect';
20 | import { checkPermissionForComponent } from 'utils/permission';
21 |
22 | const stateSelector = createStructuredSelector({
23 | user: makeLoggedInUserSelector(),
24 | otpError: makeOtpErrorSelector(),
25 | isLogged: makeIsLoggedSelector(),
26 | otpVerified: makeOtpVerificationSelector(),
27 | });
28 |
29 | function PrivateRoute({ children, path, resource, method, defaultPermission }) {
30 | const { isLogged, user, otpVerified } = useSelector(stateSelector);
31 | const [permitted, setPermitted] = useState(true);
32 |
33 | useEffect(() => {
34 | if (isLogged) {
35 | setPermitted(
36 | checkPermissionForComponent(user.role, {
37 | path,
38 | resource,
39 | method,
40 | defaultPermission,
41 | }),
42 | );
43 | }
44 | }, [user, path]);
45 |
46 | if (isLogged === null) {
47 | return ;
48 | }
49 |
50 | if (!permitted && otpVerified) {
51 | return ;
52 | }
53 | return isLogged ? children : ;
54 | }
55 |
56 | PrivateRoute.propTypes = {
57 | defaultPermission: PropTypes.bool,
58 | path: PropTypes.string,
59 | resource: PropTypes.string.isRequired,
60 | method: PropTypes.string.isRequired,
61 | children: PropTypes.node,
62 | };
63 |
64 | export default PrivateRoute;
65 |
--------------------------------------------------------------------------------
/app/containers/Profile/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for HomePage
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import loadable from 'utils/loadable';
9 | import LoadingIndicator from 'components/LoadingIndicator';
10 |
11 | export default loadable(() => import('./index'), {
12 | fallback: ,
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/Profile/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * RegisterPage Messages
3 | *
4 | * This contains all the text for the RegisterPage container.
5 | */
6 |
7 | import { defineMessages } from 'react-intl';
8 |
9 | export const scope = 'containers.Profile';
10 |
11 | export default defineMessages({
12 | helmetTitle: {
13 | id: `${scope}.helmetTitle`,
14 | defaultMessage: 'Profile - Page',
15 | },
16 | pageHeader: {
17 | id: `${scope}.pageHeader`,
18 | defaultMessage: 'Profile',
19 | },
20 | na: {
21 | id: `${scope}.na`,
22 | defaultMessage: 'Not Available',
23 | },
24 | editProfileLabel: {
25 | id: `${scope}.editProfileLabel`,
26 | defaultMessage: 'Edit Profile',
27 | },
28 | });
29 |
--------------------------------------------------------------------------------
/app/containers/PublicRoute/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Public Route
4 | *
5 | */
6 |
7 | import React, { useEffect } from 'react';
8 | import { useSelector } from 'react-redux';
9 | import { useNavigate } from 'react-router-dom';
10 | import { createStructuredSelector } from 'reselect';
11 | import { makeIsLoggedSelector } from 'containers/App/selectors';
12 | import LoadingIndicator from 'components/LoadingIndicator';
13 | import Common from 'utils/common';
14 | import { SUCCESS_REDIRECT } from 'containers/LoginPage/constants';
15 | import PropTypes from 'prop-types';
16 |
17 | const stateSelector = createStructuredSelector({
18 | isLogged: makeIsLoggedSelector(),
19 | });
20 |
21 | function PublicRoute({ children }) {
22 | const navigate = useNavigate();
23 | const { isLogged } = useSelector(stateSelector);
24 |
25 | useEffect(() => {
26 | if (isLogged) {
27 | const redirectUrl = Common.getParameterByName('path') || SUCCESS_REDIRECT;
28 | navigate(redirectUrl);
29 | }
30 | }, [isLogged]);
31 |
32 | if (isLogged === null) {
33 | return ;
34 | }
35 | return children;
36 | }
37 |
38 | PublicRoute.propTypes = {
39 | children: PropTypes.node,
40 | };
41 |
42 | export default PublicRoute;
43 |
--------------------------------------------------------------------------------
/app/containers/RegisterPage/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for RegisterPage
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import loadable from 'utils/loadable';
9 | import LoadingIndicator from 'components/LoadingIndicator';
10 |
11 | export default loadable(() => import('./index'), {
12 | fallback: ,
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/RegisterPage/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * RegisterPage actions
4 | *
5 | */
6 |
7 | import {
8 | ADD_VALIDATION_ERROR,
9 | ASYNC_END,
10 | ASYNC_START,
11 | REGISTER_PROCESS,
12 | REGISTER_SUCCESS,
13 | SET_FORM_VALUES,
14 | CLEAR_FORM_VALUES,
15 | } from 'containers/RegisterPage/constants';
16 |
17 | export function setFormValuesAction(formValues) {
18 | return {
19 | type: SET_FORM_VALUES,
20 | formValues,
21 | };
22 | }
23 |
24 | export function clearFormAction(clearFormValue) {
25 | return {
26 | type: CLEAR_FORM_VALUES,
27 | clearFormValue,
28 | };
29 | }
30 |
31 | export function asyncStartAction() {
32 | return {
33 | type: ASYNC_START,
34 | };
35 | }
36 |
37 | export function asyncEndAction() {
38 | return {
39 | type: ASYNC_END,
40 | };
41 | }
42 |
43 | export function enterValidationErrorAction(errors) {
44 | return {
45 | type: ADD_VALIDATION_ERROR,
46 | errors,
47 | };
48 | }
49 |
50 | export function enterRegisterAction() {
51 | return {
52 | type: REGISTER_PROCESS,
53 | };
54 | }
55 |
56 | export function registerSuccessAction() {
57 | return {
58 | type: REGISTER_SUCCESS,
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/app/containers/RegisterPage/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * RegisterPage constants
4 | *
5 | */
6 | const DOMAIN = 'containers/RegisterPage/';
7 |
8 | export const REGISTER_SUCCESS = `${DOMAIN}REGISTER_SUCCESS`;
9 | export const REGISTER_PROCESS = `${DOMAIN}REGISTER_PROCESS`;
10 | export const ADD_VALIDATION_ERROR = `${DOMAIN}ADD_VALIDATION_ERROR`;
11 | export const ASYNC_START = `${DOMAIN}ASYNC_START`;
12 | export const ASYNC_END = `${DOMAIN}ASYNC_END`;
13 | export const SET_FORM_VALUES = `${DOMAIN}SET_FORM_VALUES`;
14 | export const CLEAR_FORM_VALUES = `${DOMAIN}CLEAR_FORM_VALUES`;
15 |
--------------------------------------------------------------------------------
/app/containers/RegisterPage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Register Page
4 | *
5 | */
6 | import React, { useEffect } from 'react';
7 | import { useDispatch, useSelector } from 'react-redux';
8 | import { Helmet } from 'react-helmet';
9 | import { useInjectSaga } from 'utils/injectSaga';
10 | import { useInjectReducer } from 'utils/injectReducer';
11 | import { FormattedMessage } from 'react-intl';
12 | import reducer from 'containers/RegisterPage/reducer';
13 | import messages from 'containers/RegisterPage/messages';
14 | import saga from 'containers/RegisterPage/saga';
15 | import RegisterForm from 'containers/RegisterPage/registerForm';
16 | import { createStructuredSelector } from 'reselect';
17 | import { makeLoggedInUserSelector } from 'containers/App/selectors';
18 | import { hideHeaderAction } from 'containers/App/actions';
19 | import 'containers/RegisterPage/index.less';
20 | import { Row, Col } from 'antd';
21 |
22 | const key = 'register';
23 |
24 | const stateSelector = createStructuredSelector({
25 | user: makeLoggedInUserSelector(),
26 | });
27 |
28 | export default function RegisterPage() {
29 | const dispatch = useDispatch();
30 | const { user } = useSelector(stateSelector);
31 | const hideHeader = () => dispatch(hideHeaderAction(true));
32 |
33 | useInjectReducer({ key, reducer });
34 | useInjectSaga({ key, saga });
35 |
36 | useEffect(() => {
37 | hideHeader();
38 | }, [user]);
39 |
40 | return (
41 |
42 |
43 | {(title) => (
44 |
45 | {title}
46 |
47 | )}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/app/containers/RegisterPage/index.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gobeam/truthy-react-frontend/858dfd35e345ae7b10e8e1eb4aecc419df28600b/app/containers/RegisterPage/index.less
--------------------------------------------------------------------------------
/app/containers/RegisterPage/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Register Page reducer
4 | *
5 | */
6 | import produce from 'immer';
7 | import {
8 | ADD_VALIDATION_ERROR,
9 | REGISTER_SUCCESS,
10 | SET_FORM_VALUES,
11 | ASYNC_END,
12 | ASYNC_START,
13 | CLEAR_FORM_VALUES,
14 | } from 'containers/RegisterPage/constants';
15 |
16 | const EmptyFields = {
17 | email: '',
18 | name: '',
19 | password: '',
20 | confirmPassword: '',
21 | username: '',
22 | accept: false,
23 | };
24 |
25 | export const initialState = {
26 | initialValues: EmptyFields,
27 | formValues: {},
28 | errors: [],
29 | error: '',
30 | isLoading: false,
31 | clearFormValue: false,
32 | };
33 |
34 | /* eslint-disable default-case, no-param-reassign */
35 | const loginPageReducer = produce((draft, action) => {
36 | switch (action.type) {
37 | case ADD_VALIDATION_ERROR:
38 | draft.errors = action.errors;
39 | draft.isLoading = false;
40 | break;
41 | case SET_FORM_VALUES:
42 | draft.formValues = action.formValues;
43 | break;
44 | case ASYNC_START:
45 | draft.isLoading = true;
46 | break;
47 | case ASYNC_END:
48 | draft.isLoading = false;
49 | break;
50 | case CLEAR_FORM_VALUES:
51 | draft.clearFormValue = action.clearFormValue;
52 | break;
53 | case REGISTER_SUCCESS:
54 | draft.isLoading = false;
55 | break;
56 | }
57 | }, initialState);
58 |
59 | export default loginPageReducer;
60 |
--------------------------------------------------------------------------------
/app/containers/RegisterPage/saga.js:
--------------------------------------------------------------------------------
1 | import { REGISTER_PROCESS } from 'containers/RegisterPage/constants';
2 | import { call, put, select, takeLatest } from 'redux-saga/effects';
3 | import {
4 | asyncEndAction,
5 | asyncStartAction,
6 | enterValidationErrorAction,
7 | } from 'containers/RegisterPage/actions';
8 | import { makeFormValuesSelector } from 'containers/RegisterPage/selectors';
9 | import ApiEndpoint from 'utils/api';
10 | import request from 'utils/request';
11 | import messages from 'containers/RegisterPage/messages';
12 | import { showAlert, showFormattedAlert } from 'common/saga';
13 | import { POST } from 'utils/constants';
14 |
15 | export function* handleRegister() {
16 | yield put(asyncStartAction());
17 | const formValues = yield select(makeFormValuesSelector());
18 | const requestUrl = ApiEndpoint.getRegisterPath();
19 | delete formValues.confirmPassword;
20 | delete formValues.accept;
21 | const requestPayload = ApiEndpoint.makeApiPayload(
22 | requestUrl,
23 | POST,
24 | formValues,
25 | );
26 | try {
27 | const response = yield call(request, requestPayload);
28 | if (response && response.error) {
29 | return yield put(enterValidationErrorAction(response.error));
30 | }
31 | yield put(asyncEndAction());
32 | return yield showFormattedAlert('success', messages.registerSuccess);
33 | } catch (error) {
34 | yield put(asyncEndAction());
35 | if (error.data && error.data.statusCode === 422) {
36 | return yield put(enterValidationErrorAction(error.data.message));
37 | }
38 | return yield showAlert('error', error.data.message);
39 | }
40 | }
41 |
42 | export default function* registerPageSaga() {
43 | yield takeLatest(REGISTER_PROCESS, handleRegister);
44 | }
45 |
--------------------------------------------------------------------------------
/app/containers/RegisterPage/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import { initialState } from 'containers/RegisterPage/reducer';
3 |
4 | /**
5 | * Direct selector to the RegisterPage state domain
6 | */
7 |
8 | const selectRegisterPageDomain = (state) => state.register || initialState;
9 |
10 | /**
11 | * Other specific selectors
12 | */
13 |
14 | const makeInitialValuesSelector = () =>
15 | createSelector(
16 | selectRegisterPageDomain,
17 | (substate) => substate.initialValues,
18 | );
19 |
20 | const makeFormValuesSelector = () =>
21 | createSelector(selectRegisterPageDomain, (substate) => substate.formValues);
22 |
23 | const makeErrorSelector = () =>
24 | createSelector(selectRegisterPageDomain, (substate) => substate.errors);
25 |
26 | const makeIsLoadingSelector = () =>
27 | createSelector(selectRegisterPageDomain, (substate) => substate.isLoading);
28 |
29 | const makeClearFormValueSelector = () =>
30 | createSelector(
31 | selectRegisterPageDomain,
32 | (substate) => substate.clearFormValue,
33 | );
34 |
35 | export {
36 | makeClearFormValueSelector,
37 | makeFormValuesSelector,
38 | makeErrorSelector,
39 | makeIsLoadingSelector,
40 | makeInitialValuesSelector,
41 | };
42 |
--------------------------------------------------------------------------------
/app/containers/ResetPassword/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for ForgotPassword
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import loadable from 'utils/loadable';
9 | import LoadingIndicator from 'components/LoadingIndicator';
10 |
11 | export default loadable(() => import('./index'), {
12 | fallback: ,
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/ResetPassword/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * ResetPassword actions
4 | *
5 | */
6 |
7 | import {
8 | ADD_VALIDATION_ERROR,
9 | ASYNC_END,
10 | ASYNC_START,
11 | RESET_PASSWORD,
12 | SET_FORM_VALUES,
13 | SET_RESET_CODE,
14 | CLEAR_FORM_VALUES,
15 | } from 'containers/ResetPassword/constants';
16 |
17 | export function setFormValuesAction(formValues) {
18 | return {
19 | type: SET_FORM_VALUES,
20 | formValues,
21 | };
22 | }
23 |
24 | export function asyncStartAction() {
25 | return {
26 | type: ASYNC_START,
27 | };
28 | }
29 |
30 | export function setResetCodeAction(code) {
31 | return {
32 | type: SET_RESET_CODE,
33 | code,
34 | };
35 | }
36 |
37 | export function asyncEndAction() {
38 | return {
39 | type: ASYNC_END,
40 | };
41 | }
42 |
43 | export function resetPasswordAction() {
44 | return {
45 | type: RESET_PASSWORD,
46 | };
47 | }
48 |
49 | export function enterValidationErrorAction(errors) {
50 | return {
51 | type: ADD_VALIDATION_ERROR,
52 | errors,
53 | };
54 | }
55 |
56 | export function clearFormAction(clearFormValue) {
57 | return {
58 | type: CLEAR_FORM_VALUES,
59 | clearFormValue,
60 | };
61 | }
62 |
--------------------------------------------------------------------------------
/app/containers/ResetPassword/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * ResetPassword constants
4 | *
5 | */
6 |
7 | export const RESET_PASSWORD = 'containers/ResetPassword/RESET_PASSWORD';
8 | export const ADD_VALIDATION_ERROR =
9 | 'containers/ResetPassword/ADD_VALIDATION_ERROR';
10 | export const ASYNC_START = 'containers/ResetPassword/ASYNC_START';
11 | export const ASYNC_END = 'containers/ResetPassword/ASYNC_END';
12 | export const SET_FORM_VALUES = 'containers/ResetPassword/SET_FORM_VALUES';
13 | export const SET_RESET_CODE = 'containers/ResetPassword/SET_RESET_CODE';
14 | export const CLEAR_FORM_VALUES = 'containers/ResetPassword/CLEAR_FORM_VALUES';
15 |
--------------------------------------------------------------------------------
/app/containers/ResetPassword/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Reset Password Messages
3 | *
4 | * This contains all the text for the Reset Password container.
5 | */
6 |
7 | import { defineMessages } from 'react-intl';
8 |
9 | export const scope = 'containers.ResetPassword';
10 |
11 | export default defineMessages({
12 | helmetResetPasswordTitle: {
13 | id: `${scope}.helmetResetPasswordTitle`,
14 | defaultMessage: 'Reset Password Page',
15 | },
16 | resetPassword: {
17 | id: `${scope}.resetPassword`,
18 | defaultMessage: 'Reset Password',
19 | },
20 | resetSuccess: {
21 | id: `${scope}.resetSuccess`,
22 | defaultMessage: 'Password changed successful!',
23 | },
24 | resetPasswordBtn: {
25 | id: `${scope}.resetPasswordBtn`,
26 | defaultMessage: 'Reset',
27 | },
28 | back: {
29 | id: `${scope}.back`,
30 | defaultMessage: 'Go Back',
31 | },
32 | });
33 |
--------------------------------------------------------------------------------
/app/containers/ResetPassword/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * ResetPassword reducer
4 | *
5 | */
6 | import produce from 'immer';
7 | import {
8 | ADD_VALIDATION_ERROR,
9 | ASYNC_END,
10 | ASYNC_START,
11 | SET_FORM_VALUES,
12 | SET_RESET_CODE,
13 | CLEAR_FORM_VALUES,
14 | } from 'containers/ResetPassword/constants';
15 |
16 | export const initialState = {
17 | code: '',
18 | initialValues: {
19 | password: '',
20 | confirmPassword: '',
21 | },
22 | formValues: {},
23 | isLoading: false,
24 | errors: {},
25 | clearFormValue: false,
26 | };
27 |
28 | /* eslint-disable default-case, no-param-reassign */
29 | const resetPasswordReducer = produce((draft, action) => {
30 | switch (action.type) {
31 | case SET_FORM_VALUES:
32 | draft.formValues = action.formValues;
33 | break;
34 | case SET_RESET_CODE:
35 | draft.code = action.code;
36 | break;
37 | case ADD_VALIDATION_ERROR:
38 | draft.errors = action.errors;
39 | break;
40 | case ASYNC_START:
41 | draft.isLoading = true;
42 | break;
43 | case ASYNC_END:
44 | draft.isLoading = false;
45 | break;
46 | case CLEAR_FORM_VALUES:
47 | draft.clearFormValue = action.clearFormValue;
48 | break;
49 | }
50 | }, initialState);
51 |
52 | export default resetPasswordReducer;
53 |
--------------------------------------------------------------------------------
/app/containers/ResetPassword/saga.js:
--------------------------------------------------------------------------------
1 | import { call, put, select, takeLatest } from 'redux-saga/effects';
2 | import { RESET_PASSWORD } from 'containers/ResetPassword/constants';
3 | import {
4 | makeCodeSelector,
5 | makeFormValuesSelector,
6 | } from 'containers/ResetPassword/selectors';
7 | import ApiEndpoint from 'utils/api';
8 | import request from 'utils/request';
9 | import {
10 | asyncEndAction,
11 | asyncStartAction,
12 | enterValidationErrorAction,
13 | } from 'containers/ResetPassword/actions';
14 | import messages from 'containers/ResetPassword/messages';
15 | import { showAlert, showFormattedAlert } from 'common/saga';
16 | import { PUT } from 'utils/constants';
17 |
18 | export function* handleResetPassword() {
19 | yield put(asyncStartAction());
20 | const formValues = yield select(makeFormValuesSelector());
21 | const code = yield select(makeCodeSelector());
22 | const requestUrl = `/auth/reset-password`;
23 | const requestPayload = ApiEndpoint.makeApiPayload(requestUrl, PUT, {
24 | ...formValues,
25 | token: code,
26 | });
27 | try {
28 | const response = yield call(request, requestPayload);
29 | if (response && response.error) {
30 | yield put(asyncEndAction());
31 | if (typeof response.error === 'object') {
32 | return yield put(enterValidationErrorAction(response.error));
33 | }
34 | }
35 | yield showFormattedAlert('success', messages.resetSuccess);
36 | return yield put(asyncEndAction());
37 | } catch (error) {
38 | yield put(asyncEndAction());
39 | if (error.data && error.data.statusCode === 422) {
40 | return yield put(enterValidationErrorAction(error.data.message));
41 | }
42 | return yield showAlert('error', error.data.message);
43 | }
44 | }
45 |
46 | export default function* homePageSaga() {
47 | yield takeLatest(RESET_PASSWORD, handleResetPassword);
48 | }
49 |
--------------------------------------------------------------------------------
/app/containers/ResetPassword/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import { initialState } from 'containers/ResetPassword/reducer';
3 |
4 | const selectResetPasswordDomain = (state) =>
5 | state.resetPassword || initialState;
6 |
7 | const makePasswordSelector = () =>
8 | createSelector(selectResetPasswordDomain, (substate) => substate.password);
9 | const makeConfirmPasswordSelector = () =>
10 | createSelector(
11 | selectResetPasswordDomain,
12 | (substate) => substate.confirmPassword,
13 | );
14 |
15 | const makeIsLoadingSelector = () =>
16 | createSelector(selectResetPasswordDomain, (substate) => substate.isLoading);
17 |
18 | const makeErrorsSelector = () =>
19 | createSelector(selectResetPasswordDomain, (substate) => substate.errors);
20 |
21 | const makeCodeSelector = () =>
22 | createSelector(selectResetPasswordDomain, (substate) => substate.code);
23 |
24 | const makeFormValuesSelector = () =>
25 | createSelector(selectResetPasswordDomain, (substate) => substate.formValues);
26 |
27 | const makeInitialValuesSelector = () =>
28 | createSelector(
29 | selectResetPasswordDomain,
30 | (substate) => substate.initialValues,
31 | );
32 |
33 | const makeClearFormValueSelector = () =>
34 | createSelector(
35 | selectResetPasswordDomain,
36 | (substate) => substate.clearFormValue,
37 | );
38 |
39 | export {
40 | makeClearFormValueSelector,
41 | makeFormValuesSelector,
42 | makeInitialValuesSelector,
43 | makeIsLoadingSelector,
44 | makeCodeSelector,
45 | makePasswordSelector,
46 | makeConfirmPasswordSelector,
47 | makeErrorsSelector,
48 | };
49 |
--------------------------------------------------------------------------------
/app/containers/Role/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for Users
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import loadable from 'utils/loadable';
9 | import LoadingIndicator from 'components/LoadingIndicator';
10 |
11 | export default loadable(() => import('./index'), {
12 | fallback: ,
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/Role/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Role constants
4 | *
5 | */
6 |
7 | export const ADD_VALIDATION_ERROR = 'containers/Role/ADD_VALIDATION_ERROR';
8 | export const ASYNC_START = 'containers/Role/ASYNC_START';
9 | export const ASYNC_END = 'containers/Role/ASYNC_END';
10 | export const CHANGE_FORM_FIELD = 'containers/Role/CHANGE_FORM_FIELD';
11 | export const QUERY_ROLES = 'containers/Role/QUERY_ROLES';
12 | export const ASSIGN_ROLES = 'containers/Role/ASSIGN_ROLES';
13 | export const SET_PAGE_NUMBER = 'containers/Role/SET_PAGE_NUMBER';
14 | export const DELETE_ITEM_BY_ID = 'containers/Role/DELETE_ITEM_BY_ID';
15 | export const SUBMIT_FORM = 'containers/Role/SUBMIT_FORM';
16 | export const GET_ROLE_BY_ID = 'containers/Role/GET_ROLE_BY_ID';
17 | export const CLEAR_FORM = 'containers/Role/CLEAR_FORM';
18 | export const SET_KEYWORD = 'containers/Role/SET_KEYWORD';
19 | export const SET_FORM_METHOD = 'containers/Role/SET_FORM_METHOD';
20 | export const SET_FORM_VALUES = 'containers/Role/SET_FORM_VALUES';
21 | export const SET_INITIAL_VALUES = 'containers/Role/SET_INITIAL_VALUES';
22 | export const INITIATE_CLEAN = 'containers/Role/INITIATE_CLEAN';
23 | export const SET_PAGE_SIZE = 'containers/Role/SET_PAGE_SIZE';
24 | export const SET_ID = 'containers/Role/SET_ID';
25 | export const QUERY_PERMISSION_LIST = 'containers/Role/QUERY_PERMISSION_LIST';
26 | export const ASSIGN_PERMISSION_LIST = 'containers/Role/ASSIGN_PERMISSION_LIST';
27 |
--------------------------------------------------------------------------------
/app/containers/Role/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Role Messages
3 | *
4 | */
5 | import { defineMessages } from 'react-intl';
6 |
7 | export const scope = 'containers.Role';
8 |
9 | export default defineMessages({
10 | helmetTitle: {
11 | id: `${scope}.helmetTitle`,
12 | defaultMessage: 'Role',
13 | },
14 | editTitle: {
15 | id: `${scope}.editTitle`,
16 | defaultMessage: 'Edit Role',
17 | },
18 | addTitle: {
19 | id: `${scope}.addTitle`,
20 | defaultMessage: 'Add Role',
21 | },
22 | nameLabel: {
23 | id: `${scope}.nameLabel`,
24 | defaultMessage: 'Name',
25 | },
26 | namePlaceHolder: {
27 | id: `${scope}.namePlaceHolder`,
28 | defaultMessage: 'Input role name',
29 | },
30 | descriptionPlaceHolder: {
31 | id: `${scope}.descriptionPlaceHolder`,
32 | defaultMessage: 'Input description',
33 | },
34 | descriptionRequired: {
35 | id: `${scope}.descriptionRequired`,
36 | defaultMessage: 'The description should not be empty!',
37 | },
38 | nameRequired: {
39 | id: `${scope}.nameRequired`,
40 | defaultMessage: 'Role name should not be empty!',
41 | },
42 | dateLabel: {
43 | id: `${scope}.dateLabel`,
44 | defaultMessage: 'Created At',
45 | },
46 | addLabel: {
47 | id: `${scope}.addLabel`,
48 | defaultMessage: 'Add New',
49 | },
50 | descriptionLabel: {
51 | id: `${scope}.descriptionLabel`,
52 | defaultMessage: 'Description',
53 | },
54 | permissionLabel: {
55 | id: `${scope}.permissionLabel`,
56 | defaultMessage: 'Permission',
57 | },
58 | actionLabel: {
59 | id: `${scope}.actionLabel`,
60 | defaultMessage: 'Action',
61 | },
62 | createdAt: {
63 | id: `${scope}.createdAt`,
64 | defaultMessage: '{ts, date, ::yyyyMMdd}',
65 | },
66 | listTitle: {
67 | id: `${scope}.listTitle`,
68 | defaultMessage: 'Roles',
69 | },
70 | dashboardTitle: {
71 | id: `${scope}.dashboardTitle`,
72 | defaultMessage: 'Dashboard',
73 | },
74 | });
75 |
--------------------------------------------------------------------------------
/app/containers/SnackMessage/actions.js:
--------------------------------------------------------------------------------
1 | import {
2 | SNACK_UNMOUNT,
3 | AUTO_DISMISS_SNACK,
4 | CLEAR_SNACK,
5 | SHOW_SNACK_MESSAGE,
6 | } from 'containers/SnackMessage/constants';
7 |
8 | export function enqueueSnackMessageAction(snack) {
9 | return {
10 | type: SHOW_SNACK_MESSAGE,
11 | snack,
12 | };
13 | }
14 |
15 | export function clearSnackMessageAction() {
16 | return {
17 | type: CLEAR_SNACK,
18 | };
19 | }
20 |
21 | export function autoDismissSnackMessageAction() {
22 | return {
23 | type: AUTO_DISMISS_SNACK,
24 | };
25 | }
26 |
27 | export function snackUnmountAction() {
28 | return {
29 | type: SNACK_UNMOUNT,
30 | };
31 | }
32 |
--------------------------------------------------------------------------------
/app/containers/SnackMessage/constants.js:
--------------------------------------------------------------------------------
1 | export const CLEAR_SNACK = 'containers/AlertMessage/CLEAR_SNACK';
2 | export const SHOW_SNACK_MESSAGE = 'containers/AlertMessage/SHOW_SNACK_MESSAGE';
3 | export const AUTO_DISMISS_SNACK = 'containers/AlertMessage/AUTO_DISMISS_SNACK';
4 | export const SNACK_UNMOUNT = 'containers/AlertMessage/SNACK_UNMOUNT';
5 |
--------------------------------------------------------------------------------
/app/containers/SnackMessage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Snack Message
4 | *
5 | */
6 |
7 | import React, { useEffect } from 'react';
8 | import { useSelector } from 'react-redux';
9 | import { useInjectReducer } from 'utils/injectReducer';
10 | import reducer from 'containers/SnackMessage/reducer';
11 | import { useInjectSaga } from 'utils/injectSaga';
12 | import saga from 'containers/SnackMessage/saga';
13 | import { createStructuredSelector } from 'reselect';
14 | import {
15 | makeSnackMessageSelector,
16 | makeDurationSelector,
17 | makeSnackMessageTypeSelector,
18 | makeIdSelector,
19 | makeTranslateSelector,
20 | } from 'containers/SnackMessage/selectors';
21 | import { message } from 'antd';
22 | import { useIntl } from 'react-intl';
23 |
24 | const key = 'snackMessage';
25 |
26 | const stateSelector = createStructuredSelector({
27 | content: makeSnackMessageSelector(),
28 | duration: makeDurationSelector(),
29 | type: makeSnackMessageTypeSelector(),
30 | translate: makeTranslateSelector(),
31 | id: makeIdSelector(),
32 | });
33 |
34 | export default function SnackMessage() {
35 | useInjectReducer({ key, reducer });
36 |
37 | useInjectSaga({ key, saga });
38 | const intl = useIntl();
39 |
40 | const { content, type, duration, id, translate } = useSelector(stateSelector);
41 |
42 | useEffect(() => {
43 | if (content !== '' && id !== '') {
44 | message[type.toLowerCase()]({
45 | content: translate ? intl.formatMessage(content) : content,
46 | duration,
47 | key: id,
48 | });
49 | }
50 | }, [id]);
51 |
52 | return <>>;
53 | }
54 |
--------------------------------------------------------------------------------
/app/containers/SnackMessage/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Snack Message reducer
4 | *
5 | */
6 | import produce, { setAutoFreeze } from 'immer';
7 | import uuid from 'react-uuid';
8 | import {
9 | SHOW_SNACK_MESSAGE,
10 | CLEAR_SNACK,
11 | SNACK_UNMOUNT,
12 | } from 'containers/SnackMessage/constants';
13 |
14 | export const initialState = {
15 | message: '',
16 | type: '',
17 | duration: 3,
18 | translate: false,
19 | id: '',
20 | };
21 |
22 | setAutoFreeze(false);
23 | /* eslint-disable default-case, no-param-reassign */
24 | const snackMessageReducer = produce((draft, action) => {
25 | switch (action.type) {
26 | case SHOW_SNACK_MESSAGE:
27 | draft.message = action.snack.message;
28 | draft.type = action.snack.type;
29 | draft.translate = action.snack.translate;
30 | draft.duration = action.snack.duration || 3;
31 | draft.id = action.snack.id || uuid();
32 | break;
33 | case SNACK_UNMOUNT:
34 | case CLEAR_SNACK:
35 | draft.message = '';
36 | draft.type = '';
37 | draft.id = '';
38 | draft.translate = false;
39 | break;
40 | default:
41 | }
42 | }, initialState);
43 |
44 | export default snackMessageReducer;
45 |
--------------------------------------------------------------------------------
/app/containers/SnackMessage/saga.js:
--------------------------------------------------------------------------------
1 | import { delay, put, takeLatest } from 'redux-saga/effects';
2 | import { AUTO_DISMISS_SNACK } from 'containers/SnackMessage/constants';
3 | import { clearSnackMessageAction } from 'containers/SnackMessage/actions';
4 |
5 | export function* dismissSnackMessageAction() {
6 | yield delay(5000);
7 | yield put(clearSnackMessageAction());
8 | }
9 |
10 | export default function* snackBarSaga() {
11 | yield takeLatest(AUTO_DISMISS_SNACK, dismissSnackMessageAction);
12 | }
13 |
--------------------------------------------------------------------------------
/app/containers/SnackMessage/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import { initialState } from 'containers/SnackMessage/reducer';
3 |
4 | const selectSnackMessage = (state) => state.snackMessage || initialState;
5 |
6 | const makeSnackMessageSelector = () =>
7 | createSelector(selectSnackMessage, (substate) => substate.message);
8 |
9 | const makeSnackMessageTypeSelector = () =>
10 | createSelector(selectSnackMessage, (substate) => substate.type);
11 |
12 | const makeIdSelector = () =>
13 | createSelector(selectSnackMessage, (substate) => substate.id);
14 |
15 | const makeDurationSelector = () =>
16 | createSelector(selectSnackMessage, (substate) => substate.duration);
17 |
18 | const makeTranslateSelector = () =>
19 | createSelector(selectSnackMessage, (substate) => substate.translate);
20 |
21 | export {
22 | makeTranslateSelector,
23 | makeDurationSelector,
24 | makeIdSelector,
25 | makeSnackMessageSelector,
26 | makeSnackMessageTypeSelector,
27 | };
28 |
--------------------------------------------------------------------------------
/app/containers/UserAccount/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for HomePage
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import loadable from 'utils/loadable';
9 | import LoadingIndicator from 'components/LoadingIndicator';
10 |
11 | export default loadable(() => import('./index'), {
12 | fallback: ,
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/UserAccount/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * UserAccount constants
4 | *
5 | */
6 |
7 | export const ADD_VALIDATION_ERROR = 'app/UserAccount/ADD_VALIDATION_ERROR';
8 | export const ASYNC_END = 'app/UserAccount/ASYNC_END';
9 | export const ASYNC_START = 'app/UserAccount/ASYNC_START';
10 | export const CLEAR_FORM = 'app/UserAccount/CLEAR_FORM';
11 | export const INITIATE_CLEAN = 'app/UserAccount/INITIATE_CLEAN';
12 | export const SET_FORM_VALUES = 'app/UserAccount/SET_FORM_VALUES';
13 | export const SET_INITIAL_VALUES = 'app/UserAccount/SET_INITIAL_VALUES';
14 | export const SUBMIT_FORM = 'app/UserAccount/SUBMIT_FORM';
15 | export const DISABLE_TOKEN = 'app/UserAccount/DISABLE_TOKEN';
16 | export const UPDATE_TWO_FA_STATUS = 'app/UserAccount/UPDATE_TWO_FA_STATUS';
17 | export const SET_LIMIT = 'app/UserAccount/SET_LIMIT';
18 | export const SET_PAGE_SIZE = 'app/UserAccount/SET_PAGE_SIZE';
19 | export const QUERY_REFRESH_TOKEN_LIST =
20 | 'app/UserAccount/QUERY_REFRESH_TOKEN_LIST';
21 | export const ASSIGN_REFRESH_TOKEN_LIST =
22 | 'app/UserAccount/ASSIGN_REFRESH_TOKEN_LIST';
23 | export const SUBMIT_CHANGE_PASSWORD_FORM =
24 | 'app/UserAccount/SUBMIT_CHANGE_PASSWORD_FORM';
25 |
--------------------------------------------------------------------------------
/app/containers/UserAccount/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * VerifyAccount
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import { useInjectSaga } from 'utils/injectSaga';
9 | import saga from 'containers/UserAccount/saga';
10 | import reducer from 'containers/UserAccount/reducer';
11 | import { useInjectReducer } from 'utils/injectReducer';
12 | import { Tabs } from 'antd';
13 | import ProfileForm from 'containers/UserAccount/profileForm';
14 | import messages from 'containers/UserAccount/messages';
15 | import { useIntl } from 'react-intl';
16 | import SecurityTab from 'containers/UserAccount/securityTab';
17 | import LoginActivity from 'containers/UserAccount/loginActivity';
18 |
19 | const { TabPane } = Tabs;
20 | const key = 'userAccount';
21 |
22 | export default function UserAccount() {
23 | const intl = useIntl();
24 | useInjectSaga({ key, saga });
25 | useInjectReducer({ key, reducer });
26 |
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/app/containers/UserAccount/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import { initialState } from 'containers/UserAccount/reducer';
3 |
4 | const selectUserAccount = (state) => state.userAccount || initialState;
5 |
6 | const makeInitialValuesSelector = () =>
7 | createSelector(selectUserAccount, (substate) => substate.initialValues);
8 |
9 | const makeFormValuesSelector = () =>
10 | createSelector(selectUserAccount, (substate) => substate.formValues);
11 |
12 | const makeIsLoadingSelector = () =>
13 | createSelector(selectUserAccount, (substate) => substate.isLoading);
14 |
15 | const makeErrorSelector = () =>
16 | createSelector(selectUserAccount, (substate) => substate.errors);
17 |
18 | const makeInitiateCleanFieldSelector = () =>
19 | createSelector(selectUserAccount, (substate) => substate.initiateClean);
20 |
21 | const makeTokenListSelector = () =>
22 | createSelector(selectUserAccount, (substate) => substate.tokenList);
23 |
24 | const makePageSizeSelector = () =>
25 | createSelector(selectUserAccount, (substate) => substate.pageSize);
26 |
27 | const makeLimitSelector = () =>
28 | createSelector(selectUserAccount, (substate) => substate.limit);
29 |
30 | export {
31 | makePageSizeSelector,
32 | makeLimitSelector,
33 | makeTokenListSelector,
34 | makeInitialValuesSelector,
35 | makeFormValuesSelector,
36 | makeIsLoadingSelector,
37 | makeErrorSelector,
38 | makeInitiateCleanFieldSelector,
39 | };
40 |
--------------------------------------------------------------------------------
/app/containers/Users/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for Users
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import loadable from 'utils/loadable';
9 | import { Space, Skeleton, Row, Col } from 'antd';
10 |
11 | export default loadable(() => import('./index'), {
12 | fallback: (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | ),
32 | });
33 |
34 | // const LoadAble = () => (
35 | // //
36 | //
37 | //
38 | //
39 | // //
40 | // );
41 | //
42 | // export default LoadAble;
43 |
--------------------------------------------------------------------------------
/app/containers/Users/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Users constants
4 | *
5 | */
6 |
7 | export const ADD_VALIDATION_ERROR = 'containers/Users/ADD_VALIDATION_ERROR';
8 | export const ASYNC_START = 'containers/Users/ASYNC_START';
9 | export const ASYNC_END = 'containers/Users/ASYNC_END';
10 | export const QUERY_USERS = 'containers/Users/QUERY_USERS';
11 | export const QUERY_ROLES = 'containers/Users/QUERY_ROLES';
12 | export const ASSIGN_ROLES = 'containers/Users/ASSIGN_ROLES';
13 |
14 | export const SET_PAGE_NUMBER = 'containers/Users/SET_PAGE_NUMBER';
15 | export const SET_PAGE_SIZE = 'containers/Users/SET_PAGE_SIZE';
16 | export const DELETE_ITEM_BY_ID = 'containers/Users/DELETE_ITEM_BY_ID';
17 | export const SUBMIT_FORM = 'containers/Users/SUBMIT_FORM';
18 |
19 | export const GET_USER_BY_ID = 'containers/Users/GET_USER_BY_ID';
20 | export const CLEAR_FORM = 'containers/Users/CLEAR_FORM';
21 | export const CLEAR_FORM_FIELD = 'containers/Users/CLEAR_FORM_FIELD';
22 |
23 | export const ASSIGN_USERS = 'containers/Users/ASSIGN_USERS';
24 | export const SET_FORM_METHOD = 'containers/Users/SET_FORM_METHOD';
25 | export const SET_ID = 'containers/Users/SET_ID';
26 | export const SET_SEARCH_KEYWORD = 'containers/Users/SET_SEARCH_KEYWORD';
27 | export const SET_FORM_VALUES = 'containers/Users/SET_FORM_VALUES';
28 | export const SET_INITIAL_VALUES = 'containers/Users/SET_INITIAL_VALUES';
29 |
--------------------------------------------------------------------------------
/app/containers/VerifyAccount/Loadable.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Asynchronously loads the component for HomePage
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import loadable from 'utils/loadable';
9 | import LoadingIndicator from 'components/LoadingIndicator';
10 |
11 | export default loadable(() => import('./index'), {
12 | fallback: ,
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/VerifyAccount/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * VerifyAccount actions
4 | *
5 | */
6 |
7 | import {
8 | SET_VERIFY_CODE,
9 | VERIFY_VERIFY_CODE,
10 | } from 'containers/VerifyAccount/constants';
11 |
12 | /**
13 | * Check user is logged, this action starts the request saga
14 | *
15 | * @return {object} An action object with a type of IS_LOGGED
16 | */
17 |
18 | export function setVerifyCodeAction(code) {
19 | return {
20 | type: SET_VERIFY_CODE,
21 | code,
22 | };
23 | }
24 |
25 | export function verifyVerifyCodeAction() {
26 | return {
27 | type: VERIFY_VERIFY_CODE,
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/app/containers/VerifyAccount/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * VerifyAccount constants
4 | *
5 | */
6 |
7 | export const SET_VERIFY_CODE = 'app/VerifyAccount/SET_VERIFY_CODE';
8 | export const VERIFY_VERIFY_CODE = 'app/VerifyAccount/VERIFY_VERIFY_CODE';
9 |
--------------------------------------------------------------------------------
/app/containers/VerifyAccount/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * VerifyAccount
4 | *
5 | */
6 |
7 | import React, { useEffect } from 'react';
8 | import { useParams } from 'react-router-dom';
9 | import { useDispatch } from 'react-redux';
10 | import { useInjectSaga } from 'utils/injectSaga';
11 | // Import Actions
12 | import {
13 | setVerifyCodeAction,
14 | verifyVerifyCodeAction,
15 | } from 'containers/VerifyAccount/actions';
16 |
17 | import saga from 'containers/VerifyAccount/saga';
18 | import reducer from 'containers/VerifyAccount/reducer';
19 | import LoadingIndicator from 'components/LoadingIndicator';
20 | import { useInjectReducer } from 'utils/injectReducer';
21 |
22 | const key = 'verifyPage';
23 | export default function VerifyAccount() {
24 | const dispatch = useDispatch();
25 | useInjectSaga({ key, saga });
26 | useInjectReducer({ key, reducer });
27 | const { code } = useParams();
28 |
29 | useEffect(() => {
30 | if (code) {
31 | dispatch(setVerifyCodeAction(code));
32 | dispatch(verifyVerifyCodeAction());
33 | }
34 | }, [code]);
35 |
36 | return ;
37 | }
38 |
--------------------------------------------------------------------------------
/app/containers/VerifyAccount/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * VerifyAccount Messages
3 | *
4 | * This contains all the text for the VerifyAccount container.
5 | */
6 |
7 | import { defineMessages } from 'react-intl';
8 |
9 | export const scope = 'containers.VerifyAccount';
10 |
11 | export default defineMessages({
12 | activated: {
13 | id: `${scope}.activated`,
14 | defaultMessage: 'Account activated successfully, please login to continue!',
15 | },
16 | invalidVerification: {
17 | id: `${scope}.invalidVerification`,
18 | defaultMessage: 'Invalid verification link!',
19 | },
20 | });
21 |
--------------------------------------------------------------------------------
/app/containers/VerifyAccount/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * VerifyAccount reducer
4 | *
5 | */
6 | import produce from 'immer';
7 | import { SET_VERIFY_CODE } from 'containers/VerifyAccount/constants';
8 |
9 | export const initialState = {
10 | verifyCode: '',
11 | };
12 |
13 | /* eslint-disable default-case, no-param-reassign */
14 | const slackPageReducer = produce((draft, action) => {
15 | switch (action.type) {
16 | case SET_VERIFY_CODE:
17 | draft.verifyCode = action.code;
18 | break;
19 | }
20 | }, initialState);
21 |
22 | export default slackPageReducer;
23 |
--------------------------------------------------------------------------------
/app/containers/VerifyAccount/saga.js:
--------------------------------------------------------------------------------
1 | import { call, select, takeLatest } from 'redux-saga/effects';
2 | import { VERIFY_VERIFY_CODE } from 'containers/VerifyAccount/constants';
3 | import { makeVerifyCodeSelector } from 'containers/VerifyAccount/selectors';
4 | import ApiEndpoint from 'utils/api';
5 | import request from 'utils/request';
6 | import messages from 'containers/VerifyAccount/messages';
7 | import { showFormattedAlert } from 'common/saga';
8 | import { GET } from 'utils/constants';
9 |
10 | export function* handleVerifyCode() {
11 | const code = yield select(makeVerifyCodeSelector());
12 | const requestUrl = `/auth/activate-account?token=${code}`;
13 | const requestPayload = ApiEndpoint.makeApiPayload(requestUrl, GET);
14 | try {
15 | yield call(request, requestPayload);
16 | return yield showFormattedAlert('success', messages.activated);
17 | // return yield put(push(LOGIN_REDIRECT));
18 | } catch (e) {
19 | return yield showFormattedAlert('error', messages.invalidVerification);
20 | // return yield put(push(LOGIN_REDIRECT));
21 | }
22 | }
23 |
24 | export default function* homePageSaga() {
25 | yield takeLatest(VERIFY_VERIFY_CODE, handleVerifyCode);
26 | }
27 |
--------------------------------------------------------------------------------
/app/containers/VerifyAccount/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import { initialState } from 'containers/VerifyAccount/reducer';
3 |
4 | const selectVerifyPageDomain = (state) => state.verifyPage || initialState;
5 |
6 | const makeVerifyCodeSelector = () =>
7 | createSelector(selectVerifyPageDomain, (substate) => substate.verifyCode);
8 |
9 | export { makeVerifyCodeSelector };
10 |
--------------------------------------------------------------------------------
/app/global-styles.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | const GlobalStyle = createGlobalStyle`
4 | body {
5 | margin: 0;
6 | // font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
7 | // 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
8 | // sans-serif;
9 | font-family: 'Rubik', sans-serif;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 |
14 | code {
15 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
16 | monospace;
17 | }
18 |
19 | .otpInput {
20 | width: 3rem !important;
21 | height: 3rem;
22 | margin: 0 1rem;
23 | font-size: 2rem;
24 | text-align: center;
25 | border-radius: 4px;
26 | border: 1px solid rgba(0, 0, 0, 0.3);
27 | }
28 |
29 | `;
30 |
31 | export default GlobalStyle;
32 |
--------------------------------------------------------------------------------
/app/helpers/Validation.js:
--------------------------------------------------------------------------------
1 | import { Validator } from 'helpers/ValidatonModel';
2 |
3 | /**
4 | * Check if error exists
5 | * @param model
6 | */
7 | export const checkError = (model) => {
8 | const err = {};
9 | Object.keys(model).forEach((key) => {
10 | const validator = new Validator();
11 | const msg = validator.validate(
12 | model[key].value,
13 | model[key].validator,
14 | model[key].key || key,
15 | );
16 |
17 | if (msg.trim()) {
18 | err[key] = msg;
19 | }
20 | });
21 | return err;
22 | };
23 |
24 | export const getParameterByName = (name, url) => {
25 | /* eslint-disable default-case, no-param-reassign */
26 | if (!url) url = window.location.href;
27 | // eslint-disable-next-line no-useless-escape
28 | name = name.replace(/[\[\]]/g, '\\$&');
29 | const regex = new RegExp(`[?&]${name}(=([^]*)|&|#|$)`);
30 | const results = regex.exec(url);
31 | if (!results) return null;
32 | if (!results[2]) return '';
33 | return decodeURIComponent(results[2].replace(/\+/g, ' '));
34 | };
35 |
--------------------------------------------------------------------------------
/app/hooks/localstorage.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | export const useLocalStorage = (key, initialValue) => {
4 | const [storedValue, setStoredValue] = useState(() => {
5 | try {
6 | const item = window.localStorage.getItem(key);
7 | return item ? JSON.parse(item) : initialValue;
8 | } catch (error) {
9 | return initialValue;
10 | }
11 | });
12 |
13 | const setValue = (value) => {
14 | const valueToStore = value instanceof Function ? value(storedValue) : value;
15 | setStoredValue(valueToStore);
16 | window.localStorage.setItem(key, JSON.stringify(valueToStore));
17 | };
18 |
19 | return [storedValue, setValue];
20 | };
21 |
--------------------------------------------------------------------------------
/app/hooks/useCookie.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const getItem = (key) =>
4 | document.cookie.split('; ').reduce((total, currentCookie) => {
5 | const item = currentCookie.split('=');
6 | const storedKey = item[0];
7 | const storedValue = item[1];
8 |
9 | return key === storedKey ? decodeURIComponent(storedValue) : total;
10 | }, '');
11 |
12 | const setItem = (key, value, numberOfDays) => {
13 | const now = new Date();
14 |
15 | // set the time to be now + numberOfDays
16 | now.setTime(now.getTime() + numberOfDays * 60 * 60 * 24 * 1000);
17 |
18 | document.cookie = `${key}=${value}; expires=${now.toUTCString()}; path=/`;
19 | };
20 |
21 | /**
22 | *
23 | * @param {String} key The key to store our data to
24 | * @param {String} defaultValue The default value to return in case the cookie doesn't exist
25 | */
26 | const useCookie = (key, defaultValue) => {
27 | const getCookie = () => getItem(key) || defaultValue;
28 | const [cookie, setCookie] = useState(getCookie());
29 |
30 | const updateCookie = (value, numberOfDays) => {
31 | setCookie(value);
32 | setItem(key, value, numberOfDays);
33 | };
34 |
35 | return [cookie, updateCookie];
36 | };
37 |
38 | export { useCookie, getItem, setItem };
39 |
--------------------------------------------------------------------------------
/app/i18n.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | /**
3 | * i18n.js
4 | *
5 | * This will setup the i18n language files and locale data for your app.
6 | * IMPORTANT: This file is used by the internal build
7 | * script `extract-intl`, and must use CommonJS module syntax
8 | * You CANNOT use import/export in this file.
9 | */
10 |
11 | const enTranslationMessages = require('./i18n/en.json');
12 | const neTranslationMessages = require('./i18n/ne.json');
13 |
14 | const DEFAULT_LOCALE = 'en';
15 |
16 | const formatTranslationMessages = (locale, messages) => {
17 | const defaultFormattedMessages =
18 | locale !== DEFAULT_LOCALE
19 | ? formatTranslationMessages(DEFAULT_LOCALE, enTranslationMessages)
20 | : {};
21 | const flattenFormattedMessages = (formattedMessages, key) => {
22 | const formattedMessage =
23 | !messages[key] && locale !== DEFAULT_LOCALE
24 | ? defaultFormattedMessages[key]
25 | : messages[key];
26 | return Object.assign(formattedMessages, { [key]: formattedMessage });
27 | };
28 | return Object.keys(messages).reduce(flattenFormattedMessages, {});
29 | };
30 |
31 | const translationMessages = {
32 | en: formatTranslationMessages('en', enTranslationMessages),
33 | ne: formatTranslationMessages('ne', neTranslationMessages),
34 | };
35 |
36 | module.exports = {
37 | formatTranslationMessages,
38 | translationMessages,
39 | DEFAULT_LOCALE,
40 | };
41 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Truthy CMS
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/reducers/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Combine all reducers in this file and export the combined reducers.
3 | */
4 |
5 | import { combineReducers } from 'redux';
6 | import globalReducer from 'containers/App/reducer';
7 | import loginPageReducer from 'containers/LoginPage/reducer';
8 | import registerPageReducer from 'containers/RegisterPage/reducer';
9 | import alertMessageReducer from 'containers/AlertMessage/reducer';
10 | import snackMessageReducer from 'containers/SnackMessage/reducer';
11 | import verifyPageReducer from 'containers/VerifyAccount/reducer';
12 | import forgotPasswordReducer from 'containers/ForgotPassword/reducer';
13 | import resetPasswordReducer from 'containers/ResetPassword/reducer';
14 | import userAccountReducer from 'containers/UserAccount/reducer';
15 | import languageProviderReducer from 'containers/LanguageProvider/reducer';
16 | import roleReducer from 'containers/Role/reducer';
17 | import permissionReducer from 'containers/Permission/reducer';
18 | import usersReducer from 'containers/Users/reducer';
19 | import DashboardReducer from 'containers/Dashboard/reducer';
20 | import emailTemplateReducer from 'containers/EmailTemplate/reducer';
21 | import homePageReducer from 'containers/HomePage/reducer';
22 |
23 | /**
24 | * Merges the main reducer with the router state and dynamically injected reducers
25 | */
26 | export default function createReducer(injectedReducers = {}) {
27 | return combineReducers({
28 | global: globalReducer,
29 | homePage: homePageReducer,
30 | alertMessage: alertMessageReducer,
31 | snackMessage: snackMessageReducer,
32 | language: languageProviderReducer,
33 | login: loginPageReducer,
34 | register: registerPageReducer,
35 | forgotPassword: forgotPasswordReducer,
36 | resetPassword: resetPasswordReducer,
37 | verifyPage: verifyPageReducer,
38 | userAccount: userAccountReducer,
39 | dashboard: DashboardReducer,
40 | role: roleReducer,
41 | permission: permissionReducer,
42 | users: usersReducer,
43 | emailTemplate: emailTemplateReducer,
44 | ...injectedReducers,
45 | });
46 | }
47 |
--------------------------------------------------------------------------------
/app/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = (onPerfEntry) => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/app/routes/messages.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Common Messages
3 | */
4 |
5 | import { defineMessages } from 'react-intl';
6 |
7 | export const scope = 'routes.messages';
8 |
9 | export default defineMessages({
10 | dashboard: {
11 | id: `${scope}.dashboard`,
12 | defaultMessage: `Dashboard`,
13 | },
14 | rolePage: {
15 | id: `${scope}.rolePage`,
16 | defaultMessage: `Role`,
17 | },
18 | emailTemplatePage: {
19 | id: `${scope}.emailTemplatePage`,
20 | defaultMessage: `Email Template`,
21 | },
22 | userPage: {
23 | id: `${scope}.userPage`,
24 | defaultMessage: `User Management`,
25 | },
26 | permissionPage: {
27 | id: `${scope}.permissionPage`,
28 | defaultMessage: `Permission`,
29 | },
30 |
31 | settingPage: {
32 | id: `${scope}.settingPage`,
33 | defaultMessage: `Settings`,
34 | },
35 | });
36 |
--------------------------------------------------------------------------------
/app/services/cookie.service.js:
--------------------------------------------------------------------------------
1 | export default class CookieService {
2 | static checkCookieExists = (key) => {
3 | const token = this.readCookie(key);
4 | return !!token;
5 | };
6 |
7 | static readCookie = (name) => {
8 | const nameEQ = `${name}=`;
9 | const ca = document.cookie.split(';');
10 | for (let i = 0; i < ca.length; i += 1) {
11 | let c = ca[i];
12 | while (c.charAt(0) === ' ') c = c.substring(1, c.length);
13 | if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
14 | }
15 | return null;
16 | };
17 |
18 | static setCookie = (cname, cvalue, exdays) => {
19 | const d = new Date();
20 | d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
21 | const expires = `expires=${d.toUTCString()}`;
22 | document.cookie = `${cname}=${cvalue};${expires};path=/`;
23 | };
24 |
25 | static deleteCookie = (name) => {
26 | document.cookie = `${name}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/app/services/persist.service.js:
--------------------------------------------------------------------------------
1 | export const loadState = () => {
2 | try {
3 | const serializedState = localStorage.getItem('state');
4 | if (serializedState === null) {
5 | return true;
6 | }
7 | return JSON.parse(serializedState);
8 | } catch (e) {
9 | return false;
10 | }
11 | };
12 |
13 | export const saveState = (state) => {
14 | try {
15 | const serializedState = JSON.stringify(state);
16 | localStorage.setItem('state', serializedState);
17 | return true;
18 | } catch (e) {
19 | return false;
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/app/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/app/store/index.js:
--------------------------------------------------------------------------------
1 | import { loadState } from 'services/persist.service';
2 | import configureStore from 'configure-store';
3 |
4 | const initialState = loadState();
5 | export const store = configureStore(initialState);
6 |
--------------------------------------------------------------------------------
/app/utils/checkStore.js:
--------------------------------------------------------------------------------
1 | import { conformsTo, isFunction, isObject } from 'lodash';
2 | import invariant from 'invariant';
3 |
4 | /**
5 | * Validate the shape of redux store
6 | */
7 | export default function checkStore(store) {
8 | const shape = {
9 | dispatch: isFunction,
10 | subscribe: isFunction,
11 | getState: isFunction,
12 | replaceReducer: isFunction,
13 | runSaga: isFunction,
14 | injectedReducers: isObject,
15 | injectedSagas: isObject,
16 | };
17 | invariant(
18 | conformsTo(store, shape),
19 | '(app/utils...) injectors: Expected a valid redux store',
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/app/utils/colors.js:
--------------------------------------------------------------------------------
1 | export const PRIMARY_BLUE_DARK = '#0029ab';
2 | export const PRIMARY_BLUE_LIGHT = '#15a0dd';
3 | export const SECONDARY_BLUE_LIGHT = '#0098db';
4 | export const BORDER_GREY_LIGHT = 'rgba(0,0,0,0.12)';
5 | export const PRIMARY_BORDER_GREY = '#b1b1b1';
6 | export const PRIMARY_LIGHT = '#FFFFFF';
7 | export const PRIMARY_DARK = '#000000';
8 | export const PRIMARY_RED = '#e50000';
9 | export const PRIMARY_GREY = '#f2f4f7';
10 | export const SECONDARY_GREY = '#b7b7b7';
11 | export const PRIMARY_BACKGROUND_GREY = '#f7f7f7';
12 | export const SECONDARY_BACKGROUND_GREY = '#f3f3f3';
13 | export const SECONDARY_HOVER_GREY = 'rgba(0, 0, 0, 0.08)';
14 | export const TRANSPARENT = 'rgba(0, 0, 0, 0)';
15 |
--------------------------------------------------------------------------------
/app/utils/common.js:
--------------------------------------------------------------------------------
1 | export default class Common {
2 | static getParameterByName = (name, url) => {
3 | const defaultUri = '/dashboard';
4 | // eslint-disable-next-line no-param-reassign
5 | if (!url) url = window.location.href;
6 | // eslint-disable-next-line no-param-reassign,no-useless-escape
7 | name = name.replace(/[\[\]]/g, '\\$&');
8 | const regex = new RegExp(`[?&]${name}(=([^]*)|&|#|$)`);
9 | const results = regex.exec(url);
10 | if (!results) return null;
11 | if (!results[2]) return '';
12 | const uri = decodeURIComponent(results[2].replace(/\+/g, ' '));
13 | const exceptUri = ['/', '/login'];
14 | if (exceptUri.includes(uri)) {
15 | return defaultUri;
16 | }
17 | return uri;
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/app/utils/constants.js:
--------------------------------------------------------------------------------
1 | export const RESTART_ON_REMOUNT = '@@saga-injector/restart-on-remount';
2 | export const DAEMON = '@@saga-injector/daemon';
3 | export const ONCE_TILL_UNMOUNT = '@@saga-injector/once-till-unmount';
4 |
5 | export const POST = 'post';
6 | export const GET = 'get';
7 | export const DELETE = 'delete';
8 | export const PUT = 'put';
9 |
10 | export const StatusCodesList = {
11 | Success: 1001,
12 | ValidationError: 1002,
13 | InternalServerError: 1003,
14 | NotFound: 1004,
15 | UnauthorizedAccess: 1005,
16 | TokenExpired: 1006,
17 | TooManyTries: 1007,
18 | ServiceUnAvailable: 1008,
19 | ThrottleError: 1009,
20 | Forbidden: 1010,
21 | IncorrectOldPassword: 1011,
22 | UserInactive: 1012,
23 | BadRequest: 1013,
24 | InvalidCredentials: 1014,
25 | InvalidRefreshToken: 1015,
26 | UnsupportedFileType: 1016,
27 | OtpRequired: 1017,
28 | defaultItemDeleteError: 1018,
29 | RefreshTokenExpired: 1019,
30 | };
31 |
--------------------------------------------------------------------------------
/app/utils/history.js:
--------------------------------------------------------------------------------
1 | import { createBrowserHistory } from 'history';
2 | const history = createBrowserHistory();
3 | export default history;
4 |
--------------------------------------------------------------------------------
/app/utils/injectReducer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import hoistNonReactStatics from 'hoist-non-react-statics';
3 | import { useStore, ReactReduxContext } from 'react-redux';
4 |
5 | import getInjectors from './reducerInjectors';
6 |
7 | /**
8 | * Dynamically injects a reducer
9 | *
10 | * @param {string} key A key of the reducer
11 | * @param {function} reducer A reducer that will be injected
12 | *
13 | */
14 | export default ({ key, reducer }) =>
15 | (WrappedComponent) => {
16 | class ReducerInjector extends React.Component {
17 | static WrappedComponent = WrappedComponent;
18 |
19 | // eslint-disable-next-line react/static-property-placement
20 | static contextType = ReactReduxContext;
21 |
22 | // eslint-disable-next-line react/static-property-placement
23 | static displayName = `withReducer(${
24 | WrappedComponent.displayName || WrappedComponent.name || 'Component'
25 | })`;
26 |
27 | constructor(props, context) {
28 | super(props, context);
29 |
30 | getInjectors(context.store).injectReducer(key, reducer);
31 | }
32 |
33 | render() {
34 | return ;
35 | }
36 | }
37 |
38 | return hoistNonReactStatics(ReducerInjector, WrappedComponent);
39 | };
40 |
41 | const useInjectReducer = ({ key, reducer }) => {
42 | const store = useStore();
43 |
44 | React.useEffect(() => {
45 | getInjectors(store).injectReducer(key, reducer);
46 | }, []);
47 | };
48 |
49 | export { useInjectReducer };
50 |
--------------------------------------------------------------------------------
/app/utils/loadable.js:
--------------------------------------------------------------------------------
1 | import React, { lazy, Suspense } from 'react';
2 | import { ErrorBoundary } from 'react-error-boundary';
3 | import ErrorFallback from 'components/ErrorFallback';
4 |
5 | const loadable = (importFunc, { fallback = null } = { fallback: null }) => {
6 | const LazyComponent = lazy(importFunc);
7 |
8 | return (props) => (
9 | window.location.reload()}
12 | >
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default loadable;
21 |
--------------------------------------------------------------------------------
/app/utils/pagination.js:
--------------------------------------------------------------------------------
1 | export function pagination(current, totalItem, pageSize) {
2 | const last = Math.ceil(totalItem / pageSize);
3 | const start = Math.max(getPageStart(pageSize, current - 1), 0);
4 | const end = Math.min(getPageStart(pageSize, current), totalItem);
5 | const delta = 2;
6 | const left = current - delta;
7 | const right = current + delta + 1;
8 | const range = [];
9 | const rangeWithDots = [];
10 | let l = 0;
11 |
12 | for (let i = 1; i <= last; i += 1) {
13 | if (i === 1 || i === last || (i >= left && i < right)) {
14 | range.push(i);
15 | }
16 | }
17 |
18 | // eslint-disable-next-line no-restricted-syntax
19 | for (const i of range) {
20 | if (l) {
21 | if (i - l === 2) {
22 | rangeWithDots.push(l + 1);
23 | } else if (i - l !== 1) {
24 | rangeWithDots.push('...');
25 | }
26 | }
27 | rangeWithDots.push(i);
28 | l = i;
29 | }
30 |
31 | return {
32 | totalPages: last,
33 | pages: rangeWithDots,
34 | start: start + 1,
35 | end,
36 | };
37 | }
38 |
39 | function getPageStart(pageSize, pageNr) {
40 | return pageSize * pageNr;
41 | }
42 |
--------------------------------------------------------------------------------
/app/utils/permission.js:
--------------------------------------------------------------------------------
1 | export function checkPermissionForComponent(roles, route) {
2 | if (!roles || !roles.permission) return false;
3 | if (route.defaultPermission) return true;
4 | return roles.permission.some(
5 | (role) =>
6 | role.resource === route.resource &&
7 | role.path === route.path &&
8 | role.method === route.method,
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/utils/reducerInjectors.js:
--------------------------------------------------------------------------------
1 | import invariant from 'invariant';
2 | import { isEmpty, isFunction, isString } from 'lodash';
3 |
4 | import checkStore from './checkStore';
5 | import createReducer from '../reducers';
6 |
7 | export function injectReducerFactory(store, isValid) {
8 | return function injectReducer(key, reducer) {
9 | if (!isValid) checkStore(store);
10 |
11 | invariant(
12 | isString(key) && !isEmpty(key) && isFunction(reducer),
13 | '(app/utils...) injectReducer: Expected `reducer` to be a reducer function',
14 | );
15 |
16 | // Check `store.injectedReducers[key] === reducer` for hot reloading when a key is the same but a reducer is different
17 | if (
18 | Reflect.has(store.injectedReducers, key) &&
19 | store.injectedReducers[key] === reducer
20 | )
21 | return;
22 |
23 | store.injectedReducers[key] = reducer; // eslint-disable-line no-param-reassign
24 | store.replaceReducer(createReducer(store.injectedReducers));
25 | };
26 | }
27 |
28 | export default function getInjectors(store) {
29 | checkStore(store);
30 |
31 | return {
32 | injectReducer: injectReducerFactory(store, true),
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/app/utils/rwd.js:
--------------------------------------------------------------------------------
1 | export const PHONE_LANDSCAPE_VIEWPORT_WIDTH = '480px';
2 | export const TABLET_VIEWPORT_WIDTH = '768px';
3 | export const TABLET_LANDSCAPE_VIEWPORT_WIDTH = '1024px';
4 | export const DESKTOP_VIEWPORT_WIDTH = '1260px';
5 |
6 | export const HIDDEN_TOOLBAR_TITLE_VIEWPORT_WIDTH = '696px';
7 | export const TOGGLE_TOOLBAR_VIEWPORT_WIDTH = '660px';
8 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-env',
5 | {
6 | modules: false,
7 | },
8 | ],
9 | '@babel/preset-react',
10 | ],
11 | plugins: [
12 | 'styled-components',
13 | '@babel/plugin-proposal-class-properties',
14 | '@babel/plugin-syntax-dynamic-import',
15 | [
16 | 'import',
17 | {
18 | libraryName: '@ant-design/icons',
19 | libraryDirectory: '', // defaults to 'lib'
20 | camel2DashComponentName: false, // defaults to true
21 | },
22 | ],
23 | [
24 | 'formatjs',
25 | {
26 | idInterpolationPattern: '[sha512:contenthash:base64:6]',
27 | ast: true,
28 | },
29 | ],
30 | ],
31 | env: {
32 | production: {
33 | only: ['app'],
34 | plugins: [
35 | 'lodash',
36 | 'transform-react-remove-prop-types',
37 | '@babel/plugin-transform-react-inline-elements',
38 | '@babel/plugin-transform-react-constant-elements',
39 | ],
40 | },
41 | test: {
42 | plugins: [
43 | '@babel/plugin-transform-modules-commonjs',
44 | 'dynamic-import-node',
45 | ],
46 | },
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/bin/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const { execSync } = require('child_process');
3 |
4 | const RESET = '\x1b[0m';
5 | const GREEN = '\x1b[32m';
6 | const YELLOW = '\x1b[33m';
7 | const WHITE = '\x1b[37m';
8 | const RED = '\x1b[31m';
9 | const CYAN = '\x1b[36m';
10 | const MAGENTA = '\x1b[35m';
11 |
12 | const getColoredText = (text, color) => {
13 | if (color == null) {
14 | // eslint-disable-next-line no-param-reassign
15 | color = WHITE;
16 | }
17 | return color + text + RESET;
18 | };
19 |
20 | const runCommand = (command) => {
21 | try {
22 | execSync(`${command}`, { stdio: 'inherit' });
23 | } catch (e) {
24 | console.error(
25 | getColoredText(`Failed to execute ${command}. Error: ${e}`, RED),
26 | );
27 | return false;
28 | }
29 | return true;
30 | };
31 |
32 | const repoName = process.argv[2];
33 | const gitCheckoutCommand = `git clone --depth 1 https://github.com/gobeam/truthy-react-frontend ${repoName}`;
34 | const installDepsCommand = `cd ${repoName} && yarn install`;
35 |
36 | console.log(
37 | getColoredText(`Cloning the repository with name ${repoName}`, CYAN),
38 | );
39 | const checkedOut = runCommand(gitCheckoutCommand);
40 | if (!checkedOut) process.exit(-1);
41 |
42 | console.log(getColoredText(`Installing dependencies for ${repoName}`, YELLOW));
43 | const installedDeps = runCommand(installDepsCommand);
44 | if (!installedDeps) process.exit(-1);
45 |
46 | console.log(
47 | getColoredText(
48 | 'Congratulations! You are ready. Follow the following commands to start',
49 | MAGENTA,
50 | ),
51 | );
52 | console.log(getColoredText(`cd ${repoName} && yarn start`, GREEN));
53 |
--------------------------------------------------------------------------------
/internals/scripts/analyze.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const shelljs = require('shelljs');
4 | const chalk = require('chalk');
5 | const animateProgress = require('./helpers/progress');
6 | const addCheckMark = require('./helpers/checkmark');
7 |
8 | const progress = animateProgress('Generating stats');
9 |
10 | // Generate stats.json file with webpack
11 | shelljs.exec(
12 | 'webpack --config internals/webpack/webpack.prod.babel.js --profile --json > stats.json',
13 | addCheckMark.bind(null, callback), // Output a checkmark on completion
14 | );
15 |
16 | // Called after webpack has finished generating the stats.json file
17 | function callback() {
18 | clearInterval(progress);
19 | process.stdout.write(
20 | '\n\nOpen ' +
21 | chalk.magenta('http://webpack.github.io/analyse/') +
22 | ' in your browser and upload the stats.json file!' +
23 | chalk.blue(
24 | '\n(Tip: ' + chalk.italic('CMD + double-click') + ' the link!)\n\n',
25 | ),
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/internals/scripts/clean.js:
--------------------------------------------------------------------------------
1 | const shell = require('shelljs');
2 | const addCheckMark = require('./helpers/checkmark.js');
3 |
4 | if (!shell.which('git')) {
5 | shell.echo('Sorry, this script requires git');
6 | shell.exit(1);
7 | }
8 |
9 | if (!shell.test('-e', 'internals/templates')) {
10 | shell.echo('The example is deleted already.');
11 | shell.exit(1);
12 | }
13 |
14 | process.stdout.write('Cleanup started...');
15 |
16 | // Reuse existing LanguageProvider and i18n tests
17 | shell.mv(
18 | 'app/containers/LanguageProvider/tests',
19 | 'internals/templates/containers/LanguageProvider',
20 | );
21 | shell.cp('app/tests/i18n.test.js', 'internals/templates/tests/i18n.test.js');
22 |
23 | // Cleanup components/
24 | shell.rm('-rf', 'app/components/*');
25 |
26 | // Handle containers/
27 | shell.rm('-rf', 'app/containers');
28 | shell.mv('internals/templates/containers', 'app');
29 |
30 | // Handle tests/
31 | shell.mv('internals/templates/tests', 'app');
32 |
33 | // Handle translations/
34 | shell.rm('-rf', 'app/translations');
35 | shell.mv('internals/templates/translations', 'app');
36 |
37 | // Handle utils/
38 | shell.rm('-rf', 'app/utils');
39 | shell.mv('internals/templates/utils', 'app');
40 |
41 | // Replace the files in the root app/ folder
42 | shell.cp('internals/templates/app.js', 'app/app.js');
43 | shell.cp('internals/templates/global-styles.js', 'app/global-styles.js');
44 | shell.cp('internals/templates/i18n.js', 'app/i18n.js');
45 | shell.cp('internals/templates/index.html', 'app/index.html');
46 | shell.cp('internals/templates/reducers.js', 'app/reducers.js');
47 | shell.cp('internals/templates/configureStore.js', 'app/configureStore.js');
48 |
49 | // Remove the templates folder
50 | shell.rm('-rf', 'internals/templates');
51 |
52 | addCheckMark();
53 |
54 | // Commit the changes
55 | if (
56 | shell.exec('git add . --all && git commit -qm "Remove default example"')
57 | .code !== 0
58 | ) {
59 | shell.echo('\nError: Git commit failed');
60 | shell.exit(1);
61 | }
62 |
63 | shell.echo('\nCleanup done. Happy Coding!!!');
64 |
--------------------------------------------------------------------------------
/internals/scripts/helpers/checkmark.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 |
3 | /**
4 | * Adds mark check symbol
5 | */
6 | function addCheckMark(callback) {
7 | process.stdout.write(chalk.green(' ✓'));
8 | if (callback) callback();
9 | }
10 |
11 | module.exports = addCheckMark;
12 |
--------------------------------------------------------------------------------
/internals/scripts/helpers/get-npm-config.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | module.exports = JSON.parse(fs.readFileSync('package.json', 'utf8'));
4 |
--------------------------------------------------------------------------------
/internals/scripts/helpers/progress.js:
--------------------------------------------------------------------------------
1 | const readline = require('readline');
2 |
3 | /**
4 | * Adds an animated progress indicator
5 | *
6 | * @param {string} message The message to write next to the indicator
7 | * @param {number} [amountOfDots=3] The amount of dots you want to animate
8 | */
9 | function animateProgress(message, amountOfDots = 3) {
10 | let i = 0;
11 | return setInterval(() => {
12 | readline.cursorTo(process.stdout, 0);
13 | i = (i + 1) % (amountOfDots + 1);
14 | const dots = new Array(i + 1).join('.');
15 | process.stdout.write(message + dots);
16 | }, 500);
17 | }
18 |
19 | module.exports = animateProgress;
20 |
--------------------------------------------------------------------------------
/internals/scripts/helpers/xmark.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 |
3 | /**
4 | * Adds mark cross symbol
5 | */
6 | function addXMark(callback) {
7 | process.stdout.write(chalk.red(' ✘'));
8 | if (callback) callback();
9 | }
10 |
11 | module.exports = addXMark;
12 |
--------------------------------------------------------------------------------
/internals/scripts/npmcheckversion.js:
--------------------------------------------------------------------------------
1 | const { exec } = require('child_process');
2 | exec('npm -v', (err, stdout) => {
3 | if (err) throw err;
4 | if (parseFloat(stdout) < 5) {
5 | // NOTE: This can happen if you have a dependency which lists an old version of npm in its own dependencies.
6 | throw new Error(`[ERROR] You need npm version @>=5 but you have ${stdout}`);
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/internals/webpack/webpack.dev.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * DEVELOPMENT WEBPACK CONFIGURATION
3 | */
4 |
5 | const path = require('path');
6 | const webpack = require('webpack');
7 | const HtmlWebpackPlugin = require('html-webpack-plugin');
8 | const CircularDependencyPlugin = require('circular-dependency-plugin');
9 |
10 | module.exports = require('./webpack.base.babel')({
11 | mode: 'development',
12 |
13 | // Add hot reloading in development
14 | entry: [
15 | require.resolve('react-app-polyfill/ie11'),
16 | 'webpack-hot-middleware/client?reload=true',
17 | path.join(process.cwd(), 'app/app.js'), // Start with js/app.js
18 | ],
19 |
20 | // Don't use hashes in dev mode for better performance
21 | output: {
22 | filename: '[name].js',
23 | chunkFilename: '[name].chunk.js',
24 | },
25 |
26 | optimization: {
27 | splitChunks: {
28 | chunks: 'all',
29 | },
30 | },
31 |
32 | // Add development plugins
33 | plugins: [
34 | new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading
35 | new HtmlWebpackPlugin({
36 | inject: true, // Inject all files that are generated by webpack, e.g. bundle.js
37 | template: 'app/index.html',
38 | }),
39 | new CircularDependencyPlugin({
40 | exclude: /a\.js|node_modules/, // exclude node_modules
41 | failOnError: false, // show a warning when there is a circular dependency
42 | }),
43 | ],
44 |
45 | // Emit a source map for easier debugging
46 | // See https://webpack.js.org/configuration/devtool/#devtool
47 | devtool: 'eval-source-map',
48 |
49 | performance: {
50 | hints: false,
51 | },
52 | });
53 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es2016",
5 | "jsx": "preserve",
6 | "checkJs": true,
7 | "baseUrl": "app"
8 | },
9 | "exclude": ["node_modules", ".cache", "dist"]
10 | }
11 |
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | user nginx;
2 | worker_processes auto;
3 | error_log /var/log/nginx/error.log warn;
4 | pid /var/run/nginx.pid;
5 | events {
6 | worker_connections 1024;
7 | }
8 | http {
9 | include /etc/nginx/mime.types;
10 | default_type application/octet-stream;
11 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
12 | '$status $body_bytes_sent "$http_referer" '
13 | '"$http_user_agent" "$http_x_forwarded_for"';
14 | access_log /var/log/nginx/access.log main;
15 | sendfile on;
16 | #tcp_nopush on;
17 |
18 | keepalive_timeout 65;
19 | #gzip on;
20 | #include /etc/nginx/conf.d/*.conf;
21 | server {
22 | listen 80;
23 | location / {
24 | root /usr/share/nginx/html;
25 | index index.html index.htm;
26 | try_files $uri $uri/ /index.html;
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/server/argv.js:
--------------------------------------------------------------------------------
1 | module.exports = require('minimist')(process.argv.slice(2));
2 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | /* eslint consistent-return:0 import/order:0 */
2 |
3 | const express = require('express');
4 | const logger = require('./logger');
5 |
6 | const argv = require('./argv');
7 | const port = require('./port');
8 | const setup = require('./middlewares/frontendMiddleware');
9 | const isDev = process.env.NODE_ENV !== 'production';
10 | const ngrok =
11 | (isDev && process.env.ENABLE_TUNNEL) || argv.tunnel
12 | ? require('ngrok')
13 | : false;
14 | const { resolve } = require('path');
15 | const app = express();
16 |
17 | // If you need a backend, e.g. an API, add your custom backend-specific middleware here
18 | // app.use('/api', myApi);
19 |
20 | // In production we need to pass these values in instead of relying on webpack
21 | setup(app, {
22 | outputPath: resolve(process.cwd(), 'build'),
23 | publicPath: '/',
24 | });
25 |
26 | // get the intended host and port number, use localhost and port 3000 if not provided
27 | const customHost = argv.host || process.env.HOST;
28 | const host = customHost || '0.0.0.0'; // Let http.Server use its default IPv6/4 host
29 | const prettyHost = customHost || 'localhost';
30 |
31 | // use the gzipped bundle
32 | app.get('*.js', (req, res, next) => {
33 | req.url = req.url + '.gz'; // eslint-disable-line
34 | res.set('Content-Encoding', 'gzip');
35 | next();
36 | });
37 |
38 | // Start your app.
39 | app.listen(port, host, async (err) => {
40 | if (err) {
41 | return logger.error(err.message);
42 | }
43 |
44 | // Connect to ngrok in dev mode
45 | if (ngrok) {
46 | let url;
47 | try {
48 | url = await ngrok.connect(port);
49 | } catch (e) {
50 | return logger.error(e);
51 | }
52 | logger.appStarted(port, prettyHost, url);
53 | } else {
54 | logger.appStarted(port, prettyHost);
55 | }
56 | });
57 |
--------------------------------------------------------------------------------
/server/logger.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | const chalk = require('chalk');
4 | const ip = require('ip');
5 |
6 | const divider = chalk.gray('\n-----------------------------------');
7 |
8 | /**
9 | * Logger middleware, you can customize it to make messages more personal
10 | */
11 | const logger = {
12 | // Called whenever there's an error on the server we want to print
13 | error: (err) => {
14 | console.error(chalk.red(err));
15 | },
16 |
17 | // Called when express.js app starts on given port w/o errors
18 | appStarted: (port, host, tunnelStarted) => {
19 | console.log(`Server started ! ${chalk.green('✓')}`);
20 |
21 | // If the tunnel started, log that and the URL it's available at
22 | if (tunnelStarted) {
23 | console.log(`Tunnel initialised ${chalk.green('✓')}`);
24 | }
25 |
26 | console.log(`${chalk.bold('Access URLs:')}`);
27 | console.log(`${divider}`);
28 | console.log(
29 | `LAN: ${
30 | chalk.magenta(`http://${ip.address()}:${port}`) +
31 | (tunnelStarted
32 | ? `
33 | Proxy: ${chalk.magenta(tunnelStarted)}`
34 | : '')
35 | }`,
36 | );
37 | console.log(`${divider}`);
38 | console.log(`Localhost: ${chalk.magenta(`http://${host}:${port}`)}`);
39 | console.log(`${divider}`);
40 | console.log(`${chalk.blue(`Press ${chalk.italic('CTRL-C')} to stop`)}`);
41 | },
42 | };
43 |
44 | module.exports = logger;
45 |
--------------------------------------------------------------------------------
/server/middlewares/addDevMiddlewares.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const webpackDevMiddleware = require('webpack-dev-middleware');
4 | const webpackHotMiddleware = require('webpack-hot-middleware');
5 |
6 | function createWebpackMiddleware(compiler, publicPath) {
7 | return webpackDevMiddleware(compiler, {
8 | // logLevel: 'warn',
9 | publicPath,
10 | // silent: true,
11 | stats: {
12 | errorDetails: true,
13 | children: true,
14 | },
15 | });
16 | }
17 |
18 | module.exports = function addDevMiddlewares(app, webpackConfig) {
19 | const compiler = webpack(webpackConfig);
20 | const middleware = createWebpackMiddleware(
21 | compiler,
22 | webpackConfig.output.publicPath,
23 | );
24 |
25 | app.use(middleware);
26 | app.use(webpackHotMiddleware(compiler));
27 |
28 | // Since webpackDevMiddleware uses memory-fs internally to store build
29 | // artifacts, we use it instead
30 | const fs = compiler.outputFileSystem;
31 |
32 | app.get('*', (req, res) => {
33 | fs.readFile(path.join(compiler.outputPath, 'index.html'), (err, file) => {
34 | if (err) {
35 | res.sendStatus(404);
36 | } else {
37 | res.send(file.toString());
38 | }
39 | });
40 | });
41 | };
42 |
--------------------------------------------------------------------------------
/server/middlewares/addProdMiddlewares.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const express = require('express');
3 | const compression = require('compression');
4 |
5 | module.exports = function addProdMiddlewares(app, options) {
6 | const publicPath = options.publicPath || '/';
7 | const outputPath = options.outputPath || path.resolve(process.cwd(), 'build');
8 |
9 | // compression middleware compresses your server responses which makes them
10 | // smaller (applies also to assets). You can read more about that technique
11 | // and other good practices on official Express.js docs http://mxs.is/googmy
12 | app.use(compression());
13 | app.use(publicPath, express.static(outputPath));
14 |
15 | app.get('*', (req, res) =>
16 | res.sendFile(path.resolve(outputPath, 'index.html')),
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/server/middlewares/frontendMiddleware.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 |
3 | /**
4 | * Front-end middleware
5 | */
6 | module.exports = (app, options) => {
7 | const isProd = process.env.NODE_ENV === 'production';
8 |
9 | if (isProd) {
10 | const addProdMiddlewares = require('./addProdMiddlewares');
11 | addProdMiddlewares(app, options);
12 | } else {
13 | const webpackConfig = require('../../internals/webpack/webpack.dev.babel');
14 | const addDevMiddlewares = require('./addDevMiddlewares');
15 | addDevMiddlewares(app, webpackConfig);
16 | }
17 |
18 | return app;
19 | };
20 |
--------------------------------------------------------------------------------
/server/port.js:
--------------------------------------------------------------------------------
1 | const argv = require('./argv');
2 |
3 | module.exports = parseInt(process.env.PORT || argv.port || '3000', 10);
4 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "routes": [
3 | {
4 | "src": "/(.*)",
5 | "dest": "/"
6 | }
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------