repo setup done
This commit is contained in:
36
.eslintignore
Normal file
36
.eslintignore
Normal file
@@ -0,0 +1,36 @@
|
||||
dist/*
|
||||
src/model/offer-management
|
||||
|
||||
*.iml
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
/dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
#IDE
|
||||
.idea/
|
||||
|
||||
# Configuration
|
||||
config.js
|
||||
|
||||
#service-worker
|
||||
public/mockServiceWorker.js
|
||||
51
.eslintrc.json
Normal file
51
.eslintrc.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"jest": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["react", "@typescript-eslint", "react-hooks", "prettier", "import"],
|
||||
"settings": {
|
||||
"react": {
|
||||
"createClass": "createReactClass",
|
||||
"pragma": "React",
|
||||
"fragment": "Fragment",
|
||||
"version": "detect",
|
||||
"flowVersion": "0.53"
|
||||
},
|
||||
"import/resolver": {
|
||||
"node": {
|
||||
"extensions": [".js", ".jsx", ".ts", ".tsx", ".d.ts"]
|
||||
},
|
||||
"typescript": {}
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"no-console": "error",
|
||||
"@typescript-eslint/member-delimiter-style": "warn",
|
||||
"@typescript-eslint/ban-types": "warn",
|
||||
"@typescript-eslint/type-annotation-spacing": "warn",
|
||||
"@typescript-eslint/no-inferrable-types": "warn",
|
||||
"@typescript-eslint/no-empty-interface": "warn",
|
||||
"@typescript-eslint/no-empty-function": "warn",
|
||||
"@typescript-eslint/no-var-requires": "warn"
|
||||
}
|
||||
}
|
||||
42
.github/workflows/build.yml
vendored
Normal file
42
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, develop ]
|
||||
pull_request:
|
||||
branches: [ master, develop ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: [ default ]
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
|
||||
name: Build, Lint and Test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Check Build
|
||||
run: npm run build
|
||||
|
||||
- name: Check Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Check Code Formatting
|
||||
run: npm run prettier-check
|
||||
|
||||
- name: Validate unit tests
|
||||
run: npm run test
|
||||
24
.github/workflows/jira-id-checker.yml
vendored
Normal file
24
.github/workflows/jira-id-checker.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: JIRA
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master, develop ]
|
||||
types: ['opened', 'edited', 'reopened', 'synchronize']
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [ default ]
|
||||
|
||||
name: Jira ID Checker
|
||||
steps:
|
||||
- name: Login
|
||||
uses: atlassian/gajira-login@master
|
||||
env:
|
||||
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
|
||||
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
|
||||
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
|
||||
|
||||
- name: Check issue key in PR title
|
||||
uses: atlassian/gajira-find-issue-key@master
|
||||
with:
|
||||
string: ${{ github.event.pull_request.title }}
|
||||
108
.gitignore
vendored
Normal file
108
.gitignore
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
.idea
|
||||
|
||||
**/.DS_Store
|
||||
36
.husky/_/husky.sh
Normal file
36
.husky/_/husky.sh
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env sh
|
||||
if [ -z "$husky_skip_init" ]; then
|
||||
debug () {
|
||||
if [ "$HUSKY_DEBUG" = "1" ]; then
|
||||
echo "husky (debug) - $1"
|
||||
fi
|
||||
}
|
||||
|
||||
readonly hook_name="$(basename -- "$0")"
|
||||
debug "starting $hook_name..."
|
||||
|
||||
if [ "$HUSKY" = "0" ]; then
|
||||
debug "HUSKY env variable is set to 0, skipping hook"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -f ~/.huskyrc ]; then
|
||||
debug "sourcing ~/.huskyrc"
|
||||
. ~/.huskyrc
|
||||
fi
|
||||
|
||||
readonly husky_skip_init=1
|
||||
export husky_skip_init
|
||||
sh -e "$0" "$@"
|
||||
exitCode="$?"
|
||||
|
||||
if [ $exitCode != 0 ]; then
|
||||
echo "husky - $hook_name hook exited with code $exitCode (error)"
|
||||
fi
|
||||
|
||||
if [ $exitCode = 127 ]; then
|
||||
echo "husky - command not found in PATH=$PATH"
|
||||
fi
|
||||
|
||||
exit $exitCode
|
||||
fi
|
||||
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
yarn lint-staged
|
||||
2
.npmrc
Normal file
2
.npmrc
Normal file
@@ -0,0 +1,2 @@
|
||||
registry=https://nexus.cmd.navi-tech.in/repository/super-app/
|
||||
_authToken=NpmToken.1a3d3462-fb82-364c-bc64-0051e24635b3
|
||||
39
.prettierignore
Normal file
39
.prettierignore
Normal file
@@ -0,0 +1,39 @@
|
||||
dist/*
|
||||
src/model/offer-management
|
||||
|
||||
*.iml
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
/dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
#IDE
|
||||
.idea/
|
||||
|
||||
# Configuration
|
||||
config.js
|
||||
|
||||
# ESLint cache
|
||||
.eslintcache
|
||||
|
||||
#service-worker
|
||||
public/mockServiceWorker.js
|
||||
11
.prettierrc.json
Normal file
11
.prettierrc.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"printWidth": 100,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSameLine": false,
|
||||
"useTabs": false,
|
||||
"arrowParens": "avoid",
|
||||
"bracketSpacing": true
|
||||
}
|
||||
35
Dockerfile
Normal file
35
Dockerfile
Normal file
@@ -0,0 +1,35 @@
|
||||
FROM 193044292705.dkr.ecr.ap-south-1.amazonaws.com/common/node:14.15.5-alpine3.10 as build
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
RUN npm install
|
||||
RUN npm run lint
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.16.0-alpine
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
RUN adduser -u 4000 non-root-user -D ''
|
||||
RUN chown -R 4000:4000 /var/cache/nginx \
|
||||
&& chown -R 4000:4000 /etc/nginx/conf.d/ \
|
||||
&& chown -R 4000:4000 /usr/share/nginx \
|
||||
&& chmod -R g+w /var/cache/nginx \
|
||||
&& touch /var/run/nginx.pid \
|
||||
&& chown -R 4000:4000 /var/run/nginx.pid \
|
||||
&& ln -sf /dev/stdout /var/log/nginx/access.log \
|
||||
&& ln -sf /dev/stderr /var/log/nginx/error.log
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
|
||||
USER 4000
|
||||
|
||||
COPY nginx/nginx.conf /etc/nginx/conf.d
|
||||
COPY entrypoint.sh /
|
||||
COPY __mocks__ __mocks__
|
||||
|
||||
USER 0
|
||||
|
||||
RUN chmod +x entrypoint.sh
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
USER 4000
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
60
README.md
Normal file
60
README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
## morbius-portal
|
||||
|
||||
### System Requirements
|
||||
|
||||
Node version >=17.9.0
|
||||
|
||||
NPM version >=8.5.5
|
||||
|
||||
### Setup
|
||||
|
||||
Clone this repo and cd into the project directory
|
||||
|
||||
```
|
||||
cd morbius-portal
|
||||
yarn
|
||||
```
|
||||
|
||||
Install dependencies
|
||||
|
||||
```
|
||||
yarn add
|
||||
```
|
||||
|
||||
Start dev server
|
||||
|
||||
```
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
For production build
|
||||
|
||||
```
|
||||
yarn run build
|
||||
```
|
||||
|
||||
### Project Commands
|
||||
|
||||
Check Eslint-Prettier
|
||||
|
||||
```
|
||||
yarn run lint
|
||||
yarn run prettier-check
|
||||
```
|
||||
|
||||
Fix Lint and Prettier code formatting
|
||||
|
||||
```
|
||||
yarn run lint:fix
|
||||
yarn run format
|
||||
```
|
||||
|
||||
Pre commit hook
|
||||
|
||||
```
|
||||
yarn lint-staged
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Let's refine this project template as we progress. Cheers!!
|
||||
37
__mocks__/people.json
Normal file
37
__mocks__/people.json
Normal file
@@ -0,0 +1,37 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"firstName": "Mordecai",
|
||||
"lastName": "Jentges",
|
||||
"email": "mjentges0@theglobeandmail.com",
|
||||
"gender": "Male"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"firstName": "Haydon",
|
||||
"lastName": "Latliff",
|
||||
"email": "hlatliff1@paginegialle.it",
|
||||
"gender": "Male"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"firstName": "Yank",
|
||||
"lastName": "Anstead",
|
||||
"email": "yanstead2@1und1.de",
|
||||
"gender": "Male"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"firstName": "Cybil",
|
||||
"lastName": "Herculson",
|
||||
"email": "cherculson3@telegraph.co.uk",
|
||||
"gender": "Female"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"firstName": "Penelope",
|
||||
"lastName": "Charlot",
|
||||
"email": "pcharlot4@godaddy.com",
|
||||
"gender": "Female"
|
||||
}
|
||||
]
|
||||
7
config.js
Normal file
7
config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
window.config = {
|
||||
BFF_SERVICE_BASE_URL: 'https://dev-bff-service.np.navi-sa.in',
|
||||
APM_BASE_URL: 'https://apm-server.np.navi-sa.in/',
|
||||
APM_APP_NAME: 'navi-ops-tech-app-name',
|
||||
AUTH_BASE_URL: 'https://dev-dark-knight.np.navi-sa.in',
|
||||
AUTH_CLIENT_ID: 'KBDpUxvr5S'
|
||||
};
|
||||
7
config.template.js
Normal file
7
config.template.js
Normal file
@@ -0,0 +1,7 @@
|
||||
window.config = {
|
||||
BFF_SERVICE_BASE_URL: '<BFF_SERVICE_BASE_URL>',
|
||||
APM_BASE_URL: '<APM_BASE_URL>',
|
||||
APM_APP_NAME: '<APM_APP_NAME>',
|
||||
AUTH_BASE_URL: '<AUTH_BASE_URL>',
|
||||
AUTH_CLIENT_ID: '<AUTH_CLIENT_ID>'
|
||||
};
|
||||
9
entrypoint.sh
Normal file
9
entrypoint.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
sed -i "s~<BFF_SERVICE_BASE_URL>~${BFF_SERVICE_BASE_URL}~g" /usr/share/nginx/html/config.js
|
||||
sed -i "s~<APM_BASE_URL>~${APM_BASE_URL}~g" /usr/share/nginx/html/config.js
|
||||
sed -i "s~<APM_APP_NAME>~${APM_APP_NAME}~g" /usr/share/nginx/html/config.js
|
||||
sed -i "s~<AUTH_BASE_URL>~${AUTH_BASE_URL}~g" /usr/share/nginx/html/config.js
|
||||
sed -i "s~<AUTH_CLIENT_ID>~${AUTH_CLIENT_ID}~g" /usr/share/nginx/html/config.js
|
||||
|
||||
exec "$@"
|
||||
14
index.html
Normal file
14
index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sample Project</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="application/javascript" src="/config.js"></script>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
20
jest.config.js
Normal file
20
jest.config.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// eslint-disable-next-line no-undef
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testMatch: ['**/__tests__/**/*.+(ts|tsx|js)'],
|
||||
transform: {
|
||||
'^.+\\.tsx': 'ts-jest'
|
||||
},
|
||||
setupFilesAfterEnv: ['./setupTests.ts'],
|
||||
moduleNameMapper: {
|
||||
'\\.(css|scss|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||
'identity-obj-proxy'
|
||||
},
|
||||
snapshotSerializers: ['enzyme-to-json/serializer'],
|
||||
globals: {
|
||||
window: {
|
||||
config: {},
|
||||
XMLHttpRequest: function () {}
|
||||
}
|
||||
}
|
||||
};
|
||||
20
nginx/nginx.conf
Normal file
20
nginx/nginx.conf
Normal file
@@ -0,0 +1,20 @@
|
||||
server {
|
||||
listen 8080;
|
||||
client_max_body_size 100M;
|
||||
|
||||
gzip on;
|
||||
gzip_comp_level 4;
|
||||
gzip_types text/css application/javascript;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
78
package.json
Normal file
78
package.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "collections-portal",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"clean": "rm -rf dist",
|
||||
"copy-mocks": "cp -r __mocks__/ dist/__mocks__/",
|
||||
"copy-config": "cp config.template.js dist/config.js",
|
||||
"build": "npm run clean && tsc && vite build && npm run copy-mocks && npm run copy-config",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint \"**/?*.{ts,tsx,js}\"",
|
||||
"lint:fix": "eslint --fix \"**/?*.{ts,tsx,js}\"",
|
||||
"prettier-check": "prettier --check \"./**/*.{js,jsx,ts,tsx,css,scss,md}\"",
|
||||
"format": "prettier --write \"./**/*.{js,jsx,ts,tsx,css,scss,md}\"",
|
||||
"test": "jest",
|
||||
"lint-staged": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,css,scss,md}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{js,ts,tsx}": [
|
||||
"prettier --write",
|
||||
"eslint --cache --fix"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "yarn lint-staged"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@elastic/apm-rum": "^5.10.2",
|
||||
"@reduxjs/toolkit": "^1.7.2",
|
||||
"@super-app/dark-knight": "^1.0.5",
|
||||
"classnames": "^2.3.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-google-login": "^5.2.2",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-router-dom": "^6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/enzyme": "^3.10.11",
|
||||
"@types/jest": "^27.4.0",
|
||||
"@types/react": "^17.0.2",
|
||||
"@types/react-dom": "^17.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.11.0",
|
||||
"@typescript-eslint/parser": "^5.11.0",
|
||||
"@vitejs/plugin-react": "^1.0.7",
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.6",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-to-json": "^3.6.2",
|
||||
"eslint": "^8.8.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^27.5.1",
|
||||
"lint-staged": "13.0.3",
|
||||
"minimist": "^1.2.6",
|
||||
"postcss": "^8.4.6",
|
||||
"postcss-import": "^14.0.2",
|
||||
"postcss-nested": "^5.0.6",
|
||||
"prettier": "^2.5.1",
|
||||
"ts-jest": "^27.1.3",
|
||||
"typescript": "^4.5.4",
|
||||
"vite": "^2.8.0",
|
||||
"husky": "^8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=6.14.15",
|
||||
"node": ">=12.2.0"
|
||||
}
|
||||
}
|
||||
4
postcss.config.js
Normal file
4
postcss.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// eslint-disable-next-line no-undef
|
||||
module.exports = {
|
||||
plugins: [require('postcss-import'), require('postcss-nested')]
|
||||
};
|
||||
4
setupTests.ts
Normal file
4
setupTests.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { configure } from 'enzyme';
|
||||
import * as Adapter from '@wojtekmaj/enzyme-adapter-react-17';
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
18
src/App.module.css
Normal file
18
src/App.module.css
Normal file
@@ -0,0 +1,18 @@
|
||||
.app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.pageLayout {
|
||||
background-color: var(--main-bg);
|
||||
height: calc(100vh - 64px);
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 16px 16px 24px 0;
|
||||
border-radius: 8px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
113
src/App.tsx
Normal file
113
src/App.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { forwardRef, useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { init as initApm } from '@elastic/apm-rum';
|
||||
import DarkKnight from '@super-app/dark-knight';
|
||||
import AppRouter from './AppRouter';
|
||||
import { setAuthData, setToast, ToastSeverity } from './reducers/commonSlice';
|
||||
import { AppConfig } from './types/AppConfig';
|
||||
import Loader from './components/common/Loader';
|
||||
import Header from './components/Header/Header';
|
||||
import { RootState } from './store';
|
||||
import styles from './App.module.css';
|
||||
import Home from './components/Home/Home';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
config: AppConfig;
|
||||
}
|
||||
}
|
||||
|
||||
const config = window.config ?? {};
|
||||
|
||||
const initOptions = {
|
||||
url: config.AUTH_BASE_URL,
|
||||
clientId: config.AUTH_CLIENT_ID,
|
||||
onLoad: 'login-required'
|
||||
};
|
||||
|
||||
const auth = DarkKnight(initOptions);
|
||||
|
||||
interface AlertProps {
|
||||
children?: React.ReactNode;
|
||||
onClose?: any;
|
||||
severity: ToastSeverity;
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const authData = useSelector((state: RootState) => state.common.authData);
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
const { toast } = useSelector((state: RootState) => state.common);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
/** Uncomment below lines to enable authentication */
|
||||
// const token = localStorage.getItem('react-token');
|
||||
// auth
|
||||
// .init({
|
||||
// onLoad: 'check-sso',
|
||||
// enableLogging: true,
|
||||
// sessionToken: token ?? undefined
|
||||
// })
|
||||
// .then((authenticated: boolean) => {
|
||||
// if (!authenticated) {
|
||||
// auth.login({
|
||||
// redirectUri: `${window.location.protocol}//${window.location.host}/agent/login/?redirectUri=${window.location.href}`
|
||||
// });
|
||||
// }
|
||||
// dispatch(setAuthData(auth?.idTokenParsed));
|
||||
// localStorage.setItem('react-token', auth.sessionToken ?? '');
|
||||
// });
|
||||
}, []);
|
||||
|
||||
const handleLogoutClick = () => {
|
||||
const options = {
|
||||
logoutCallback: () => localStorage.removeItem('react-token')
|
||||
};
|
||||
|
||||
auth?.logout(options).catch(() => {
|
||||
dispatch(
|
||||
setToast({
|
||||
message: 'Logout failed! Please try again.',
|
||||
severity: ToastSeverity.ERROR
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleToastClose = (_event: React.SyntheticEvent | Event, reason: string) => {
|
||||
if (reason === 'clickaway') {
|
||||
return;
|
||||
}
|
||||
|
||||
setShowToast(false);
|
||||
};
|
||||
|
||||
initApm({
|
||||
serviceName: config.APM_APP_NAME,
|
||||
serverUrl: config.APM_BASE_URL,
|
||||
serviceVersion: '1.0.0'
|
||||
});
|
||||
|
||||
const { name: userName, sessionToken } = authData || {};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.app}>
|
||||
{sessionToken ? (
|
||||
<div className={styles.pageLayout}>
|
||||
<Header userName={userName} onLogout={handleLogoutClick} />
|
||||
<AppRouter />
|
||||
</div>
|
||||
) : (
|
||||
// <Loader showLoader={!sessionToken} />
|
||||
<>
|
||||
<Header userName={userName} onLogout={handleLogoutClick} />
|
||||
<Home />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
13
src/AppRouter.tsx
Normal file
13
src/AppRouter.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import Home from './components/Home/Home';
|
||||
|
||||
const AppRouter = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/home" />} />
|
||||
<Route path="/home" element={<Home />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppRouter;
|
||||
7
src/__tests__/App.test.tsx
Normal file
7
src/__tests__/App.test.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import App from '../App';
|
||||
|
||||
describe('App component', () => {
|
||||
it('sample test', () => {
|
||||
expect(true).toBeTruthy();
|
||||
});
|
||||
});
|
||||
BIN
src/assets/fonts/webfonts/Inter-Bold.ttf
Normal file
BIN
src/assets/fonts/webfonts/Inter-Bold.ttf
Normal file
Binary file not shown.
BIN
src/assets/fonts/webfonts/Inter-Medium.ttf
Normal file
BIN
src/assets/fonts/webfonts/Inter-Medium.ttf
Normal file
Binary file not shown.
BIN
src/assets/fonts/webfonts/Inter-Regular.ttf
Normal file
BIN
src/assets/fonts/webfonts/Inter-Regular.ttf
Normal file
Binary file not shown.
1
src/assets/images/navi-logo.svg
Normal file
1
src/assets/images/navi-logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="90" height="32" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="url(#a)" d="M0 0h89.905v32H0z"/><defs><pattern id="a" patternContentUnits="objectBoundingBox" width="1" height="1"><use xlink:href="#b" transform="scale(.00847 .0238)"/></pattern><image id="b" width="118" height="42" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHYAAAAqCAYAAABm66+dAAAHgklEQVR4Ae2bvY7VOBTH7yPwCDzBKnK6LXanWdZJtuANGCExCRVT0kEHEkjwBjPF9jMvwEcPElMibQESJVoNEg0Fkld/J/+bE89xnNzLDHelXCljx/FxnPPzsY8/ZrVSfi7Lrrs8v+fy/JUz5tzlubvUy5gPLs9PXJ7fcll2TanSkrSNBqBUZ8yzS4WYaiRtQ7q5zXcsskIDHmqev/upUCV0Y45E9ZboJhrYOagEbMzzTb5nkek04Ix5nrTUqtLH2Vg64YyF9+/rZUqZLNtbQG2ggc5RGlfwt2/O/05OhvmYfnQ0TJdgtDgaA2Xfv0/JvtvgsxYRZ8zhqLU+fdpC5V8Jl2lfv6bg9M8lVMpr8GValmULqZkacMa8ngUWMAiXYKaC1aA+etRDlzBl3JiHMz9ryZ6cp4YWS5iA++lTezcFbAwqumQJUY8fL6RmaiCp1BhYWi7gTgGLPPIHS+U4q8PsgRvzeuZnLdlngX37trdSQoLlsmuOAaJlUwbe8OfPvOsBxuTz/HQhNVMDs8ZYgIXyQ1BjYMO8nOJIC44Dbd+3jLEzqa5Wq+QcFiD4I9gQLuDF4FAWIaEiL8F+/x6X7ctclhjnonVZtheFQsWyC4YDxDSEL1+21humh3kAUULFc1g50sesvS3ny9xvWvJ3Gkh2xxLUVceNOVxApTVQ2ma/tM2Dv2zTr9S5LMtcnn8ZWONVA9TeZ8xZ+pOWHJVtjquicbxK2/Tz/p2Da8zZsjebbrR7e/vXCLQP6/OBpIdrzMcdsNzTBeoATfRGA1sWzUdVwBmz72AxWvd4uWkA2o8Rau2WxFADSle8H+YZ3Pt9WnjNV3ANXrzczNZAURzcxNha/nFn2TCZrb1FYPc0gLGoKO7+zmublg3ZH1HOlWvJb763h9qu8gwUTine28Rpwhyuss0reVFp1jbXS9sc9R5jPy2oivoccz/mHQtRTlXUzyATllXa5oMspyya17IuZVH7Iz4IZTriKHfsvXzmnaTgG6viwM/xK9u8rWzzz/oqmr8p50M/pu7CKUVjxgf/Qa1XK4wvobKRpU2/CCLMC/BBkYNbWKgGNCwHoCJeqt+d8g1QzDchT+iDFyo3GD/D97FRlLb+Vz4rbf1yXYSH+j89pYjVFvlhbfzgcBKMtaLb1r9WSBfRy5ZWP4yXRX0S1gUWzHJLW38ZPg/mnMwYhJjCSLmgTB3szkHllGriKUVd+b2llrY+9R4jvMaifn5RuY1DdxrocgXrQ7pUKOJe3jbHrRUdHPoyA8VLmSGEi5YHSw7fLe+1HkkuHUYt1hnz8CfMWYcbCoQZhhPmtTrYFoDmJLXjbmg5jZPKgmK17q+y9Rm7QKn8bgwcLO0RrgQLWaavQ9u8kmWFcTTMdV7ffQ8XIFSwk04pYmfmzZuLOzTY1XnxwrmppxSR/8mTaUB7wMlTijGwISipMBVa54wwX9j9QblaQ2F+b+GK5UqwyBsuKKBcrbEgr9YQQgvXwaZOKULBPO0g904BiUdbsNd6+3YaGPdgx/Zve6B9eYlTiipYW49uIGgygE1IrcMUjJ+2Tp7kgKcqrcs3BjHGonz13Z3nzPczDBsghgE0ID5HGAM7fkoRiiYQAMR9CJXpGhSZhnz4oTyZnoonTlDMURQVosoIsCqgxFjolew96KBBBGB9PkyJ1o4b8utOVOgPyMbHb4mBTf83nQSrQQ030WOgOq6zweb56CnFFCQqQIYpmdBSAAEysoxYfAgMU5reK6aMNvWBk8TnCLU8Wpetg41BkOkE+/jxsPsFqKlQUR5/8y12PV2QH854ChLzyTAlc9lgUZdwDIeTJOuIBjFoJLZRG/jmYDHGbgt1G7CJU4opSFJZjKdkrgTsyKKD5jShzqy/DHWwqf8EAJD9/e0slda/ucWunRr5QYynIDGfDFMyGtiwq5TlMa4B0bpi5PdTpME46+fT/lv98qN4FisD5cTApv/Tjl4xwczpfgl1G4tNzGVTkKh0GaZk1OcRz1WWq42LY1DCqQ8XShSnKbqIoYNtzzuNe6nybPCmUAEW0yX80FAk8LG4MfppAKFNFYLwcEXWdXSKzGB889ZTn4dTjXWBXeTCuNguKER9BHVaFSxLRk9E8J2xtWKX58ejioYnjGOiU+aqY5DQKHCUdU45EzYEpkAKAUyRCVd8ABprwWFZvA+tjw1jzGIhqzUGyvp3JhqparEouFsr3r1TigmniQqdAol5GU6R0aypU/SH8s/6HvdkK3v3lt+OE2PiAIwy3WE9EGrdN+W1BQkp28qP7+7s1hHUGacUp0AKlTFVRnOiqHQ1tPVZmJ6yWA9HWYr05USmOPJ7ohbLTN0Ge3olaqy7/RHPJu7qsN5TITE/wjkyoYcaguM9xsI5XrGsT6wBaQsSUg7xJFgKuDy/6fL8dHTc/REAh2VgKDhG42I9poZzILHMuTJ+60yxRkLF+ErHap3Wdc1TLBayFzzhRBfOb5kMlgII/Vnjyz+lOFjUlu+fEodSAEpeqZa+iQzqgnEXYyIsDBeAEyjrKuuBOGT4bCxEPimb+gaWVd2482t5o/6Nl7V3f/kPcOhP7xsQSOkAAAAASUVORK5CYII="/></defs></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
27
src/assets/styles/variables.css
Normal file
27
src/assets/styles/variables.css
Normal file
@@ -0,0 +1,27 @@
|
||||
:root {
|
||||
/* colors */
|
||||
--grayscale-1: #1c1c1c;
|
||||
--grayscale-2: #585757;
|
||||
--grayscale-3: #969696;
|
||||
|
||||
--grayscale-warm-1: #1d0b00;
|
||||
--grayscale-warm-2: #564840;
|
||||
--grayscale-warm-3: #99918c;
|
||||
|
||||
--grayscale-cold-1: #000f1d;
|
||||
--grayscale-cold-2: #404b56;
|
||||
--grayscale-cold-3: #8c9399;
|
||||
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #8c9399;
|
||||
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f7f7f7;
|
||||
--bg-secondary-warm: #f6f5f5;
|
||||
--bg-secondary-cold: #f5f6f6;
|
||||
--bg-dark: #12183e;
|
||||
|
||||
--border: #e8e8e8;
|
||||
--border-warm: #e9e7e6;
|
||||
--border-cold: #e6e7e9;
|
||||
}
|
||||
7
src/async-actions/common.ts
Normal file
7
src/async-actions/common.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import ApiHelper, { ApiKeys, getApiUrl } from '../utils/ApiHelper';
|
||||
|
||||
export const fetchPeopleList = createAsyncThunk('people/list', async () => {
|
||||
const url = getApiUrl(ApiKeys.PEOPLE);
|
||||
return await ApiHelper({ url });
|
||||
});
|
||||
17
src/components/Header/Header.module.css
Normal file
17
src/components/Header/Header.module.css
Normal file
@@ -0,0 +1,17 @@
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px 32px;
|
||||
background-color: var(--white);
|
||||
|
||||
.user {
|
||||
display: flex;
|
||||
|
||||
.username {
|
||||
align-self: center;
|
||||
margin-right: 16px;
|
||||
padding-right: 16px;
|
||||
border-right: 1px solid grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/components/Header/Header.tsx
Normal file
26
src/components/Header/Header.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import naviLogo from '../../assets/images/navi-logo.svg';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styles from './Header.module.css';
|
||||
|
||||
interface HeaderProps {
|
||||
userName?: string;
|
||||
onLogout: () => void;
|
||||
}
|
||||
|
||||
export default function Header({ userName, onLogout }: HeaderProps) {
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<Link to="/">
|
||||
<img src={naviLogo} width={90} height={32} alt="Navi logo" />
|
||||
</Link>
|
||||
{userName && (
|
||||
<div className={styles.user}>
|
||||
<div className={styles.username}>{userName}</div>
|
||||
<button className={styles.addItem} onClick={onLogout}>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
3
src/components/Home/Home.module.css
Normal file
3
src/components/Home/Home.module.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.home {
|
||||
padding: 20px 32px;
|
||||
}
|
||||
27
src/components/Home/Home.tsx
Normal file
27
src/components/Home/Home.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { fetchPeopleList } from '../../async-actions/common';
|
||||
import { RootState } from '../../store';
|
||||
import styles from './Home.module.css';
|
||||
|
||||
const Home = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { peopleList } = useSelector((state: RootState) => state.common);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchPeopleList());
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.home}>
|
||||
<div>People List</div>
|
||||
<ul>
|
||||
{peopleList?.map(person => (
|
||||
<li key={person.id}>{person.firstName}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
17
src/components/common/Loader.tsx
Normal file
17
src/components/common/Loader.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import cx from 'classnames';
|
||||
import styles from './common.module.css';
|
||||
|
||||
interface LoaderProps {
|
||||
showLoader: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Loader = ({ showLoader, className }: LoaderProps) => {
|
||||
if (!showLoader) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div className={styles.loader} />;
|
||||
};
|
||||
|
||||
export default Loader;
|
||||
71
src/components/common/common.module.css
Normal file
71
src/components/common/common.module.css
Normal file
@@ -0,0 +1,71 @@
|
||||
.loaderContainer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
z-index: 1500;
|
||||
|
||||
.loader {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.loader {
|
||||
border: 16px solid #f3f3f3; /* Light grey */
|
||||
border-top: 16px solid #3498db; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.dialogContainer {
|
||||
.dialogHeader {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.dialogTitle {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: var(--grayscale-1);
|
||||
}
|
||||
|
||||
.dialogSubTitle {
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--grayscale-2);
|
||||
}
|
||||
|
||||
.dialogActions {
|
||||
padding: 24px !important;
|
||||
border-top: 1px solid var(--border);
|
||||
|
||||
.cancelButton {
|
||||
color: var(--grayscale-1);
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--border);
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding: 8px 12px;
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/favicon.ico
Normal file
BIN
src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 250 KiB |
32
src/index.css
Normal file
32
src/index.css
Normal file
@@ -0,0 +1,32 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
|
||||
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
src: local('Inter'), url(./assets/fonts/webfonts/Inter-Regular.ttf) format('truetype');
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
src: local('Inter'), url(./assets/fonts/webfonts/Inter-Bold.ttf) format('truetype');
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
src: local('Inter'), url(./assets/fonts/webfonts/Inter-Medium.ttf) format('truetype');
|
||||
font-weight: 500;
|
||||
}
|
||||
7
src/logo.svg
Normal file
7
src/logo.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
20
src/main.tsx
Normal file
20
src/main.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import store from './store';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import './assets/styles/variables.css';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
50
src/reducers/commonSlice.ts
Normal file
50
src/reducers/commonSlice.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { DarkKnightTokenParsed } from '@super-app/dark-knight';
|
||||
import { fetchPeopleList } from '../async-actions/common';
|
||||
|
||||
export enum ToastSeverity {
|
||||
SUCCESS = 'success',
|
||||
ERROR = 'error',
|
||||
INFO = 'info',
|
||||
WARNING = 'warning'
|
||||
}
|
||||
|
||||
export interface ToastData {
|
||||
message: string;
|
||||
severity: ToastSeverity;
|
||||
}
|
||||
|
||||
export interface CommonState {
|
||||
authData?: DarkKnightTokenParsed;
|
||||
toast?: ToastData;
|
||||
peopleList?: any[]; // This is for demonstration purposes. should be removed.
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
authData: {} as DarkKnightTokenParsed
|
||||
} as CommonState;
|
||||
|
||||
export const commonSlice = createSlice({
|
||||
name: 'common',
|
||||
initialState,
|
||||
reducers: {
|
||||
setAuthData: (state, action) => {
|
||||
state.authData = { ...state.authData, ...action.payload };
|
||||
},
|
||||
setToast(state, action) {
|
||||
state.toast = action.payload;
|
||||
}
|
||||
},
|
||||
extraReducers: builder => {
|
||||
// This is for demonstration purposes only.
|
||||
// This does not belong here. You should be creating separate slices for your entities.
|
||||
// should be removed.
|
||||
builder.addCase(fetchPeopleList.fulfilled, (state, action) => {
|
||||
state.peopleList = action.payload;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const { setAuthData, setToast } = commonSlice.actions;
|
||||
|
||||
export default commonSlice.reducer;
|
||||
14
src/store/index.ts
Normal file
14
src/store/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { combineReducers } from 'redux';
|
||||
import commonReducer from '../reducers/commonSlice';
|
||||
|
||||
const reducer = combineReducers({
|
||||
common: commonReducer
|
||||
});
|
||||
|
||||
const store = configureStore({
|
||||
reducer
|
||||
});
|
||||
|
||||
export default store;
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
7
src/types/AppConfig.ts
Normal file
7
src/types/AppConfig.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface AppConfig {
|
||||
BFF_SERVICE_BASE_URL: string;
|
||||
APM_BASE_URL: string;
|
||||
APM_APP_NAME: string;
|
||||
AUTH_BASE_URL: string;
|
||||
AUTH_CLIENT_ID: string;
|
||||
}
|
||||
101
src/utils/ApiHelper.ts
Normal file
101
src/utils/ApiHelper.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { setToast, ToastSeverity } from '../reducers/commonSlice';
|
||||
import store from '../store';
|
||||
|
||||
const API_ROOT = `${window.config.BFF_SERVICE_BASE_URL}/api/v1`;
|
||||
const MOCK_DIR = '__mocks__';
|
||||
|
||||
// set this to `true` to use mock data for local development
|
||||
// to connect with real apis this should be set to `false`
|
||||
const USE_MOCK = true;
|
||||
|
||||
export enum ApiKeys {
|
||||
PEOPLE
|
||||
}
|
||||
|
||||
// TODO: try to get rid of `as`
|
||||
const API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
|
||||
API_URLS[ApiKeys.PEOPLE] = '/people';
|
||||
|
||||
// TODO: try to get rid of `as`
|
||||
const MOCK_API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
|
||||
MOCK_API_URLS[ApiKeys.PEOPLE] = 'people.json';
|
||||
|
||||
interface ApiHelperParams {
|
||||
url: string;
|
||||
method?: string;
|
||||
headers?: HeadersInit;
|
||||
body?: BodyInit;
|
||||
dataFormatter?: React.FunctionComponent;
|
||||
}
|
||||
|
||||
const ApiHelper = ({ url, method = 'GET', body, headers, dataFormatter }: ApiHelperParams) => {
|
||||
const token = localStorage.getItem('react-token');
|
||||
|
||||
return fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Session-Token': `${token}`
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json().then(data => ({ ok: true, data }));
|
||||
} else {
|
||||
return response.json().then(data => ({ ok: false, data }));
|
||||
}
|
||||
})
|
||||
.then(result => {
|
||||
if (!result.ok) {
|
||||
showError(result.data);
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if (typeof dataFormatter === 'function') {
|
||||
return dataFormatter(result.data);
|
||||
}
|
||||
return result.data;
|
||||
});
|
||||
};
|
||||
|
||||
export const showError = (error?: Error) => {
|
||||
store.dispatch(
|
||||
setToast({
|
||||
message: error?.message || 'Something went wrong',
|
||||
severity: ToastSeverity.ERROR
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export function getApiUrl(
|
||||
apiKey: ApiKeys,
|
||||
params?: Record<string, string | number>,
|
||||
queryParams?: Record<string, string | number>
|
||||
) {
|
||||
if (USE_MOCK) {
|
||||
return `${MOCK_DIR}/${MOCK_API_URLS[apiKey]}`;
|
||||
}
|
||||
|
||||
let apiUrl = API_URLS[apiKey];
|
||||
|
||||
// replace all {placeholders} with their values in params
|
||||
if (params) {
|
||||
Object.keys(params).forEach(paramKey => {
|
||||
apiUrl = apiUrl.replaceAll(`{${paramKey}}`, `${params[paramKey]}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (queryParams) {
|
||||
apiUrl +=
|
||||
'?' +
|
||||
Object.keys(queryParams)
|
||||
.map(key => `${key}=${queryParams[key]}`)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
return `${API_ROOT}${apiUrl}`;
|
||||
}
|
||||
|
||||
export default ApiHelper;
|
||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src", "config.js", "config.template.js"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
8
tsconfig.node.json
Normal file
8
tsconfig.node.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
10
vite.config.ts
Normal file
10
vite.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
https: true
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user