repo setup done

This commit is contained in:
aman.singh
2022-08-23 13:56:14 +05:30
commit d1a9dc25af
51 changed files with 6164 additions and 0 deletions

36
.eslintignore Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn lint-staged

2
.npmrc Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;

View File

@@ -0,0 +1,7 @@
import App from '../App';
describe('App component', () => {
it('sample test', () => {
expect(true).toBeTruthy();
});
});

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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

View 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;
}

View 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 });
});

View 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;
}
}
}

View 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>
);
}

View File

@@ -0,0 +1,3 @@
.home {
padding: 20px 32px;
}

View 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;

View 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;

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

32
src/index.css Normal file
View 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
View 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
View 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')
);

View 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
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

21
tsconfig.json Normal file
View 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
View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node"
},
"include": ["vite.config.ts"]
}

10
vite.config.ts Normal file
View 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
}
});

4898
yarn.lock Normal file

File diff suppressed because it is too large Load Diff