Compare commits

...

150 Commits

Author SHA1 Message Date
ameye 59819203fc Merge branch 'ref-strict-validation' of DigiFi/digifi-www into master 2024-09-17 19:16:40 +00:00
victorAnumudu 69b0f8a54d added reference strict validation 2024-09-17 19:19:47 +01:00
ameye ceb1658cdc Merge branch 'payment-page-update' of DigiFi/digifi-www into master 2024-09-04 20:22:49 +00:00
victorAnumudu 5a7adf4537 page page updated 2024-09-04 20:59:50 +01:00
victor.ebuka 45d5bd870f Merge branch 'fixed-bug' of DigiFi/digifi-www into master 2024-09-03 23:08:17 +00:00
victorAnumudu 263c6740c5 fixed bug 2024-09-04 00:07:26 +01:00
victor.ebuka 07c1a8ae06 Merge branch 'error-fix' of DigiFi/digifi-www into master 2024-09-03 22:48:41 +00:00
victorAnumudu bc2167e67a build error fixed 2024-09-03 23:47:31 +01:00
ameye 4d6a7380bb Merge branch 'highest-education' of DigiFi/digifi-www into master 2024-09-03 17:00:09 +00:00
victorAnumudu b203c59ff7 highest education added 2024-09-03 17:57:38 +01:00
ameye fe2168ce53 Merge branch 'all-state-added' of DigiFi/digifi-www into master 2024-09-03 16:43:41 +00:00
victorAnumudu 1c1af302aa all states added 2024-09-03 17:41:53 +01:00
ameye 945a5425f0 Merge branch 'employment-detail' of DigiFi/digifi-www into master 2024-08-06 20:05:36 +00:00
victorAnumudu 25545ad47e loan detail page added 2024-08-06 21:04:14 +01:00
ameye 5efc935f05 Merge branch 'add-card-API' of DigiFi/digifi-www into master 2024-08-05 19:12:05 +00:00
victorAnumudu 69c43e1d88 added add card API 2024-08-05 20:02:21 +01:00
ameye cb23cbd57c Merge branch 'loan-popout' of DigiFi/digifi-www into master 2024-08-05 15:50:44 +00:00
victorAnumudu 9c8e7dada4 added loan popout 2024-08-05 16:27:44 +01:00
ameye 22814f7e10 Merge branch 'disbursement-account' of DigiFi/digifi-www into master 2024-07-17 18:59:31 +00:00
victorAnumudu bc9e26aad1 added disbursement account as payload 2024-07-17 19:58:09 +01:00
ameye 5cacd0f073 Merge branch 'apply-loan-payload-update' of DigiFi/digifi-www into master 2024-07-11 12:26:31 +00:00
victorAnumudu 475e5c46a1 apply loan payload update 2024-07-11 13:09:18 +01:00
ameye 4af2f7ef10 Merge branch 'bank-name-variable' of DigiFi/digifi-www into master 2024-06-28 23:39:12 +00:00
victorAnumudu c60f49dc90 added bank name in env variable 2024-06-28 23:40:05 +01:00
ameye 157151e6d1 Merge branch 'employer_uid_payload_update' of DigiFi/digifi-www into master 2024-06-13 15:06:02 +00:00
victorAnumudu 2f7f482293 updated apply loan payload for employer uid 2024-06-13 15:00:24 +01:00
ameye ee86d40bcc Merge branch 'employer-uid' of DigiFi/digifi-www into master 2024-06-11 18:36:57 +00:00
victorAnumudu 409acd300d employer uid payload added 2024-06-11 19:01:16 +01:00
ameye fc7edec093 Merge branch 'employer-info-update' of DigiFi/digifi-www into master 2024-06-10 19:15:14 +00:00
victorAnumudu 4637944fbd changed layout structure 2024-06-10 20:13:13 +01:00
tokslaw cc44af7e55 Merge branch 'auto_logout_FIX_CONFLICT' of DigiFi/digifi-www into master 2024-05-15 15:21:17 +00:00
Elias df5b4c89a4 fix merge conflict:auto logout after 7mins 2024-05-15 15:34:25 +01:00
ameye a37a5c24f5 Merge branch 'update_env' of DigiFi/digifi-www into master 2024-05-15 08:55:09 +00:00
ameye 890452af63 Merge branch 'bvn_code_max_input' of DigiFi/digifi-www into master 2024-05-15 08:55:02 +00:00
ameye 69b6850002 Merge branch 'action_btn_to_extreme_right' of DigiFi/digifi-www into master 2024-05-15 08:54:57 +00:00
Elias dd8290af9a action button moved to extreme right 2024-05-14 12:34:15 +01:00
Elias 306ab06142 Merge branch 'master' into update_env 2024-05-14 11:56:57 +01:00
tokslaw 4d258a965b Merge branch 'layout-padding' of DigiFi/digifi-www into master 2024-05-09 17:29:13 +00:00
Elias f63171273e max input length: bvn & code 2024-05-07 15:19:40 +01:00
Elias b4bf96e841 include contact details in .env 2024-05-07 14:33:46 +01:00
victorAnumudu e20b7e32f1 adjusted layout padding on mobile view 2024-05-06 19:00:56 +01:00
ameye 6bd533c7ca Merge branch 'pending-loan-list' of DigiFi/digifi-www into master 2024-05-06 16:51:12 +00:00
victorAnumudu 408777353d pending loan list added 2024-05-06 16:52:00 +01:00
victorAnumudu a2e039eab4 initial commit 2024-05-06 14:44:42 +01:00
ameye 8d6cc5861e Merge branch 'link-path' of DigiFi/digifi-www into master 2024-05-06 12:23:07 +00:00
victorAnumudu 18967d720c link path corrected 2024-05-06 12:27:16 +01:00
ameye 044b2ef917 Merge branch 'bug-fix' of DigiFi/digifi-www into master 2024-05-03 16:59:47 +00:00
ameye 4f7fdfb2ba Merge branch 'agent-id' of DigiFi/digifi-www into master 2024-05-03 16:59:43 +00:00
victorAnumudu ab389d6632 load profile bug fix 2024-05-03 17:57:32 +01:00
victorAnumudu 29538e3b6e made agent id optional 2024-05-02 10:09:36 +01:00
ameye dd2df0d695 Merge branch 'get-user-details' of DigiFi/digifi-www into master 2024-05-01 16:52:44 +00:00
victorAnumudu a97db9a661 added get user by ID API 2024-05-01 17:33:41 +01:00
ameye 135cbce348 Merge branch 'loan-application-submit' of DigiFi/digifi-www into master 2024-04-30 19:13:50 +00:00
victorAnumudu 814bfe041a added endpoint for loan application 2024-04-30 19:03:22 +01:00
ameye d90d515f60 Merge branch 'loan-application-details' of DigiFi/digifi-www into master 2024-04-30 10:57:52 +00:00
victorAnumudu e93a3dea68 collected laon application details 2024-04-30 01:02:48 +01:00
ameye a50d5ec82d Merge branch 'logout-implemented' of DigiFi/digifi-www into master 2024-04-27 23:33:38 +00:00
victorAnumudu 333ada0a1c implemented logout 2024-04-27 23:52:11 +01:00
ameye bb718953ad Merge branch 'verify-bvn-bug' of DigiFi/digifi-www into master 2024-04-27 01:41:59 +00:00
victorAnumudu a31b36686d verify bvn auto api calling bug fixed 2024-04-27 02:36:40 +01:00
CHIEFSOFT\ameye 00f4e1b565 extra host 2024-04-26 19:31:33 -04:00
ameye 6d302def04 Merge branch 'added-axios-package' of DigiFi/digifi-www into master 2024-04-26 23:18:29 +00:00
victorAnumudu 82dd11a772 added axios package and api for start bvn verification 2024-04-26 23:41:41 +01:00
ameye 7e9c395f4a Merge branch 'form-validation-contd' of DigiFi/digifi-www into master 2024-04-24 12:32:27 +00:00
victorAnumudu 2cb5c471f6 form validation with formik 2024-04-24 11:42:04 +01:00
ameye 4b008f6785 Merge branch 'input-validation' of DigiFi/digifi-www into master 2024-04-23 18:50:24 +00:00
ameye f632099128 Merge branch 'docker-compose-props' of DigiFi/digifi-www into master 2024-04-23 18:50:20 +00:00
ameye 122eb31732 Merge branch 'header-z-index' of DigiFi/digifi-www into master 2024-04-23 18:50:17 +00:00
victorAnumudu 0207bf631a started input validation on forms 2024-04-23 19:25:50 +01:00
victorAnumudu 378ff4a625 added props to docker compose file 2024-04-23 15:41:54 +01:00
victorAnumudu f88b6df24c increase z-index level for the header component 2024-04-23 11:36:18 +01:00
ameye fe759c6d0a Merge branch 'select-input' of DigiFi/digifi-www into master 2024-04-23 10:26:53 +00:00
victorAnumudu 104295bdb2 select input added where necessary 2024-04-22 22:38:35 +01:00
ameye 44933d4362 Merge branch 'loan-application-process' of DigiFi/digifi-www into master 2024-04-22 17:08:37 +00:00
victorAnumudu 9ce7110a5d added steps to loan application 2024-04-22 15:42:40 +01:00
ameye 085b2d4aaa Merge branch 'get-started-page' of DigiFi/digifi-www into master 2024-04-22 11:39:10 +00:00
victorAnumudu 747c945659 get started first step added 2024-04-22 09:36:48 +01:00
ameye 94f9803ec5 Merge branch 'otp-input' of DigiFi/digifi-www into master 2024-04-17 18:49:10 +00:00
victorAnumudu 3998596fba validation bug fix 2024-04-16 20:56:34 +01:00
victorAnumudu 886fd64347 validated otp input to be numbers 2024-04-16 20:17:57 +01:00
tokslaw f95fd66c57 Merge branch 'layout-update' of DigiFi/digifi-www into master 2024-04-04 09:35:17 +00:00
tokslaw 98b5d4bc4f Merge branch 'Corporate' of DigiFi/digifi-www into master 2024-04-04 09:33:45 +00:00
victorAnumudu fd2b2245f5 updated layout style 2024-04-03 17:58:45 +01:00
victorAnumudu a7e97e1890 updated layout style 2024-04-03 17:55:55 +01:00
Pascallina Ocheme 537d609117 Modified pathlink 2024-03-26 12:17:15 +01:00
tokslaw 5e0d21b5dd Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-25 12:09:09 +00:00
chukwumdiebube ojinta ea4e6b7a59 Added new pages to dashboard 2024-03-23 17:03:13 +01:00
ameye 07f7a90c0d Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-23 10:58:39 +00:00
chukwumdiebube ojinta 2a2b8ee85d changed check prop to defaultChecked 2024-03-23 06:27:16 +01:00
chukwumdiebube ojinta e2b3978ab6 Added Applicants Attestation Page 2024-03-23 06:23:50 +01:00
chukwumdiebube ojinta b8f9958517 Fixed build error 2024-03-23 05:28:00 +01:00
chukwumdiebube ojinta 06cadbb742 Merge branch 'master' of https://gitlab.chiefsoft.net/DigiFi/digifi-www into first-homepage-layout 2024-03-23 05:24:26 +01:00
chukwumdiebube ojinta 067e7a395d Fix build 2024-03-23 05:23:53 +01:00
tokslaw 5b6695c1f6 Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-22 22:33:05 +00:00
chukwumdiebube ojinta 911c67e874 Completed layout for Loan Amount 2024-03-22 23:27:58 +01:00
ameye a33784fc9e Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-22 21:39:18 +00:00
chukwumdiebube ojinta b3efba09d6 fixed build and added updates to loan amount component 2024-03-22 22:35:11 +01:00
tokslaw ff03282310 Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-22 21:20:41 +00:00
Ebube 05dfb50f62 You're added comp 2024-03-22 18:27:50 +01:00
Ebube 08af493d65 Added You're almost done page 2024-03-22 18:16:18 +01:00
Ebube e1bd0293f1 Added You are almost there comp 2024-03-22 17:19:58 +01:00
ameye a5b77abc92 Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-21 10:33:44 +00:00
Ebube dd6d92654c Fixed Layout and switch functionality 2024-03-21 11:29:38 +01:00
Ebube dd9652fe58 Merge branch 'master' of https://gitlab.chiefsoft.net/DigiFi/digifi-www into first-homepage-layout 2024-03-21 10:06:08 +01:00
Ebube 266627d941 Corrected Form Layout 2024-03-21 10:05:44 +01:00
ameye c4ffd49509 Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-20 17:49:40 +00:00
Ebube 21b4bf5e8f Merge branch 'master' of https://gitlab.chiefsoft.net/DigiFi/digifi-www into first-homepage-layout 2024-03-20 18:19:10 +01:00
Ebube b71490aa8a fixed footer link 2024-03-20 18:18:39 +01:00
tokslaw be9672991e Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-20 13:33:20 +00:00
Ebube 97e6951685 added Enter Btn 2024-03-20 14:00:17 +01:00
Ebube 1bd523c493 added valid types 2024-03-20 13:37:49 +01:00
Ebube 32f2a358b2 Added social links to env and added hero text to the pages 2024-03-20 13:12:08 +01:00
tokslaw ee94a59f75 Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-20 03:49:03 +00:00
Ebube 4c24ce5903 Merge branch 'master' of https://gitlab.chiefsoft.net/DigiFi/digifi-www into first-homepage-layout 2024-03-20 01:38:28 +01:00
Ebube 86c0a236fe Added personal image to the pages 2024-03-20 01:37:31 +01:00
ameye 2daae9b0e0 Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-19 12:35:47 +00:00
Ebube 2890677472 fix build error 2024-03-19 13:33:33 +01:00
Ebube 4a3f46c19b added otp to letsGetStarted 2024-03-19 13:11:21 +01:00
Ebube 79f5af7692 Added layout to lets get started bvn 2024-03-19 09:04:49 +01:00
ameye 2a59165054 Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-18 17:21:25 +00:00
Ebube e3cf881e09 fix build error 2024-03-18 17:49:55 +01:00
Ebube dde4072593 added pages for top header nav links 2024-03-18 17:42:36 +01:00
Ebube df6fe828e3 added Terms and Condition Page 2024-03-18 17:23:04 +01:00
Ebube db08d1201c complete footer and home layout page created 2024-03-18 15:52:12 +01:00
ameye 32acf978c3 Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-18 11:32:51 +00:00
Ebube f7ef9ff3a0 fix for production build 2024-03-18 12:31:17 +01:00
ameye 37c012461f Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-18 10:08:29 +00:00
Ebube 0a295cc81c Merge branch 'master' of https://gitlab.chiefsoft.net/DigiFi/digifi-www into first-homepage-layout 2024-03-18 11:01:37 +01:00
Ebube e5c605560b Added footer and input component 2024-03-18 11:01:05 +01:00
ameye 26b95bab3d Merge branch 'dashboard-home-reponsive' of DigiFi/digifi-www into master 2024-03-18 09:43:27 +00:00
victorAnumudu cbbd23fde3 made dashboard home page reponsive 2024-03-18 10:39:54 +01:00
tokslaw 18c1a78d4c Merge branch 'aside-menu-bug' of DigiFi/digifi-www into master 2024-03-18 04:51:08 +00:00
victorAnumudu 979330478c made aside bar to close on mobile view whenever a link is clicked 2024-03-18 01:32:52 +01:00
Ebube f402efeb85 Merge branch 'master' of https://gitlab.chiefsoft.net/DigiFi/digifi-www into first-homepage-layout 2024-03-17 20:08:16 +01:00
Ebube 38becd42ac . 2024-03-17 20:04:31 +01:00
ameye bf87780204 Merge branch 'dashboard-home-page' of DigiFi/digifi-www into master 2024-03-16 18:36:09 +00:00
victorAnumudu 81b2f439ce added some component to dashboard home page 2024-03-16 17:47:17 +01:00
ameye 9d86fc51c8 Merge branch 'dashboard-variables' of DigiFi/digifi-www into master 2024-03-16 13:04:12 +00:00
victorAnumudu 435bc08f2f removed unused variables 2024-03-16 14:01:02 +01:00
ameye 197d5f32ed Merge branch 'dashboard-component' of DigiFi/digifi-www into master 2024-03-16 10:09:36 +00:00
victorAnumudu eaf959ab84 merged branch 2024-03-16 03:09:41 +01:00
victorAnumudu 7cbfae619b added dashboard layout 2024-03-16 02:53:40 +01:00
victorAnumudu ea74a092e5 made login page responsive 2024-03-14 11:44:16 +01:00
ameye 90dc2adb92 Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-14 09:05:44 +00:00
Ebube bacfa1b404 Merge branch 'master' of https://gitlab.chiefsoft.net/DigiFi/digifi-www into first-homepage-layout 2024-03-14 04:55:25 +01:00
Ebube db21572651 corrected footer and linked the click to apply 2024-03-14 04:54:46 +01:00
ameye 0ac92704bc Merge branch 'first-homepage-layout' of DigiFi/digifi-www into master 2024-03-14 00:52:35 +00:00
Ebube 9ec1013173 added background for sidebar 2024-03-14 00:07:27 +01:00
Ebube 6f1f7ee682 first homepage layout 2024-03-13 23:31:36 +01:00
ameye 1c52e88c08 Merge branch 'login-page' of DigiFi/digifi-www into master 2024-03-13 14:46:31 +00:00
126 changed files with 7596 additions and 184 deletions
+16
View File
@@ -1 +1,17 @@
DIGIFI_PORT=5173
# Social Links
FACEBOOK_URL=https://www.facebook.com
TWITTER_URL=https://twitter.com
INSTAGRAM_URL=https://www.instagram.com
# BACKEND END POINTS
VITE_USERS_ENDPOINT='https://digifi-apidev.chiefsoft.net/digiusers/v1'
# ENQUIRIES CONTACTS
VITE_CALL_ENDPOINT='09099000000'
VITE_EMAIL_ENDPOINT='fcmbloan@support.com'
#BANK NAME
VITE_BANK_NAME='First City Monument Bank'
VITE_BANK_NAME_SHORT='FCMB'
+12
View File
@@ -1 +1,13 @@
DIGIFI_PORT=5173
# Social Links
VITE_FACEBOOK_URL=https://www.facebook.com
VITE_TWITTER_URL=https://twitter.com
VITE_INSTAGRAM_URL=https://www.instagram.com
# BACKEND END POINTS
VITE_USERS_ENDPOINT='https://digifi-apidev.chiefsoft.net/digiusers/v1'
#BANK NAME
VITE_BANK_NAME='First City Monument Bank'
VITE_BANK_NAME_SHORT='FCMB'
+12
View File
@@ -1 +1,13 @@
DIGIFI_PORT=5173
# Social Links
FACEBOOK_URL=https://www.facebook.com
TWITTER_URL=https://twitter.com
INSTAGRAM_URL=https://www.instagram.com
# BACKEND END POINTS
VITE_USERS_ENDPOINT='https://digifi-apidev.chiefsoft.net/digiusers/v1'
#BANK NAME
VITE_BANK_NAME='First City Monument Bank'
VITE_BANK_NAME_SHORT='FCMB'
+7 -2
View File
@@ -11,8 +11,13 @@ services:
ports:
- 6030:5173
expose:
- "5173"
- "5173"
extra_hosts:
- digifi-apidev.chiefsoft.net:10.10.33.15
- backend.wrenchboard.api.test:10.10.33.15
environment:
- PORT=${DIGIFI_PORT}
tty: true
stdin_open: true
volumes:
src:
src:
+100
View File
@@ -9,10 +9,12 @@
"version": "0.0.0",
"dependencies": {
"@reduxjs/toolkit": "^2.2.1",
"axios": "^1.6.8",
"clsx": "2.1.0",
"formik": "2.4.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
"react-redux": "^8.0.5",
"react-router-dom": "6.3.0",
"react-select": "^5.8.0",
@@ -1805,6 +1807,11 @@
"node": ">=8"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/autoprefixer": {
"version": "10.4.18",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz",
@@ -1842,6 +1849,16 @@
"postcss": "^8.1.0"
}
},
"node_modules/axios": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/babel-plugin-macros": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
@@ -2030,6 +2047,17 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@@ -2124,6 +2152,14 @@
"node": ">=0.10.0"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -2550,6 +2586,25 @@
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
@@ -2565,6 +2620,19 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/formik": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/formik/-/formik-2.4.5.tgz",
@@ -3078,6 +3146,25 @@
"node": ">=8.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
@@ -3532,6 +3619,11 @@
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
"integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -3588,6 +3680,14 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
},
"node_modules/react-icons": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz",
"integrity": "sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+2
View File
@@ -11,10 +11,12 @@
},
"dependencies": {
"@reduxjs/toolkit": "^2.2.1",
"axios": "^1.6.8",
"clsx": "2.1.0",
"formik": "2.4.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
"react-redux": "^8.0.5",
"react-router-dom": "6.3.0",
"react-select": "^5.8.0",
+45 -5
View File
@@ -1,5 +1,5 @@
.btn-primary {
background: #5A2C82;
background: #5A2C82 !important;
color: #FFFFFF;
display: block;
text-decoration: none;
@@ -8,27 +8,67 @@
}
.btn-active {
background: #D10056;
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: 300px;
width: 330px;
/* Adjust the width as needed */
height: 100vh;
background-color: #fff;
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.3s ease-in-out;
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%);
}
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: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 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

+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 };
+17
View File
@@ -0,0 +1,17 @@
type Props = {
width?: string
height?: string
}
export default function CustomSpinner({width='w-6', height='h-6'}:Props) {
return (
<div role="status">
<svg aria-hidden="true" className={`inline ${width} ${height} text-gray-200 animate-spin dark:text-gray-600 fill-blue-600`} viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
</svg>
{/* <span className="sr-only">Loading...</span> */}
</div>
)
}
@@ -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,310 @@
import React, { FC, useState, useEffect } from 'react';
import NairaBag from '../../assets/images/dashboard/naira-bag.png';
import {useNavigate} from 'react-router-dom'
import { Button, Icons } from '../';
import { useSelector } from 'react-redux';
import { PendingTableList } from '../../core/models';
import { NewDateTimeFormatter } from '../../lib/NewDateTimeFormatter';
import { getUserPendingLoanList } from '../../core/apiRequest';
import {FormatAmount} from '../../lib/FormatAmount'
import PendingLoanPopout from './PendingLoanPopout';
import { RouteHandler } from '../../router/routes';
import TableWrapper from '../tableWrapper/TableWrapper';
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;
}
interface PopoutProps<T> {
show?: boolean
data?: T
}
const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({
handleNextStep,
step,
}) => {
const navigate = useNavigate()
const { userDetails } = useSelector((state: any) => state?.userDetails); // CHECKS IF USER Details are avaliable
const [loanPopout, setLoanPopout] = useState<PopoutProps<PendingTableList>>({show:false, data:{}})
const closePopout = () => {
setLoanPopout({show:false, data:{}})
}
const [userLoanList, setUserLoanList] = useState<{
loading: boolean;
data: Array<PendingTableList>;
}>({ loading: true, data: [] });
useEffect(() => {
let token = localStorage.getItem('token');
let uid = localStorage.getItem('uid');
if (!token || !uid) {
return;
}
getUserPendingLoanList(uid)
.then((res) => {
if (!res || !res.data.loans) {
setUserLoanList({ loading: false, data: [] });
return;
}
setUserLoanList({ loading: false, data: res?.data?.loans });
})
.catch((err) => {
setUserLoanList({ loading: false, data: [] });
console.log(err)
});
}, []);
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">
<TableWrapper
data={userLoanList.data}
itemsPerPage={7}
>
{({ data }:{data:any}) => (
<>
<div className='w-full h-[420px] overflow-auto'>
<table className="py-2 w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" className="px-4 py-2">
Date
</th>
<th scope="col" className="px-4 py-2">
Amount
</th>
<th scope="col" className="px-4 py-2">
Payment Term
</th>
<th scope="col" className="px-4 py-2">
Status
</th>
<th scope="col" className="px-4 py-2">
Action
</th>
</tr>
</thead>
<tbody>
{(data && data.length > 0) ? data?.map((item:any) => (
<tr key={item?.application_uid} className="bg-white border-b dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600">
<td className="px-3 py-2">
{NewDateTimeFormatter(item?.added)}
</td>
<td className="px-3 py-2">
{FormatAmount(item?.loan_amount)}
</td>
<td className="px-3 py-2">
{item?.payment_month}
</td>
<td className="px-3 py-2">
<button
className={`${!item?.status_text?.button && 'pointer-events-none border-0'} border p-2`}
onClick={()=>setLoanPopout({show:true, data:item})}
>
{item?.status_text?.text || 'Pending'}
</button>
</td>
<td className="px-3 py-2 flex gap-2">
<button className="flex flex-nowrap items-center px-2 py-1 border-2 border-black" onClick={()=>navigate(RouteHandler.dashboardReference, {state:{application_uid: item?.application_uid}})}>
View
<Icons name="arrow-right" />
</button>
</td>
</tr>
))
:
<tr className="w-3 p-3">
<td className="px-3 py-2" colSpan={5}>
<div className="flex justify-center items-center">
No Record Found
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
</>
)}
</TableWrapper>
</div>
)}
</div>
{loanPopout.show && <PendingLoanPopout data={loanPopout.data} action={closePopout} />}
</>
);
};
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,111 @@
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import {Formik, Form} from 'formik'
import * as Yup from "yup";
import { RouteHandler } from "../../router/routes";
import { Button, InputCompOne } from "..";
// To get the validation schema
const validationSchema = Yup.object().shape({
firstname: Yup.string()
.required("Required"),
lastname: Yup.string()
.required("Required"),
internal_email : Yup.string().required("Required").email("Invalid"),
});
export default function DashboardProfile() {
let navigate = useNavigate();
const navigateToHome = () => navigate(RouteHandler.dashboardHome);
const { userDetails } = useSelector((state:any) => state?.userDetails); // GETS USER DETAILS
const initialValues = {
firstname: userDetails.firstname,
lastname: userDetails.lastname,
internal_email: userDetails.internal_email
};
//FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => {
console.log('good', values)
};
return (
<div className="w-full">
<div className='my-[2rem] flex items-center'>
<button onClick={navigateToHome} className='py-2 px-4 text-lg text-white flex justify-center items-center bg-[#5C2684]'>&lt; Back</button>
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props)=>(
<Form>
<div className="flex flex-col lg:flex-row items-start gap-[2rem]">
<div className='w-full lg:max-w-[30rem] flex flex-col gap-[2rem]'>
<InputCompOne
parentClass="w-full"
name="firstname"
floatLabel="Firstname"
// 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.firstname}
onChange={props.handleChange}
// error={(props.errors.firstname && props.touched.firstname) ? props.errors.firstname : ''}
/>
<InputCompOne
parentClass="w-full"
name="internal_email"
floatLabel="Email"
// labelClass="font-bold text-[1.125rem]"
input
// disabled={true}
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Mr. Mark John"
value={props.values.internal_email}
onChange={props.handleChange}
// error={(props.errors.internal_email && props.touched.internal_email) ? props.errors.internal_email : ''}
/>
</div>
<div className='w-full lg:max-w-[30rem] flex flex-col gap-[2rem]'>
<InputCompOne
parentClass="w-full"
name="lastname"
floatLabel="Lastname"
// labelClass="font-bold text-[1.125rem]"
input
// disabled={true}
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Mr. Mark John"
value={props.values.lastname}
onChange={props.handleChange}
// error={(props.errors.lastname && props.touched.lastname) ? props.errors.lastname : ''}
/>
</div>
</div>
<div className='hidden w-full lg:max-w-[416px] flex flex-col gap-[2rem]'>
<div className="w-full">
<Button
className="my-4 btn-Y text-black w-full h-11"
text="Update"
type="submit"
/>
</div>
</div>
</Form>
)}
</Formik>
</div>
);
}
@@ -0,0 +1,109 @@
import {useState} from 'react'
import ModalWrapper from '../modal/ModalWrapper'
import { PendingTableList } from '../../core/models'
import { NewDateTimeFormatter } from '../../lib/NewDateTimeFormatter'
import { addCard } from '../../core/apiRequest'
import CustomSpinner from '../CustomSpinner'
interface Props<T> {
action: ()=>void
data?: T
}
export default function PendingLoanPopout({data, action}:Props<PendingTableList>) {
const [addCardStatus, setAddCardStatus] = useState<{
loading: boolean;
status: boolean
msg: string
}>({ loading: false, status: false, msg: ''});
const handleAddCard = (appID:string | undefined) => {
let reqData = {
application_uid: appID
}
setAddCardStatus({ loading: true, status: false, msg: ''})
addCard(reqData).then(res => {
if(res?.data?.call_return != '100'){
setAddCardStatus({ loading: false, status: false, msg: 'failed to add card'})
setTimeout(()=>{
setAddCardStatus({ loading: false, status: false, msg: ''})
},3000)
}
window.location.href = res?.data?.redirect_url
setAddCardStatus({ loading: false, status: true, msg: 'card added'})
action() // TO CLOSE MODAL
}).catch(err => {
setAddCardStatus({ loading: false, status: false, msg: 'failed to add card'})
console.log('ERR', err)
setTimeout(()=>{
setAddCardStatus({ loading: false, status: false, msg: ''})
},3000)
})
}
return (
<ModalWrapper>
<div className='modal-container'>
<div className='modal-header'>
<h1 className='modal-title'>Add Card</h1>
<button
type="button"
className="modal-close-btn"
name='cancel'
onClick={action}
>
<svg
width="36"
height="36"
viewBox="0 0 36 36"
fill="none"
className="fill-current"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M36 16.16C36 17.4399 36 18.7199 36 20.0001C35.7911 20.0709 35.8636 20.2554 35.8385 20.4001C34.5321 27.9453 30.246 32.9248 22.9603 35.2822C21.9006 35.6251 20.7753 35.7657 19.6802 35.9997C18.4003 35.9997 17.1204 35.9997 15.8401 35.9997C15.5896 35.7086 15.2189 35.7732 14.9034 35.7093C7.77231 34.2621 3.08728 30.0725 0.769671 23.187C0.435002 22.1926 0.445997 21.1199 0 20.1599C0 18.7198 0 17.2798 0 15.8398C0.291376 15.6195 0.214408 15.2656 0.270759 14.9808C1.71321 7.69774 6.02611 2.99691 13.0428 0.700951C14.0118 0.383805 15.0509 0.386897 15.9999 0C17.2265 0 18.4532 0 19.6799 0C19.7156 0.124041 19.8125 0.136067 19.9225 0.146719C27.3 0.868973 33.5322 6.21922 35.3801 13.427C35.6121 14.3313 35.7945 15.2484 36 16.16ZM33.011 18.0787C33.0433 9.77105 26.3423 3.00309 18.077 2.9945C9.78479 2.98626 3.00344 9.658 2.98523 17.8426C2.96667 26.1633 9.58859 32.9601 17.7602 33.0079C26.197 33.0577 32.9787 26.4186 33.011 18.0787Z"
fill=""
fillOpacity="0.6"
/>
<path
d="M15.9309 18.023C13.9329 16.037 12.007 14.1207 10.0787 12.2072C9.60071 11.733 9.26398 11.2162 9.51996 10.506C9.945 9.32677 11.1954 9.0811 12.1437 10.0174C13.9067 11.7585 15.6766 13.494 17.385 15.2879C17.9108 15.8401 18.1633 15.7487 18.6375 15.258C20.3586 13.4761 22.1199 11.7327 23.8822 9.99096C24.8175 9.06632 26.1095 9.33639 26.4967 10.517C26.7286 11.2241 26.3919 11.7413 25.9133 12.2178C24.1757 13.9472 22.4477 15.6855 20.7104 17.4148C20.5228 17.6018 20.2964 17.7495 20.0466 17.9485C22.0831 19.974 24.0372 21.8992 25.9689 23.8468C26.9262 24.8119 26.6489 26.1101 25.4336 26.4987C24.712 26.7292 24.2131 26.3441 23.7455 25.8757C21.9945 24.1227 20.2232 22.3892 18.5045 20.6049C18.0698 20.1534 17.8716 20.2269 17.4802 20.6282C15.732 22.4215 13.9493 24.1807 12.1777 25.951C11.7022 26.4262 11.193 26.7471 10.4738 26.4537C9.31345 25.9798 9.06881 24.8398 9.98589 23.8952C11.285 22.5576 12.6138 21.2484 13.9387 19.9355C14.5792 19.3005 15.2399 18.6852 15.9309 18.023Z"
fill="#"
fillOpacity="0.6"
/>
</svg>
</button>
</div>
<div className='modal-body'>
<div className='w-full flex flex-col gap-2'>
<div className='py-2 w-full flex gap-1'>
<p className='font-semibold'>ID :</p>
<p>{data?.application_uid}</p>
</div>
<div className='py-2 w-full flex gap-1'>
<p className='font-semibold'>Loan Amount :</p>
<p>{data?.loan_amount}</p>
</div>
<div className='py-2 w-full flex gap-1'>
<p className='font-semibold'>Payment Month :</p>
<p>{data?.payment_month}</p>
</div>
<div className='py-2 w-full flex gap-1'>
<p className='font-semibold'>Added :</p>
<p>{NewDateTimeFormatter(data?.added)}</p>
</div>
</div>
</div>
<div className='modal-footer'>
<button onClick={action} name='cancel' className='custom-btn text-red-500 border border-red-500 hover:text-red-700'>Cancel</button>
{addCardStatus.loading ?
<CustomSpinner width='w-6' height='h-6' />
:
<button onClick={()=>handleAddCard(data?.application_uid)} name='proceed' className={`custom-btn border border-sky-500 text-sky-500 hover:text-sky-700 ${addCardStatus.loading && 'opacity-20 pointer-events-none'}`}>Add Card</button>
}
</div>
</div>
</ModalWrapper>
)
}
@@ -0,0 +1,365 @@
import {useState, useEffect} from 'react'
import { Button, InputCompOne } from '../../shared/index';
import {Formik, Form} from 'formik'
import * as Yup from "yup";
import { getEmployer } from '../../../core/apiRequest';
import CustomSpinner from '../../CustomSpinner';
import { FormatAmount } from '../../../lib/FormatAmount';
// 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: "",
job_sector: "",
industry: "",
start_date: "",
official_email:"",
annual_salary: "",
net_montlty: "",
salary_date: "",
employee_id: "",
highest_eductaion: "",
employer_uid: "",
isChecked: true
};
// 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(),
}),
job_sector: Yup.string().when('isChecked', {
is: true,
then: () => Yup.string().required('required'),
}),
industry: Yup.string().when('isChecked', {
is: true,
then: () => Yup.string().required('required'),
}),
start_date: Yup.string()
.required("Required"),
official_email : Yup.string().when('isChecked', {
is: true,
then: () => Yup.string().required('required'),
})
.email("Invalid"),
annual_salary: Yup.string()
.required("Required")
.test("no-e", "Invalid", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
}),
net_montlty: Yup.string()
.required("Required")
.test("no-e", "Invalid", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
}),
salary_date: Yup.string()
.required("Required"),
employee_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 EmploymentDetail() {
const [employerList, setEmployerList] = useState<any>({
loading: true,
data: {}
})
//FUNCTION TO HANDLE SUBMIT
const handleSubmit = () => {
console.log('good')
};
useEffect(()=>{
getEmployer().then(res => {
setEmployerList({loading:false, data:res?.data?.employer})
}).catch(err => {
console.log(err)
setEmployerList({loading:false, data:{}})
})
},[])
const formInitialValue = (employerList.loading || Object.keys(employerList?.data)?.length < 1) ? initialValues : {...initialValues, ...employerList?.data}
return (
<>
{employerList.loading ?
<div className='flex flex-col justify-center items-center h-96'>
<CustomSpinner width='w-8' height='h-8' />
</div>
:
<div className="w-full">
<Formik
initialValues={formInitialValue}
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
disabled={true}
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="official_email"
floatLabel="Employers official email"
// labelClass="font-bold text-[1.125rem]"
input
disabled={true}
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="example@gmail.com"
value={props.values.official_email}
onChange={props.handleChange}
// error={(props.errors.official_email && props.touched.official_email) ? props.errors.official_email : ''}
/>
<InputCompOne
parentClass="w-full"
name="industry"
floatLabel="Select your industry"
// labelClass="font-bold text-[1.125rem]"
select={true}
disabled={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={props.values.industry}
selectValue={props.values.industry}
onChange={props.handleChange}
// error={(props.errors.industry && props.touched.industry) ? props.errors.industry : ''}
/>
<InputCompOne
parentClass="w-full"
name="job_sector"
floatLabel="Job Sector"
// labelClass="font-bold text-[1.125rem]"
select={true}
disabled={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={props.values.job_sector}
selectValue={props.values.job_sector}
onChange={props.handleChange}
// error={(props.errors.job_sector && props.touched.job_sector) ? props.errors.job_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
disabled={true}
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}
disabled={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={props.values.highest_eductaion}
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="start_date"
floatLabel="Date of resumption"
// labelClass="font-bold text-[1.125rem]"
input
disabled={true}
inputType='text'
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="12/12/2015"
value={props.values.start_date}
onChange={props.handleChange}
// error={(props.errors.start_date && props.touched.start_date) ? props.errors.start_date : ''}
/>
<InputCompOne
parentClass="w-full"
name="salary_date"
floatLabel="Salary payment date"
// labelClass="font-bold text-[1.125rem]"
input
disabled={true}
inputType='text'
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="30th of every month"
value={(props.values.salary_date)}
onChange={props.handleChange}
// error={(props.errors.salary_date && props.touched.salary_date) ? props.errors.salary_date : ''}
/>
</div>
<div className="w-full flex flex-col sm:flex-row items-center gap-4">
<InputCompOne
parentClass="w-full"
name="annual_salary"
floatLabel="Annual Income"
// labelClass="font-bold text-[1.125rem]"
input
disabled={true}
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem] text-right"
placeholder="1,200,000"
value={FormatAmount(props.values.annual_salary)}
onChange={props.handleChange}
// error={(props.errors.annual_salary && props.touched.annual_salary) ? props.errors.annual_salary : ''}
/>
<InputCompOne
parentClass="w-full"
name="net_montlty"
floatLabel="Net monthly salary"
// labelClass="font-bold text-[1.125rem]"
input
disabled={true}
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem] text-right"
placeholder="100,000"
value={FormatAmount(props.values.net_montlty)}
onChange={props.handleChange}
// error={(props.errors.net_montlty && props.touched.net_montlty) ? props.errors.net_montlty : ''}
/>
</div>
<InputCompOne
parentClass="w-full"
name="employee_id"
floatLabel="Employee ID"
// labelClass="font-bold text-[1.125rem]"
input
disabled={true}
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="LS/001/005"
value={props.values.employee_id}
onChange={props.handleChange}
// error={(props.errors.employee_id && props.touched.employee_id) ? props.errors.employee_id : ''}
/>
<div className="hidden 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,146 @@
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_account: values?.account, disbursement: values}).then(res=>{
console.log('APPLY FOR LOAN', res)
handleNextStep({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 {import.meta.env.VITE_BANK_NAME_SHORT} 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 {import.meta.env.VITE_BANK_NAME_SHORT}
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,182 @@
import { Button, InputCompOne, Stepper } from '../../shared/index';
import { state } from '../../../utils/states';
import {Formik, Form} from 'formik'
import * as Yup from "yup";
type Props = {
handleNextStep:(value:{})=>any
}
const initialValues = {
gender: "",
address: "",
marital_status: "",
state: "",
email:"",
country:"NG"
};
// 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 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,248 @@
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()
.notOneOf([Yup.ref('ref_name')], "Name cannot be the same")
.required("Required"),
ref_two_email: Yup.string()
.email("Invalid")
.notOneOf([Yup.ref('ref_email')], "Email cannot be the same")
.required("Required"),
ref_two_phone_number: Yup.string()
.notOneOf([Yup.ref('ref_phone_number')], "Phone number cannot be the same")
.required("Required"),
ref_two_relationship: Yup.string()
.required("Required"),
ref_two_bvn: Yup.string()
.notOneOf([Yup.ref('ref_bvn')], "BVN number cannot be the same")
.required("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 };
@@ -0,0 +1,176 @@
import {useEffect, useState} from 'react'
import {useLocation, useNavigate} from 'react-router-dom'
import { Button, InputCompOne } from "../../shared";
import {Formik, Form} from 'formik'
import * as Yup from "yup";
import { RouteHandler } from '../../../router/routes';
import { getLoanDetail } from '../../../core/apiRequest';
import CustomSpinner from '../../CustomSpinner';
interface LoanDetail {
loan_amount: string;
payment_month: string;
sales_agent: string;
[key: string]: any; // to accommodate any additional properties
}
// interface InitialValues {
// loan_amount: string;
// payment_month: string;
// sales_agent: string;
// }
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()
});
type LocationState = {
application_uid: string
}
export default function ReferenceDetails() {
const location = useLocation()
const navigate = useNavigate()
// const applicationUID = location?.state?.application_uid
const stateExist = location?.state as LocationState
//FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:{}) => {
// handleNextStep(values)
console.log(values)
};
const [loanDetail, setLoanDetail] = useState<{loading:boolean, data:LoanDetail}>({
loading: true,
data: {
loan_amount: '',
payment_month: '',
sales_agent: '',
},
})
useEffect(()=>{
if(!stateExist){
navigate(RouteHandler.dashboardHome)
return
}
getLoanDetail({application_uid:stateExist.application_uid}).then(res => {
setLoanDetail({loading:false, data:res?.data?.loan})
}).catch(err => {
console.log(err)
setLoanDetail((prev:any) => ({...prev, loading:false}))
})
},[])
// const formInitialValue:LoanDetail = (loanDetail.loading) ? initialValues : loanDetail?.data
const formInitialValue = (loanDetail.loading || Object.keys(loanDetail?.data)?.length < 1) ? initialValues : {...initialValues, ...loanDetail?.data}
return (
<>
{loanDetail.loading ?
<div className='flex flex-col justify-center items-center h-96'>
<CustomSpinner width='w-8' height='h-8' />
</div>
:
<div className="w-full">
<Formik
initialValues={formInitialValue}
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'>Loan Details</p>
<div className='w-full flex flex-wrap items-center gap-4'>
<InputCompOne
parentClass="w-full sm:max-w-[10rem] flex flex-col gap-4"
name="loan_amount"
label="Amount"
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}
disabled={true}
onChange={props.handleChange}
// error={(props.errors.loan_amount && props.touched.loan_amount) ? props.errors.loan_amount : ''}
/>
<InputCompOne
parentClass="w-full sm:max-w-[10rem] flex flex-col gap-4"
name="payment_month"
label="Months?"
labelClass="font-bold text-[1.125rem]"
select={true}
disabled={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={props.values.payment_month}
selectValue={props.values.payment_month}
onChange={props.handleChange}
// error={(props.errors.payment_month && props.touched.payment_month) ? props.errors.payment_month : ''}
/>
<InputCompOne
parentClass="w-full sm:max-w-[10rem] flex flex-col gap-4"
name="sales_agent"
label="agent ID"
labelClass="font-bold text-[1.125rem]"
floatLabel='Enter agent ID'
input
disabled={true}
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 : ''}
/>
</div>
<Button
className="hidden 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" },
// ]
// }
+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> {import.meta.env.VITE_BANK_NAME} (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>
);
};
+26 -23
View File
@@ -1,30 +1,33 @@
import React from 'react'
import { Link } from 'react-router-dom'
import FBook from '../../assets/icons/facebook.svg'
import Twitter from '../../assets/icons/twitter.svg'
import Instagram from '../../assets/icons/instagram.svg'
// import { Link } from "react-router-dom";
import { socialsIcons } from "../../utils/data";
export default function Footer() {
let socialsIcons = [
{name: 'facebook', image: FBook},
{name: 'twitter', image: Twitter},
{name: 'instagram', image: Instagram},
]
const date = new Date().getFullYear();
return (
<div className='w-full h-10 absolute bottom-0 bg-sky-50/50 flex items-center'>
<div className='containerMode flex justify-between items-center flex-wrap gap-2'>
<p className='text-[10px]'>{new Date().getFullYear()} @ First City Monument Bank Limited</p>
<div className='footer-social-icons flex justify-end items-center gap-2'>
{socialsIcons.map((icon, index)=>(
<Link key={index} className='w-5 h-5' to='#'>
<img src={icon.image} alt={icon.name} />
</Link>
))}
</div>
<div className="w-full h-[5.4375rem] 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} @ {import.meta.env.VITE_BANK_NAME} 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;
}
+4 -1
View File
@@ -1,3 +1,6 @@
import Footer from "./Footer";
import TopFooterOne from "./TopFooterOne";
import MidFooter from "./MidFooter";
import BottomFooterOne from "./BottomFooterOne";
export { Footer };
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;
+311
View File
@@ -0,0 +1,311 @@
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 BasicInfo: 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 */}
<h1 className="font-semibold text-[2.375rem] text-[#5C2684] my-[.5rem]">
Lets Get You Started
</h1>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props)=>(
<Form>
<div className="w-full rounded py-3 bg-[#5C2684] px-5">
<p className="text-base text-[#FBB700] tracking-[3%] font-extrabold w-fit">
BASIC INFORMATION
</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 BasicInfo;
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" },
]
}
@@ -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;
+27
View File
@@ -0,0 +1,27 @@
import BasicInfo from "./BasicInfo";
import YourAreAlmostThere from "./YourAreAlmostThere";
import LoanAmountComp from "./LoanAmountComp";
import ApplicantsAttestation from "./ApplicantsAttestation";
const GetStarted = ({handleNextStep, step}:{handleNextStep:any, step:string|number|any}) => {
return (
<div className="w-full flex items-center justify-center">
<div className="containerMode">
{/* Main */}
<main>
{step === 2 && (
<BasicInfo
handleNextStep={handleNextStep}
/>
)}
{step === 3 && <YourAreAlmostThere handleNextStep={handleNextStep} />}
{step === 4 && <LoanAmountComp handleNextStep={handleNextStep} />}
{step === 5 && <ApplicantsAttestation />}
</main>
</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 };
+71 -48
View File
@@ -1,16 +1,27 @@
import { useState, ChangeEvent } from "react";
import React, { useState, ChangeEvent } from "react";
import Logo from "../../assets/icons/logo.svg";
import Button from "../shared/Button";
import { lowerMenuItems } from "../../utils/data";
import { _lowerMenuItems } from "../../utils/data";
import Sidebar from "./Sidebar";
import { Link } from "react-router-dom";
import HeaderMenuItem from "./HeaderMenuItem";
import { RouteHandler } from "../../router/routes";
type LowerMenuItem = {
id: string | number;
export type LowerMenuItem = {
name: string;
linkPath: string;
subItems?: LowerMenuItem[];
};
const Header = () => {
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);
@@ -22,13 +33,12 @@ const Header = () => {
setIsSidebarOpen((prev) => !prev);
};
console.log(isSidebarOpen);
return (
<div className="relative my-2 flex items-center justify-center">
{isSidebarOpen && (
<div className="relative mt-2 py-2 flex items-center justify-center border-b-2 border-[#E3DEDA]">
{!hideSidebar && (
<Sidebar toggleSidebar={toggleSidebar} isSidebarOpen={isSidebarOpen} />
)}
<div className="containerMode flex justify-between gap-1 xl:gap-8">
<Link to="/">
<img
@@ -37,50 +47,63 @@ const Header = () => {
className="w-[90px] h-[90px] xl:w-[117px] xl:h-[117px]"
/>
</Link>
<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">
{["Open An Account", "Internet Banking", "Contact Us"].map(
(text: string) => (
<li key={text} className="hidden sm:flex">
<a href="#">
{!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: RouteHandler.letsGetStarted },
{
text: "Internet Banking",
href: RouteHandler.businessBanking,
},
{ text: "Contact Us", href: RouteHandler.cooperateBanking },
].map((item: { text: string; href: string }) => (
<li key={item.text} className="hidden sm:flex">
<a href={item.href}>
<Button
className={text === "Open An Account" ? "btn-active" : ""}
text={text}
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="currentColor"
onClick={toggleSidebar}
className="w-6 h-6"
>
<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">
{lowerMenuItems.map((item: LowerMenuItem) => (
<li
key={item.id}
className="cursor-pointer text-[13.5px] font-medium text-[#525252] tracking-[1px] leading-[-0.3pt]"
>
{item.name}
))}
<li className="w-full lg:w-fit">
<SearchInput
onChange={handleSearchChange}
value={searchValue}
/>
</li>
))}
</ul>
</div>
</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>
);
+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;
+6 -3
View File
@@ -1,16 +1,19 @@
const Sidebar = ({
toggleSidebar,
isSidebarOpen
isSidebarOpen,
}: {
toggleSidebar: () => void;
isSidebarOpen: boolean;
}) => {
const isActive = isSidebarOpen ? "sidebar-close" : "sidebar-open";
return (
<div className={`sidebar ${isSidebarOpen ? "open" : ""}`}>
// <div className={`sidebar ${isSidebarOpen ? "open" : ""}`}>
<div className={`${isActive} sidebar`}>
{/* Sidebar content goes here */}
<button onClick={toggleSidebar}>Close Sidebar</button>
</div>
);
};
export default Sidebar
export default Sidebar;
+18 -9
View File
@@ -1,4 +1,5 @@
import { top_header_data } from "../../utils/data";
import { Link } from "react-router-dom";
import styles from "./header.module.css";
const TopHeader = () => {
@@ -7,28 +8,36 @@ const TopHeader = () => {
<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`}>
<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`}>
<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>
<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 }) => (
{top_header_data.map(({ id, name, href }) => (
<li key={id}>
<a href="#" className={`py-[11px] px-[15px]`}>
<Link to={href} className={`py-[11px] px-[15px]`}>
{name}
</a>
</Link>
</li>
))}
</ul>
-11
View File
@@ -1,11 +0,0 @@
const Hero = () => {
return (
<div>
Hero
</div>
)
}
export default Hero
-3
View File
@@ -1,3 +0,0 @@
import Hero from "./Hero";
export { Hero };
+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 {import.meta.env.VITE_BANK_NAME_SHORT}
</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 };
+44 -37
View File
@@ -1,66 +1,73 @@
import { Button, Footer } from '../../components'
import {useState} from 'react'
import { Button, Footer, FloatLabelInput } from '../../components'
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 h-screen overflow-y-auto bg-[url('../../../src/assets/images/sign-in.png')] bg-top bg-cover`}>
<div className='containerMode h-full'>
<div className='grid grid-cols-1 md:grid-cols-2 items-center h-full'>
<div className='bg-white col-start-1 md:col-start-2 w-full h-[450px] rounded-2xl shadow-lg'>
<div className='w-full p-10 sm:p-20 flex flex-col justify-between items-center h-full'>
<div className='bg-white col-start-1 md:col-start-2 w-full rounded-2xl shadow-lg'>
<div className='w-full p-10 sm:p-20 md: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-2 text-center font-medium text-black dark:text-black'>Please login to admin dashboard</p>
<p className='text-xl mt-4 text-center font-medium text-black dark:text-black'>Please login to admin dashboard</p>
</div>
<div className='w-full'>
{/* INPUTS */}
{/* THIS INPUTS WILL BE MADE A COMPONENT LATER, TO AVOID REPEATINGS THINGS */}
<div className='w-full'>
<div className='relative my-2 py-2'>
<input
id={'id'}
name={'email'}
className={`peer pr-8 w-full h-12 outline-none border-b-2 border-b-purple-500 placeholder:text-transparent transition-all duration-500`}
// type={type == "password" ? inputType : type}
type='text'
placeholder={'email'}
// value={value}
// onChange={onChange}
<FloatLabelInput
id='email'
name='email'
type='email'
placeHolder='Email'
labelName='Email'
value={formDetails.email}
inputClass=''
onChange={handleFormChange}
/>
<label
htmlFor={'email'}
className={`text-sm text-black/70 absolute left-0 -top-1 translate-y-0 peer-focus:-top-1 peer-focus:translate-y-0 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 transition-all duration-500`}
>
{'Email'}
</label>
</div>
<div className='relative my-2 py-2'>
<input
id={'id'}
name={'password'}
className={`peer pr-8 w-full h-12 outline-none border-b-2 border-b-purple-500 placeholder:text-transparent transition-all duration-500`}
// type={type == "password" ? inputType : type}
<FloatLabelInput
id='password'
name='password'
type='password'
placeholder={'password'}
// value={value}
// onChange={onChange}
placeHolder='Password'
labelName='Password'
value={formDetails.password}
inputClass=''
onChange={handleFormChange}
/>
<label
htmlFor={'email'}
className={`text-sm text-black/70 absolute left-0 -top-1 translate-y-0 peer-focus:-top-1 peer-focus:translate-y-0 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 transition-all duration-500`}
>
{'Password'}
</label>
</div>
</div>
<div className='mt-10 w-full sm:flex justify-between items-center gap-2'>
<Button
text='Login'
className='rounded-md w-full sm:w-2/5 text-xl'
className='rounded-md w-full sm:w-2/5 text-xl capitalize font-bold'
/>
<Link to='#' className='text-black text-sm'>Forget your password</Link>
<Link to='#' className='text-black text-sm'>Forget your password?</Link>
</div>
</div>
</div>
@@ -69,7 +76,7 @@ export default function Login() {
</div>
{/* FOOTER SECTION */}
<div className='page-footer'>
<div className='page-footer w-full fixed bottom-0'>
<Footer />
</div>
</div>
+30
View File
@@ -0,0 +1,30 @@
type Props ={
text: string
onClick?: ()=>any
className?: string
shrinkAside?: boolean
icon?: string
loading?: boolean
disabled?: boolean
}
export default function MainBtn({
onClick,
className,
text,
shrinkAside,
icon,
loading,
disabled
}:Props) {
return (
<button
disabled={disabled}
className={`py-3 px-4 rounded-md ${className || ''} ${(disabled || loading) && 'opacity-60'}`}
onClick={onClick}
>
{icon && <i className={`fa-solid ${icon}`}></i>}
{shrinkAside ? '' : loading? 'Loading...' : text}
</button>
)
}
+64
View File
@@ -0,0 +1,64 @@
import {useEffect, useState} from 'react'
import { getPaymentDetails } from '../../core/apiRequest'
type Props = {
reference: string | null
}
// type PaymentPayloads = {
// uid?: string
// event?: string
// customer_code?: string
// plan_name?: string
// plan_code?: string
// subscription_code?: string | null,
// amount?: string
// authorization_code?: string
// gateway_response?: string
// gateway_status?: string
// reference?: string
// added?: string
// }
export default function Payment({reference}:Props) {
const [paymentDetails, setPaymentDetails] = useState<any>({
loading: true,
data: {}
})
useEffect(()=>{
getPaymentDetails({reference}).then(res => {
setPaymentDetails({loading:false, data:res?.data?.payment})
console.log(res?.data?.payment)
}).catch(err => {
setPaymentDetails({loading:false, data:{}})
console.log(err)
})
},[])
return (
<div className='w-full'>
<div className='p-4'>
<h1 className='p-2 mb-3 text-2xl'>Confirmation</h1>
<div className='p-8 w-full max-w-2xl bg-white shadow-md rounded-md'>
{paymentDetails.loading ?
<p>Loading...</p>
:
(paymentDetails?.data && Object.keys(paymentDetails?.data).length > 0) ?
<>
{Object.keys(paymentDetails?.data).map((item) => (
<div key={item} className='p-2 flex gap-1'>
<p className='w-64 font-semibold'>{item}</p>
<p className=''>{paymentDetails?.data[item]}</p>
</div>
))}
</>
:
<p className='p-2'>No Payment Found!</p>
}
</div>
</div>
</div>
)
}
+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 };
+11 -4
View File
@@ -1,4 +1,11 @@
export * from "./Header"
export * from "./Hero"
export * from "./shared"
export * from "./Footer"
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";
+14
View File
@@ -0,0 +1,14 @@
import { ReactNode } from "react"
type Props = {
children: ReactNode
}
export default function ModalWrapper({children}:Props) {
return (
<div className="z-50 fixed inset-0">
<div className="bg-black/50 fixed inset-0"></div>
<div className="relative h-full flex flex-col justify-center items-center">{children}</div>
</div>
)
}
@@ -0,0 +1,134 @@
import { ReactNode, useEffect, useState } from "react";
import { PendingTableList } from "../../core/models";
type PaginatedListProps = {
data: Array<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:any) => prev - numberOfSelection);
}
};
const handleNext = () => {
if (currentPage < data.length) {
setCurrentPage((prev:any) => 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>
);
}
+21 -5
View File
@@ -1,11 +1,27 @@
import React from "react";
type ButtonProps = {
className?: string;
interface ButtonProps {
text: string;
};
className?: string;
type?: "button" | "submit" | "reset";
onClick?: React.MouseEventHandler<HTMLButtonElement>;
}
const Button = ({ text, className }: ButtonProps) => {
return <button className={`btn-primary uppercase text-[11px] lg:text-[13px] p-[6px] lg:px-[10px] ${className}`}>{text}</button>;
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;
+42
View File
@@ -0,0 +1,42 @@
type Props = {
id?:string,
name?:string,
type?:string,
placeHolder?:string,
labelName?:string,
inputClass?:string,
value:string,
onChange:(event?: any)=>any
}
export default function FloatLabelInput({
id,
name,
type,
placeHolder,
labelName,
value,
inputClass,
onChange
}:Props) {
return (
<div className='w-full'>
<input
id={id ? id : ''}
name={name? name : ''}
className={`peer pr-8 w-full h-12 outline-none border-b-2 border-b-[#5A2C82] placeholder:text-transparent transition-all duration-500 ${inputClass && inputClass}`}
type={type ? type : 'text'}
placeholder={placeHolder ? placeHolder : ''}
value={value}
onChange={onChange}
/>
<label
htmlFor={id ? id : ''}
className={`cursor-pointer text-sm text-black/70 absolute left-0 -top-1 translate-y-0 peer-focus:-top-1 peer-focus:translate-y-0 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 transition-all duration-500`}
>
{labelName ? labelName : ''}
</label>
</div>
)
}
+154
View File
@@ -0,0 +1,154 @@
import React, { forwardRef } from "react";
import { Icons } from "../Icons";
export interface InputCompOneProps {
label?: string;
labelClass?: string;
labelSpan?: string;
labelSpanClass?: string;
floatLabel?: string;
placeholder?: string;
value?: string | any;
onChange?: (e:any) => any;
onInput?: (e:any) => any;
name: string;
tabIndex?: number;
ref?: React.RefObject<HTMLInputElement>;
selectValue?: string;
input?: boolean;
select?: boolean;
selectOptions?: {loading:boolean, data:{ [index: string]: string; }[]} | any;
inputType?: string;
inputClass?: string;
parentInputClass?: string;
selectClass?: string;
parentClass?: string;
maxLength?: number;
error?: string;
disabled?: boolean
}
const InputCompOne = forwardRef<HTMLInputElement, InputCompOneProps>(
(
{
label,
labelClass,
labelSpan,
labelSpanClass,
floatLabel,
placeholder,
value,
onChange,
onInput,
name,
tabIndex,
selectValue,
input = false,
select = false,
selectOptions = {loading:false, data:[]},
inputType = "text",
inputClass,
parentInputClass,
selectClass,
parentClass,
maxLength,
error,
disabled=false
},
forwardedRef
) => {
return (
<div className={parentClass}>
{label && (
<label htmlFor={label ? label : floatLabel} className={`flex gap-2 items-center flex-wrap ${labelClass}`}>
{label}
{labelSpan && <span className={labelSpanClass}>{labelSpan}</span>}
{error && label && <span className='text-[10px] text-red-500'>{error}</span>}
</label>
)}
{input && (
<div className={`relative ${parentInputClass}`}>
<input
type={inputType}
placeholder={placeholder}
value={value}
onChange={onChange}
onInput={onInput}
name={name}
tabIndex={tabIndex}
ref={forwardedRef}
className={`px-4 ${floatLabel && 'peer pt-4 placeholder:text-transparent'} ${inputClass}`}
maxLength={maxLength}
id={label ? label : floatLabel}
disabled={disabled}
/>
{floatLabel &&
<label
htmlFor={label ? label : floatLabel}
className={`flex items-center gap-2 cursor-pointer text-sm text-black/70 dark:text-white absolute left-4 top-0 translate-y-0 peer-focus:top-0 peer-focus:translate-y-0 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 transition-all duration-500`}
>
{floatLabel}
{error && floatLabel && !label && <span className='text-[10px] text-red-500'>{error}</span>}
</label>
}
</div>
)}
{select && (
<div className={`relative ${parentInputClass}`}>
<select
name={name}
id={label ? label : floatLabel}
value={selectValue}
className={`px-4 appearance-none ${floatLabel && 'peer pt-4'} ${selectClass} ${disabled && 'opacity-50'}`}
onChange={onChange}
disabled={disabled}
>
{typeof(selectOptions) == 'string' ?
<option value={selectValue}>{selectValue}</option>
:selectOptions.loading ?
<option value=''>Loading...</option>
: selectOptions.data.length && name == 'employer_uid' ?
<>
<option value=''>Please Select</option>
{selectOptions.data.map(({ uid, name }:any) => (
<option key={uid} value={uid}>
{name}
</option>
))}
</>
: selectOptions.data.length && name != 'employer_uid' ?
selectOptions.data.map(({ value, label }:any) => (
<option key={value} value={value}>
{label}
</option>
))
:
<option value=''>Not Found</option>
}
{/* {selectOptions.map(({ value, label }) => (
<option key={value} value={value}>
{label}
</option>
))} */}
</select>
{floatLabel &&
<label
htmlFor={label ? label : floatLabel}
className={`flex items-center gap-2 cursor-pointer text-sm text-black/70 dark:text-white absolute left-4 top-0 translate-y-0 peer-focus:top-0 peer-focus:translate-y-0 transition-all duration-500`}
>
{floatLabel}
{error && floatLabel && !label && <span className='text-[10px] text-red-500'>{error}</span>}
</label>
}
{/* select custon arrow */}
<div className='absolute right-4 top-1/2 -translate-y-1/2'>
<Icons name='arrow-down' />
</div>
</div>
)}
</div>
);
}
);
export default InputCompOne;
+25
View File
@@ -0,0 +1,25 @@
import React from "react";
interface StepperProps {
step: number;
}
const Stepper: React.FC<StepperProps> = ({ step = 0 }) => {
// const [activeStep, setActiveStep] = useState(step);
return (
<div className="flex justify-between items-center gap-5">
{[...Array(5)].map((_, index) => (
<div
key={index}
className={`w-[1.875rem] border-[.1875rem] rounded-sm ${(step === index
? "border-[#E8B4FF]"
: "border-[#5C2684]")}`}
// onClick={() => setActiveStep(index)}
/>
))}
</div>
);
};
export default Stepper;
+4 -1
View File
@@ -1,3 +1,6 @@
import Button from "./Button";
import InputCompOne from "./InputCompOne";
import FloatLabelInput from "./FloatLabelInput";
import Stepper from "./Stepper";
export {Button}
export { Button, FloatLabelInput, InputCompOne, Stepper };
@@ -0,0 +1,149 @@
import { ReactNode, useEffect, useState } from "react";
import MainBtn from "../MainBtn";
const data1:{ name: string; email: string; status: string; location: string; }[] = [];
type PaginatedListProps = {
data: any,
itemsPerPage: number,
filterItem?: string[],
titleClass?:string,
children: (data:any) => ReactNode;
}
export default function TableWrapper({
data = data1,
itemsPerPage = 5,
filterItem,
children,
}:PaginatedListProps) {
const [isLoading, setIsLoading] = useState(true)
const [searchTerm, setSearchTerm] = useState("");
const [filteredData, setFilteredData] = useState(data);
const [currentPage, setCurrentPage] = useState(0);
const [newData, setNewData] = useState<{ name: string; email: string; status: string; location: string; }[]>([]);
const numberOfSelection = itemsPerPage;
const handlePrev = () => {
if (currentPage != 0) {
setCurrentPage((prev:any) => prev - numberOfSelection);
}
};
const handleNext = () => {
if (currentPage < data.length) {
setCurrentPage((prev:any) => 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(() => {
setIsLoading(true)
setTimeout(()=>{
setNewData(
filteredData?.slice(currentPage, numberOfSelection + currentPage)
);
setIsLoading(false)
},1000)
}, [currentPage, filteredData]);
useEffect(()=>{
setCurrentPage(0)
},[itemsPerPage])
return (
<div className="p-2 w-full bg-white border-b dark:bg-gray-800 rounded-md">
{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>
)}
<div className="flex flex-col">
<div className="w-full">
{children({ data: newData })}
</div>
{/* PAGINATION BUTTON */}
{(newData.length > 0 && data.length > itemsPerPage) &&
<div className='p-2 w-full flex flex-col lg:flex-row justify-center items-center gap-3 md:gap-6'>
<div className="text-sm text-center lg:text-left font-normal text-gray-500 dark:text-gray-400 block w-full">Showing <span className="font-semibold text-gray-900 dark:text-white">
{isLoading ? '----' : `${currentPage + 1}-${currentPage + numberOfSelection >= data.length ? data.length : (currentPage + numberOfSelection)}`}</span> of <span className="font-semibold text-gray-900 dark:text-white">{data.length}</span>
</div>
<div className='flex items-center gap-3 md:gap-6'>
<MainBtn
onClick={handlePrev}
text='Prev'
className={`${currentPage == 0 ? 'bg-sky-600/50 pointer-events-none' : 'bg-sky-600'} text-white-light`}
disabled={isLoading}
/>
<MainBtn
onClick={handleNext}
text='Next'
className={`${currentPage + numberOfSelection >= data.length ? 'bg-sky-600/50 pointer-events-none' : 'bg-sky-600'} text-white-light`}
disabled={isLoading}
/>
</div>
</div>
}
</div>
{/* show prev and next button if data exist */}
{/* {data.length > 0 && (
{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>
)
}
})}
</div>
)} */}
{isLoading && <TableIsLoading />}
</div>
);
}
const TableIsLoading = () => {
return (
<div className="w-full fixed z-[991] inset-0 flex justify-center items-center bg-white/50">
<p className="rounded-md shadow-md p-4 bg-white/90 dark:bg-gray-900 text-brown dark:text-white">Loading...</p>
</div>
)
}
+94
View File
@@ -0,0 +1,94 @@
import { postAuxEnd, getAuxEnd } from "./axiosCall";
// FUNCTION TO START BVN VALIDATION
export const validateBVN = (postData:any) => {
let reqData = {
...postData
}
return postAuxEnd('/bvn', reqData)
}
// FUNCTION TO VERIFY OTP AND LOGIN
export const verifyOTP = (postData:any) => {
let reqData = {
...postData
}
return postAuxEnd('/bvn/verify', reqData)
}
// FUNCTION TO APPLY FOR LOAN
export const applyForLoan = (postData:any) => {
let reqData = {
customer_uid: localStorage.getItem('uid'),
...postData
}
return postAuxEnd('/loan/apply', reqData)
}
// FUNCTION TO GET USER BY CUSTOMER UID
export const getUserByID = (uid:string) => {
let reqData = {
// customer_uid: localStorage.getItem('uid'),
}
return getAuxEnd(`/profile?uid=${uid}`, reqData)
}
// FUNCTION TO GET USER BY CUSTOMER UID
export const getUserPendingLoanList = (uid:string) => {
let reqData = {
// customer_uid: localStorage.getItem('uid'),
}
return getAuxEnd(`/dash?uid=${uid}`, reqData)
}
// FUNCTION TO GET LIST OF EMPLOYERS
export const getEmployersList = () => {
let reqData = {
// customer_uid: localStorage.getItem('uid'),
}
return getAuxEnd(`/employers`, reqData)
}
// FUNCTION TO GET USER EMPLOYER
export const getEmployer = () => {
let reqData = {
uid: localStorage.getItem('uid'),
}
return getAuxEnd(`/dash/employer?uid=${reqData?.uid}`, null)
}
// FUNCTION TO GET LOAN DETAILS
export const getLoanDetail = (postData:any) => {
let reqData = {
uid: localStorage.getItem('uid'),
...postData
}
return getAuxEnd(`/loan/loandetail?uid=${reqData?.uid}&application_uid=${reqData?.application_uid}`, null)
}
// FUNCTION TO GET PAYMENT DETAILS
export const getPaymentDetails = (postData:any) => {
let reqData = {
uid: localStorage.getItem('uid'),
...postData
}
return getAuxEnd(`/payment/status?uid=${reqData?.uid}&reference=${reqData?.reference}`, null)
}
// FUNCTION TO ADD CARD
export const addCard = (postData:any) => {
let reqData = {
uid: localStorage.getItem('uid'),
...postData
}
return postAuxEnd('/addcard', reqData)
}
// FUNCTION TO GET USER AGREEMENTS LIST
export const getAgreementsList = () => {
let reqData = {
uid: localStorage.getItem('uid'),
}
return getAuxEnd(`/agreements?uid=${reqData?.uid}`, null)
}
+81
View File
@@ -0,0 +1,81 @@
import axios from "axios";
export function postAuxEnd(uri: string, reqData: any): Promise<any> {
const endPoint = import.meta.env.VITE_USERS_ENDPOINT + uri;
const formData = new FormData();
for (let value in reqData) {
if (typeof reqData[value] === "object") {
// for (let innerValue in reqData[value]) {
// let innerReqData = reqData[value]
// console.log('SAMPLE',innerReqData)
// formData.append(reqData[value][innerValue], reqData[value][innerValue]);
// }
formData.append(value, JSON.stringify(reqData[value]));
// formData.append(value, reqData[value]);
} else {
formData.append(value, reqData[value]);
}
}
return axios
.post(endPoint, formData)
.then((response: {}) => {
// if (response.data.internal_return == "-9999") {
// localStorage.clear();
// window.location.href = `/login?sessionExpired=true`;
// }
return response;
})
.catch((error: any) => {
if (error.response) {
//response status is an error code
console.log(
"ERROR-------------------------------------------------------"
);
console.log(error.response.status);
console.log(
"ERROR-------------------------------------------------------"
);
} else if (error.request) {
//response not received though the request was sent
console.log(
"ERROR2-------------------------------------------------------"
);
console.log(error?.request);
console.log(
"ERROR2-------------------------------------------------------"
);
} else {
//an error occurred when setting up the request
console.log(
"ERROR3-------------------------------------------------------"
);
console.log(error);
console.log(
"ERROR3-------------------------------------------------------"
);
}
});
}
export function getAuxEnd(uri: string, reqData: any): Promise<any> {
const endPoint = import.meta.env.VITE_USERS_ENDPOINT + uri;
const formData = new FormData();
for (let value in reqData) {
formData.append(value, reqData[value]);
}
return axios
.get(endPoint, reqData)
.then((response: {}) => {
// if (response.data.internal_return == "-9999") {
// localStorage.clear();
// window.location.href = `/login?sessionExpired=true`;
// }
return response;
})
.catch((error: any) => {
console.log(
"ERROR3-------------------------------------------------------", error
);
});
}
+32
View File
@@ -0,0 +1,32 @@
export interface RequestStatus {
loading?:boolean
status?:boolean | undefined
message?:string
name?:string
data?:{}[] | [any] | {}
}
export interface User {
firstname?:string
lastname?:string
last_login?:string
message?:string
token?:string
customer_uid?:string
call_return?:string
}
export type PendingTableList = {
status?: string | boolean
application_uid?: string
added?: string
loan_amount?: string
payment_month?: string
status_text?: {
text?: string
button?: boolean
advise?: string
}
};
+31 -1
View File
@@ -14,6 +14,36 @@ body {
@layer components {
.containerMode {
@apply container mx-auto px-5 xxs:max-w-full sm:max-w-[98%] lg:max-w-[1100px];
/* @apply container mx-auto px-5 xxs:max-w-full sm:max-w-[98%] lg:max-w-[1100px]; */
@apply container mx-auto px-5 max-w-[1500px]
}
.custom-btn{@apply min-w-[100px] transition-all duration-300 p-2 rounded-full}
/* MODAL COMPONENT */
.modal-container{
animation-name: zoom;
animation-duration: .2s;
animation-timing-function: linear;
@apply w-4/5 max-w-[600px] bg-white shadow-md rounded-2xl overflow-hidden
}
.modal-header {
@apply w-full flex items-center justify-between p-5 py-8 border-b bg-sky-500
}
.modal-title {
@apply text-2xl leading-8 font-bold text-white dark:text-white tracking-wide flex items-center
}
.modal-close-btn {
@apply text-white dark:text-red-500
}
.modal-body {
@apply w-full p-5 min-h-[150px] max-h-[500px] overflow-y-auto break-words flex flex-col justify-center items-center
}
.modal-footer {
@apply w-full p-5 border-t flex justify-between items-center gap-4
}
/* MODAL COMPONENT */
}
+225
View File
@@ -0,0 +1,225 @@
import { useState, useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom';
import Logo from '../../assets/icons/logo.svg';
import { Icons } from '../../components';
import { contactDetails } from '../../utils/data';
type Props = {
asideDisplay?: () => void;
logoutUser: () => void;
};
export default function Aside({ asideDisplay, logoutUser }: Props) {
const { pathname } = useLocation();
const [openNestedLink, setOpenNestedLink] = useState<{ name: string | null }>(
{ name: '' }
);
const handleOpenNestedLink = (e: any) => {
if (!e || !e.target) {
return setOpenNestedLink({ name: '' });
}
if (openNestedLink.name && openNestedLink.name == e.target.name) {
setOpenNestedLink({ name: '' });
} else {
setOpenNestedLink({ name: e.target.name });
}
};
// Track user activity
useEffect(() => {
let timeout: number;
const resetTimeout = () => {
clearTimeout(timeout);
timeout = window.setTimeout(() => {
// Logout user after 7 minutes of inactivity
logoutUser();
}, 7 * 60 * 1000); // 7 minutes in milliseconds
};
const handleUserActivity = () => {
resetTimeout();
};
// Attach event listeners to track user activity
document.addEventListener('mousemove', handleUserActivity);
document.addEventListener('keypress', handleUserActivity);
// Initialize timeout
resetTimeout();
// Clear timeout and remove event listeners on component unmount
return () => {
clearTimeout(timeout);
document.removeEventListener('mousemove', handleUserActivity);
document.removeEventListener('keypress', handleUserActivity);
};
}, [logoutUser]);
return (
<div className="py-5 px-10 flex flex-col h-full bg-inherit">
<Link to="/">
<img src={Logo} alt="Logo" className="w-[72px] h-[63px]" />
</Link>
<div className="mt-10 h-full overflow-y-auto bg-inherit">
{asideLinks.map((link, index) => {
if (link.nestedLink?.length) {
let allNestedLinks = link.nestedLink.map((item) => item.link);
return (
<div
key={index}
className="w-full relative bg-inherit overflow-hidden"
>
<button
name={link.name}
onClick={(e) => handleOpenNestedLink(e)}
className={`py-2 pl-2 text-left relative w-full overflow-hidden rounded-lg flex justify-between items-center z-10 bg-inherit ${
allNestedLinks.includes(pathname)
? ' text-[#5C2684]'
: ' text-[#585858]'
}`}
>
{link.name}
{/* <div className={`mr-2 ${openNestedLink.name == link.name ? '-rotate-90' : 'rotate-90'} transition-all duration-300`}>
<Icons
name='greater-than'
fillColor={`${openNestedLink.name == link.name ? '#5C2684' : '#585858'}`}
/>
</div> */}
</button>
<div
className={`transition-all duration-300 w-full z-1 ${
openNestedLink.name == link.name
? 'relative top-0'
: 'absolute -top-[500px]'
}`}
>
{link.nestedLink.map((nextLink, index) => (
<Link
onClick={() => {
asideDisplay && asideDisplay();
}}
key={index}
to={nextLink.link ? nextLink.link : '#'}
className={`w-full my-1 flex items-center gap-2 py-2 pl-5 text-base font-medium ${
pathname == nextLink.link
? ' text-[#5C2684]'
: 'text-[#585858]'
}`}
>
<Icons
name={nextLink.icon}
fillColor={`${
pathname == nextLink.link ? '#5C2684' : '#585858'
}`}
/>
{nextLink.name}
</Link>
))}
</div>
</div>
);
} else {
return (
<Link
onClick={() => {
asideDisplay && asideDisplay();
}}
key={index}
to={link.link ? link.link : '#'}
className={`w-full my-4 flex items-center gap-2 py-2 pl-5 rounded-lg text-base font-medium ${
pathname == link.link ? 'text-[#5C2684]' : 'text-[#585858]'
}`}
>
<Icons
name={link.icon}
fillColor={`${pathname == link.link ? '#5C2684' : '#585858'}`}
/>
{link.name}
</Link>
);
}
})}
</div>
<div className="w-full flex justify-center items-center flex-col gap-3">
<button
className="py-3 px-6 bg-red-100 text-red-500 font-medium rounded-md w-full"
onClick={() => logoutUser()}
>
Log out
</button>
<div className="flex flex-col gap-[.4375rem] text-[.75rem]">
<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> */}
{contactDetails.map(({ name, value }) => (
<p
key={name}
className="font-extrabold tracking-[3%] text-[#5A5A5A]"
>
{name}: {value}
</p>
))}
</div>
</div>
</div>
);
}
type AsideLinksType = {
name: string;
link?: string;
icon: string;
nestedLink?: {
name: string;
link: string;
icon: string;
}[];
}[];
const asideLinks: AsideLinksType = [
{
name: 'Dashboard',
link: '/dashboard/home',
icon: 'dash-icon',
nestedLink: [],
},
{
name: 'Your Profile',
link: '/dashboard/profile',
icon: 'dash-icon',
nestedLink: [],
},
{
name: 'Employment Details',
link: '/dashboard/verification',
icon: 'dash-icon',
nestedLink: [],
},
{
name: 'Reference Details',
link: "/dashboard/reference",
icon: 'dash-icon',
nestedLink: [],
},
{
name: 'Agreements',
link: '/dashboard/legals',
icon: 'dash-icon',
nestedLink: [],
},
// {name: 'Nested Link', icon: 'home', nestedLink:[
// {name: 'Link 2', link: '/dashboard/not-found', icon: 'legals'},
// {name: 'Link 1', link: '/dashboard/not-found', icon: 'home'}
// ]
// },
];
@@ -0,0 +1,61 @@
import {useState, useEffect} from 'react'
import DashboardLayout from "./DashboardLayout";
import { Outlet, useNavigate } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { RouteHandler } from '../../router/routes';
import { updateUserDetails } from '../../store/UserDetails';
import { getUserByID } from '../../core/apiRequest';
import Logo from '../../assets/images/logo.png'
export default function DashboardAuth() {
const navigate = useNavigate()
const dispatch = useDispatch()
const { userDetails } = useSelector((state:any) => state?.userDetails); // CHECKS IF USER Details are avaliable
const [loading, setLoading] = useState(true)
useEffect(()=>{
let token = localStorage.getItem('token')
let uid = localStorage.getItem('uid')
if(!token || !uid){
navigate(RouteHandler.letsGetStarted, {replace:true})
return
}
const getUser = () => { // FUNCTION TO GET USER BY ID
// let data = {firstname:'firstname', lastname:'lastname', uid:'28273737646466464'}
getUserByID(uid).then(res=>{
if(!res.data.call_return || !Object.keys(res.data.customer).length){
navigate(RouteHandler.letsGetStarted, {replace:true})
return
}
setLoading(false)
dispatch(updateUserDetails(res.data.customer));
}).catch(err=>{
navigate(RouteHandler.letsGetStarted, {replace:true})
console.log('USER ERROR', err)
})
}
if(!Object.keys(userDetails).length){
getUser()
}
},[])
return (
<>
{loading && !Object.keys(userDetails).length ?
<div className='w-full h-screen flex flex-col justify-center items-center gap-4'>
<img className='animate-pulse' src={Logo} alt='Logo' />
<p className='animate-pulse'>loading...</p>
</div>
:
<DashboardLayout>
<Outlet />
</DashboardLayout>
}
</>
)
}
@@ -0,0 +1,124 @@
import { ReactNode, useState, useEffect } from "react";
import { RouteHandler } from "../../router/routes";
import { useNavigate } from "react-router-dom";
import Aside from "./Aside";
export default function DashboardLayout({ children }: { children: ReactNode }) {
const navigate = useNavigate();
const [showAside, setShowAside] = useState<boolean>(false);
const asideDisplay = (): void => {
setShowAside((prev) => !prev);
};
useEffect(() => {
const handleResize = () => {
return setShowAside(false);
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
// Assume this interface for ChildProps
// interface ChildProps {
// customProp?: string;
// }
// const enhanceChildren = React.Children.map(children, (child) => {
// if (React.isValidElement<ChildProps>(child)) {
// return React.cloneElement(child, { customProp: "Hello, World!" });
// }
// return child;
// });
const logoutUser = () => {
localStorage.clear()
navigate(RouteHandler.letsGetStarted, {replace:true})
}
return (
<div className="w-full max-w-[2000px] mx-auto h-screen flex bg-[#020202] text-black">
<aside className="max-w-[18.75rem] w-full bg-white hidden md:block border-r-2 border-[#E6E6E6]">
<Aside logoutUser={logoutUser} />
</aside>
<aside
className={`max-w-[18.75rem] w-full md:hidden bg-white border-r-2 border-[#E6E6E6] fixed top-0 bottom-0 z-50 transition-all duration-500 ${
showAside ? "left-0" : "-left-[200%]"
}`}
>
<Aside logoutUser={logoutUser} asideDisplay={asideDisplay} />
</aside>
<main className="dash-bg-image bg-[#F9F9F9] relative w-full overflow-y-auto overflow-x-hidden">
<header className={`p-5 md:hidden sticky z-10 top-0 w-full bg-[#F9F9F9] border-b-2 border-[#E6E6E6]`}>
<div className='h-14 w-full flex justify-end items-center gap-5'>
{/* MENU HAND BURGER */}
{/* <div className='w-full'>Welcome Austin Catherine</div> */}
<div
className="relative md:hidden w-5 h-[20px] flex flex-col items-center justify-between"
onClick={asideDisplay}
>
<div
className={`absolute left-0 w-5 h-1 bg-black/80 dark:bg-white transition-all duration-500 ${
showAside ? "top-1/2 -translate-y-1/2 rotate-45" : "top-0"
}`}
></div>
<div
className={`absolute left-0 w-5 h-1 bg-black/80 dark:bg-white transition-all duration-300 ${
showAside
? "top-1/2 -translate-y-1/2 rotate-[2000deg] opacity-0"
: "top-1/2 -translate-y-1/2"
}`}
></div>
<div
className={`absolute left-0 w-5 h-1 bg-black/80 dark:bg-white transition-all duration-500 ${
showAside
? "top-1/2 -translate-y-1/2 -rotate-45"
: "bottom-0"
}`}
></div>
</div>
</div>
</header>
<div className="flex p-2 md:p-5 relative">
<div className="w-full p-2 md:p-5">{children}</div>
</div>
</main>
</div>
);
}
// {/* <header className={`p-5 sticky z-10 top-0 w-full bg-[#F9F9F9] border-b-2 border-[#E6E6E6]`}>
// <div className='h-14 w-full flex justify-end items-center gap-5'>
// {/* MENU HAND BURGER */}
// <div className='w-full'>Welcome Austin Catherine</div>
// <div
// className="relative md:hidden w-5 h-[20px] flex flex-col items-center justify-between"
// onClick={asideDisplay}
// >
// <div
// className={`absolute left-0 w-5 h-1 bg-black/80 dark:bg-white transition-all duration-500 ${
// showAside ? "top-1/2 -translate-y-1/2 rotate-45" : "top-0"
// }`}
// ></div>
// <div
// className={`absolute left-0 w-5 h-1 bg-black/80 dark:bg-white transition-all duration-300 ${
// showAside
// ? "top-1/2 -translate-y-1/2 rotate-[2000deg] opacity-0"
// : "top-1/2 -translate-y-1/2"
// }`}
// ></div>
// <div
// className={`absolute left-0 w-5 h-1 bg-black/80 dark:bg-white transition-all duration-500 ${
// showAside
// ? "top-1/2 -translate-y-1/2 -rotate-45"
// : "bottom-0"
// }`}
// ></div>
// </div>
// </div>
// </header> */}
+3
View File
@@ -0,0 +1,3 @@
import DashboardAuth from "./DashboardAuth";
export { DashboardAuth };
+24
View File
@@ -0,0 +1,24 @@
import React from "react";
import { Footer, Header } from "../components";
interface GetStartedLayoutProps {
children: React.ReactNode;
}
const GetStartedLayout: React.FC<GetStartedLayoutProps> = ({ children }) => {
return (
<div className="containerMode mb-[5.4375rem]">
<div className='sticky z-50 top-0 bg-white'>
<Header hideSidebar={true} hideMenu={true} />
</div>
<div className="flex flex-col min-h-[70vh] justify-between">
{children}
</div>
<div className="fixed bottom-0 left-0 bg-white w-full">
<Footer />
</div>
</div>
);
};
export default GetStartedLayout;
+27
View File
@@ -0,0 +1,27 @@
import React, { ReactNode } from "react";
import {
BottomFooterOne,
Header,
MidFooter,
TopFooterOne,
TopHeader,
} from "../components";
interface HomeLayoutProps {
children: ReactNode;
}
const HomeLayout: React.FC<HomeLayoutProps> = ({ children }) => {
return (
<>
<TopHeader />
<Header />
{children}
<TopFooterOne />
<MidFooter />
<BottomFooterOne />
</>
);
};
export default HomeLayout;
+24
View File
@@ -0,0 +1,24 @@
import React from "react";
import { Footer, LetsGetStartedNav } from "../components";
// import layoutImage from "../assets/images/test1-reverse.png";
const LetsGetStartedLayout = ({ children }: { children: React.ReactNode }) => {
return (
<div className='containerMode mb-[5.4375rem]'>
<div className="w-full min-h-[90vh] grid lg:grid-cols-2">
<div className="w-full flex flex-col my-3">
<LetsGetStartedNav />
{children}
</div>
<div className="w-full h-96 lg:h-full bg-[url(../src/assets/images/test1-reverse.png)] bg-cover bg-no-repeat">
{/* <img src={layoutImage} alt="" className="w-full h-full object-cover" /> */}
</div>
</div>
<div className="fixed bottom-0 left-0 bg-[#F7F7F7] w-full">
<Footer />
</div>
</div>
);
};
export default LetsGetStartedLayout;

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