initial commit

This commit is contained in:
victorAnumudu
2024-06-14 18:06:25 +01:00
commit e707f95288
150 changed files with 11018 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+30
View File
@@ -0,0 +1,30 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default {
// other rules...
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
}
```
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Digifi</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+4175
View File
File diff suppressed because it is too large Load Diff
+38
View File
@@ -0,0 +1,38 @@
{
"name": "digifi-employer",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@reduxjs/toolkit": "^2.2.5",
"axios": "^1.7.2",
"formik": "^2.4.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.2.1",
"react-redux": "^9.1.2",
"react-router-dom": "^6.23.1",
"yup": "^1.4.0"
},
"devDependencies": {
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"typescript": "^5.2.2",
"vite": "^5.2.0"
}
}
+6
View File
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+74
View File
@@ -0,0 +1,74 @@
.btn-primary {
background: #5A2C82 !important;
color: #FFFFFF;
display: block;
text-decoration: none;
font-weight: 500;
cursor: pointer;
}
.btn-active {
background: #D10056 !important;
}
.btn-R {
padding-inline: 3rem !important;
font-weight: bold !important;
font-size: 16px !important;
background-color: #5A2C82;
}
.btn-W {
background: white !important;
border-radius: 8px;
font-size: 18px;
font-weight: bold;
color: #FBB700;
line-height: 25px;
align-items: center !important;
}
.btn-Y {
background: #FBB700 !important;
border-radius: 8px;
font-size: 18px;
font-weight: bold;
/* color: white; */
line-height: 25px;
align-items: center !important;
}
.sidebar {
position: fixed;
top: 0;
left: 0;
width: 330px;
/* Adjust the width as needed */
height: 100vh;
background-color: #5c2684;
color: #FFFFFF;
padding-top: .9375rem;
/* Set the background color */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
/* Add a box-shadow for visual separation */
transform: translateX(-100%);
/* Initially hide the sidebar */
transition: transform 0.4s ease;
/* Add a transition for smooth animation */
z-index: 9;
}
.sidebar.open {
transform: translateX(0);
/* Show the sidebar by removing the translation */
}
.sidebar-open {
border: 1px solid red;
}
.sidebar-close {
border: 1px solid green;
transform: translateX(0%);
}
+12
View File
@@ -0,0 +1,12 @@
import Routers from "./router/Router";
import "./App.css";
function App() {
return (
<>
<Routers />
</>
);
}
export default App;
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="50px" height="50px"><g fill="#ffffff" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(5.12,5.12)"><path d="M32,11h5c0.552,0 1,-0.448 1,-1v-6.737c0,-0.524 -0.403,-0.96 -0.925,-0.997c-1.591,-0.113 -4.699,-0.266 -6.934,-0.266c-6.141,0 -10.141,3.68 -10.141,10.368v6.632h-7c-0.552,0 -1,0.448 -1,1v7c0,0.552 0.448,1 1,1h7v19c0,0.552 0.448,1 1,1h7c0.552,0 1,-0.448 1,-1v-19h7.222c0.51,0 0.938,-0.383 0.994,-0.89l0.778,-7c0.066,-0.592 -0.398,-1.11 -0.994,-1.11h-8v-5c0,-1.657 1.343,-3 3,-3z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 856 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="64px" height="64px"><g fill="#ffffff" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(4,4)"><path d="M21.58008,7c-8.039,0 -14.58008,6.54494 -14.58008,14.58594v20.83203c0,8.04 6.54494,14.58203 14.58594,14.58203h20.83203c8.04,0 14.58203,-6.54494 14.58203,-14.58594v-20.83398c0,-8.039 -6.54494,-14.58008 -14.58594,-14.58008zM47,15c1.104,0 2,0.896 2,2c0,1.104 -0.896,2 -2,2c-1.104,0 -2,-0.896 -2,-2c0,-1.104 0.896,-2 2,-2zM32,19c7.17,0 13,5.83 13,13c0,7.17 -5.831,13 -13,13c-7.17,0 -13,-5.831 -13,-13c0,-7.169 5.83,-13 13,-13zM32,23c-4.971,0 -9,4.029 -9,9c0,4.971 4.029,9 9,9c4.971,0 9,-4.029 9,-9c0,-4.971 -4.029,-9 -9,-9z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 993 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="50px" height="50px"><g fill="#ffffff" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(5.12,5.12)"><path d="M41,4h-32c-2.76,0 -5,2.24 -5,5v32c0,2.76 2.24,5 5,5h32c2.76,0 5,-2.24 5,-5v-32c0,-2.76 -2.24,-5 -5,-5zM17,20v19h-6v-19zM11,14.47c0,-1.4 1.2,-2.47 3,-2.47c1.8,0 2.93,1.07 3,2.47c0,1.4 -1.12,2.53 -3,2.53c-1.8,0 -3,-1.13 -3,-2.53zM39,39h-6c0,0 0,-9.26 0,-10c0,-2 -1,-4 -3.5,-4.04h-0.08c-2.42,0 -3.42,2.06 -3.42,4.04c0,0.91 0,10 0,10h-6v-19h6v2.56c0,0 1.93,-2.56 5.81,-2.56c3.97,0 7.19,2.73 7.19,8.26z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 878 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="50px" height="50px"><g fill="#ffffff" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(5.12,5.12)"><path d="M6.91992,6l14.2168,20.72656l-14.9082,17.27344h3.17773l13.13867,-15.22266l10.44141,15.22266h10.01367l-14.87695,-21.6875l14.08008,-16.3125h-3.17578l-12.31055,14.26172l-9.7832,-14.26172z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 664 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="50px" height="50px"><g fill="#ffffff" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(5.12,5.12)"><path d="M25,2c-12.682,0 -23,10.318 -23,23c0,3.96 1.023,7.854 2.963,11.29l-2.926,10.44c-0.096,0.343 -0.003,0.711 0.245,0.966c0.191,0.197 0.451,0.304 0.718,0.304c0.08,0 0.161,-0.01 0.24,-0.029l10.896,-2.699c3.327,1.786 7.074,2.728 10.864,2.728c12.682,0 23,-10.318 23,-23c0,-12.682 -10.318,-23 -23,-23zM36.57,33.116c-0.492,1.362 -2.852,2.605 -3.986,2.772c-1.018,0.149 -2.306,0.213 -3.72,-0.231c-0.857,-0.27 -1.957,-0.628 -3.366,-1.229c-5.923,-2.526 -9.791,-8.415 -10.087,-8.804c-0.295,-0.389 -2.411,-3.161 -2.411,-6.03c0,-2.869 1.525,-4.28 2.067,-4.864c0.542,-0.584 1.181,-0.73 1.575,-0.73c0.394,0 0.787,0.005 1.132,0.021c0.363,0.018 0.85,-0.137 1.329,1.001c0.492,1.168 1.673,4.037 1.819,4.33c0.148,0.292 0.246,0.633 0.05,1.022c-0.196,0.389 -0.294,0.632 -0.59,0.973c-0.296,0.341 -0.62,0.76 -0.886,1.022c-0.296,0.291 -0.603,0.606 -0.259,1.19c0.344,0.584 1.529,2.493 3.285,4.039c2.255,1.986 4.158,2.602 4.748,2.894c0.59,0.292 0.935,0.243 1.279,-0.146c0.344,-0.39 1.476,-1.703 1.869,-2.286c0.393,-0.583 0.787,-0.487 1.329,-0.292c0.542,0.194 3.445,1.604 4.035,1.896c0.59,0.292 0.984,0.438 1.132,0.681c0.148,0.242 0.148,1.41 -0.344,2.771z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="50px" height="50px"><g fill="#ffffff" fill-rule="evenodd" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(5.12,5.12)"><path d="M13,5l3,9v6h2v-6l3,-9h-2l-2,6l-2,-6zM24,9c-1.06641,0 -1.58984,0.16797 -2.24219,0.70313c-0.625,0.52734 -0.79687,0.93359 -0.75781,2.29688v5c0,0.99609 0.16406,1.65234 0.76563,2.23438c0.625,0.58203 1.21484,0.76563 2.23438,0.76563c1.06641,0 1.64844,-0.18359 2.25,-0.76562c0.625,-0.55859 0.75,-1.23828 0.75,-2.23437v-5c0,-0.88281 -0.15625,-1.71875 -0.76172,-2.27734c-0.625,-0.57422 -1.26953,-0.72266 -2.23828,-0.72266zM29,9v9c0,0.97266 0.98047,2 2,2c1.01953,0 1.55859,-0.51172 2,-1v1h2v-11h-2v8c-0.01172,0.68359 -0.81641,1 -1,1c-0.20703,0 -1,-0.04297 -1,-1v-8zM24,11c0.30078,0 1,-0.00391 1,1v5c0,0.96875 -0.67578,1 -1,1c-0.30078,0 -1,-0.01172 -1,-1v-5c0,-0.81641 0.43359,-1 1,-1zM10,22c-3.59375,0 -6,2.38281 -6,6v9.5c0,3.61719 2.40625,6.5 6,6.5h30c3.59375,0 6,-2.38281 6,-6v-10c0,-3.61719 -2.40625,-6 -6,-6zM12,26h6v2h-2v12h-2v-12h-2zM26,26h2v4c0.23047,-0.35937 0.57422,-0.64453 0.90234,-0.80469c0.32031,-0.16406 0.64453,-0.25781 0.97266,-0.25781c0.64844,0 1.15625,0.23438 1.50391,0.67188c0.34766,0.44141 0.62109,1.02734 0.62109,1.89063v6c0,0.74219 -0.25,1.20313 -0.57812,1.59766c-0.32031,0.39453 -0.80078,0.89453 -1.42187,0.90234c-1.05078,0.01172 -1.61328,-0.55078 -2,-1v1h-2zM18,29h2v8c0,0.23047 0.26953,1.00781 1,1c0.8125,-0.00781 0.82031,-0.76562 1,-1v-8h2v11h-2v-1c-0.37109,0.4375 -0.5625,0.57422 -0.98047,0.78125c-0.41406,0.23438 -0.83594,0.21875 -1.22656,0.21875c-0.48437,0 -1.03516,-0.4375 -1.29297,-0.76562c-0.23047,-0.30078 -0.5,-0.60937 -0.5,-1.23437zM36.19922,29c0.94922,0 1.61719,0.20313 2.12109,0.73438c0.51563,0.53125 0.67969,1.15234 0.67969,2.15234v3.11328h-4v1.54688c0,0.55859 0.07422,0.91406 0.21875,1.125c0.13672,0.23047 0.41406,0.33203 0.78125,0.32813c0.40625,-0.00391 0.66406,-0.08594 0.80078,-0.26953c0.14063,-0.16406 0.19922,-0.62891 0.19922,-1.23047v-0.5h2v0.59375c0,1.08984 -0.08594,1.90234 -0.625,2.43359c-0.50781,0.55859 -1.30078,0.81641 -2.33984,0.81641c-0.94922,0 -1.69141,-0.28125 -2.22266,-0.85937c-0.53125,-0.57812 -0.80859,-1.37109 -0.80859,-2.39062v-4.70703c0,-0.90625 0.31641,-1.57812 0.89844,-2.17578c0.46875,-0.48047 1.34766,-0.71094 2.29688,-0.71094zM29,30.5c-0.55078,0 -0.99219,0.49609 -1,1v6c0.00781,0.28906 0.44922,0.5 1,0.5c0.55078,0 1,-0.42578 1,-0.97656v-5.02344c0,-1 -0.44922,-1.5 -1,-1.5zM36,31c-0.55078,0 -0.99219,0.46484 -1,1v1h2v-1c0,-0.61328 -0.44922,-1 -1,-1z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

+42
View File
@@ -0,0 +1,42 @@
import { Icons } from "../"
type Props = {
title?: string,
descText?: string,
iconName?: string,
iconColor?: string,
cardClass?: string,
titleClass?: string,
descTextClass?: string,
onClick?: ()=>any
}
export default function DefaultCard({
title,
descText,
iconName,
iconColor,
cardClass,
titleClass,
descTextClass,
onClick
}:Props) {
return (
<button
className={`h-full w-full rounded-lg p-5 shadow-lg hover:shadow-none bg-no-repeat bg-[90%] flex justify-between gap-4 items-center transition-all duration-300 ${cardClass && cardClass}`}
onClick={onClick}
>
<div className='w-3/4'>
<h1 className={`mb-1 text-[#FFF] text-lg text-left font-bold ${titleClass && titleClass}`}>{title}</h1>
<p className={`text-sm text-left ${descTextClass && descTextClass}`}>{descText}</p>
</div>
{iconName && // DISPLAYS ICON IF THERE IS ICON NAME PRESENT
<div className='group-hover:-translate-x-2 transition-all duration-300'>
<Icons name={iconName} fillColor={`${iconColor ? iconColor : '#FFF'}`} />
</div>
}
</button>
)
}
+3
View File
@@ -0,0 +1,3 @@
import DefaultCard from "./DefaultCard";
export { DefaultCard };
@@ -0,0 +1,116 @@
import { Button, InputCompOne, Stepper } from "..";
import {Formik, Form} from 'formik'
import * as Yup from "yup";
type Props = {
handleNextStep:(value:{})=>any
}
const initialValues = {
loan_amount: "",
payment_month: "",
sales_agent: "",
};
// To get the validation schema
const validationSchema = Yup.object().shape({
payment_month: Yup.string()
.required("Required"),
loan_amount: Yup.string()
.required("Required")
.test("no-e", "Invalid", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
}),
sales_agent: Yup.string()
});
export default function DashboardFormInit({handleNextStep}:Props) {
//FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:{}) => {
handleNextStep(values)
};
return (
<div className="w-full">
<div className="w-full flex justify-center">
<Stepper step={0} />
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props)=>(
<Form>
<div className="mt-[3.25rem] flex flex-col gap-9">
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="loan_amount"
label="How Much Do You Want To Apply For?"
labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem] text-right"
placeholder="350,000"
value={props.values.loan_amount}
onChange={props.handleChange}
error={(props.errors.loan_amount && props.touched.loan_amount) ? props.errors.loan_amount : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="payment_month"
label="For How Many Months?"
labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={paymentMonth}
selectValue={props.values.payment_month}
onChange={props.handleChange}
error={(props.errors.payment_month && props.touched.payment_month) ? props.errors.payment_month : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="sales_agent"
label="Direct sales agent ID ( Optional )"
labelClass="font-bold text-[1.125rem]"
floatLabel='Enter agent ID'
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Agent ID"
value={props.values.sales_agent}
onChange={props.handleChange}
error={(props.errors.sales_agent && props.touched.sales_agent) ? props.errors.sales_agent : ''}
/>
<Button
className="my-8 max-w-[25.875rem] btn-Y text-black w-full h-11"
text="Next"
type="submit"
/>
</div>
</Form>
)}
</Formik>
</div>
);
}
interface SelectOption {
loading: boolean;
data: {value: string;
label: string}[]
}
const paymentMonth: SelectOption = {
loading: false,
data: [
{ value: "", label: "Please Select" },
{ value: "6", label: "6 Months" },
{ value: "12", label: "12 Months" },
{ value: "18", label: "18 Months" },
{ value: "24", label: "24 Months" },
]
}
@@ -0,0 +1,36 @@
import React, { FC } from "react";
import DashboardHomeIntro from "./DashboardHomeIntro";
import DashboardFormInit from "./DashboardFormInit";
import DashboardHomeDetail from "./home/DashboardHomeDetail";
import DashboardHomeEmploymentInfo from "./home/DashboardHomeEmploymentInfo";
import DashboardHomeRefereeInfo from "./home/DashboardHomeRefereeInfo";
import DashboardHomeAttestation from "./home/DashboardHomeAttestation";
interface DashboardHomeProps {}
const DashboardHome: FC<DashboardHomeProps> = () => {
const [step, setStep] = React.useState(1);
const [applicationDetails, setApplicationDetails] = React.useState({});
const handleNextStep = (values:{}={}) => {
if (step < 7) {
setStep(step + 1);
}
setApplicationDetails((prev:{}) => ({...prev, ...values}))
}
return (
<div className="w-full">
{step === 1 && <DashboardHomeIntro step={step} handleNextStep={handleNextStep} />}
{step === 2 && <DashboardFormInit handleNextStep={handleNextStep} />}
{step === 3 && <DashboardHomeDetail handleNextStep={handleNextStep} />}
{step === 4 && <DashboardHomeEmploymentInfo handleNextStep={handleNextStep} />}
{step === 5 && <DashboardHomeRefereeInfo handleNextStep={handleNextStep} />}
{step === 6 && <DashboardHomeAttestation handleNextStep={handleNextStep} applicationDetails={applicationDetails} />}
{step === 7 && <DashboardHomeIntro step={step} handleNextStep={handleNextStep} />}
{/* <DashboardHomeAttestation handleNextStep={handleNextStep} applicationDetails={applicationDetails} /> */}
</div>
);
};
export default DashboardHome;
@@ -0,0 +1,269 @@
import React, { FC, useState, useEffect } from 'react';
import NairaBag from '../../assets/images/dashboard/naira-bag.png';
import { Button, Icons } from '../';
import { useSelector } from 'react-redux';
import PendingList from '../paginated-list/PendingList';
import { PendingTableList } from '../../core/models';
import { NewDateTimeFormatter } from '../../lib/NewDateTimeFormatter';
import { getUserPendingLoanList } from '../../core/apiRequest';
export interface DashBoardCardProps {
title?: string;
desc?: string;
descSpan?: string;
descSpanClass?: string;
onClick?: any;
cardClass?: string;
titleClass?: string;
descClass?: string;
btnTitle?: string;
btnTextClass?: string;
image?: any;
imgClass?: string;
}
export const DashBoardCard: React.FC<DashBoardCardProps> = ({
title,
desc,
onClick,
cardClass,
titleClass,
descClass,
descSpan,
descSpanClass,
btnTitle,
btnTextClass,
image,
imgClass,
}) => {
return (
<div
className={`h-full w-full rounded-lg p-5 shadow-lg hover:shadow-none bg-no-repeat bg-[90%] flex justify-between gap-4 items-center transition-all duration-300 ${
cardClass && cardClass
}`}
// onClick={onClick}
>
<div className="w-3/4 flex flex-col gap-[2.3125rem]">
{title && (
<h1
className={`mb-1 text-[#FFF] text-lg text-left font-bold ${
titleClass && titleClass
}`}
>
{title}
</h1>
)}
{desc && (
<p className={`text-lg text-left ${descClass && descClass}`}>
{desc}{' '}
{descSpan && (
<span className={`${descSpanClass && descSpanClass}`}>
{descSpan}
</span>
)}
</p>
)}
{btnTitle && (
<Button className={btnTextClass} text={btnTitle} onClick={onClick} />
)}
</div>
{image && <img className={imgClass} src={image} alt="card-image" />}
</div>
);
};
interface DashboardHomeIntroProps {
handleNextStep: (value: {}) => any;
step?: number | string;
}
const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({
handleNextStep,
step,
}) => {
const { userDetails } = useSelector((state: any) => state?.userDetails); // CHECKS IF USER Details are avaliable
const [userLoanList, setUserLoanList] = useState<{
loading: boolean;
data: PendingTableList;
}>({ loading: true, data: [] });
useEffect(() => {
let token = localStorage.getItem('token');
let uid = localStorage.getItem('uid');
if (!token || !uid) {
return;
}
getUserPendingLoanList(uid)
.then((res) => {
console.log('RES', res);
console.log('RES', userLoanList);
if (!res || !res.data.loans) {
setUserLoanList({ loading: false, data: [] });
return;
}
setUserLoanList({ loading: false, data: res?.data?.loans });
})
.catch((err) => {
console.log(err);
setUserLoanList({ loading: false, data: [] });
});
}, []);
return (
<div className="w-full">
{step == 1 ? (
<>
<h1 className="font-bold my-5 text-2xl">
Hello, {userDetails.firstname}
</h1>
<div className="group w-full lg:w-[27.8125rem] h-[12.75rem] mt-7 ">
<DashBoardCard
cardClass="bg-[#5C2684] relative"
desc="Begin your application and get up to "
descSpan="5 million naira loan."
descClass="leading-[1.5625rem] text-lg text-white"
descSpanClass="font-bold"
btnTitle="Apply here"
btnTextClass="w-[11.125rem] h-[2.8125rem] flex justify-center item-center btn-W text-[#FBB700]"
image={NairaBag}
imgClass="translate-y-4 -rotate-6"
onClick={() => handleNextStep({})}
/>
</div>
</>
) : (
<>
<h1 className="font-bold my-5 text-2xl">
Welcome Back, {userDetails.firstname}
</h1>
<div className="group w-full lg:w-[27.8125rem] h-[12.75rem] mt-7 ">
<DashBoardCard
cardClass="bg-[#5C2684] relative"
desc="Your loan application has been reviewed and accepted, please confirm for disbursement."
// descSpan="5 million naira loan."
descClass="leading-[1.5625rem] text-lg text-white"
// descSpanClass="font-bold"
btnTitle="View and accept"
btnTextClass="w-[11.125rem] h-[2.8125rem] flex justify-center item-center btn-W text-[#FBB700]"
image={NairaBag}
imgClass="translate-y-4 -rotate-6"
// onClick={handleNextStep}
/>
</div>
</>
)}
{userLoanList.loading ? null : (
<div className="mt-5 w-full">
<PendingList
data={userLoanList.data}
itemsPerPage={5}
tableTitle="Current Applications"
>
{(data: any) => (
<div className="w-full p-4 rounded-lg shadow-lg bg-white overflow-x-auto min-h-[250px] max-h-[450px]">
<table className="text-[12px] sm:text-base w-full table-auto">
<thead>
<tr className="text-left border-b-2">
<th className="px-1 py-4">Date</th>
<th className="px-1 py-4 text-right">Amount</th>
<th className="px-1 py-4 text-center min-w-[110px]">
Payment Term
</th>
<th className="px-1 py-4 text-center">Status</th>
<th className="px-1 py-4 text-right">Action</th>
</tr>
</thead>
<tbody>
{data.map((item: any, index: any) => (
<tr key={index || item} className="even:bg-slate-100">
<td className="px-1 py-2">
{NewDateTimeFormatter(item?.added)}
</td>
<td className="px-1 py-2 text-right">
{item?.loan_amount}
</td>
<td className="px-1 py-2 text-center">
{item?.payment_month}
</td>
<td className="px-1 py-2 text-center">
{item?.status}
</td>
<td className="px-1 py-2 text-right">
<button className="px-2 py-1 border-2 border-black">
View
<Icons name="arrow-right" />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</PendingList>
</div>
)}
</div>
);
};
export default DashboardHomeIntro;
// {/* <div className="group w-full lg:w-96 h-32">
// <DefaultCard
// descText="You currently do not have any open application. Click on apply for a loan to get started."
// iconName="arrow"
// iconColor="#FBB700"
// cardClass={`p-4 bg-[#FFFAFA] border border-[#EE4040]`}
// descTextClass="text-[#423131] leading-5"
// onClick={() => {
// console.log("working");
// }}
// />
// </div> */}
// {/* <div className="w-full mt-20 flex gap-16 flex-wrap">
// <div className="group h-40 w-full lg:w-80">
// <DefaultCard
// title="Apply for a loan"
// descText="You currently do not have any open application. Click on apply for a loan to get started."
// iconName="greater-than"
// iconColor="#FFF"
// cardClass={`bg-[#5C2684] bg-[url('../../../src/assets/images/dashboard/card_bg.png')]`}
// titleClass="text-[#FFF]"
// descTextClass="text-[#EFEFEF] leading-5"
// onClick={() => {
// console.log("working");
// }}
// />
// </div>
// <div className="group h-40 w-full lg:w-80">
// <DefaultCard
// title="Loan history"
// descText="You currently do not have any open application. Click on apply for a loan to get started."
// iconName="greater-than"
// iconColor="#FFF"
// cardClass={`bg-[#635D4D] bg-[url('../../../src/assets/images/dashboard/card_bg.png')]`}
// titleClass="text-[#FFF]"
// descTextClass="text-[#EFEFEF] leading-5"
// onClick={() => {
// console.log("working");
// }}
// />
// </div>
// <div className="group h-40 w-full lg:w-80">
// <DefaultCard
// title="How it works?"
// descText="Steps to follow to complete your loan application successfully."
// iconName="greater-than"
// iconColor="#FFF"
// cardClass={`bg-[#635D4D] bg-[url('../../../src/assets/images/dashboard/card_bg.png')]`}
// titleClass="text-[#FFF]"
// descTextClass="text-[#EFEFEF] leading-5"
// onClick={() => {
// console.log("working");
// }}
// />
// </div>
// </div> */}
@@ -0,0 +1,72 @@
import { InputCompOne } from "..";
import { useNavigate } from "react-router-dom";
import { RouteHandler } from "../../router/routes";
export default function DashboardProfile() {
let navigate = useNavigate();
const navigateToProfile = () => navigate(RouteHandler.dashboardHome);
return (
<div className="w-full">
<div className='my-[2rem] flex items-center'>
<button onClick={navigateToProfile} className='w-6 h-6 text-lg flex justify-center items-center rounded-full bg-gray-500'>&lt;</button>
</div>
<div className="max-w-[25.875rem] w-full p-4 rounded-xl flex flex-col gap-1 bg-[#FBB700]/30">
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="applyIshInput"
label="Full name"
labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="John James"
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="applyIshInput"
label="Phone number"
labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="07000000000"
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="applyIshInput"
label="Residential address"
labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Somewhere in lagos"
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="applyIshInput"
label="Select your state"
labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Lagos"
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="applyIshInput"
label="Email address"
labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="johndoe@gmail.com"
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="applyIshInput"
label="Date of birth"
labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="12/10/1994"
/>
</div>
</div>
);
}
@@ -0,0 +1,147 @@
import { Button, InputCompOne, Stepper } from '../../shared/index';
import {Formik, Form} from 'formik'
import * as Yup from "yup";
import { applyForLoan } from '../../../core/apiRequest';
// import { useNavigate } from "react-router-dom";
// import { RouteHandler } from '../../../router/routes';
type Props = {
handleNextStep:(value:{})=>any
applicationDetails: {}
}
const initialValues = {
account: "",
checked: false
};
// To get the validation schema
const validationSchema = Yup.object().shape({
account: Yup.string()
.required("Required"),
checked: Yup.bool() // use bool instead of boolean
.oneOf([true], "You must accept the terms and conditions")
});
export default function DashboardHomeAttestation({handleNextStep, applicationDetails}:Props) {
// let navigate = useNavigate();
// const navigateToProfile = () => navigate(RouteHandler.dashboardProfile);
//FUNCTION TO HANDLE LOAN APPLICATION
const handleSubmit = (values:any) => {
delete values.checked
applyForLoan({...applicationDetails, disbursement: values}).then(res=>{
console.log('APPLY FOR LOAN', res)
handleNextStep({disbursement: values})
console.log('ApplicationDetails', {...applicationDetails, disbursement: values})
}).catch(err=>{
console.log(err)
})
// applyForLoan(payload).then(res=>{
// console.log('APPLY FOR LOAN', res)
// // handleNextStep({disbursement: values})
// }).catch(err=>{
// console.log(err)
// })
};
return (
<div className="w-full">
<div className="w-full flex justify-center">
<Stepper step={4} />
</div>
<p className='my-10 text-red-500 text-lg md:text-2xl'>Applicant's Attestation and Debit Instruction</p>
<p className='text-red-500 text-base'>NB: Must be your FCMB account number</p>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props)=>(
<Form>
<div className="flex flex-col gap-9">
<div className="flex items-center gap-[4.125rem]">
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="account"
floatLabel="Disbursement account number"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="0102547896"
value={props.values.account}
onChange={props.handleChange}
error={(props.errors.account && props.touched.account) ? props.errors.account : ''}
/>
</div>
<div className='max-w-[25.875rem]'>
<div className='flex gap-4 items-start'>
<input
type='checkbox'
name="checked"
className='w-4 h-4 p-2 accent-purple-600 text-purple-600 bg-gray-100 border-gray-300 rounded focus:ring-purple-500'
onChange={props.handleChange}
/>
<p className='text-[12px] text-justify'>By pressing, you agree that you have read, understood and accept the <span className='text-blue-600'>applicatant's attestation</span> and <span className='text-blue-600'>terms and conditions</span> for FCMB
premium salary loan. You also give us permission to collect financial information and run credit checks on the account provided through our partners
</p>
</div>
{props.errors.checked && props.touched.checked && <span className='text-[10px] text-red-500'>{props.errors.checked}</span>}
</div>
<Button
className="my-8 max-w-[25.875rem] btn-Y text-black w-full h-11"
text="Apply"
type="submit"
/>
</div>
</Form>
)}
</Formik>
</div>
);
}
// const payload = {
// "loan_amount": "100000",
// "payment_month": "18",
// "sales_agent": "Testing1234",
// "gender": "male",
// "address": "World bank housing Estate, Umuahia",
// "marital_status": "single",
// "state": "abia",
// "email": "test5070@gmail.com",
// "country": "NG",
// "employment": {
// "job_title": "Information Officer",
// "name": "Testing Testing",
// "sector": "private (non academic)",
// "industry": "engineering",
// "resumption_date": "2024-04-05",
// "email": "test50700@gmail.com",
// "annual_income": "600000",
// "monthly_salary": "50000",
// "salary_payment_date": "2024-04-19",
// "employment_id": "2555566",
// "highest_eductaion": "b.sc + professional qualification"
// },
// "loan_reference": [
// {
// "fullname": "John Mike",
// "relationship": "Brother",
// "phone_number": "07055566611",
// "email": "refone@gmail.com",
// "bvn": "11111111111"
// },
// {
// "fullname": "Mary Paul",
// "relationship": "Brother",
// "phone_number": "07055577711",
// "email": "reftwo@gmail.com",
// "bvn": "22222222222"
// }
// ],
// disbursement:{account: '1122334456'}
// }
@@ -0,0 +1,190 @@
import { Button, InputCompOne, Stepper } from '../../shared/index';
import {Formik, Form} from 'formik'
import * as Yup from "yup";
type Props = {
handleNextStep:(value:{})=>any
}
const initialValues = {
gender: "",
address: "",
marital_status: "",
state: "",
email:"",
country:""
};
// To get the validation schema
const validationSchema = Yup.object().shape({
gender: Yup.string()
.required("Required"),
address: Yup.string()
.required("Required"),
marital_status: Yup.string()
.required("Required"),
state: Yup.string()
.required("Required"),
email: Yup.string()
.email("Invalid")
.required("Required"),
country: Yup.string()
.required("Required"),
});
export default function DashboardHomeDetail({handleNextStep}:Props) {
//FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => {
handleNextStep(values)
};
return (
<div className="w-full">
<div className="w-full flex justify-center">
<Stepper step={1} />
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props)=>(
<Form>
<div className="mt-[3.25rem] flex flex-col gap-9">
<div className="flex items-center gap-[4.125rem]">
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="gender"
label="Select your gender"
labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={gender}
selectValue={props.values.gender}
onChange={props.handleChange}
error={(props.errors.gender && props.touched.gender) ? props.errors.gender : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="address"
label="Residential address"
labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Somewhere in lagos"
value={props.values.address}
onChange={props.handleChange}
error={(props.errors.address && props.touched.address) ? props.errors.address : ''}
/>
</div>
<div className="flex items-center gap-[4.125rem]">
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="marital_status"
label="Marital status"
labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={maritalStatus}
selectValue={props.values.marital_status}
onChange={props.handleChange}
error={(props.errors.marital_status && props.touched.marital_status) ? props.errors.marital_status : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="state"
label="Select your state"
labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={state}
selectValue={props.values.state}
onChange={props.handleChange}
error={(props.errors.state && props.touched.state) ? props.errors.state : ''}
/>
</div>
<div className="flex items-center gap-[4.125rem]">
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="email"
label="Email address"
labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="johndoe@gmail.com"
value={props.values.email}
onChange={props.handleChange}
error={(props.errors.email && props.touched.email) ? props.errors.email : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="country"
label="Select your country"
labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={country}
selectValue={props.values.country}
onChange={props.handleChange}
error={(props.errors.country && props.touched.country) ? props.errors.country : ''}
/>
</div>
<Button
className="my-8 max-w-[25.875rem] btn-Y text-black w-full h-11"
text="Next"
type="submit"
/>
</div>
</Form>
)}
</Formik>
</div>
);
}
interface SelectOption {
loading: boolean;
data: {value: string;
label: string}[]
}
const gender: SelectOption = {
loading: false,
data: [
{ value: "", label: "Please Select" },
{ value: "male", label: "Male" },
{ value: "female", label: "Female" },
{ value: "others", label: "Prefer not to say" },
]
}
const maritalStatus: SelectOption = {
loading: false,
data: [
{ value: "", label: "Please Select" },
{ value: "single", label: "Single" },
{ value: "married", label: "Married" },
{ value: "divorced", label: "Divorced" },
]
}
const state: SelectOption = {
loading: false,
data: [
{ value: "", label: "Please Select" },
{ value: "abia", label: "Abia" },
{ value: "imo", label: "Imo" },
{ value: "lagos", label: "Lagos" },
]
}
const country: SelectOption = {
loading: false,
data: [
{ value: "", label: "Please Select" },
{ value: "NG", label: "Nigeria" },
]
}
@@ -0,0 +1,353 @@
import {useState, useEffect} from 'react'
import { Button, InputCompOne, Stepper } from '../../shared/index';
import {Formik, Form} from 'formik'
import * as Yup from "yup";
import { getEmployersList } from '../../../core/apiRequest';
type Props = {
handleNextStep:(value:{})=>any
}
// type EmployerProps = {
// loading?: boolean,
// data?: Array<{[index:string]: string}> | {[index:string]: Array<{[index:string]: string}> }
// }
const initialValues = {
job_title: "",
name: "",
sector: "",
industry: "",
resumption_date: "",
email:"",
annual_income: "",
monthly_salary: "",
salary_payment_date: "",
employment_id: "",
highest_eductaion: "",
employer_uid: "",
isChecked: false
};
// To get the validation schema
const validationSchema = Yup.object().shape({
isChecked: Yup.bool(), // use bool instead of boolean
// .oneOf([true, false], "You must accept the terms and conditions"),
job_title: Yup.string()
.required("Required"),
name: Yup.string().when('isChecked', {
is: true,
then: () => Yup.string().required('required'),
otherwise: () => Yup.string(),
}),
sector: Yup.string().when('isChecked', {
is: true,
then: () => Yup.string().required('required'),
}),
industry: Yup.string().when('isChecked', {
is: true,
then: () => Yup.string().required('required'),
}),
resumption_date: Yup.string()
.required("Required"),
email: Yup.string().when('isChecked', {
is: true,
then: () => Yup.string().required('required'),
})
.email("Invalid"),
annual_income: Yup.string()
.required("Required")
.test("no-e", "Invalid", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
}),
monthly_salary: Yup.string()
.required("Required")
.test("no-e", "Invalid", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
}),
salary_payment_date: Yup.string()
.required("Required"),
employment_id: Yup.string()
.required("Required"),
highest_eductaion: Yup.string()
.required("Required"),
employer_uid: Yup.string().when('isChecked', {
is: false,
then: () => Yup.string().required('required'),
}),
});
export default function DashboardHomeEmploymentInfo({handleNextStep}:Props) {
const [employersList, setEmployersList] = useState<any>({
loading: true,
data: []
})
//FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => {
// Remember to changed the checked value's name
if(values.employer_uid){
let employer_uid = values.employer_uid
delete values.employer_uid
handleNextStep({employer_uid, employment: values})
}else{
handleNextStep({employment: values})
}
};
useEffect(()=>{
getEmployersList().then(res => {
setEmployersList({loading:false, data:res?.data})
}).catch(err => {
console.log(err)
setEmployersList({loading:false, data:[]})
})
},[])
return (
<div className="w-full">
<div className="w-full flex justify-center">
<Stepper step={2} />
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props)=>(
<Form>
<div className="mt-[3.25rem] flex flex-col gap-9">
<p className='text-red-500 text-lg md:text-2xl'>Employment Informaton</p>
<div className="flex flex-col lg:flex-row items-start gap-[2rem]">
<div className='w-full lg:max-w-[30rem] flex flex-col'>
<div className='w-full gap-[2rem]'>
<InputCompOne
parentClass="w-full"
name="employer_uid"
floatLabel="Employer Name"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={{loading:employersList?.loading, data: employersList?.data?.records}}
selectValue={props.values.employer_uid}
onChange={props.handleChange}
error={(props.errors.employer_uid && props.touched.employer_uid) ? props.errors.employer_uid : ''}
disabled={props.values.isChecked}
/>
<div className='flex gap-4 items-start my-2'>
<input
type='checkbox'
name="isChecked"
className='w-4 h-4 p-2 accent-purple-600 text-purple-600 bg-gray-100 border-gray-300 rounded focus:ring-purple-500'
onChange={props.handleChange}
checked={props.values.isChecked}
/>
<p className='text-[12px] text-justify'>Check here if employer is not on the list</p>
</div>
<div className={`hidden p-4 ${props.values.isChecked && 'hidden'}`}>
Name: {'Name'}
</div>
</div>
<div className={`w-full flex flex-col gap-[2rem] ${!props.values.isChecked && 'hidden'}`}>
<InputCompOne
parentClass="w-full"
name="name"
floatLabel="Employer name"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Mr. Mark John"
value={props.values.name}
onChange={props.handleChange}
error={(props.errors.name && props.touched.name) ? props.errors.name : ''}
/>
<InputCompOne
parentClass="w-full"
name="email"
floatLabel="Employers official email"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="example@gmail.com"
value={props.values.email}
onChange={props.handleChange}
error={(props.errors.email && props.touched.email) ? props.errors.email : ''}
/>
<InputCompOne
parentClass="w-full"
name="industry"
floatLabel="Select your industry"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={industry}
selectValue={props.values.industry}
onChange={props.handleChange}
error={(props.errors.industry && props.touched.industry) ? props.errors.industry : ''}
/>
<InputCompOne
parentClass="w-full"
name="sector"
floatLabel="Job Sector"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={jobSector}
selectValue={props.values.sector}
onChange={props.handleChange}
error={(props.errors.sector && props.touched.sector) ? props.errors.sector : ''}
/>
</div>
</div>
<div className='w-full lg:max-w-[30rem] flex flex-col gap-[2rem]'>
<InputCompOne
parentClass="w-full"
name="job_title"
floatLabel="Job Title"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Software Engineer"
value={props.values.job_title}
onChange={props.handleChange}
error={(props.errors.job_title && props.touched.job_title) ? props.errors.job_title : ''}
/>
<InputCompOne
parentClass="w-full"
name="highest_eductaion"
floatLabel="Highest level of education"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={highestEductaion}
selectValue={props.values.highest_eductaion}
onChange={props.handleChange}
error={(props.errors.highest_eductaion && props.touched.highest_eductaion) ? props.errors.highest_eductaion : ''}
/>
<div className="w-full flex flex-col sm:flex-row items-center gap-4">
<InputCompOne
parentClass="w-full"
name="resumption_date"
floatLabel="Date of resumption"
// labelClass="font-bold text-[1.125rem]"
input
inputType='date'
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="12/12/2015"
value={props.values.resumption_date}
onChange={props.handleChange}
error={(props.errors.resumption_date && props.touched.resumption_date) ? props.errors.resumption_date : ''}
/>
<InputCompOne
parentClass="w-full"
name="salary_payment_date"
floatLabel="Salary payment date"
// labelClass="font-bold text-[1.125rem]"
input
inputType='date'
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="30th of every month"
value={props.values.salary_payment_date}
onChange={props.handleChange}
error={(props.errors.salary_payment_date && props.touched.salary_payment_date) ? props.errors.salary_payment_date : ''}
/>
</div>
<div className="w-full flex flex-col sm:flex-row items-center gap-4">
<InputCompOne
parentClass="w-full"
name="annual_income"
floatLabel="Annual Income"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem] text-right"
placeholder="1,200,000"
value={props.values.annual_income}
onChange={props.handleChange}
error={(props.errors.annual_income && props.touched.annual_income) ? props.errors.annual_income : ''}
/>
<InputCompOne
parentClass="w-full"
name="monthly_salary"
floatLabel="Net monthly salary"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem] text-right"
placeholder="100,000"
value={props.values.monthly_salary}
onChange={props.handleChange}
error={(props.errors.monthly_salary && props.touched.monthly_salary) ? props.errors.monthly_salary : ''}
/>
</div>
<InputCompOne
parentClass="w-full"
name="employment_id"
floatLabel="Employee ID"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="LS/001/005"
value={props.values.employment_id}
onChange={props.handleChange}
error={(props.errors.employment_id && props.touched.employment_id) ? props.errors.employment_id : ''}
/>
<div className="w-full">
<Button
className="my-4 btn-Y text-black w-full h-11"
text="Next"
type="submit"
/>
</div>
</div>
</div>
</div>
</Form>
)}
</Formik>
</div>
);
}
interface SelectOption {
loading: boolean;
data: {value: string;
label: string}[]
}
const jobSector: SelectOption = {
loading: false,
data: [
{ value: "", label: "Please Select" },
{ value: "private (non academic)", label: "Private (non academic)" },
]
}
const industry: SelectOption = {
loading: false,
data: [
{ value: "", label: "Please Select" },
{ value: "engineering", label: "Engineering" },
]
}
const highestEductaion: SelectOption = {
loading: false,
data: [
{ value: "", label: "Please Select" },
{ value: "b.sc + professional qualification", label: "B.Sc + Professional Qualification" },
]
}
@@ -0,0 +1,244 @@
import { Button, InputCompOne, Stepper } from '../../shared/index';
import {Formik, Form} from 'formik'
import * as Yup from "yup";
type Props = {
handleNextStep:(value:{})=>any
}
const initialValues = {
ref_name: "",
ref_email: "",
ref_phone_number: "",
ref_relationship: "",
ref_bvn: "",
ref_two_name: "",
ref_two_email: "",
ref_two_phone_number: "",
ref_two_relationship: "",
ref_two_bvn: "",
};
// To get the validation schema
const validationSchema = Yup.object().shape({
ref_name: Yup.string()
.required("Required"),
ref_email: Yup.string()
.email("Invalid")
.required("Required"),
ref_phone_number: Yup.string()
.required("Required"),
ref_relationship: Yup.string()
.required("Required"),
ref_bvn: Yup.string()
.required("BVN is required")
.test("no-e", "Invalid number", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
})
.min(11, "must be 11 digits")
.max(11, "must be 11 digits"),
ref_two_name: Yup.string()
.required("Required"),
ref_two_email: Yup.string()
.email("Invalid")
.required("Required"),
ref_two_phone_number: Yup.string()
.required("Required"),
ref_two_relationship: Yup.string()
.required("Required"),
ref_two_bvn: Yup.string()
.required("BVN is required")
.test("no-e", "Invalid number", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
})
.min(11, "must be 11 digits")
.max(11, "must be 11 digits"),
});
export default function DashboardHomeRefereeInfo({handleNextStep}:Props) {
//FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => {
let refOne = {
fullname: values.ref_name,
relationship: values.ref_relationship,
phone_number: values.ref_phone_number,
email: values.ref_email,
bvn: values.ref_bvn
}
let refTwo = {
fullname: values.ref_two_name,
relationship: values.ref_two_relationship,
phone_number: values.ref_two_phone_number,
email: values.ref_two_email,
bvn: values.ref_two_bvn
}
handleNextStep({loan_reference:[refOne, refTwo]})
};
return (
<div className="w-full">
<div className="w-full flex justify-center">
<Stepper step={3} />
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props)=>(
<Form>
<div className="mt-[3.25rem] flex flex-col gap-9">
<p className='text-red-500 text-lg md:text-2xl'>Reference Details <span className='text-base'>(Must be 18 years and above)</span></p>
<div className="flex items-center gap-[4.125rem]">
<div className='w-full max-w-[25.875rem]'>
<p className='text-red-500 text-base'>Reference one</p>
<div className='w-full flex flex-col gap-9'>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="ref_name"
floatLabel="Full name"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="John James"
value={props.values.ref_name}
onChange={props.handleChange}
error={(props.errors.ref_name && props.touched.ref_name) ? props.errors.ref_name : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="ref_relationship"
floatLabel="Relationship"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Sister"
value={props.values.ref_relationship}
onChange={props.handleChange}
error={(props.errors.ref_relationship && props.touched.ref_relationship) ? props.errors.ref_relationship : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="ref_phone_number"
floatLabel="Phone number"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="07000000000"
value={props.values.ref_phone_number}
onChange={props.handleChange}
error={(props.errors.ref_phone_number && props.touched.ref_phone_number) ? props.errors.ref_phone_number : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="ref_email"
floatLabel="Email address"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="demo@gamil.com"
value={props.values.ref_email}
onChange={props.handleChange}
error={(props.errors.ref_email && props.touched.ref_email) ? props.errors.ref_email : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="ref_bvn"
floatLabel="BVN"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="2228457896"
value={props.values.ref_bvn}
onChange={props.handleChange}
error={(props.errors.ref_bvn && props.touched.ref_bvn) ? props.errors.ref_bvn : ''}
/>
</div>
</div>
<div className='w-full max-w-[25.875rem]'>
<p className='text-red-500 text-base'>Reference two</p>
<div className='w-full flex flex-col gap-9'>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="ref_two_name"
floatLabel="Full name"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="John James"
value={props.values.ref_two_name}
onChange={props.handleChange}
error={(props.errors.ref_two_name && props.touched.ref_two_name) ? props.errors.ref_two_name : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="ref_two_relationship"
floatLabel="Relationship"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Sister"
value={props.values.ref_two_relationship}
onChange={props.handleChange}
error={(props.errors.ref_two_relationship && props.touched.ref_two_relationship) ? props.errors.ref_two_relationship : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="ref_two_phone_number"
floatLabel="Phone number"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="07000000000"
value={props.values.ref_two_phone_number}
onChange={props.handleChange}
error={(props.errors.ref_two_phone_number && props.touched.ref_two_phone_number) ? props.errors.ref_two_phone_number : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="ref_two_email"
floatLabel="Email address"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="demo@gamil.com"
value={props.values.ref_two_email}
onChange={props.handleChange}
error={(props.errors.ref_two_email && props.touched.ref_two_email) ? props.errors.ref_two_email : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="ref_two_bvn"
floatLabel="BVN"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="2228457896"
value={props.values.ref_two_bvn}
onChange={props.handleChange}
error={(props.errors.ref_two_bvn && props.touched.ref_two_bvn) ? props.errors.ref_two_bvn : ''}
/>
</div>
</div>
</div>
<Button
className="my-8 max-w-[25.875rem] btn-Y text-black w-full h-11"
text="Next"
type="submit"
/>
</div>
</Form>
)}
</Formik>
</div>
);
}
+4
View File
@@ -0,0 +1,4 @@
import DashboardHome from './DashboardHome'
import DashboardProfile from './DashboardProfile';
export { DashboardHome, DashboardProfile };
+69
View File
@@ -0,0 +1,69 @@
import { footerCustomerLinks, footerSocialLinks } from "../../utils/data";
interface FooterLinksProps {
href: string;
icon?: string;
text?: string;
}
const BottomFooterOne = () => {
const date: number = new Date().getFullYear();
return (
<footer className="pt-[1.25rem] pb-[1.875rem]">
<div className="containerMode flex flex-col gap-2 w-full">
<div className="flex flex-wrap flex-[100] justify-between w-full gap-2">
<SocialIconButtons />
<CustomerLinks />
</div>
<p className="text-[.8125rem] text-[#333] leading-[1.42857]">
© <span>{date}</span> First City Monument Bank (Licensed by the
Central Bank of Nigeria)
</p>
</div>
</footer>
);
};
export default BottomFooterOne;
const SocialIconButtons = () => {
const icons = footerSocialLinks.map(
({ href, icon }: FooterLinksProps, idx: number) => (
<li key={idx}>
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="bg-[#592B81] py-[.3125rem] px-[.625rem] text-white w-[2.625rem] h-[2.625rem] flex items-center justify-center rounded-[3.125rem]"
>
{icon && <img src={icon} alt="icon" />}
</a>
</li>
)
);
return <ul className="flex flex-[33.333] items-center gap-1">{icons}</ul>;
};
const CustomerLinks = () => {
const links = footerCustomerLinks.map(
({ href, text }: FooterLinksProps, idx: number) => (
<li key={idx} className="list-none">
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="py-[.75rem] text-[.8125rem] uppercase text-[#606161] flex items-center justify-center"
>
{text}
</a>
</li>
)
);
return (
<div className="flex-[66.667] flex items-center flex-nowrap md:flex-wrap gap-2">
{links}
</div>
);
};
+33
View File
@@ -0,0 +1,33 @@
// import { Link } from "react-router-dom";
import { socialsIcons } from "../../utils/data";
export default function Footer() {
const date = new Date().getFullYear();
return (
<div className="w-full h-20 bg-[F7F7F7] flex items-center">
<div className="containerMode flex justify-center md:justify-between items-center flex-wrap gap-2">
<p className="text-[.9375rem] tracking-[2%] font-semibold text-[#969696]">
{date} @ First City Monument Bank Limited
</p>
<div className="footer-social-icons flex justify-end items-center gap-2">
{renderSocialLinks()}
</div>
</div>
</div>
);
}
function renderSocialLinks(): JSX.Element {
const link = socialsIcons.map(function (item, index) {
let { name, image, link } = item;
return (
<a href={link} key={index} className="w-[1.875rem] h-[1.875rem]">
<img src={image} alt={name} />
</a>
);
});
return <ul className="flex items-center gap-2">{link}</ul>;
}
+15
View File
@@ -0,0 +1,15 @@
import styles from "./footer.module.css"
const MidFooter = () => {
return (
<div className={`h-[2.3125rem] text-[1.25rem] ${styles.lower_footer}`}>
<div className="containerMode flex justify-end p-[.375rem] w-full text-white font-medium text-[.6875rem] md:text-[1.25rem]">
<div className="flex gap-2 items-center justify-end px-2 text-[11px] md:text-[13px]">
<p className="text-[20px] font-extralight">my bank and I</p>
</div>
</div>
</div>
)
}
export default MidFooter
+35
View File
@@ -0,0 +1,35 @@
import { footerItems } from "../../utils/data";
import TopFooterOneMenu from "./TopFooterOneMenu";
export interface TopFooterOneMenuProps {
category: string;
subItems: {
text: string;
href?: string;
}[];
}
const TopFooterOne = () => {
const footerListItems: TopFooterOneMenuProps[] = footerItems;
return (
<footer className="bg-[#f7f7f7] text-[#898B8B] border border-[#ececec] p-5">
<div className="containerMode w-full flex flex-col gap-[1.875rem]">
<h4 className="uppercase text-[1.3125rem] font-bold my-[.625rem] cursor-default">
sitemap
</h4>
<div className="grid grid-cols-2 md:grid-cols-6 gap-2 md:gap-0">
{footerListItems.map(({ category, subItems }, index) => (
<TopFooterOneMenu
key={`${category}-${index}`}
category={category}
subItems={subItems}
/>
))}
</div>
</div>
</footer>
);
};
export default TopFooterOne;
@@ -0,0 +1,29 @@
import React from "react";
import { Link } from "react-router-dom";
import { TopFooterOneMenuProps } from "./TopFooterOne";
const TopFooterOneMenu: React.FC<TopFooterOneMenuProps> = ({
category,
subItems,
}) => {
return (
<ul className="flex gap-2 flex-col">
<li className="text-[.6875rem] font-bold text-[#5e2785] cursor-default">
{category}
</li>
<ul className="flex flex-col gap-1">
{subItems.map(({ href = "#", text }) => (
<li
key={text}
className="text-[.6875rem] text-[#5e2785] hover:underline w-fit"
>
{href ? <Link to={href}>{text}</Link> : <span>{text}</span>}
</li>
))}
</ul>
</ul>
);
};
export default TopFooterOneMenu;
+8
View File
@@ -0,0 +1,8 @@
.lower_footer{
background: url(../../assets/images/footer_back.jpg) no-repeat;
background-size: cover;
/* padding: 0.4rem 0; */
display: flex;
align-items: center;
justify-content: center;
}
+6
View File
@@ -0,0 +1,6 @@
import Footer from "./Footer";
import TopFooterOne from "./TopFooterOne";
import MidFooter from "./MidFooter";
import BottomFooterOne from "./BottomFooterOne";
export { Footer, TopFooterOne, MidFooter, BottomFooterOne };
@@ -0,0 +1,29 @@
import React from "react";
import DebitAccount from "./DebitAccount";
const ApplicantsAttestation: React.FC = () => {
return (
<>
<div className="flex justify-between items-center w-full mt-8 mb-[45px]">
<h1 className="font-semibold text-[2.375rem] text-[#5C2684] my-[.5rem] max-w-[34rem]">
Applicants Attestation and Debit Instruction
</h1>
<div className="flex flex-col gap-[.4375rem]">
<p className="font-extrabold tracking-[3%] text-[#FBB700] underline">
For more enquiries and support
</p>
<p className="font-extrabold tracking-[3%] text-[#5A5A5A]">
Call: 09099000000
</p>
<p className="font-extrabold tracking-[3%] text-[#5A5A5A]">
Email: fcmbloan@support.com
</p>
</div>
</div>
<DebitAccount />
</>
);
};
export default ApplicantsAttestation;
+89
View File
@@ -0,0 +1,89 @@
import React from "react";
import * as Yup from "yup";
import { Form, Formik } from "formik";
import { InputCompOne } from "../shared";
// To get the validation schema
const validationSchema = Yup.object().shape({
bvn: Yup.string()
.required("BVN is required")
.test("no-e", "Invalid number", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
})
.min(11, "must be 11 digits")
.max(11, "must be 11 digits")
});
// initial values for formik
let initialValues = {
bvn: ''
};
type Props = {
handleNextStep:()=>any
}
const BVN = ({handleNextStep}:Props) => {
const firstInputRef = React.useRef<HTMLInputElement>(null);
const handleSubmit = (values:any) => {
console.log('values', values)
handleNextStep()
};
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props:any) => (
<Form className="">
<div className="w-full">
<div className="containerMode flex justify-between gap-1 xl:gap-8 flex-col">
<div className="my-[4rem] flex items-center justify-center w-full">
<h1 className="font-bold text-[2.375rem] text-[#5C2684] my-[.5rem] text-center">
Lets Get You Started
</h1>
</div>
<div className="mx-auto flex flex-col gap-8 max-w-[31.625rem]">
<InputCompOne
parentClass="flex flex-col gap-2"
label="Enter Your BVN "
name="bvn"
parentInputClass="w-full"
labelSpan="( To get your BVN, dial *565*0# )"
labelSpanClass="text-[13px] text-[#5a5a5a] font-semibold"
placeholder="Enter your BVN"
labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#282828] mb-[2px] flex item-center gap-[4px]"
input
inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4"
value={props.values.bvn}
onChange={props.handleChange}
ref={firstInputRef}
maxLength={11}
error={(props.errors.bvn && props.touched.bvn) && props.errors.bvn}
/>
<button
type='submit'
className="w-full h-[3.625rem] rounded bg-[#FBB700] rounded-2 px-4 text-[18px] text-[#282828] font-semibold disabled:text-[#282828] disabled:text-opacity-50"
>
Enter
</button>
<p className="text-[#5C2684] mt-[1.5625rem] max-w-[31.625rem]">
***Every personal information attached to your BVN is safe and secured. It is only inportant for us to verify your information and also give you access to your application profile/account
</p>
</div>
</div>
</div>
</Form>
)}
</Formik>
);
};
export default BVN;
@@ -0,0 +1,27 @@
import React from "react";
import { InputCompOne } from "..";
const CreditAccount: React.FC = () => {
return (
<>
<div className="w-full rounded py-3 bg-[#5C2684] px-5">
<p className="text-base text-[#FBB700] tracking-[3%] font-extrabold w-fit">
CREDIT ACCOUNT ( Your account to receive your loan )
</p>
</div>
<InputCompOne
parentClass="max-w-[29.4375rem] w-full my-5 ml-5"
label="Disbursement Account Number "
name="disbursementAccountNumber"
labelSpan="( Your FCMB Account )"
labelSpanClass="text-[12px] text-[#5C2684] ml-1"
parentInputClass="w-full"
labelClass="font-bold text-[18px] leading-[21.7808px] tracking-[2%] text-[#5C2684] mb-[2px]"
input
inputClass="w-full h-[36px] bg-[#EFEFEF] px-[2px] rounded-[6px]"
/>
</>
);
};
export default CreditAccount;
+146
View File
@@ -0,0 +1,146 @@
import React from "react";
import {useNavigate} from 'react-router-dom'
import { Button, InputCompOne } from "..";
import { RouteHandler } from "../../router/routes";
import {Formik, Form} from 'formik'
import * as Yup from "yup";
const initialValues = {
disburse_account: "",
bank_name: "",
account_name: "",
account_number: "",
checked: false
};
// To get the validation schema
const validationSchema = Yup.object().shape({
disburse_account: Yup.string()
.required("Required"),
bank_name: Yup.string()
.required("Required"),
account_name: Yup.string()
.required("Required"),
account_number: Yup.string()
.required("Required"),
checked: Yup.bool() // use bool instead of boolean
.oneOf([true], "You must accept the terms and conditions")
});
const DebitAccount: React.FC = () => {
const navigate = useNavigate()
//FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => {
console.log(values)
navigate(RouteHandler.letsGetStarted, {replace:true})
};
return (
<>
<div className="w-full rounded py-3 mb-9 bg-[#5C2684] px-5">
<p className="text-base text-[#FBB700] tracking-[3%] font-extrabold w-fit">
CREDIT ACCOUNT ( Your account to receive your loan )
</p>
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props)=>(
<Form>
<InputCompOne
parentClass="max-w-[29.4375rem] w-full my-5 ml-5"
label="Disbursement Account Number "
name="disburse_account"
labelSpan="( Your FCMB Account )"
labelSpanClass="text-[12px] text-[#5C2684] ml-1"
parentInputClass="w-full"
labelClass="font-bold text-[18px] leading-[21.7808px] tracking-[2%] text-[#5C2684] mb-[2px]"
input
inputClass="w-full h-[36px] bg-[#EFEFEF] rounded-[6px]"
value={props.values.disburse_account}
onChange={props.handleChange}
error={(props.errors.disburse_account && props.touched.disburse_account) ? props.errors.disburse_account : ''}
/>
<div className="mt-9 flex flex-col gap-9">
<div className="w-full rounded py-3 bg-[#5C2684] px-5">
<p className="text-base text-[#FBB700] tracking-[3%] font-extrabold w-fit">
DEBIT ACCOUNT ( Your salary account for monthly repayment )
</p>
</div>
<InputCompOne
parentClass="max-w-[471px] w-full ml-5"
label="Bank Name"
name="bank_name"
parentInputClass="w-full"
labelClass="font-bold text-[18px] leading-[21.7808px] tracking-[2%] text-[#5C2684] mb-[2px]"
input
inputClass="w-full h-[36px] bg-[#EFEFEF] rounded-[6px]"
value={props.values.bank_name}
onChange={props.handleChange}
error={(props.errors.bank_name && props.touched.bank_name) ? props.errors.bank_name : ''}
/>
<div className="flex items-center gap-[59px]">
<InputCompOne
parentClass="max-w-[471px] w-full ml-5"
label="Account Number"
name="account_number"
parentInputClass="w-full"
labelClass="font-bold text-[18px] leading-[21.7808px] tracking-[2%] text-[#5C2684] mb-[2px]"
input
inputClass="w-full h-[36px] bg-[#EFEFEF] rounded-[6px]"
value={props.values.account_number}
onChange={props.handleChange}
error={(props.errors.account_number && props.touched.account_number) ? props.errors.account_number : ''}
/>
<InputCompOne
parentClass="max-w-[471px] w-full ml-5"
label="Account Name"
name="account_name"
parentInputClass="w-full"
labelClass="font-bold text-[18px] leading-[21.7808px] tracking-[2%] text-[#5C2684] mb-[2px]"
input
inputClass="w-full h-[36px] bg-[#EFEFEF] rounded-[6px]"
value={props.values.account_name}
onChange={props.handleChange}
error={(props.errors.account_name && props.touched.account_name) ? props.errors.account_name : ''}
/>
</div>
<div className="max-w-[578px] flex items-center">
<input
type="checkbox"
// checked={true}
name='checked'
onChange={props.handleChange}
className="form-checkbox h-[25px] w-[25px] rounded-sm text-[#5c2684] "
style={{ backgroundColor: "#5C2684" }}
/>
<label className="ml-2 text-gray-700">
I have read, understood and accept the{" "}
<span className="text-[#4545CB]">applicant's attestation</span> and
all the <span className="text-[#4545CB]">terms and conditions</span>{" "}
for FCMB premium salary loan.
</label>
{props.errors.checked && props.touched.checked && <span className='text-[10px] text-red-500'>{props.errors.checked}</span>}
</div>
<Button
className="my-8 max-w-[33.875rem] btn-R bg-[#5A2C82] w-full h-11"
text="Apply"
type="submit"
/>
</div>
</Form>
)}
</Formik>
</>
);
};
export default DebitAccount;
@@ -0,0 +1,320 @@
import React from "react";
import { Button, InputCompOne } from "..";
import {Formik, Form} from 'formik'
import * as Yup from "yup";
const initialValues = {
title: "",
marital_status: "",
agent_id: "",
bvn: "",
first_name: "",
phone: "",
email: "",
surname: "",
dob: "",
second_name: "",
spouse_bvn: "",
};
// To get the validation schema
const validationSchema = Yup.object().shape({
title: Yup.string()
.required("Required"),
marital_status: Yup.string()
.required("Required"),
agent_id: Yup.string()
.required("Required"),
bvn: Yup.string()
.required("BVN is required")
.test("no-e", "Invalid number", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
})
.min(11, "must be 11 digits")
.max(11, "must be 11 digits"),
first_name: Yup.string()
.required("Required"),
phone: Yup.string()
.required("Required"),
email: Yup.string()
.required("Required")
.email("Wrong email format"),
surname: Yup.string()
.required("Required"),
dob: Yup.string()
.required("Required"),
});
interface BasicInfoProps {
handleNextStep: any;
}
const EmployerValidation: React.FC<BasicInfoProps> = ({
handleNextStep,
}) => {
// const inputRef = useRef<HTMLInputElement>(null);
// const handleInput = (e: React.FormEvent<HTMLInputElement>) => {
// const { name, value } = e.target as HTMLInputElement;
// if (name === "bvn") {
// const isNumeric = /^[0-9]+$/.test(value);
// if (isNumeric) {
// if (value.length === 10) {
// setHideOTPComponent(false);
// } else {
// setHideOTPComponent(true);
// }
// } else {
// console.log("Invalid BVN");
// }
// }
// };
//FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => {
console.log(values)
handleNextStep()
};
return (
<>
{/* Header */}
<div className="w-full mb-10">
<h1 className="font-semibold text-lg md:text-xl text-[#5C2684]">
EMPLOYER'S COMMITMENT AND VALIDATION
</h1>
<p className="text-[10px]">(This is to be filled and validated by the employer of the applicant)</p>
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props)=>(
<Form>
<div className="w-full">
<p className="my-10">We hereby confirm that <span className="font-bold">Mr Victor Badmus (The Applicant)</span> who applied for our loan facility is a permanent and confirmed (Non-contract) staff of <span className="font-bold">Globalcom Nigeria limited</span> and that he is actively on the company's payroll
</p>
<p className="my-10">We hereby irrevocably and unconditionally undertake to domicile his/her salaries to First City Monument Bank Ltd (FCMB) Accounts number.
</p>
</div>
<div className="w-full rounded py-3 bg-[#5C2684] px-5">
<p className="text-base text-[#FBB700] tracking-[3%] font-extrabold w-fit">
Employers Validation
</p>
</div>
<div className="mt-8 grid grid-cols-2">
<div className="flex flex-col gap-4 max-w-[15.6875rem]">
<InputCompOne
parentInputClass="max-w-[224px] w-full"
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="title"
label="Title"
labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#5C2684] mb-[2px]"
select={true}
selectClass="w-full h-[36px] rounded-[6px]"
selectOptions={titleOptions}
selectValue={props.values.title}
onChange={props.handleChange}
error={(props.errors.title && props.touched.title) ? props.errors.title : ''}
/>
<InputCompOne
parentInputClass="max-w-[224px] w-full"
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="marital_status"
label="Marital Status"
labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#5C2684] mb-[2px]"
select={true}
selectClass="w-full h-[36px] rounded-[6px]"
selectOptions={maritalStatusOptions}
selectValue={props.values.marital_status}
onChange={props.handleChange}
error={(props.errors.marital_status && props.touched.marital_status) ? props.errors.marital_status : ''}
/>
<InputCompOne
parentInputClass="max-w-[224px] w-full"
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="agent_id"
label="Direct Sales Agent ID"
labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#5C2684] mb-[2px]"
select={true}
selectClass="w-full h-[36px] rounded-[6px]"
selectOptions={{loading: false, data:[{ value: "", label: "Select" }, { value: "dd", label: "AB001" }]}}
selectValue={props.values.agent_id}
onChange={props.handleChange}
error={(props.errors.agent_id && props.touched.agent_id) ? props.errors.agent_id : ''}
/>
<InputCompOne
label="BVN"
name="bvn"
parentInputClass="w-full"
labelSpan="( To get your BVN, dial *565*0# )"
labelSpanClass="text-[11px] text-[#7a7373]"
placeholder="Enter your BVN"
labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#5C2684] mb-[2px] gap-[2px]"
input={true}
inputClass="w-full h-[36px] bg-[#EFEFEF] rounded-[6px]"
value={props.values.bvn}
onChange={props.handleChange}
error={(props.errors.bvn && props.touched.bvn) ? props.errors.bvn : ''}
/>
</div>
</div>
<div className="mt-5">
<div className="grid grid-cols-2 gap-4">
<InputCompOne
parentClass="max-w-[20.3125rem]"
label="First Name"
name="first_name"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.first_name}
onChange={props.handleChange}
error={(props.errors.first_name && props.touched.first_name) ? props.errors.first_name : ''}
/>
<InputCompOne
parentClass="max-w-[20.3125rem]"
label="Phone Number"
name="phone"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.phone}
onChange={props.handleChange}
error={(props.errors.phone && props.touched.phone) ? props.errors.phone : ''}
/>
<InputCompOne
parentClass="max-w-[20.3125rem]"
label="Email Address"
name="email"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.email}
onChange={props.handleChange}
error={(props.errors.email && props.touched.email) ? props.errors.email : ''}
/>
<InputCompOne
parentClass="max-w-[20.3125rem]"
label="Surname"
name="surname"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem] px-3"
value={props.values.surname}
onChange={props.handleChange}
error={(props.errors.surname && props.touched.surname) ? props.errors.surname : ''}
/>
<InputCompOne
parentClass="max-w-[20.3125rem]"
label="Date of Birth"
name="dob"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputType='date'
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem] px-3"
value={props.values.dob}
onChange={props.handleChange}
error={(props.errors.dob && props.touched.dob) ? props.errors.dob : ''}
/>
<InputCompOne
parentClass="max-w-[20.3125rem]"
label="Second Name"
name="second_name"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem] px-3"
value={props.values.second_name}
onChange={props.handleChange}
error={(props.errors.second_name && props.touched.second_name) ? props.errors.second_name : ''}
/>
</div>
</div>
<div className='w-full'>
<div className="w-full rounded py-3 bg-[#5C2684] px-5 mt-5">
<p className="text-base text-[#FBB700] tracking-[3%] font-extrabold w-fit">
SPOUSE DETAILS ( If not applicable, please move to the next stage )
</p>
</div>
<div className="mt-8 grid grid-cols-2">
<div className="flex flex-col gap-4 max-w-[15.6875rem]">
<InputCompOne
parentClass="max-w-[20.3125rem]"
label="BVN"
name="spouse_bvn"
parentInputClass="w-full"
labelSpan="( To get your BVN, dial *565*0# )"
labelSpanClass="text-[11px] text-[#7a7373]"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.spouse_bvn}
onChange={props.handleChange}
error={(props.errors.spouse_bvn && props.touched.spouse_bvn) ? props.errors.spouse_bvn : ''}
/>
</div>
</div>
</div>
<>
<Button
className="mt-8 btn-R bg-[#5A2C82]"
text="Enter"
type="submit"
/>
</>
</Form>
)}
</Formik>
</>
);
};
export default EmployerValidation;
interface SelectOption {
loading: boolean;
data: {value: string;
label: string}[]
}
const maritalStatusOptions: SelectOption = {
loading: false,
data: [
{ value: "", label: "Select" },
{ value: "single", label: "Single" },
{ value: "married", label: "Married" },
{ value: "divorced", label: "Divorced" },
{ value: "widowed", label: "Widowed" },
]
}
const titleOptions: SelectOption = {
loading: false,
data: [
{ value: "", label: "Select" },
{ value: "ms", label: "Ms" },
{ value: "mr", label: "Mr" },
{ value: "miss", label: "Miss" },
{ value: "mrs", label: "Mrs" },
]
}
+28
View File
@@ -0,0 +1,28 @@
import EmployerValidation from "./EmployerValidation";
import YourAreAlmostThere from "./YourAreAlmostThere";
import LoanAmountComp from "./LoanAmountComp";
import ApplicantsAttestation from "./ApplicantsAttestation";
const GetStarted = ({handleNextStep, step}:{handleNextStep?:any, step?:string|number|any}) => {
return (
<>
{/* Main */}
{/* // {step === 2 && (
// <BasicInfo
// handleNextStep={handleNextStep}
// />
// )}
// {step === 3 && <YourAreAlmostThere handleNextStep={handleNextStep} />}
// {step === 4 && <LoanAmountComp handleNextStep={handleNextStep} />}
// {step === 5 && <ApplicantsAttestation />} */}
<div className="my-10 bg-white w-full rounded-2xl border-2 border-black">
<div className="w-full p-5 sm:p-10 lg:p-20">
<EmployerValidation handleNextStep={handleNextStep} />
</div>
</div>
</>
);
};
export default GetStarted;
@@ -0,0 +1,202 @@
import { Button, InputCompOne } from "..";
import {Formik, Form} from 'formik'
import * as Yup from "yup";
const initialValues = {
monthly_salary: "",
loan_amount: "",
duration: 6
};
// To get the validation schema
const validationSchema = Yup.object().shape({
duration: Yup.number()
.required("Required"),
monthly_salary: Yup.string()
.required("Required")
.test("no-e", "Invalid", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
}),
loan_amount: Yup.string()
.required("Required")
.test("no-e", "Invalid", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
}),
});
// interface SliderProps {
// handleSliderChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
// value: number;
// }
interface LoanAmountProps {
handleNextStep: any;
}
const LoanAmountComp: React.FC<LoanAmountProps> = ({ handleNextStep }) => {
// const [value, setValue] = React.useState(6);
// const handleSliderChange = (e: any) => {
// setValue(e.target.value);
// };
//FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => {
console.log(values)
handleNextStep()
};
return (
<>
<div className="flex justify-between items-center w-full mt-8 mb-[2.8125rem]">
<h1 className="font-semibold text-[38px] text-[#5C2684] my-[8px]">
Loan Amount
</h1>
<div className="flex flex-col gap-[7px]">
<p className="font-extrabold tracking-[3%] text-[#FBB700] underline">
For more enquiries and support
</p>
<p className="font-extrabold tracking-[3%] text-[#5A5A5A]">
Call: 09099000000
</p>
<p className="font-extrabold tracking-[3%] text-[#5A5A5A]">
Email: fcmbloan@support.com
</p>
</div>
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props)=>(
<Form>
<div className="flex flex-col gap-[45px] justify-center ml-[40px] mb-[40px]">
<InputCompOne
parentClass="max-w-[29.4375rem] w-full"
label="Your Monthly Salary*"
name="monthly_salary"
parentInputClass="w-full"
labelClass="font-bold text-[18px] leading-[21.7808px] tracking-[2%] text-[#5C2684] mb-[2px]"
input
inputClass="w-full h-[51px] bg-[#EFEFEF] rounded-[6px] placeholder:text-green-600 placeholder:font-bold px-4"
placeholder="150,000"
value={props.values.monthly_salary}
onChange={props.handleChange}
error={(props.errors.monthly_salary && props.touched.monthly_salary) ? props.errors.monthly_salary : ''}
/>
<div className="w-full border-[.1875rem] rounded-xl border-black min-h-[884px] py-4 px-8 max-w-[784px]">
<p className="leading-[22px] tracking-[3%] text-[#5C2684] w-[729px] mb-[2.625rem]">
The maximum amount you can apply for on this offer is based on the
information you shared with us in your loan application. We have
made this offer to suit your monthly remuneration and to enable you
pay your loan on-time
</p>
<InputCompOne
parentClass="max-w-[45.8125rem] w-full mb-3"
label="How much do you want to apply for?"
name="loan_amount"
parentInputClass="w-full"
labelClass="font-bold text-[18px] leading-[21.7808px] tracking-[2%] text-[#5C2684] mb-[2px]"
input
inputClass="w-full h-[51px] bg-[#EFEFEF] rounded-[6px] placeholder:text-green-600 placeholder:font-bold px-9"
placeholder="350,000"
value={props.values.loan_amount}
onChange={props.handleChange}
error={(props.errors.loan_amount && props.touched.loan_amount) ? props.errors.loan_amount : ''}
/>
<div className="flex items-center justify-between w-full">
<div className=" h-[68px] flex flex-col py-1 px-[13px] shadow-md text-[#5C2684]">
<span>Minimum Offer:</span>
<p>
<b>N</b>100,000
</p>
</div>
<div className="h-[68px] flex flex-col py-1 px-[13px] shadow-md text-[#5C2684]">
<span>Maximum Offer:</span>
<p>
<b>N</b>500,000
</p>
</div>
</div>
<>
<div className="flex flex-col items-start mt-11 mb-16">
<p className="text-lg font-semibold">For how many months? <span className='text-[10px] text-red-500'>{(props.errors.duration && props.touched.duration) ? props.errors.duration : ''}</span></p>
<div className="w-full">
<input
type="range"
min="6"
max="24"
value={props.values.duration}
onChange={props.handleChange}
className="slider w-full h-2 bg-gray-300 rounded-lg appearance-none cursor-pointer"
style={{
background: `linear-gradient(90deg, #6B21A8 ${
((props.values.duration - 6) / 18) * 100
}%, #D1D5DB ${((props.values.duration - 6) / 18) * 100}%)`,
}}
/>
</div>
<div className="mt-4 text-lg font-semibold text-gray-700 w-full flex items-center text-center justify-center">
{props.values.duration} months
</div>
</div>
</>
<div className="w-full flex items-center justify-center flex-col">
<div className="w-[279px] h-[130px] mb-[76px] flex items-center justify-center flex-col">
<p className="text-[#FBB700]">Your Monthly Repayment</p>
<p>N</p>
</div>
<Button
className="max-w-[462px] w-full bg-[#5C2684] rounded h-[2.75rem]"
text="Submit"
type='submit'
/>
</div>
</div>
</div>
</Form>
)}
</Formik>
</>
);
};
export default LoanAmountComp;
// const Slider: React.FC<SliderProps> = ({ handleSliderChange, value }) => {
// return (
// <div className="flex flex-col items-start mt-11 mb-16">
// <p className="text-lg font-semibold">For how many months?</p>
// <div className="w-full">
// <input
// type="range"
// min="6"
// max="24"
// value={value}
// onChange={handleSliderChange}
// className="slider w-full h-2 bg-gray-300 rounded-lg appearance-none cursor-pointer"
// style={{
// background: `linear-gradient(90deg, #6B21A8 ${
// ((value - 6) / 18) * 100
// }%, #D1D5DB ${((value - 6) / 18) * 100}%)`,
// }}
// />
// </div>
// <div className="mt-4 text-lg font-semibold text-gray-700 w-full flex items-center text-center justify-center">
// {value} months
// </div>
// </div>
// );
// };
@@ -0,0 +1,402 @@
import React from "react";
import { Button, InputCompOne } from "..";
import {Formik, Form} from 'formik'
import * as Yup from "yup";
const initialValues = {
job_title: "",
employer_name: "",
employer_email:"",
date_of_resumption: "",
employee_id: "",
annual_income: "",
monthly_salary: "",
salary_payment_date: "",
ref_name: "",
ref_email: "",
ref_number: "",
ref_relationship: "",
ref_bvn: "",
ref_two_name: "",
ref_two_email: "",
ref_two_number: "",
ref_two_relationship: "",
ref_two_bvn: "",
};
// To get the validation schema
const validationSchema = Yup.object().shape({
job_title: Yup.string()
.required("Required"),
employer_name: Yup.string()
.required("Required"),
date_of_resumption: Yup.string()
.required("Required"),
employer_email: Yup.string()
.email("Invalid")
.required("Required"),
annual_income: Yup.string()
.required("Required")
.test("no-e", "Invalid", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
}),
monthly_salary: Yup.string()
.required("Required")
.test("no-e", "Invalid", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
}),
salary_payment_date: Yup.string()
.required("Required"),
employee_id: Yup.string()
.required("Required"),
ref_name: Yup.string()
.required("Required"),
ref_email: Yup.string()
.email("Invalid")
.required("Required"),
ref_number: Yup.string()
.required("Required"),
ref_relationship: Yup.string()
.required("Required"),
ref_bvn: Yup.string()
.required("BVN is required")
.test("no-e", "Invalid number", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
})
.min(11, "must be 11 digits")
.max(11, "must be 11 digits"),
ref_two_name: Yup.string()
.required("Required"),
ref_two_email: Yup.string()
.email("Invalid")
.required("Required"),
ref_two_number: Yup.string()
.required("Required"),
ref_two_relationship: Yup.string()
.required("Required"),
ref_two_bvn: Yup.string()
.required("BVN is required")
.test("no-e", "Invalid number", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
})
.min(11, "must be 11 digits")
.max(11, "must be 11 digits"),
});
interface YourAreAlmostThereProps {
handleNextStep: any;
}
const YourAreAlmostThere: React.FC<YourAreAlmostThereProps> = ({ handleNextStep }) => {
//FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => {
console.log(values)
handleNextStep()
};
return (
<>
<h1 className="font-semibold text-[2.375rem] text-[#5C2684] my-[.5rem]">
Youre almost there
</h1>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props)=>(
<Form>
<div className="flex flex-col gap-6">
<>
<div className="w-full rounded py-3 bg-[#5C2684] px-5">
<p className="text-base text-[#FBB700] tracking-[3%] font-extrabold w-fit">
EMPLOYMENT DETAILS
</p>
</div>
<div className="flex flex-col gap-4">
<InputCompOne
parentClass="max-w-[17.9375rem] w-full"
label="Job Title"
name="job_title"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.job_title}
onChange={props.handleChange}
error={(props.errors.job_title && props.touched.job_title) ? props.errors.job_title : ''}
/>
<div className="flex items-center gap-[3.6875rem]">
<InputCompOne
parentClass="max-w-[23.1875rem] w-full"
label="Employers Name"
name="employer_name"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.employer_name}
onChange={props.handleChange}
error={(props.errors.employer_name && props.touched.employer_name) ? props.errors.employer_name : ''}
/>
<InputCompOne
parentClass="max-w-[23.1875rem] w-full"
label="Employers Official Email"
name="employer_email"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.employer_email}
onChange={props.handleChange}
error={(props.errors.employer_email && props.touched.employer_email) ? props.errors.employer_email : ''}
/>
</div>
<div className="flex items-center gap-[9rem]">
<InputCompOne
parentClass="max-w-[17.9375rem] w-full"
label="Resumption Date"
name="date_of_resumption"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputType='date'
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.date_of_resumption}
onChange={props.handleChange}
error={(props.errors.date_of_resumption && props.touched.date_of_resumption) ? props.errors.date_of_resumption : ''}
/>
<InputCompOne
parentClass="max-w-[17.9375rem] w-full"
label="Employee ID."
name="employee_id"
labelSpan="Upload your work ID"
labelSpanClass="text-[11px] text-[#7a7373]"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.employee_id}
onChange={props.handleChange}
error={(props.errors.employee_id && props.touched.employee_id) ? props.errors.employee_id : ''}
/>
</div>
<div className="flex items-center gap-[3.6875rem]">
<InputCompOne
parentClass="max-w-[23.1875rem] w-full"
label="Salary ( Gross annual income )"
name="annual_income"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.annual_income}
onChange={props.handleChange}
error={(props.errors.annual_income && props.touched.annual_income) ? props.errors.annual_income : ''}
/>
<InputCompOne
parentClass="max-w-[23.1875rem] w-full"
label="Salary ( Net monthly Income )"
name="monthly_salary"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.monthly_salary}
onChange={props.handleChange}
error={(props.errors.monthly_salary && props.touched.monthly_salary) ? props.errors.monthly_salary : ''}
/>
</div>
<InputCompOne
parentClass="max-w-[20.3125rem]"
label="Salary Payment Date"
name="salary_payment_date"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputType='date'
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.salary_payment_date}
onChange={props.handleChange}
error={(props.errors.salary_payment_date && props.touched.salary_payment_date) ? props.errors.salary_payment_date : ''}
/>
</div>
</>
<>
<div className="w-full rounded py-3 bg-[#5C2684] px-5">
<p className="text-base text-[#FBB700] tracking-[3%] font-extrabold w-fit">
REFERENCE DETAILS ( Must be 18 years and above )
</p>
</div>
<div className="">
<div className="flex flex-col gap-[3.4375rem]">
<div className="flex flex-col gap-4">
<div className="flex items-center gap-[6.5625rem]">
<InputCompOne
parentClass="max-w-[20.3125rem] w-full"
label="Name"
name="ref_name"
labelSpan="1st reference"
labelSpanClass="text-[12px] text-[#5C2684] ml-1"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] px-[.125rem] rounded-[.375rem]"
value={props.values.ref_name}
onChange={props.handleChange}
error={(props.errors.ref_name && props.touched.ref_name) ? props.errors.ref_name : ''}
/>
<InputCompOne
parentClass="max-w-[20.3125rem] w-full"
label="Relationship with He/She"
name="ref_relationship"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] px-[.125rem] rounded-[.375rem]"
value={props.values.ref_relationship}
onChange={props.handleChange}
error={(props.errors.ref_relationship && props.touched.ref_relationship) ? props.errors.ref_relationship : ''}
/>
</div>
<div className="flex items-center gap-[6.5625rem]">
<InputCompOne
parentClass="max-w-[20.3125rem] w-full"
label="Phone Number"
name="ref_number"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.ref_number}
onChange={props.handleChange}
error={(props.errors.ref_number && props.touched.ref_number) ? props.errors.ref_number : ''}
/>
<InputCompOne
parentClass="max-w-[20.3125rem] w-full"
label="Email Address"
name="ref_email"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.ref_email}
onChange={props.handleChange}
error={(props.errors.ref_email && props.touched.ref_email) ? props.errors.ref_email : ''}
/>
</div>
<InputCompOne
parentClass="max-w-[20.3125rem] w-full"
label="BVN"
name="ref_bvn"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.ref_bvn}
onChange={props.handleChange}
error={(props.errors.ref_bvn && props.touched.ref_bvn) ? props.errors.ref_bvn : ''}
/>
</div>
<div className="flex flex-col gap-4">
<div className="flex items-center gap-[6.5625rem]">
<InputCompOne
parentClass="max-w-[20.3125rem] w-full"
label="Name"
name="ref_two_name"
labelSpan="2nd reference"
labelSpanClass="text-[12px] text-[#5C2684] ml-[4px]"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.ref_two_name}
onChange={props.handleChange}
error={(props.errors.ref_two_name && props.touched.ref_two_name) ? props.errors.ref_two_name : ''}
/>
<InputCompOne
parentClass="max-w-[20.3125rem] w-full"
label="Relationship with He/She"
name="ref_two_relationship"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.ref_two_relationship}
onChange={props.handleChange}
error={(props.errors.ref_two_relationship && props.touched.ref_two_relationship) ? props.errors.ref_two_relationship : ''}
/>
</div>
<div className="flex items-center gap-[6.5625rem]">
<InputCompOne
parentClass="max-w-[20.3125rem] w-full"
label="Phone Number"
name="ref_two_number"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.ref_two_number}
onChange={props.handleChange}
error={(props.errors.ref_two_number && props.touched.ref_two_number) ? props.errors.ref_two_number : ''}
/>
<InputCompOne
parentClass="max-w-[20.3125rem] w-full"
label="Email Address"
name="ref_two_email"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.ref_two_email}
onChange={props.handleChange}
error={(props.errors.ref_two_email && props.touched.ref_two_email) ? props.errors.ref_two_email : ''}
/>
</div>
<InputCompOne
parentClass="max-w-[20.3125rem] w-full"
label="BVN"
name="ref_two_bvn"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.ref_two_bvn}
onChange={props.handleChange}
error={(props.errors.ref_two_bvn && props.touched.ref_two_bvn) ? props.errors.ref_two_bvn : ''}
/>
</div>
</div>
</div>
</>
<Button
className="my-8 max-w-[20.3125rem] btn-R bg-[#5A2C82]"
text="Continue"
type="submit"
/>
</div>
</Form>
)}
</Formik>
</>
);
};
export default YourAreAlmostThere;
+3
View File
@@ -0,0 +1,3 @@
import GetStarted from "./GetStarted";
export { GetStarted };
+151
View File
@@ -0,0 +1,151 @@
import React, { useState, ChangeEvent } from "react";
import Logo from "../../assets/icons/logo.svg";
import Button from "../shared/Button";
import { _lowerMenuItems } from "../../utils/data";
import Sidebar from "./Sidebar";
import { Link } from "react-router-dom";
import HeaderMenuItem from "./HeaderMenuItem";
// import { RouteHandler } from "../../router/routes";
export type LowerMenuItem = {
name: string;
linkPath: string;
subItems?: LowerMenuItem[];
};
type HiddenMenuItems = {
hideSidebar?: boolean;
hideMenu?: boolean;
};
const Header: React.FC<HiddenMenuItems> = ({
hideSidebar = false,
hideMenu = false,
}) => {
const [searchValue, setSearchValue] = useState<string>("");
const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(false);
const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearchValue(e.target.value);
};
const toggleSidebar = () => {
setIsSidebarOpen((prev) => !prev);
};
return (
<div className="relative mt-2 py-2 flex items-center justify-center border-b-2 border-[#E3DEDA] shadow-md">
{!hideSidebar && (
<Sidebar toggleSidebar={toggleSidebar} isSidebarOpen={isSidebarOpen} />
)}
<div className="containerMode flex justify-between gap-1 xl:gap-8">
<Link to="/">
<img
src={Logo}
alt="Logo"
className="w-[90px] h-[90px] xl:w-[117px] xl:h-[117px]"
/>
</Link>
{!hideMenu && (
<div className="flex flex-col-reverse lg:flex-col grow lg:grow-0 justify-between items-end">
<ul className="flex gap-0 lg:gap-[10px] items-center justify-end w-full flex-wrap">
{[
{ text: "Open An Account", href: '' },
{
text: "Internet Banking",
href: '',
},
{ text: "Contact Us", href: '' },
].map((item: { text: string; href: string }) => (
<li key={item.text} className="hidden sm:flex">
<a href={item.href}>
<Button
className={
item.text === "Open An Account" ? "btn-active" : ""
}
text={item.text}
/>
</a>
</li>
))}
<li className="w-full lg:w-fit">
<SearchInput
onChange={handleSearchChange}
value={searchValue}
/>
</li>
</ul>
<div className="flex lg:hidden">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1"
strokeLinecap="round"
strokeLinejoin="round"
onClick={toggleSidebar}
className="w-6 h-6 stroke-[#A6368C]"
>
<path
fillRule="evenodd"
d="M3 6.75A.75.75 0 013.75 6h16.5a.75.75 0 010 1.5H3.75A.75.75 0 013 6.75zM3 12a.75.75 0 01.75-.75h16.5a.75.75 0 010 1.5H3.75A.75.75 0 013 12zm0 5.25a.75.75 0 01.75-.75h16.5a.75.75 0 010 1.5H3.75a.75.75 0 01-.75-.75z"
clipRule="evenodd"
/>
</svg>
</div>
<ul className="hidden lg:flex gap-[10px] items-center justify-end flex-wrap relative">
{_lowerMenuItems.map((item: LowerMenuItem, idx: number) => (
<HeaderMenuItem key={idx} item={item} />
))}
</ul>
</div>
)}
</div>
</div>
);
};
export default Header;
const SearchInput = ({
value,
onChange,
}: {
value: string;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
}) => {
return (
<div className="flex items-center border border-[#5A2C82] overflow-hidden w-full">
<input
type="text"
value={value}
onChange={onChange}
className="py-[6px] px-[12px] w-full outline-none text-[#333] text-sm"
placeholder="Search..."
/>
<button
type="button"
className="flex items-center justify-center bg-transparent text-[#5A2C82] py-[4px] px-[12px]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
/>
</svg>
</button>
</div>
);
};
+35
View File
@@ -0,0 +1,35 @@
import React, { useState } from "react";
import { LowerMenuItem } from "./Header";
interface MenuItemProps {
item: LowerMenuItem;
}
const HeaderMenuItem: React.FC<MenuItemProps> = ({ item }) => {
const [showSubMenu, setShowSubMenu] = useState<boolean>(false);
const toggleSubMenu = () => {
setShowSubMenu(!showSubMenu);
};
return (
<li
className={`cursor-pointer text-[13.5px] font-medium text-[#525252] tracking-[1px] leading-[-0.3pt]`}
onMouseEnter={toggleSubMenu}
onMouseLeave={toggleSubMenu}
>
<a href={item.linkPath}>{item.name}</a>
{showSubMenu && item.subItems && (
<ul
className={`absolute bg-white shadow-md p-4 z-20 `}
>
{item.subItems.map((subItem, index) => (
<HeaderMenuItem key={index} item={subItem} />
))}
</ul>
)}
</li>
);
};
export default HeaderMenuItem;
+19
View File
@@ -0,0 +1,19 @@
const Sidebar = ({
toggleSidebar,
isSidebarOpen,
}: {
toggleSidebar: () => void;
isSidebarOpen: boolean;
}) => {
const isActive = isSidebarOpen ? "sidebar-close" : "sidebar-open";
return (
// <div className={`sidebar ${isSidebarOpen ? "open" : ""}`}>
<div className={`${isActive} sidebar`}>
{/* Sidebar content goes here */}
<button onClick={toggleSidebar}>Close Sidebar</button>
</div>
);
};
export default Sidebar;
+54
View File
@@ -0,0 +1,54 @@
import { top_header_data } from "../../utils/data";
import { Link } from "react-router-dom";
import styles from "./header.module.css";
const TopHeader = () => {
return (
<>
<div className="flex flex-col sm:hidden bg-[#5c2684]">
<ul className="flex flex-col justify-center items-center pt-[0.4rem] text-[13px] font-light">
{["Open An Account", "Internet Banking", "Contact Us"].map((text) => (
<li key={text} className="w-full">
<a
href="#"
className={`p-2 cursor-pointer text-white w-full items-center justify-center flex`}
>
{text}
</a>
</li>
))}
<li className="w-full flex items-center justify-center">
<a
href="#"
className={`p-2 mt-2 flex gap-2 bg-[#74449E] cursor-pointer text-white w-full items-center justify-center`}
>
<p className="uppercase">Today's Share price:</p>
<span className="text-[#F8B51F] text-base md:text-lg">
$ 4.00
</span>
</a>
</li>
</ul>
</div>
<div className={styles.top_header}>
<div className="containerMode flex justify-between w-full text-white font-medium text-[11px] md:text-[13px]">
<ul className="flex items-center py-[0.4rem] flex-wrap">
{top_header_data.map(({ id, name, href }) => (
<li key={id}>
<Link to={href} className={`py-[11px] px-[15px]`}>
{name}
</Link>
</li>
))}
</ul>
<div className="hidden sm:flex gap-2 items-center bg-[#74449E] px-2 text-[11px] md:text-[13px]">
<p className="uppercase">Today's Share price:</p>
<span className="text-[#F8B51F] text-base md:text-lg">$ 4.00</span>
</div>
</div>
</div>
</>
);
};
export default TopHeader;
+8
View File
@@ -0,0 +1,8 @@
.top_header{
background: url(../../assets/images/topbar_back.jpg) no-repeat;
background-size: cover;
/* padding: 0.4rem 0; */
display: flex;
align-items: center;
justify-content: center;
}
+4
View File
@@ -0,0 +1,4 @@
import TopHeader from "./TopHeader";
import Header from "./Header";
export { TopHeader, Header };
+16
View File
@@ -0,0 +1,16 @@
import styles from "./hero.module.css";
const Hero = () => {
return (
<div
className={`w-full relative mb-0 sm:mb-[2.25rem] regLap:h-[30rem] xl:h-[26.875rem] lg:h-[25rem] md:h-[21.875rem] sm:h-[18.75rem] h-[15.625rem] object-cover ${styles.heroBg}`}
>
<div className="containerMode flex justify-between gap-1 xl:gap-8">
<h1 className="max-w-[32.9375rem] font-extrabold text-[1.3rem] leading-[2.5rem] sm:text-[3.625rem] sm:leading-[4.3869rem] text-[#5C2684] cursor-default">
PREMIUM SALARY LOAN
</h1>
</div>
</div>
);
};
export default Hero;
+36
View File
@@ -0,0 +1,36 @@
import React from "react";
import styles from "./hero.module.css";
import { Link } from "react-router-dom";
interface PersonalHeroProps {
heading?: string;
body?: string;
buttonLink?: string;
buttonText?: string;
}
const PersonalHero: React.FC<PersonalHeroProps> = ({
heading,
body,
buttonLink = "#",
buttonText,
}) => {
return (
<div
className={`w-full max-[28.125rem] relative mb-0 sm:mb-[2.25rem] regLap:h-[30rem] xl:h-[26.875rem] lg:h-[25rem] md:h-[21.875rem] sm:h-[18.75rem] h-[15.625rem] object-cover ${styles.personalHeroBg}`}
>
<div className="containerMode flex justify-between gap-1 xl:gap-8 flex-col">
<h1 className="max-w-[32.9375rem] font-extrabold text-[1.3rem] leading-[2.5rem] sm:text-[3.625rem] sm:leading-[4.3869rem] text-[#5C2684] cursor-default">
{heading}
</h1>
<p className="p-[.3125rem] pr-5 font-medium table w-[25.375rem]">{body}</p>
<Link to={buttonLink}>
<button className="bg-[#A6368C] text-white text-[.9375rem] w-[10.9375rem] py-[.4375rem] px-[.625rem]">
{buttonText} &nbsp;
</button>
</Link>
</div>
</div>
);
};
export default PersonalHero;
+19
View File
@@ -0,0 +1,19 @@
.heroBg{
background: url(../../../assets/images/hero-test.png) no-repeat;
background-size: cover;
background-position: center;
/* padding: 0.4rem 0; */
display: flex;
align-items: center;
justify-content: center;
}
.personalHeroBg{
background: url(../../../assets/images/personal-page.jpg) no-repeat;
background-size: cover;
background-position: center;
/* padding: 0.4rem 0; */
display: flex;
align-items: center;
justify-content: center;
}
+4
View File
@@ -0,0 +1,4 @@
import Hero from "./Hero";
import PersonalHero from "./PersonalHero";
export { Hero, PersonalHero };
@@ -0,0 +1,22 @@
const EligiblityBox = () => {
return (
<div className="w-[23.4rem] sm:w-[24.875rem] h-fit rounded bg-[#5C2684] px-[17px] py-[1.625rem] flex flex-col gap-4">
<h2 className="font-extrabold text-lg text-[#FBB700]">
REQUIRED ELIGIBILITY
</h2>
<ul className="flex flex-col gap-[.625rem] list-disc pl-[2rem] text-white">
<li className="text-base leading-[1.5625rem]">
Have a verifiable source of income
</li>
<li className="text-base leading-[1.5625rem]">
You must have a valid BVN
</li>
<li className="text-base leading-[1.5625rem]">
Must have a salary or current bank account with FCMB
</li>
</ul>
</div>
);
};
export default EligiblityBox;
@@ -0,0 +1,45 @@
import { Link } from "react-router-dom";
import { RouteHandler } from "../../../router/routes";
const FeatureText = () => {
return (
<div className="w-full sm:w-2/3 px-0 sm:px-[15px] flex flex-col">
<div className="mt-5 text-[.9375rem] text-[#454545] leading-[1.4375rem] cursor-default">
<p className="mb-[.9375rem] text-justify sm:text-left">
Premium Salary Plus loan provides confirmed staff of commercial
organizations more usable funds. The employees organization must have
been rated on Moodys with a minimum BB- rating, employees interested
in the product must be eligible for minimum loan amount of 2,000,000.
</p>
<p className="mb-[.9375rem]">
<strong>Features</strong>
</p>
<ul className="flex flex-col gap-[.625rem] list-disc pl-[2.5rem]">
<li>Minimum loan amount - N2 Million</li>
<li>Maximum tenure - 60 Months</li>
<li>Minimum tenure - 12 Months</li>
<li>Management fee - 1% flat upfront (0.5% for top-up loan)</li>
<li>
Collateral - Domiciliate of salary, terminal benefits and other
allowances
</li>
<li>Insurance fee - 0.9%*loan amount*tenure (in years)</li>
</ul>
</div>
<Link
to={RouteHandler.letsGetStarted}
className="text-[#5C2684] mt-[1.5625rem] w-fit"
>
*** <span className="hover:underline">Click here to apply</span>
</Link>
<Link
to={RouteHandler.termsAndConditions}
className="mt-[.5625rem] font-bold"
>
Terms and conditions apply
</Link>
</div>
);
};
export default FeatureText;
@@ -0,0 +1,15 @@
import FeatureText from "./FeatureText";
import EligiblityBox from "./EligiblityBox";
const Requirements = () => {
return (
<div className="mt[5.3125rem] mb-[7.875rem] min-h-[28.6875rem]">
<div className="containerMode flex flex-col sm:flex-row justify-between w-full gap-2 sm:gap-[6rem]">
<FeatureText />
<EligiblityBox />
</div>
</div>
);
};
export default Requirements;
@@ -0,0 +1,3 @@
import Requirements from "./Requirements";
export { Requirements };
+4
View File
@@ -0,0 +1,4 @@
import { Hero, PersonalHero } from "./Hero";
import { Requirements } from "./Requirements";
export {Hero, Requirements, PersonalHero}
+121
View File
@@ -0,0 +1,121 @@
import { FaCaretDown, FaCaretRight } from "react-icons/fa";
import dashIcon from "../../assets/images/dashboard/dashDefault.svg";
type Props = {
name: string;
fillColor?: string;
className?:string;
};
export default function Icons({ name, fillColor, className }: Props) {
return (
<>
{name == "home" ? (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.1667 14.9875V19.9875H17.5C18.8807 19.9875 20 18.8682 20 17.4875V9.88673C20.0002 9.4538 19.832 9.03778 19.5308 8.72673L12.4492 1.07087C11.1996 -0.281086 9.09074 -0.364094 7.73879 0.885437C7.67457 0.944812 7.6127 1.00665 7.55336 1.07087L0.48418 8.72423C0.173945 9.03657 -0.000117128 9.45899 5.9134e-08 9.89923V17.4875C5.9134e-08 18.8682 1.1193 19.9875 2.5 19.9875H5.83332V14.9875C5.84891 12.7152 7.68355 10.8596 9.89867 10.8061C12.1879 10.7509 14.1492 12.6381 14.1667 14.9875Z"
fill={fillColor ? fillColor : "#5C2684"}
/>
<path
d="M10 12.4875C8.6193 12.4875 7.5 13.6068 7.5 14.9875V19.9875H12.5V14.9875C12.5 13.6068 11.3807 12.4875 10 12.4875Z"
fill={fillColor ? fillColor : "#5C2684"}
/>
</svg>
) : name == "profile" ? (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 10C12.7614 10 15 7.76142 15 5C15 2.23858 12.7614 0 10 0C7.23858 0 5 2.23858 5 5C5 7.76142 7.23858 10 10 10Z"
fill={fillColor ? fillColor : "#5C2684"}
/>
<path
d="M10 11.6667C5.85977 11.6713 2.50461 15.0265 2.5 19.1667C2.5 19.6269 2.87309 20 3.33332 20H16.6666C17.1269 20 17.5 19.6269 17.5 19.1667C17.4954 15.0265 14.1402 11.6713 10 11.6667Z"
fill={fillColor ? fillColor : "#5C2684"}
/>
</svg>
) : name == "verification" ? (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19.728 6.8281L18.1812 5.28145C17.814 4.91432 17.2203 4.91432 16.857 5.28145L7.50177 14.6356L3.15031 10.2807C2.78313 9.91359 2.1894 9.91359 1.82613 10.2807L0.275384 11.8313C-0.0917946 12.1984 -0.0917946 12.7921 0.275384 13.1592L6.83772 19.7246C7.2049 20.0918 7.79864 20.0918 8.16191 19.7246L19.7241 8.15603C20.0913 7.78499 20.0913 7.19133 19.728 6.8281ZM7.06037 10.9681C7.30256 11.2142 7.70098 11.2142 7.94316 10.9681L16.068 2.8365C16.3101 2.59044 16.3101 2.19597 16.068 1.95382L14.3024 0.184543C14.0602 -0.0615144 13.6618 -0.0615144 13.4196 0.184543L7.50177 6.10164L5.33776 3.93399C5.09558 3.68794 4.69715 3.68794 4.45497 3.93399L2.68548 5.70327C2.4433 5.94932 2.4433 6.3438 2.68548 6.58595L7.06037 10.9681Z"
fill={fillColor ? fillColor : "#5C2684"}
/>
</svg>
) : name == "payments" ? (
<svg
width="20"
height="16"
viewBox="0 0 20 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 14.2857C0 15.2321 0.746528 16 1.66667 16H18.3333C19.2535 16 20 15.2321 20 14.2857V8H0V14.2857ZM6.66667 11.8571C6.66667 11.6214 6.85417 11.4286 7.08333 11.4286H11.8056C12.0347 11.4286 12.2222 11.6214 12.2222 11.8571V13.2857C12.2222 13.5214 12.0347 13.7143 11.8056 13.7143H7.08333C6.85417 13.7143 6.66667 13.5214 6.66667 13.2857V11.8571ZM2.22222 11.8571C2.22222 11.6214 2.40972 11.4286 2.63889 11.4286H5.13889C5.36806 11.4286 5.55556 11.6214 5.55556 11.8571V13.2857C5.55556 13.5214 5.36806 13.7143 5.13889 13.7143H2.63889C2.40972 13.7143 2.22222 13.5214 2.22222 13.2857V11.8571ZM20 1.71429V3.42857H0V1.71429C0 0.767857 0.746528 0 1.66667 0H18.3333C19.2535 0 20 0.767857 20 1.71429Z"
fill={fillColor ? fillColor : "#5C2684"}
/>
</svg>
) : name == "legals" ? (
<svg
width="20"
height="16"
viewBox="0 0 20 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8 10.5H7.99937C7.99937 9.99438 8.04125 10.2272 5.34156 4.82781C4.79 3.725 3.21062 3.72281 2.65812 4.82781C-0.0643752 10.2734 0.000625 10.0103 0.000625 10.5H0C0 11.8806 1.79094 13 4 13C6.20906 13 8 11.8806 8 10.5ZM4 5.5L6.25 10H1.75L4 5.5ZM19.9994 10.5C19.9994 9.99438 20.0413 10.2272 17.3416 4.82781C16.79 3.725 15.2106 3.72281 14.6581 4.82781C11.9356 10.2734 12.0006 10.0103 12.0006 10.5H12C12 11.8806 13.7909 13 16 13C18.2091 13 20 11.8806 20 10.5H19.9994ZM13.75 10L16 5.5L18.25 10H13.75ZM16.5 14H11V4.78906C11.7347 4.4675 12.2863 3.80531 12.4497 3H16.5C16.7763 3 17 2.77625 17 2.5V1.5C17 1.22375 16.7763 1 16.5 1H11.9888C11.5325 0.39625 10.8153 0 10 0C9.18469 0 8.4675 0.39625 8.01125 1H3.5C3.22375 1 3 1.22375 3 1.5V2.5C3 2.77625 3.22375 3 3.5 3H7.55031C7.71375 3.805 8.265 4.4675 9 4.78906V14H3.5C3.22375 14 3 14.2238 3 14.5V15.5C3 15.7762 3.22375 16 3.5 16H16.5C16.7763 16 17 15.7762 17 15.5V14.5C17 14.2238 16.7763 14 16.5 14Z"
fill={fillColor ? fillColor : "#5C2684"}
/>
</svg>
) : name == "arrow" ? (
<svg
width="15"
height="15"
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.3072 1.05822L7.03885 0.34847C7.34865 0.0479466 7.8496 0.0479466 8.15611 0.34847L14.563 6.56035C14.8728 6.86087 14.8728 7.34682 14.563 7.64415L8.15611 13.8592C7.84631 14.1597 7.34536 14.1597 7.03885 13.8592L6.3072 13.1495C5.9941 12.8458 6.00069 12.3502 6.32038 12.0529L10.2917 8.38267H0.819787C0.381453 8.38267 0.0288086 8.04058 0.0288086 7.61538V6.59232C0.0288086 6.16711 0.381453 5.82503 0.819787 5.82503H10.2917L6.32038 2.15481C5.9974 1.85748 5.99081 1.36194 6.3072 1.05822Z"
fill={fillColor ? fillColor : "#FBB700"}
/>
</svg>
) : name == "greater-than" ? (
<svg
width="11"
height="16"
viewBox="0 0 11 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.74365 8.64502L3.10303 15.2856C2.64404 15.7446 1.90186 15.7446 1.44775 15.2856L0.344238 14.1821C-0.114746 13.7231 -0.114746 12.981 0.344238 12.5269L5.05127 7.81982L0.344238 3.11279C-0.114746 2.65381 -0.114746 1.91162 0.344238 1.45752L1.44287 0.344238C1.90186 -0.114746 2.64404 -0.114746 3.09814 0.344238L9.73877 6.98486C10.2026 7.44385 10.2026 8.18604 9.74365 8.64502Z"
fill={fillColor ? fillColor : "#FFF"}
/>
</svg>
) :name == 'arrow-down'?
<FaCaretDown className={`text-xl ${className && className}`} />
:name == 'arrow-right'?
<FaCaretRight className={`text-xl ${className && className}`} />
:name == "dash-icon" ? (
<img src={dashIcon} alt="dash-icon" />
) : null}
</>
);
}
+3
View File
@@ -0,0 +1,3 @@
import Icons from "./Icons";
export { Icons };
@@ -0,0 +1,9 @@
import React from 'react'
const InternetBanking: React.FC = () => {
return (
<div>InternetBanking</div>
)
}
export default InternetBanking
+3
View File
@@ -0,0 +1,3 @@
import InternetBanking from "./InternetBanking";
export { InternetBanking };
@@ -0,0 +1,260 @@
import React from 'react';
import * as Yup from 'yup';
import { Form, Formik } from 'formik';
import { InputCompOne } from '..';
import { useNavigate } from 'react-router-dom';
import { RouteHandler } from '../../router/routes';
import { useDispatch } from 'react-redux';
import { updateUserDetails } from '../../store/UserDetails';
import { validateBVN, verifyOTP } from '../../core/apiRequest';
import { RequestStatus } from '../../core/models';
// To get the validation schema
const validationSchema = Yup.object().shape({
bvn: Yup.string()
.required('BVN is required')
.test('no-e', 'Invalid number', (value: any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
})
.min(11, 'must be 11 digits')
.max(11, 'must be 11 digits'),
otp: Yup.string()
// .when('require_otp', {
// is: true,
// then: Yup.string().required("OTP is required")
// })
// .required("OTP is required")
.test('no-e', 'Invalid number', (value: any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
})
.min(5, 'must be 5 digits')
.max(5, 'must be 5 digits'),
// .test("no-e", "must be 11 characters", (value:any) => {
// if (value.length < 11) {
// return false;
// }
// return true;
// })
});
// initial values for formik
let initialValues = {
bvn: '',
otp: '',
};
type ValidBVN = {
verification_id: string;
valid: undefined | boolean;
};
const LetsGetStarted: React.FC = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
// const [pinValues, setPinValues] = React.useState({
// bvn: "",
// otp: "",
// });
// const otpInputRef = React.useRef<HTMLInputElement>(null);
const [requestStatusBVN, setRequestStatusBVN] = React.useState<RequestStatus>(
{ loading: false, status: undefined, message: '' }
);
const [requestStatusOTP, setRequestStatusOTP] = React.useState<RequestStatus>(
{ loading: false, status: undefined, message: '' }
);
const [bvnIsValid, setBvnIsValid] = React.useState<ValidBVN>({
verification_id: '',
valid: undefined,
});
// e: React.FormEvent<HTMLInputElement>
// let { value } = e.target as HTMLInputElement;
const bvnValidation = (values: any) => {
// Function to Validate BVN
let bvn = values.bvn;
setRequestStatusBVN({ loading: true, status: false, message: '' });
validateBVN({ bvn })
.then((res) => {
if (!res || !res.data.call_return) {
setBvnIsValid({ verification_id: '', valid: false });
setRequestStatusBVN({
loading: false,
status: false,
message: 'unable to verify BVN',
});
return setTimeout(() => {
setRequestStatusBVN({ loading: false, status: false, message: '' });
}, 4000);
}
setBvnIsValid({
verification_id: res.data.verification_id,
valid: true,
});
setRequestStatusBVN({
loading: false,
status: true,
message: 'verified',
});
})
.catch((err) => {
setBvnIsValid({ verification_id: '', valid: false });
console.log(err);
});
};
const handleSubmit = (values: any) => {
// Function to VERIFY OTP AND LOGIN USER
setRequestStatusOTP({ loading: true, status: false, message: '' });
// console.log('values', values)
verifyOTP({ ...values, verification_id: bvnIsValid.verification_id })
.then((res) => {
if (!res || !res.data.call_return) {
setRequestStatusOTP({
loading: false,
status: false,
message: 'wrong otp',
});
return setTimeout(() => {
setRequestStatusOTP({ loading: false, status: false, message: '' });
}, 4000);
}
// console.log(res.data)
localStorage.setItem('token', res.data?.token);
localStorage.setItem('uid', res?.data?.customer[0]?.uid);
dispatch(updateUserDetails({ ...res?.data?.customer[0] }));
navigate(RouteHandler.dashboardHome, { replace: true });
})
.catch((err) => {
setRequestStatusOTP({
loading: false,
status: false,
message: 'something went wrong, try again',
});
console.log(err);
return setTimeout(() => {
setRequestStatusOTP({ loading: false, status: false, message: '' });
}, 4000);
});
};
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={bvnIsValid.valid ? handleSubmit : bvnValidation}
>
{(props: any) => (
<Form className="">
<div className="w-full">
<div className="containerMode flex justify-between gap-1 xl:gap-8 flex-col">
<div className="my-[4rem] flex items-center justify-center w-full">
<h1 className="font-bold text-[2.375rem] text-[#5C2684] my-[.5rem] text-center">
Lets Get You Started
</h1>
</div>
<div className="mx-auto flex flex-col gap-8 max-w-[31.625rem] ">
<div className="w-full">
<InputCompOne
parentClass="flex flex-col gap-2"
label="Enter Your BVN "
name="bvn"
parentInputClass="w-full"
labelSpan="( To get your BVN, dial *565*0# )"
labelSpanClass="text-[13px] text-[#5a5a5a] font-semibold"
placeholder="Enter your BVN"
labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#282828] mb-[2px] flex item-center gap-[4px]"
input
inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4"
value={props.values.bvn}
onChange={props.handleChange}
error={
props.errors.bvn && props.touched.bvn && props.errors.bvn
}
maxLength={11}
/>
<p
className={`p-2 ${
!requestStatusBVN.status
? 'text-red-500'
: 'text-emerald-500'
}`}
>
{requestStatusBVN.loading
? 'verifying...'
: requestStatusBVN.message}
</p>
</div>
{bvnIsValid.valid && (
<InputCompOne
parentClass="flex flex-col gap-2"
label="Enter OTP "
name="otp"
parentInputClass="w-full"
labelSpan="( Please check your BVN phone number for verification pin )"
labelSpanClass="text-[13px] text-[#5a5a5a] font-semibold"
placeholder="Enter your OTP"
labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#282828] mb-[2px] flex item-center gap-[4px]"
input
inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4"
value={props.values.otp}
onChange={props.handleChange}
error={
props.errors.otp && props.touched.otp && props.errors.otp
}
maxLength={5}
/>
)}
<button
type="submit"
className="w-full h-[3.625rem] rounded bg-[#FBB700] rounded-2 px-4 text-[18px] text-[#282828] font-semibold disabled:text-[#282828] disabled:text-opacity-50"
disabled={
requestStatusBVN.loading ||
(!props.values.otp && bvnIsValid.valid)
}
>
Enter
</button>
<p
className={`p-2 ${
!requestStatusOTP.status
? 'text-red-500'
: 'text-emerald-500'
}`}
>
{requestStatusOTP.message}
</p>
{bvnIsValid.valid || bvnIsValid.valid == undefined ? (
<p className="text-[#5C2684] mt-[1.5625rem] w-fit">
***Every personal information attached to your BVN is safe
and secure. It is only important for us to verify your
information and also give you access to your application
profile/account.
</p>
) : (
<p className="text-[#5C2684] mt-[1.5625rem] w-fit">
***Did not receive OTP? Click to resend
</p>
)}
</div>
</div>
</div>
</Form>
)}
</Formik>
);
};
export default LetsGetStarted;
@@ -0,0 +1,22 @@
import React from "react";
import { Link } from "react-router-dom";
import Logo from "../../assets/icons/logo.svg";
const LetsGetStartedNav: React.FC = () => {
return (
<div className="w-full">
<div className="containerMode flex justify-between gap-1 xl:gap-8">
<Link to="/">
<img
src={Logo}
alt="Logo"
className="w-[52px] h-[43px] xl:w-[72px] xl:h-[63px]"
/>
</Link>
</div>
</div>
);
};
export default LetsGetStartedNav;
+4
View File
@@ -0,0 +1,4 @@
import LetsGetStarted from "./LetsGetStarted";
import LetsGetStartedNav from "./LetsGetStartedNav";
export { LetsGetStarted, LetsGetStartedNav };
+428
View File
@@ -0,0 +1,428 @@
import React from "react";
const Main: React.FC = () => {
return (
<div className="w-full my-3">
<div className="containerMode">
<p className="mb-[15px]">
<strong>PRIVACY POLICY</strong>
</p>
<p className="mb-[15px]">
<strong>
<span>1.Your Privacy is important to us.</span>
</strong>
</p>
<p className="mb-[15px]">
This privacy statement sets out the privacy policy of
fcmbgroupplc.com, which provides a portal, or gateway, to the
financial services offered by the First City Monument Bank Limited and
the other members of the FCMB Group Plc (Collectively, FCMB).
</p>
<p className="mb-[15px]">
This policy explains how we collect, share, use, and protect
information when you visit or use this website and any other online
services, platforms, or products offered by FCMB or any of its banking
and non-banking affiliates and subsidiaries that link to or reference
this policy (collectively, our services).
</p>
<p className="mb-[15px]">
<strong>1.1 FCMB and You</strong>
</p>
<p className="mb-[15px]">
First City Monument Bank Limited is a private limited liability
company registered in the Federal Republic of Nigeria under RC No.
46713. Its head-office is at Primrose Tower, 17A Tinubu Street, Lagos
State, Nigeria.
</p>
<p className="mb-[15px]">
FCMB Group Plc hosts the fcmbgroupplc.com website and provides
technical support, access and links to the Local Sites of First City
Group members. fcmbgroupplc.com does not offer financial services or
products. Financial services and products may only be obtained by
registering with a Local Site. The First City Group provides financial
products and services to a global clientele through its affiliated
companies and branches located in 36 states and the Federal Capital
Territory in Nigeria, and in the UK. Privacy and personal data
protection principles vary from one country to another. When you
access or link to a Local Site, please read the privacy statement
issued by the Local Site to determine the policies that apply to
information or data maintained by the Local Site.
</p>
<p className="mb-[15px]">
<strong>
<span>2. Information we may collect about you </span>
</strong>
</p>
<p className="mb-[15px]">
<strong>2.1 Your Personal Information</strong>
</p>
<p className="mb-[15px]">
At FCMB, we strive to meet your needs and provide you with exceptional
services. In the course of consuming our services through various
channels, such as forms, phone calls, correspondence, service point
interfaces, and other available channels, we collect information that
you provide to us. This information may include, but is not limited
to, contact data, log/Technical information, Financial Data, Marketing
and Communications Data, identity verification details (this includes
Personally Identifiable Information (PII), otherwise known as Personal
Information or Personal Data, which includes email address, phone
number, contact address, limited financial information, location data,
device data etc.) and documents, services consumed or desired, mode of
consumption, preferences, location, general events, and instructions
and transactions relating to the services.
</p>
<p className="mb-[15px]">
The lawful basis we rely on for processing your Personal Information
are:
</p>
<ol>
<li>
Your Consent: Where you agree to us collecting your Personal
Information by using our Services.
</li>
<li>
We have a contractual obligation: Without your Personal Information,
we cannot provide our Services to you.
</li>
<li>
We have a legal obligation: To ensure we are fully compliant with
all applicable financial legislations such as Anti-Money Laundering
and Countering the Financing of Terrorism (AML/CFT) Laws, we must
collect and store your Personal Information. We protect against
fraud by checking your identity with your Personal Information.
</li>
</ol>
<p className="mb-[15px]">
Additionally, to better serve your needs, we may utilize information
about you collected from third parties and service partners. It is
important to note that these third-party sources are not under the
control of FCMB, and we are not responsible for how they use the
information.
</p>
<p className="mb-[15px]">
<strong>2.2 Usage and other information</strong>
</p>
<p className="mb-[15px]">
In addition to the personal information described above, we may
collect certain information about your use of our online services. For
example, we may capture the IP address of the device you use to
connect to the online service, the type of operating system and
browser you use, and information about the site you came from, the
parts of our online service you access, and the site you visit next.
FCMB or our third-party partners may also use cookies, web beacons or
other technologies to collect and store other information about your
visit to, or use of, our online services. In addition, we may later
associate the usage and other information we collect online with
personal information about you.
</p>
<p className="mb-[15px]">
<strong>2.3 FCMB Mobile</strong>
</p>
<p className="mb-[15px]">
For the convenience of our FCMB customers, we provide access to our
products and services through our mobile applications and
mobile-optimized websites ("FCMB Mobile"). When you engage with us
through FCMB Mobile, we may collect certain information to enhance
your experience. This information may include unique device
identifiers for your mobile device, screen resolution, device
settings, location information, and analytical data regarding your
mobile device usage. Please note that we may request your permission
before collecting specific information, such as precise geo-location
data,- contact or image data and other personal identifiable
information through FCMB Mobile. Rest assured that any information
collected is handled with the utmost care and in accordance with our
privacy policy.
</p>
<p className="mb-[15px]">
<strong>2.4 Additional sources of information</strong>
</p>
<p className="mb-[15px]">
We may also collect information about you from additional online and
offline sources including from co-branded partner sites or
commercially available third-party sources, such as credit reporting
agencies. We may combine this information with the personal and other
information we have collected about you under this Privacy Policy.
</p>
<p className="mb-[15px]">
<strong>2.5 Non-Personal Information</strong>
</p>
<p className="mb-[15px]">
In order to achieve our goal of providing you with the best-in-class
service, we may also collect, store, use and transfer non-personal
information or anonymized data such as statistical or demographic
data. These may be collected or sourced during your visits to perform
certain tasks such as grant you access to some parts of our web site
or conduct research on your behaviour on our site in order to improve
our services. We will not disclose your information to any person
outside our organization except as described in this Privacy Policy.
</p>
<p className="mb-[15px]">
<strong>
<span>3. Our Use of Information</span>
</strong>
</p>
<p className="mb-[15px]">
FCMB and/or subsidiaries may use or process the information discussed
above in a number of ways, such as to:
</p>
<ol>
<li>Manage your preferences;</li>
<li>
Create and manage any accounts or transactions you may have with us,
verify your identity, provide our services, and respond to your
inquiries;
</li>
<li>
Process your applications and transactions (including authorization,
clearing, chargebacks and other related dispute resolution
activities);
</li>
<li>
Protect against and prevent fraud, unauthorized transactions, claims
and other liabilities as well as enhance the security of your
account or our online services;
</li>
<li>
Provide, administer and communicate with you about our products,
services, offers, programs and promotions as well as those of our
merchants and partners;
</li>
<li>
Evaluate your interest in employment and contact you regarding
possible employment with FCMB;
</li>
<li>
Evaluate and improve our business, including developing new products
and services;
</li>
<li>To target advertisements, newsletters, and service updates;</li>
<li>As necessary to establish, exercise and defend legal rights;</li>
<li>
Perform analytics concerning your use of our online services,
including your responses to our emails and the pages and
advertisements you view;
</li>
<li>
As may be required by applicable laws and regulations, including for
compliance with Know Your Customers and risk assessment, Anti-Money
Laundering, anti-corruption and sanctions screening requirements, or
as requested by any judicial process, law enforcement or
governmental agency having or claiming jurisdiction over FCMB or
affiliates;
</li>
<li>
To use data analytics to improve our Website, products, or services,
and user experiences;
</li>
<li>
For other purposes for which we provide specific notice at the time
you provide or we collect your information.
</li>
</ol>
<p className="mb-[15px]">
We may also use data that we collect on an aggregate or anonymous
basis (such that it does not identify any individual customers) for
various business purposes, where permissible under applicable laws and
regulations.
</p>
<p className="mb-[15px]">
<strong>
<span>4. Cookies</span>
</strong>
</p>
<p className="mb-[15px]">
This website, along with most other major websites, uses cookies.
Cookies are pieces of information that a website transfers to the
cookie file on your computers hard disk. Cookies enable users to
navigate around the website and (where appropriate) enable us to
tailor the content to fit the needs of visitors who have accessed the
site.
</p>
<p className="mb-[15px]">
Firstcitygroup.com uses two types of cookies on this website:
</p>
<ol>
<li>
Session cookies, which are temporary cookies that remain in the
cookie file of your computer until you close your browser (at which
point they are deleted).
</li>
<li>
Persistent or stored cookies that remain permanently on the cookie
file of your computer.
</li>
</ol>
<p className="mb-[15px]">
Cookies cannot look into your computer and obtain information about
you or your family or read any material kept on your hard drive and,
unless you have logged onto an authenticated page, cookies cannot be
used to identify who you are.
</p>
<p className="mb-[15px]">
Cookies cannot be used by anyone else who has access to the computer
to find out anything about you, other than the fact that someone using
the computer has visited a certain website. Cookies do not in any way
compromise the security of your computer.
</p>
<p className="mb-[15px]">
Cookies will not be used to contact you for marketing purposes other
than by means of advertisements offered within this website.
</p>
<p className="mb-[15px]">
Cookies may be used to record details of pages relating to particular
products and services that you have visited on this website. This is
to provide fcmb.com with generic usage statistics to allow the company
to improve this website and to provide you with information that may
interest you.
</p>
<p className="mb-[15px]">
The web browsers of most computers are initially set up to accept
cookies. If you prefer, you can set your web browser to disable
cookies or to inform you when a website is attempting to add a cookie.
You can also delete cookies that have previously been added to your
computers cookie file.
</p>
<p className="mb-[15px]">
You can set your browser to disable persistent cookies and/or session
cookies but if you disable session cookies, although you will be able
to view this websites unsecured pages, you may not be able to log
onto any authenticated pages.
</p>
<p className="mb-[15px]">
Please visit{" "}
<a href="http://www.allaboutcookies.org/manage-cookies/">
http://www.allaboutcookies.org/manage-cookies/
</a>{" "}
to discover how to disable and delete cookies.
</p>
<p className="mb-[15px]">
<strong>
<span>5. Disclosures</span>
</strong>
</p>
<p className="mb-[15px]">
<strong>5.1 Disclosures</strong>
</p>
<p className="mb-[15px]">
We may divulge individual data to any individual performing review,
lawful, operational, or different services for us. We will utilize
data which does not identify the person for these exercises at
whatever point achievable. Data divulged to vendors or contractors for
operational purposes may not be re-disclosed to others by such a
vendor or contractor. We may reveal individual data when needed to do
as such by a court request, or court order. We may divulge individual
data as we esteem it proper to secure the wellbeing of our customers
or for an investigation identified with open security or to report an
action that has all the earmarks of being disregarding law. We may
divulge individual data to ensure the security and dependability of
this site and to take safety measures against accountability.
</p>
<p className="mb-[15px]">
<strong>5.2 Disclosures to Third Parties</strong>
</p>
<p className="mb-[15px]">
Data about you that is accessible to you by means of fcmb.com,
including your personal data, can become subject to the legal systems
and laws in force in the country where the data is held, received or
stored by you or us. Such data can become subject to disclosure
pursuant to the laws of the country.
</p>
<p className="mb-[15px]">
We may reveal your name and other personal data and other monetary
data about you at the request of regulatory agency or in connection
with an examination of us as a bank. This information could be
revealed to internal and external attorneys or auditors, and to others
whom we are required to make such revelations.
</p>
<p className="mb-[15px]">
<strong>
<span>6. Information Security and Retention</span>
</strong>
</p>
<p className="mb-[15px]">
At FCMB, we are fully committed to protecting the information we
collect. We maintain administrative, technical, and physical controls
to actively safeguard the Personal Information you provide or we
collect. These controls are designed to protect against loss, theft,
unauthorized access, disclosure, copying, misuse, or modification.
</p>
<p className="mb-[15px]">
Our security measures actively include secure servers, firewalls, data
encryption, and restricted access granted only to employees for
fulfilling their job responsibilities.
</p>
<p className="mb-[15px]">
When using a password for any of your accounts, it is essential that
you actively ensure its confidentiality and refrain from sharing it
with anyone.
</p>
<p className="mb-[15px]">
We actively conduct our business in accordance with these principles
to actively ensure the confidentiality and protection of your Personal
Information. While transmitting information online may not be entirely
secure, we actively take all reasonable steps to ensure the security
and protection of your Personal Information.
</p>
<p className="mb-[15px]">
We will only retain personal information on our servers for as long as
it is actively necessary while providing services to you. In the event
you close your account, we actively store your information on our
servers to comply with regulatory obligations and actively monitor,
detect, and prevent fraud. Any retention of your Personal Data is
solely for such length of time as may be required by law, regulation,
and the internal policies of FCMB, her members and/or affiliates.
</p>
<p className="mb-[15px]">
<strong>6.1 Data Protection on the Internet</strong>
</p>
<p className="mb-[15px]">
At FCMB we utilize encryption innovation to ensure the transmission of
data to or from you by means of fcmb.com. For security reasons and to
protect the security of your information, access to fcmb.com is
restricted to authorized users only. However, because information
about you, your account data and other transactions can be accessed
through a public network, the Internet, there can be no guarantee that
your account information will remain secure and you accept the risk
that unauthorized persons may view such information. If you believe
that an unauthorized person has accessed your information, please
contact the Bank immediately.
</p>
<p className="mb-[15px]">
<strong>
<span>7. Updates to this Policy</span>
</strong>
</p>
<p className="mb-[15px]">
From time to time, we may change, amend or review this Privacy Policy
from time to time to reflect new services or changes in our Privacy
Policy and place any updates on this page. All changes made will be
posted on this page and where changes will materially affect you, we
will notify you of this change by placing a notice online or via mail.
If you keep using our Services, you consent to all amendments of this
Privacy Policy.
</p>
<p className="mb-[15px]">
<strong>
<span>8. Contact us</span>
</strong>
</p>
<p className="mb-[15px]">
For issues relating to personal data, please contact us via any of the
below:
</p>
<p className="mb-[15px]">
Corporate Address: Primrose Tower, 17A, Tinubu Street, Marina, Lagos
</p>
<p className="mb-[15px]">Telephone: 07003290000, 01-2798800</p>
<p className="mb-[15px]">
Email: <a href="#">customerservice@fcmb.com</a>
</p>
<p className="mb-[15px]">
Whatsapp: (+234) 09099999814 or (+234) 09099999815
</p>
</div>
</div>
);
};
export default Main;
+3
View File
@@ -0,0 +1,3 @@
import Main from "./Main";
export { Main as TsAndCs };
+92
View File
@@ -0,0 +1,92 @@
import { useState } from "react";
import { Button, FloatLabelInput } from "..";
import { useNavigate } from "react-router-dom";
import { RouteHandler } from "../../router/routes";
type FormType = {
email: string;
password: string;
};
type HandleChange = {
name: string;
value: string;
};
export default function Login() {
const navigate = useNavigate()
let [formDetails, setFormDetails] = useState<FormType>({
email: "",
password: "",
});
const handleFormChange = ({
target: { name, value },
}: {
target: HandleChange;
}): any => {
setFormDetails((prev) => ({ ...prev, [name]: value }));
};
return (
<div className={`w-full overflow-y-auto bg-top bg-cover`}>
<div className="w-full flex justify-center">
<div className="w-2/3 md:max-w-[570px]">
<div className="bg-white w-full rounded-2xl border-2 border-black">
<div className="w-full p-5 sm:p-10 lg:p-20 flex flex-col justify-between items-center h-full">
<div className="mb-4">
<h1 className="text-2xl text-center font-bold leading-3 tracking-wide text-black dark:text-black">
Welcome!
</h1>
<p className="text-xl mt-4 text-center font-medium text-black dark:text-black">
Please login with your email and default password provided to you
</p>
</div>
<div className="w-full">
{/* INPUTS */}
<div className="w-full">
<div className="relative my-2 py-2">
<FloatLabelInput
id="email"
name="email"
type="email"
placeHolder="Email"
labelName="Email"
value={formDetails.email}
inputClass=""
onChange={handleFormChange}
/>
</div>
<div className="relative my-2 py-2">
<FloatLabelInput
id="password"
name="password"
type="password"
placeHolder="Password"
labelName="Password"
value={formDetails.password}
inputClass=""
onChange={handleFormChange}
/>
</div>
</div>
<div className="mt-10 w-full sm:flex justify-between items-center gap-2">
<Button
text="Enter"
className="rounded-md w-full sm:w-2/5 text-xl capitalize font-bold"
onClick={()=>{navigate(RouteHandler.otppage, {replace:true})}}
/>
{/* <Link to='' className='text-black text-sm'>Forget your password?</Link> */}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
+111
View File
@@ -0,0 +1,111 @@
import { useState } from "react";
import { Button, FloatLabelInput } from "..";
// import { Link } from "react-router-dom";
type FormType = {
email: string;
password: string;
};
type HandleChange = {
name: string;
value: string;
};
export default function Login() {
let [formDetails, setFormDetails] = useState<FormType>({
email: "",
password: "",
});
const handleFormChange = ({
target: { name, value },
}: {
target: HandleChange;
}): any => {
setFormDetails((prev) => ({ ...prev, [name]: value }));
};
return (
<div className={`w-full overflow-y-auto bg-top bg-cover`}>
<div className="w-full flex justify-center">
<div className="w-4/5 md:max-w-[570px]">
<div className="bg-white w-full rounded-2xl border-2 border-black">
<div className="w-full p-5 sm:p-10 lg:p-20 flex flex-col justify-between items-center">
<div className="mb-4">
<h1 className="text-2xl text-center font-bold leading-3 tracking-wide text-black dark:text-black">
Enter OTP
</h1>
<p className="text-xl mt-4 text-center font-medium text-black dark:text-black">
Please enter verification code sent to you
</p>
</div>
<div className="w-full sm:w-4/5">
{/* INPUTS */}
<div className="w-full flex justify-between gap-8">
<div className="relative my-2 py-2">
<FloatLabelInput
id="email"
name="email"
type="email"
// placeHolder="Email"
// labelName="Email"
value={formDetails.email}
inputClass=""
onChange={handleFormChange}
/>
</div>
<div className="relative my-2 py-2">
<FloatLabelInput
id="password"
name="password"
type="password"
// placeHolder="Password"
// labelName="Password"
value={formDetails.password}
inputClass=""
onChange={handleFormChange}
/>
</div>
<div className="relative my-2 py-2">
<FloatLabelInput
id="password"
name="password"
type="password"
// placeHolder="Password"
// labelName="Password"
value={formDetails.password}
inputClass=""
onChange={handleFormChange}
/>
</div>
<div className="relative my-2 py-2">
<FloatLabelInput
id="password"
name="password"
type="password"
// placeHolder="Password"
// labelName="Password"
value={formDetails.password}
inputClass=""
onChange={handleFormChange}
/>
</div>
</div>
<div className="mt-10 w-full">
<Button
text="Enter"
className="rounded-md w-full text-xl capitalize font-bold"
/>
{/* <Link to='#' className='text-black text-sm'>Forget your password?</Link> */}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
+4
View File
@@ -0,0 +1,4 @@
import Login from "./Login";
import OTP from "./OTP"
export { Login, OTP };
+11
View File
@@ -0,0 +1,11 @@
export * from "./Header";
export * from "./Home";
export * from "./GetStarted";
export * from "./shared";
export * from "./Footer";
// export * from "./DashboardLayout";
export * from "./Icons";
export * from "./Dashboard";
export * from "./Cards";
export * from "./LetsGetStated";
export * from "./TsAndCs";
@@ -0,0 +1,134 @@
import { ReactNode, useEffect, useState } from "react";
import { PendingTableList } from "../../core/models";
type PaginatedListProps = {
data: PendingTableList,
itemsPerPage?: number,
filterItem?: string[],
tableTitle?: string,
titleClass?:string,
children: (data:PendingTableList) => ReactNode;
}
export default function PendingList({
data,
itemsPerPage = 5,
filterItem,
tableTitle,
titleClass,
children,
}:PaginatedListProps) {
const [searchTerm, setSearchTerm] = useState("");
const [filteredData, setFilteredData] = useState(data);
const [currentPage, setCurrentPage] = useState(0);
const [newData, setNewData] = useState<any>([]);
const numberOfSelection = itemsPerPage;
const handlePrev = () => {
if (currentPage != 0) {
setCurrentPage((prev) => prev - numberOfSelection);
}
};
const handleNext = () => {
if (currentPage < data.length) {
setCurrentPage((prev) => prev + numberOfSelection);
}
};
const handleSearch = ({ target: { value } }:{target: {value:string}}, name:string) => {
setSearchTerm(value);
let newFilteredData:any = data.filter((item:any) =>
item[name].toLowerCase().startsWith(value.toLowerCase())
);
setFilteredData(newFilteredData);
setCurrentPage(0);
};
useEffect(() => {
setNewData(
filteredData?.slice(currentPage, numberOfSelection + currentPage)
);
}, [currentPage, filteredData]);
useEffect(()=>{
setCurrentPage(0)
},[itemsPerPage])
return (
<div className="w-full">
<h1 className={`text-2xl mb-5 font-semibold ${titleClass && titleClass}`}>{tableTitle}</h1>
{data.length > 0 && filterItem && (
<div className="mb-10 flex justify-end items-center gap-2">
{filterItem.map((item, index) => (
<label
key={index}
className="flex flex-col sm:flex-row items-center gap-2 text-slate-600 dark:text-slate-100 transition-all duration-500"
>
Search by {item[0].toUpperCase() + item.slice(1)}
<input
name={item}
type="text"
className="py-1 px-2 text-sm min-w-[100px] text-black dark:text-white bg-white dark:bg-slate-800 rounded-full border-0 outline-none ring-1 ring-slate-300 dark:ring-white transition-all duration-500"
value={searchTerm}
onChange={(e) => {
handleSearch(e, item);
}}
/>
</label>
))}
</div>
)}
{children(newData)}
{/* show prev and next button if data exist */}
{(data.length > 0 && data.length > itemsPerPage) && (
<div className="mt-5 md:mt-10 w-full flex gap-4 justify-center items-center">
<button
onClick={handlePrev}
className={`w-6 h-6 md:w-12 md:h-12 text-sm md:text-lg rounded-full flex justify-center items-center transition-all duration-300 ${
currentPage == 0
? "text-slate-400 border-slate-400 dark:text-slate-400 dark:border-slate-400 pointer-events-none"
: "text-slate-600 border-slate-600 dark:text-white dark:border-white"
}`}
>
&lt;
</button>
{data.length && data.map((item, index)=>{
item = item
if(index%itemsPerPage == 0 && index >= currentPage && index <= currentPage+itemsPerPage){
return (
<button
key={index}
onClick={handleNext}
className={`w-6 h-6 md:w-12 md:h-12 text-sm md:text-lg rounded-full flex justify-center items-center border transition-all duration-300 ${
currentPage != index
? "text-slate-400 border-slate-400 dark:text-slate-400 dark:border-slate-400"
: "text-slate-600 border-slate-600 dark:text-white dark:border-white pointer-events-none"
}`}
>
{index/itemsPerPage +1}
</button>
)
}
})}
<button
onClick={handleNext}
className={`w-6 h-6 md:w-12 md:h-12 text-sm md:text-lg rounded-full flex justify-center items-center transition-all duration-300 ${
currentPage + numberOfSelection >= data.length
? "text-slate-400 border-slate-400 dark:text-slate-400 dark:border-slate-400 pointer-events-none"
: "text-slate-600 border-slate-600 dark:text-white dark:border-white"
}`}
>
&gt;
</button>
</div>
)}
</div>
);
}
+27
View File
@@ -0,0 +1,27 @@
import React from "react";
interface ButtonProps {
text: string;
className?: string;
type?: "button" | "submit" | "reset";
onClick?: React.MouseEventHandler<HTMLButtonElement>;
}
const Button: React.FC<ButtonProps> = ({
text,
className,
onClick,
type = "button",
}) => {
return (
<button
className={`btn-primary uppercase text-[11px] lg:text-[13px] p-[6px] lg:px-[10px] ${className}`}
onClick={onClick}
type={type}
>
{text}
</button>
);
};
export default Button;

Some files were not shown because too many files have changed in this diff Show More