├── .env.example ├── .eslintrc.js ├── .github └── workflows │ └── webpack.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .prettierrc.js ├── .vscode ├── settings.json └── tasks.json ├── README.md ├── __json_server_mock__ ├── db.json └── seed.ts ├── commitlint.config.js ├── config ├── env.js ├── getHttpsConfig.js ├── jest │ ├── babelTransform.js │ ├── cssTransform.js │ └── fileTransform.js ├── modules.js ├── paths.js ├── webpack.config.js ├── webpack │ └── persistentCache │ │ └── createEnvironmentHash.js └── webpackDevServer.config.js ├── docs ├── React17+React Hook+TS4 最佳实践仿 Jira 企业级项目.md └── readme.md ├── jest.config.ts ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── mockServiceWorker.js └── robots.txt ├── scripts ├── build.js ├── start.js └── test.js ├── src ├── App.test.tsx ├── App.tsx ├── __test__ │ └── api.test.ts ├── api │ ├── apiClient.ts │ ├── auth.ts │ └── user.ts ├── assets │ └── Icons │ │ └── logo.svg ├── components │ ├── Auth │ │ ├── Login.tsx │ │ └── Register.tsx │ ├── Button │ │ └── FlowButton.tsx │ ├── Footer │ │ └── FlowFooter.tsx │ ├── Logo │ │ └── Logo.tsx │ ├── Navigation │ │ ├── FlowNavBar.tsx │ │ ├── NavLink.tsx │ │ ├── NavRoutes.tsx │ │ └── NavUserBar.tsx │ ├── SearchPanel │ │ └── SearchPanel.tsx │ ├── Select │ │ └── FlowSelect.tsx │ ├── Sidebar │ │ └── Sidebar.tsx │ ├── Table │ │ ├── FlowTable.tsx │ │ └── ReactTableCard.tsx │ ├── card │ │ └── Card.tsx │ └── dnd │ │ ├── DnD.tsx │ │ ├── board.tsx │ │ ├── column.tsx │ │ ├── constants.ts │ │ ├── data.ts │ │ ├── primatives │ │ ├── quote-item.tsx │ │ └── quote-list.tsx │ │ └── reorder.ts ├── context │ └── AuthContext.tsx ├── index.css ├── index.tsx ├── lib │ ├── customHooks.ts │ └── helper.ts ├── react-app-env.d.ts ├── react-table-config.d.ts ├── reportWebVitals.ts ├── setupTests.ts ├── type │ ├── Api.ts │ ├── Common.ts │ └── User.ts └── views │ ├── About.tsx │ ├── Dashboad.tsx │ ├── Layout.tsx │ ├── LoginView.tsx │ ├── Projects.tsx │ └── Users.tsx ├── tailwind.config.js ├── tsconfig.json └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_JSON_SERVER_API=http://localhost:3003 2 | # msw 3 | REACT_APP_API_URL=/api -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // 定义ESLint的解析器 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:react/recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'prettier', 8 | 'react-app', 9 | 'react-app/jest', 10 | ], // 定义文件继承的子规范 11 | plugins: ['react-hooks', 'eslint-plugin-react', 'react', 'prettier', 'html'], // 定义了该eslint文件所依赖的插件 12 | env: { 13 | // 指定代码的运行环境 14 | browser: true, 15 | node: true, 16 | es2021: true, 17 | }, 18 | settings: { 19 | // 自动发现React的版本,从而进行规范react代码 20 | react: { 21 | pragma: 'React', 22 | version: 'detect', 23 | }, 24 | }, 25 | parserOptions: { 26 | // 指定ESLint可以解析JSX语法 27 | ecmaFeatures: { 28 | jsx: true, 29 | }, 30 | ecmaVersion: 'latest', 31 | sourceType: 'module', 32 | }, 33 | rules: { 34 | // 自定义的一些规则: 0 关闭 1 警告 2 错误 35 | 'prettier/prettier': 1, 36 | 'linebreak-style': [2, 'unix'], 37 | 'react-hooks/rules-of-hooks': 2, 38 | 'react-hooks/exhaustive-deps': 1, 39 | 'react/jsx-uses-react': 1, 40 | 'react/jsx-uses-vars': 1, 41 | 'react/react-in-jsx-scope': 0, 42 | 'valid-typeof': [ 43 | 1, 44 | { 45 | requireStringLiterals: false, 46 | }, 47 | ], 48 | sime: 0, 49 | '@typescript-eslint/no-var-requires': 0, 50 | 'react/prop-types': 0, 51 | 'jsx-a11y/anchor-is-valid': 0, 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/webpack.yml: -------------------------------------------------------------------------------- 1 | name: NodeJS with Webpack 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | pull_request: 7 | branches: ['main'] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | 25 | - name: Build 26 | run: | 27 | yarn install 28 | REACT_APP_API_URL=/api yarn build 29 | - name: GitHub Pages 30 | uses: crazy-max/ghaction-github-pages@v3.0.0 31 | with: 32 | # Git branch where site will be deployed 33 | target_branch: gh-pages 34 | # Build directory to deploy 35 | build_dir: build 36 | # Write the given domain name to the CNAME file 37 | fqdn: jira.iknow.fun 38 | # Prevent Jekyll from building the site 39 | jekyll: false 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | -------------------------------------------------------------------------------- /.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 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .eslintcache 25 | .env -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit "${1}" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, //一行的字符数,如果超过会进行换行,默认为80 3 | tabWidth: 2, //一个tab代表几个空格数,默认为80 4 | useTabs: false, //是否使用tab进行缩进,默认为false,表示用空格进行缩减 5 | singleQuote: true, //字符串是否使用单引号,默认为false,使用双引号 6 | semi: false, //行位是否使用分号,默认为true 7 | trailingComma: 'es5', //是否使用尾逗号,有三个可选值"" 8 | bracketSpacing: true, //对象大括号直接是否有空格,默认为true,效果:{ foo: bar } 9 | arrowParens: 'always', // 箭头函数单个参数加分号 10 | bracketSameLine: true, 11 | }; 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.wordWrap": "on", 4 | "javascript.format.insertSpaceBeforeFunctionParenthesis": true, 5 | "eslint.validate": ["javascript", "html", "vue", "typescript"], 6 | "eslint.format.enable": true, 7 | "eslint.options": { 8 | "overrideConfig": { 9 | "env": { 10 | "browser": true, 11 | "node": true, 12 | "es2021": true 13 | }, 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true 17 | }, 18 | "ecmaVersion": "latest", 19 | "sourceType": "module" 20 | }, 21 | "rules": { 22 | "no-debugger": "off" 23 | } 24 | } 25 | }, 26 | "editor.formatOnSave": true, 27 | "html.format.indentHandlebars": true, 28 | "html.format.preserveNewLines": true, 29 | "editor.codeActionsOnSave": { 30 | "source.organizeImports": true, 31 | "source.fixAll": true, 32 | "source.fixAll.eslint": false 33 | }, 34 | "emmet.includeLanguages": { 35 | "vue": "html", 36 | "javascript": "html" 37 | }, 38 | "[vue]": { 39 | "editor.defaultFormatter": "octref.vetur" 40 | }, 41 | "[html]": { 42 | "editor.defaultFormatter": "esbenp.prettier-vscode" 43 | }, 44 | "editor.defaultFormatter": "esbenp.prettier-vscode", 45 | "[javascript]": { 46 | "editor.defaultFormatter": "esbenp.prettier-vscode" 47 | }, 48 | "[jsonc]": { 49 | "editor.defaultFormatter": "esbenp.prettier-vscode" 50 | }, 51 | "[css]": { 52 | "editor.defaultFormatter": "esbenp.prettier-vscode" 53 | }, 54 | "[typescript]": { 55 | "editor.defaultFormatter": "esbenp.prettier-vscode" 56 | }, 57 | "[json]": { 58 | "editor.defaultFormatter": "esbenp.prettier-vscode" 59 | }, 60 | // vue backup 61 | "vetur.format.defaultFormatterOptions": { 62 | "prettier": { 63 | "semi": true, 64 | "printWidth": 120, 65 | "singleQuote": false, 66 | "trailingComma": "aways", 67 | "arrowParens": "aways" 68 | } 69 | }, 70 | "vetur.format.defaultFormatter.js": "prettier-eslint", 71 | "vetur.format.defaultFormatter.html": "js-beautify-html" 72 | } 73 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "shell", 8 | "command": "yarn start", 9 | "group": "build", 10 | "problemMatcher": [], 11 | "label": "yarn:start" 12 | }, 13 | { 14 | "type": "shell", 15 | "command": "yarn server", 16 | "group": "build", 17 | "problemMatcher": [], 18 | "label": "yarn:server" 19 | }, 20 | { 21 | "type": "shell", 22 | "label": "start & server", 23 | "group": "build", 24 | "dependsOn": ["yarn:start", "yarn:server"] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Jira 2 | 3 | Demo: [jira.iknow.fun](https://jira.iknow.fun) 4 | 5 | [文档和笔记](./docs/readme.md) 6 | 7 | ## Available Scripts 8 | 9 | **`yarn start`** 10 | 11 | Frontend: `http://localhost:3000` 12 | 13 | Msw Mock Backend: `http://localhost:3000/api` 14 | 15 | **`yarn test`** 16 | 17 | Launches the test runner in the interactive watch mode.\ 18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | **`yarn build`** 21 | 22 | Builds the app for production to the `build` folder. 23 | 24 | **`yarn server`** 25 | 26 | json-server 27 | 28 | Json Server Mock Backend: `http://localhost:3003` 29 | 30 | https://github.com/typicode/json-server#getting-started 31 | 32 | **`yarn seed`** 33 | 34 | Seeding `./__json_server_mock__/db.json` 35 | 36 | ## Commit Message 37 | 38 | https://github.com/conventional-changelog/commitlint/tree/master/@commitlint/config-conventional 39 | 40 | ``` 41 | [ 42 | 'build', 43 | 'chore', 44 | 'ci', 45 | 'docs', 46 | 'feat', 47 | 'fix', 48 | 'perf', 49 | 'refactor', 50 | 'revert', 51 | 'style', 52 | 'test' 53 | ]; 54 | ``` 55 | 56 | ``` 57 | echo "foo: some message" # fails 58 | echo "fix: some message" # passes 59 | ``` 60 | 61 | https://nitayneeman.com/posts/understanding-semantic-commit-messages-using-git-and-angular/#motivation 62 | 63 | ### 👷 build 64 | 65 | The `build` type (formerly known as `chore`) is used to identify **development** changes related to the build system (involving scripts, configurations or tools) and package dependencies. 66 | 67 | ### 💚 ci 68 | 69 | The `ci` type is used to identify **development** changes related to the continuous integration and deployment system - involving scripts, configurations or tools. 70 | 71 | ### 📝 docs 72 | 73 | The `docs` type is used to identify documentation changes related to the project - whether intended externally for the end users (in case of a library) or internally for the developers. 74 | 75 | ### ✨ feat 76 | 77 | The `feat` type is used to identify **production** changes related to new backward-compatible abilities or functionality. 78 | 79 | ### 🐛 fix 80 | 81 | The `fix` type is used to identify **production** changes related to backward-compatible bug fixes. 82 | 83 | ### ⚡️ perf 84 | 85 | The `perf` type is used to identify **production** changes related to backward-compatible performance improvements. 86 | 87 | ### ♻️ refactor 88 | 89 | The `refactor` type is used to identify **development** changes related to modifying the codebase, which neither adds a feature nor fixes a bug - such as removing redundant code, simplifying the code, renaming variables, etc. 90 | 91 | ### 🎨 style 92 | 93 | The `style` type is used to identify **development** changes related to styling the codebase, regardless of the meaning - such as indentations, semi-colons, quotes, trailing commas and so on. 94 | 95 | ### ✅ test 96 | 97 | The `test` type is used to identify **development** changes related to tests - such as refactoring existing tests or adding new tests. 98 | 99 | ## UI 100 | 101 | https://flowbite.com/ 102 | 103 | https://flowbite-react.com/ 104 | 105 | ## jira-dev-tool Configuration 106 | 107 | [jira-dev-tool](/docs/readme.md#jira-dev-tool-mock-后端的配置) 108 | -------------------------------------------------------------------------------- /__json_server_mock__/db.json: -------------------------------------------------------------------------------- 1 | {"users":[{"id":1,"username":"Jordane.Mann","firstName":"Jordane","lastName":"Mann","gender":"Intersex person","email":"Jordane.Mann@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/604.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1991-10-06T15:28:06.374Z","registeredAt":"2021-10-04T14:01:45.697Z"},{"id":2,"username":"Adam.Frami","firstName":"Adam","lastName":"Frami","gender":"Trans","email":"Adam.Frami@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/418.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1944-04-21T10:36:42.402Z","registeredAt":"2021-08-20T15:40:26.505Z"},{"id":3,"username":"Henriette.Spencer","firstName":"Henriette","lastName":"Spencer","gender":"F2M","email":"Henriette.Spencer@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/1152.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1968-06-24T06:41:13.536Z","registeredAt":"2021-08-11T14:18:43.736Z"},{"id":4,"username":"Lucy.Daniel","firstName":"Lucy","lastName":"Daniel","gender":"Cis Male","email":"Lucy.Daniel@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/551.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1967-03-20T15:56:37.619Z","registeredAt":"2021-11-25T17:12:25.921Z"},{"id":5,"username":"Edyth.Schroeder","firstName":"Edyth","lastName":"Schroeder","gender":"Woman","email":"Edyth.Schroeder@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/968.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1984-06-23T06:18:34.689Z","registeredAt":"2022-03-26T01:09:42.997Z"},{"id":6,"username":"Alfredo.Thiel","firstName":"Alfredo","lastName":"Thiel","gender":"Trans","email":"Alfredo.Thiel@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/1184.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1971-11-26T23:29:15.147Z","registeredAt":"2021-07-30T12:08:04.897Z"},{"id":7,"username":"Reagan.Conroy","firstName":"Reagan","lastName":"Conroy","gender":"Cisgender Male","email":"Reagan.Conroy@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/1124.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1964-06-30T18:44:38.506Z","registeredAt":"2021-12-30T07:29:39.544Z"},{"id":8,"username":"Magali.DuBuque","firstName":"Magali","lastName":"DuBuque","gender":"Intersex person","email":"Magali.DuBuque@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/571.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1968-11-30T23:16:17.913Z","registeredAt":"2021-11-28T10:13:32.631Z"},{"id":9,"username":"Felipe.Schinner","firstName":"Felipe","lastName":"Schinner","gender":"Trans","email":"Felipe.Schinner@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/921.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1971-08-07T13:09:58.402Z","registeredAt":"2022-01-25T18:49:09.846Z"},{"id":10,"username":"Quincy.Torphy","firstName":"Quincy","lastName":"Torphy","gender":"Female to Male","email":"Quincy.Torphy@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/405.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1983-02-12T22:33:42.125Z","registeredAt":"2021-08-20T18:15:52.597Z"},{"id":11,"username":"Bryce.Upton","firstName":"Bryce","lastName":"Upton","gender":"FTM","email":"Bryce.Upton@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/885.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1946-12-17T17:44:59.624Z","registeredAt":"2022-05-29T07:09:13.852Z"},{"id":12,"username":"Herta.Dicki","firstName":"Herta","lastName":"Dicki","gender":"Cisgender Woman","email":"Herta.Dicki@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/247.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1982-05-27T20:46:48.654Z","registeredAt":"2022-03-17T05:29:47.815Z"},{"id":13,"username":"Brendan.Rolfson","firstName":"Brendan","lastName":"Rolfson","gender":"Cisgender Man","email":"Brendan.Rolfson@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/948.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1972-08-04T18:03:16.244Z","registeredAt":"2022-04-29T22:37:28.670Z"},{"id":14,"username":"Maximo.Crooks","firstName":"Maximo","lastName":"Crooks","gender":"Intersex man","email":"Maximo.Crooks@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/242.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1952-02-11T19:27:04.961Z","registeredAt":"2021-11-16T03:15:42.897Z"},{"id":15,"username":"Dana.Murazik","firstName":"Dana","lastName":"Murazik","gender":"Trans Person","email":"Dana.Murazik@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/442.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1946-04-14T17:32:25.366Z","registeredAt":"2021-09-19T01:24:17.321Z"},{"id":16,"username":"Demario.Vandervort","firstName":"Demario","lastName":"Vandervort","gender":"Transgender Female","email":"Demario.Vandervort@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/1165.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1976-01-03T18:15:09.836Z","registeredAt":"2021-11-06T20:12:13.086Z"},{"id":17,"username":"Kitty.Kilback","firstName":"Kitty","lastName":"Kilback","gender":"F2M","email":"Kitty.Kilback@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/1172.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1980-03-20T15:26:22.443Z","registeredAt":"2022-05-30T05:20:39.530Z"},{"id":18,"username":"Norwood.Abbott","firstName":"Norwood","lastName":"Abbott","gender":"Man","email":"Norwood.Abbott@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/134.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"2001-10-29T06:54:28.926Z","registeredAt":"2022-01-07T08:19:28.310Z"},{"id":19,"username":"Gerry.Mayert","firstName":"Gerry","lastName":"Mayert","gender":"Other","email":"Gerry.Mayert@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/344.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1960-08-23T23:55:56.427Z","registeredAt":"2021-09-11T15:06:46.253Z"},{"id":20,"username":"Marilyne.Waters","firstName":"Marilyne","lastName":"Waters","gender":"Trans Person","email":"Marilyne.Waters@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/522.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1973-10-16T12:26:08.595Z","registeredAt":"2022-01-21T13:50:27.926Z"},{"id":21,"username":"Hassie.Maggio","firstName":"Hassie","lastName":"Maggio","gender":"Other","email":"Hassie.Maggio@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/487.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1977-03-11T07:40:58.094Z","registeredAt":"2021-08-29T19:22:27.211Z"},{"id":22,"username":"Ryley.Jaskolski","firstName":"Ryley","lastName":"Jaskolski","gender":"Woman","email":"Ryley.Jaskolski@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/989.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1953-04-19T09:53:49.208Z","registeredAt":"2021-09-27T13:09:30.897Z"},{"id":23,"username":"Jarvis.Johnston","firstName":"Jarvis","lastName":"Johnston","gender":"Two* person","email":"Jarvis.Johnston@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/1122.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1976-05-13T14:48:32.136Z","registeredAt":"2021-09-03T12:03:01.738Z"},{"id":24,"username":"Jarrell.Casper","firstName":"Jarrell","lastName":"Casper","gender":"Transexual","email":"Jarrell.Casper@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/1208.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1965-10-24T20:17:48.419Z","registeredAt":"2022-01-31T20:59:07.652Z"},{"id":25,"username":"Miller.Hilpert","firstName":"Miller","lastName":"Hilpert","gender":"Male to female transsexual woman","email":"Miller.Hilpert@simplejira.com","avatar":"https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/293.jpg","password":"$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau","birthdate":"1968-01-01T17:10:04.554Z","registeredAt":"2022-01-01T08:12:48.621Z"}]} -------------------------------------------------------------------------------- /__json_server_mock__/seed.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker' 2 | import fs from 'fs' 3 | import path from 'path' 4 | import { User } from '../src/type/User' 5 | 6 | type DBJson = { 7 | users: User[] 8 | } 9 | 10 | export function createRandomUser(id: number): User { 11 | const user: User = { 12 | id: id + 1, 13 | username: '', 14 | firstName: faker.name.firstName(), 15 | lastName: faker.name.lastName(), 16 | gender: faker.name.gender(), 17 | email: '', 18 | avatar: faker.image.avatar(), 19 | password: '$2a$10$pEVcmfqCpY/A.dzP2WKxTOXyNNwNb.xkZMTisMeKnYQTOu12a5Fau', // nEiEQtUZ7DgL09m 20 | birthdate: faker.date.birthdate(), 21 | registeredAt: faker.date.past(), 22 | } 23 | user.username = user.firstName + '.' + user.lastName 24 | user.email = user.username + '@simplejira.com' 25 | return user 26 | } 27 | 28 | const generateUser = () => { 29 | const users: User[] = [] 30 | // Create 10 users 31 | Array.from({ length: 25 }).forEach((item, index) => { 32 | users.push(createRandomUser(index)) 33 | }) 34 | return users 35 | } 36 | 37 | const writeData = (data: DBJson, name = 'db.json') => { 38 | return fs.writeFileSync(path.join(__dirname, name), JSON.stringify(data)) 39 | } 40 | 41 | const starter = () => { 42 | console.log('Start seed...') 43 | 44 | const data = { users: generateUser() } 45 | writeData(data) 46 | } 47 | 48 | starter() 49 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] } 2 | -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const paths = require('./paths') 4 | 5 | // Make sure that including paths.js after env.js will read .env variables. 6 | delete require.cache[require.resolve('./paths')] 7 | 8 | const NODE_ENV = process.env.NODE_ENV 9 | if (!NODE_ENV) { 10 | throw new Error('The NODE_ENV environment variable is required but was not specified.') 11 | } 12 | 13 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 14 | const dotenvFiles = [ 15 | `${paths.dotenv}.${NODE_ENV}.local`, 16 | // Don't include `.env.local` for `test` environment 17 | // since normally you expect tests to produce the same 18 | // results for everyone 19 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | paths.dotenv, 22 | ].filter(Boolean) 23 | 24 | // Load environment variables from .env* files. Suppress warnings using silent 25 | // if this file is missing. dotenv will never modify any environment variables 26 | // that have already been set. Variable expansion is supported in .env files. 27 | // https://github.com/motdotla/dotenv 28 | // https://github.com/motdotla/dotenv-expand 29 | dotenvFiles.forEach((dotenvFile) => { 30 | if (fs.existsSync(dotenvFile)) { 31 | require('dotenv-expand')( 32 | require('dotenv').config({ 33 | path: dotenvFile, 34 | }) 35 | ) 36 | } 37 | }) 38 | 39 | // We support resolving modules according to `NODE_PATH`. 40 | // This lets you use absolute paths in imports inside large monorepos: 41 | // https://github.com/facebook/create-react-app/issues/253. 42 | // It works similar to `NODE_PATH` in Node itself: 43 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 44 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 45 | // Otherwise, we risk importing Node.js core modules into an app instead of webpack shims. 46 | // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 47 | // We also resolve them to make sure all tools using them work consistently. 48 | const appDirectory = fs.realpathSync(process.cwd()) 49 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 50 | .split(path.delimiter) 51 | .filter((folder) => folder && !path.isAbsolute(folder)) 52 | .map((folder) => path.resolve(appDirectory, folder)) 53 | .join(path.delimiter) 54 | 55 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 56 | // injected into the application via DefinePlugin in webpack configuration. 57 | const REACT_APP = /^REACT_APP_/i 58 | 59 | function getClientEnvironment(publicUrl) { 60 | const raw = Object.keys(process.env) 61 | .filter((key) => REACT_APP.test(key)) 62 | .reduce( 63 | (env, key) => { 64 | env[key] = process.env[key] 65 | return env 66 | }, 67 | { 68 | // Useful for determining whether we’re running in production mode. 69 | // Most importantly, it switches React into the correct mode. 70 | NODE_ENV: process.env.NODE_ENV || 'development', 71 | // Useful for resolving the correct path to static assets in `public`. 72 | // For example, . 73 | // This should only be used as an escape hatch. Normally you would put 74 | // images into the `src` and `import` them in code to get their paths. 75 | PUBLIC_URL: publicUrl, 76 | // We support configuring the sockjs pathname during development. 77 | // These settings let a developer run multiple simultaneous projects. 78 | // They are used as the connection `hostname`, `pathname` and `port` 79 | // in webpackHotDevClient. They are used as the `sockHost`, `sockPath` 80 | // and `sockPort` options in webpack-dev-server. 81 | WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST, 82 | WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH, 83 | WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT, 84 | // Whether or not react-refresh is enabled. 85 | // It is defined here so it is available in the webpackHotDevClient. 86 | FAST_REFRESH: process.env.FAST_REFRESH !== 'false', 87 | } 88 | ) 89 | // Stringify all values so we can feed into webpack DefinePlugin 90 | const stringified = { 91 | 'process.env': Object.keys(raw).reduce((env, key) => { 92 | env[key] = JSON.stringify(raw[key]) 93 | return env 94 | }, {}), 95 | } 96 | 97 | return { raw, stringified } 98 | } 99 | 100 | module.exports = getClientEnvironment 101 | -------------------------------------------------------------------------------- /config/getHttpsConfig.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const crypto = require('crypto') 4 | const chalk = require('react-dev-utils/chalk') 5 | const paths = require('./paths') 6 | 7 | // Ensure the certificate and key provided are valid and if not 8 | // throw an easy to debug error 9 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { 10 | let encrypted 11 | try { 12 | // publicEncrypt will throw an error with an invalid cert 13 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test')) 14 | } catch (err) { 15 | throw new Error(`The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`) 16 | } 17 | 18 | try { 19 | // privateDecrypt will throw an error with an invalid key 20 | crypto.privateDecrypt(key, encrypted) 21 | } catch (err) { 22 | throw new Error(`The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${err.message}`) 23 | } 24 | } 25 | 26 | // Read file and throw an error if it doesn't exist 27 | function readEnvFile(file, type) { 28 | if (!fs.existsSync(file)) { 29 | throw new Error( 30 | `You specified ${chalk.cyan(type)} in your env, but the file "${chalk.yellow(file)}" can't be found.` 31 | ) 32 | } 33 | return fs.readFileSync(file) 34 | } 35 | 36 | // Get the https config 37 | // Return cert files if provided in env, otherwise just true or false 38 | function getHttpsConfig() { 39 | const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env 40 | const isHttps = HTTPS === 'true' 41 | 42 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { 43 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE) 44 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE) 45 | const config = { 46 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), 47 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'), 48 | } 49 | 50 | validateKeyAndCerts({ ...config, keyFile, crtFile }) 51 | return config 52 | } 53 | return isHttps 54 | } 55 | 56 | module.exports = getHttpsConfig 57 | -------------------------------------------------------------------------------- /config/jest/babelTransform.js: -------------------------------------------------------------------------------- 1 | const babelJest = require('babel-jest').default 2 | 3 | const hasJsxRuntime = (() => { 4 | if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { 5 | return false 6 | } 7 | 8 | try { 9 | require.resolve('react/jsx-runtime') 10 | return true 11 | } catch (e) { 12 | return false 13 | } 14 | })() 15 | 16 | module.exports = babelJest.createTransformer({ 17 | presets: [ 18 | [ 19 | require.resolve('babel-preset-react-app'), 20 | { 21 | runtime: hasJsxRuntime ? 'automatic' : 'classic', 22 | }, 23 | ], 24 | ], 25 | babelrc: false, 26 | configFile: false, 27 | }) 28 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | // This is a custom Jest transformer turning style imports into empty objects. 2 | // http://facebook.github.io/jest/docs/en/webpack.html 3 | 4 | module.exports = { 5 | process() { 6 | return 'module.exports = {};' 7 | }, 8 | getCacheKey() { 9 | // The output is always the same. 10 | return 'cssTransform' 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const camelcase = require('camelcase') 3 | 4 | // This is a custom Jest transformer turning file imports into filenames. 5 | // http://facebook.github.io/jest/docs/en/webpack.html 6 | 7 | module.exports = { 8 | process(src, filename) { 9 | const assetFilename = JSON.stringify(path.basename(filename)) 10 | 11 | if (filename.match(/\.svg$/)) { 12 | // Based on how SVGR generates a component name: 13 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 14 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 15 | pascalCase: true, 16 | }) 17 | const componentName = `Svg${pascalCaseFilename}` 18 | return `const React = require('react'); 19 | module.exports = { 20 | __esModule: true, 21 | default: ${assetFilename}, 22 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 23 | return { 24 | $$typeof: Symbol.for('react.element'), 25 | type: 'svg', 26 | ref: ref, 27 | key: null, 28 | props: Object.assign({}, props, { 29 | children: ${assetFilename} 30 | }) 31 | }; 32 | }), 33 | };` 34 | } 35 | 36 | return `module.exports = ${assetFilename};` 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /config/modules.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const paths = require('./paths') 4 | const chalk = require('react-dev-utils/chalk') 5 | const resolve = require('resolve') 6 | 7 | /** 8 | * Get additional module paths based on the baseUrl of a compilerOptions object. 9 | * 10 | * @param {Object} options 11 | */ 12 | function getAdditionalModulePaths(options = {}) { 13 | const baseUrl = options.baseUrl 14 | 15 | if (!baseUrl) { 16 | return '' 17 | } 18 | 19 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl) 20 | 21 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is 22 | // the default behavior. 23 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { 24 | return null 25 | } 26 | 27 | // Allow the user set the `baseUrl` to `appSrc`. 28 | if (path.relative(paths.appSrc, baseUrlResolved) === '') { 29 | return [paths.appSrc] 30 | } 31 | 32 | // If the path is equal to the root directory we ignore it here. 33 | // We don't want to allow importing from the root directly as source files are 34 | // not transpiled outside of `src`. We do allow importing them with the 35 | // absolute path (e.g. `src/Components/Button.js`) but we set that up with 36 | // an alias. 37 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 38 | return null 39 | } 40 | 41 | // Otherwise, throw an error. 42 | throw new Error( 43 | chalk.red.bold( 44 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." + 45 | ' Create React App does not support other values at this time.' 46 | ) 47 | ) 48 | } 49 | 50 | /** 51 | * Get webpack aliases based on the baseUrl of a compilerOptions object. 52 | * 53 | * @param {*} options 54 | */ 55 | function getWebpackAliases(options = {}) { 56 | const baseUrl = options.baseUrl 57 | 58 | if (!baseUrl) { 59 | return {} 60 | } 61 | 62 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl) 63 | 64 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 65 | return { 66 | src: paths.appSrc, 67 | } 68 | } 69 | } 70 | 71 | /** 72 | * Get jest aliases based on the baseUrl of a compilerOptions object. 73 | * 74 | * @param {*} options 75 | */ 76 | function getJestAliases(options = {}) { 77 | const baseUrl = options.baseUrl 78 | 79 | if (!baseUrl) { 80 | return {} 81 | } 82 | 83 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl) 84 | 85 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 86 | return { 87 | '^src/(.*)$': '/src/$1', 88 | } 89 | } 90 | } 91 | 92 | function getModules() { 93 | // Check if TypeScript is setup 94 | const hasTsConfig = fs.existsSync(paths.appTsConfig) 95 | const hasJsConfig = fs.existsSync(paths.appJsConfig) 96 | 97 | if (hasTsConfig && hasJsConfig) { 98 | throw new Error( 99 | 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' 100 | ) 101 | } 102 | 103 | let config 104 | 105 | // If there's a tsconfig.json we assume it's a 106 | // TypeScript project and set up the config 107 | // based on tsconfig.json 108 | if (hasTsConfig) { 109 | const ts = require(resolve.sync('typescript', { 110 | basedir: paths.appNodeModules, 111 | })) 112 | config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config 113 | // Otherwise we'll check if there is jsconfig.json 114 | // for non TS projects. 115 | } else if (hasJsConfig) { 116 | config = require(paths.appJsConfig) 117 | } 118 | 119 | config = config || {} 120 | const options = config.compilerOptions || {} 121 | 122 | const additionalModulePaths = getAdditionalModulePaths(options) 123 | 124 | return { 125 | additionalModulePaths: additionalModulePaths, 126 | webpackAliases: getWebpackAliases(options), 127 | jestAliases: getJestAliases(options), 128 | hasTsConfig, 129 | } 130 | } 131 | 132 | module.exports = getModules() 133 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath') 4 | 5 | // Make sure any symlinks in the project folder are resolved: 6 | // https://github.com/facebook/create-react-app/issues/637 7 | const appDirectory = fs.realpathSync(process.cwd()) 8 | const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath) 9 | 10 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 11 | // "public path" at which the app is served. 12 | // webpack needs to know it to put the right