Compare commits

...

27 Commits

Author SHA1 Message Date
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 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
30 changed files with 1235 additions and 498 deletions
+8 -1
View File
@@ -3,4 +3,11 @@ DIGIFI_PORT=5173
# Social Links # Social Links
FACEBOOK_URL=https://www.facebook.com FACEBOOK_URL=https://www.facebook.com
TWITTER_URL=https://twitter.com TWITTER_URL=https://twitter.com
INSTAGRAM_URL=https://www.instagram.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'
+4 -1
View File
@@ -3,4 +3,7 @@ DIGIFI_PORT=5173
# Social Links # Social Links
VITE_FACEBOOK_URL=https://www.facebook.com VITE_FACEBOOK_URL=https://www.facebook.com
VITE_TWITTER_URL=https://twitter.com VITE_TWITTER_URL=https://twitter.com
VITE_INSTAGRAM_URL=https://www.instagram.com VITE_INSTAGRAM_URL=https://www.instagram.com
# BACKEND END POINTS
VITE_USERS_ENDPOINT='https://digifi-apidev.chiefsoft.net/digiusers/v1'
+4 -1
View File
@@ -3,4 +3,7 @@ DIGIFI_PORT=5173
# Social Links # Social Links
FACEBOOK_URL=https://www.facebook.com FACEBOOK_URL=https://www.facebook.com
TWITTER_URL=https://twitter.com TWITTER_URL=https://twitter.com
INSTAGRAM_URL=https://www.instagram.com INSTAGRAM_URL=https://www.instagram.com
# BACKEND END POINTS
VITE_USERS_ENDPOINT='https://digifi-apidev.chiefsoft.net/digiusers/v1'
+5 -2
View File
@@ -11,10 +11,13 @@ services:
ports: ports:
- 6030:5173 - 6030:5173
expose: expose:
- "5173" - "5173"
extra_hosts:
- digifi-apidev.chiefsoft.net:10.10.33.15
- backend.wrenchboard.api.test:10.10.33.15
environment: environment:
- PORT=${DIGIFI_PORT} - PORT=${DIGIFI_PORT}
tty: true tty: true
stdin_open: true stdin_open: true
volumes: volumes:
src: src:
+100
View File
@@ -9,10 +9,12 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^2.2.1", "@reduxjs/toolkit": "^2.2.1",
"axios": "^1.6.8",
"clsx": "2.1.0", "clsx": "2.1.0",
"formik": "2.4.5", "formik": "2.4.5",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-icons": "^5.0.1",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"react-router-dom": "6.3.0", "react-router-dom": "6.3.0",
"react-select": "^5.8.0", "react-select": "^5.8.0",
@@ -1805,6 +1807,11 @@
"node": ">=8" "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": { "node_modules/autoprefixer": {
"version": "10.4.18", "version": "10.4.18",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz",
@@ -1842,6 +1849,16 @@
"postcss": "^8.1.0" "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": { "node_modules/babel-plugin-macros": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "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", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" "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": { "node_modules/commander": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@@ -2124,6 +2152,14 @@
"node": ">=0.10.0" "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": { "node_modules/didyoumean": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -2550,6 +2586,25 @@
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"dev": true "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": { "node_modules/foreground-child": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
@@ -2565,6 +2620,19 @@
"url": "https://github.com/sponsors/isaacs" "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": { "node_modules/formik": {
"version": "2.4.5", "version": "2.4.5",
"resolved": "https://registry.npmjs.org/formik/-/formik-2.4.5.tgz", "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.5.tgz",
@@ -3078,6 +3146,25 @@
"node": ">=8.6" "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": { "node_modules/minimatch": {
"version": "9.0.3", "version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "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", "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
"integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" "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": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "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", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" "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": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+1
View File
@@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^2.2.1", "@reduxjs/toolkit": "^2.2.1",
"axios": "^1.6.8",
"clsx": "2.1.0", "clsx": "2.1.0",
"formik": "2.4.5", "formik": "2.4.5",
"react": "^18.2.0", "react": "^18.2.0",
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

+20 -22
View File
@@ -3,20 +3,20 @@ import {Formik, Form} from 'formik'
import * as Yup from "yup"; import * as Yup from "yup";
type Props = { type Props = {
handleNextStep:()=>any handleNextStep:(value:{})=>any
} }
const initialValues = { const initialValues = {
amount: "", loan_amount: "",
duration: "", payment_month: "",
id: "", sales_agent: "",
}; };
// To get the validation schema // To get the validation schema
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
duration: Yup.string() payment_month: Yup.string()
.required("Required"), .required("Required"),
amount: Yup.string() loan_amount: Yup.string()
.required("Required") .required("Required")
.test("no-e", "Invalid", (value:any) => { .test("no-e", "Invalid", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) { if (value && /^[0-9]*$/.test(value) == false) {
@@ -24,16 +24,14 @@ const validationSchema = Yup.object().shape({
} }
return true; return true;
}), }),
id: Yup.string() sales_agent: Yup.string()
.required("Required")
}); });
export default function DashboardFormInit({handleNextStep}:Props) { export default function DashboardFormInit({handleNextStep}:Props) {
//FUNCTION TO HANDLE SUBMIT //FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => { const handleSubmit = (values:{}) => {
console.log(values) handleNextStep(values)
handleNextStep()
}; };
return ( return (
@@ -51,40 +49,40 @@ export default function DashboardFormInit({handleNextStep}:Props) {
<div className="mt-[3.25rem] flex flex-col gap-9"> <div className="mt-[3.25rem] flex flex-col gap-9">
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="amount" name="loan_amount"
label="How Much Do You Want To Apply For?" label="How Much Do You Want To Apply For?"
labelClass="font-bold text-[1.125rem]" labelClass="font-bold text-[1.125rem]"
input input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem] text-right" inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem] text-right"
placeholder="350,000" placeholder="350,000"
value={props.values.amount} value={props.values.loan_amount}
onChange={props.handleChange} onChange={props.handleChange}
error={(props.errors.amount && props.touched.amount) ? props.errors.amount : ''} error={(props.errors.loan_amount && props.touched.loan_amount) ? props.errors.loan_amount : ''}
/> />
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="duration" name="payment_month"
label="For How Many Months?" label="For How Many Months?"
labelClass="font-bold text-[1.125rem]" labelClass="font-bold text-[1.125rem]"
select={true} select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={duration} selectOptions={paymentMonth}
selectValue={props.values.duration} selectValue={props.values.payment_month}
onChange={props.handleChange} onChange={props.handleChange}
error={(props.errors.duration && props.touched.duration) ? props.errors.duration : ''} error={(props.errors.payment_month && props.touched.payment_month) ? props.errors.payment_month : ''}
/> />
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="id" name="sales_agent"
label="Direct sales agent ID ( Optional )" label="Direct sales agent ID ( Optional )"
labelClass="font-bold text-[1.125rem]" labelClass="font-bold text-[1.125rem]"
floatLabel='Enter agent ID' floatLabel='Enter agent ID'
input input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Agent ID" placeholder="Agent ID"
value={props.values.id} value={props.values.sales_agent}
onChange={props.handleChange} onChange={props.handleChange}
error={(props.errors.id && props.touched.id) ? props.errors.id : ''} error={(props.errors.sales_agent && props.touched.sales_agent) ? props.errors.sales_agent : ''}
/> />
<Button <Button
className="my-8 max-w-[25.875rem] btn-Y text-black w-full h-11" className="my-8 max-w-[25.875rem] btn-Y text-black w-full h-11"
@@ -106,7 +104,7 @@ interface SelectOption {
} }
const duration: SelectOption = { const paymentMonth: SelectOption = {
loading: false, loading: false,
data: [ data: [
{ value: "", label: "Please Select" }, { value: "", label: "Please Select" },
+6 -3
View File
@@ -10,12 +10,14 @@ interface DashboardHomeProps {}
const DashboardHome: FC<DashboardHomeProps> = () => { const DashboardHome: FC<DashboardHomeProps> = () => {
const [step, setStep] = React.useState(1); const [step, setStep] = React.useState(1);
const [applicationDetails, setApplicationDetails] = React.useState({});
const handleNextStep = () => { const handleNextStep = (values:{}={}) => {
if (step < 7) { if (step < 7) {
setStep(step + 1); setStep(step + 1);
} }
}; setApplicationDetails((prev:{}) => ({...prev, ...values}))
}
return ( return (
<div className="w-full"> <div className="w-full">
@@ -24,8 +26,9 @@ const DashboardHome: FC<DashboardHomeProps> = () => {
{step === 3 && <DashboardHomeDetail handleNextStep={handleNextStep} />} {step === 3 && <DashboardHomeDetail handleNextStep={handleNextStep} />}
{step === 4 && <DashboardHomeEmploymentInfo handleNextStep={handleNextStep} />} {step === 4 && <DashboardHomeEmploymentInfo handleNextStep={handleNextStep} />}
{step === 5 && <DashboardHomeRefereeInfo handleNextStep={handleNextStep} />} {step === 5 && <DashboardHomeRefereeInfo handleNextStep={handleNextStep} />}
{step === 6 && <DashboardHomeAttestation handleNextStep={handleNextStep} />} {step === 6 && <DashboardHomeAttestation handleNextStep={handleNextStep} applicationDetails={applicationDetails} />}
{step === 7 && <DashboardHomeIntro step={step} handleNextStep={handleNextStep} />} {step === 7 && <DashboardHomeIntro step={step} handleNextStep={handleNextStep} />}
{/* <DashboardHomeAttestation handleNextStep={handleNextStep} applicationDetails={applicationDetails} /> */}
</div> </div>
); );
}; };
@@ -1,6 +1,11 @@
import React, { FC } from "react"; import React, { FC, useState, useEffect } from "react";
import NairaBag from "../../assets/images/dashboard/naira-bag.png"; import NairaBag from "../../assets/images/dashboard/naira-bag.png";
import { Button } from "../"; import { Button, Icons } from "../";
import { useSelector } from "react-redux";
import PendingList from "../paginated-list/PendingList";
import { PendingTableList } from "../../core/models";
import { NewDateTimeFormatter } from "../../lib/NewDateTimeFormatter";
import { getUserPendingLoanList } from "../../core/apiRequest";
export interface DashBoardCardProps { export interface DashBoardCardProps {
title?: string; title?: string;
@@ -68,16 +73,40 @@ export const DashBoardCard: React.FC<DashBoardCardProps> = ({
}; };
interface DashboardHomeIntroProps { interface DashboardHomeIntroProps {
handleNextStep: any; handleNextStep:(value:{})=>any
step?:number|string step?:number|string
} }
const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({ handleNextStep, step }) => { const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({ handleNextStep, step }) => {
const { userDetails } = useSelector((state:any) => state?.userDetails); // CHECKS IF USER Details are avaliable
const [userLoanList, setUserLoanList] = useState<{loading:boolean, data:PendingTableList}>({loading: true, data:[]})
useEffect(()=>{
let token = localStorage.getItem('token')
let uid = localStorage.getItem('uid')
if(!token || !uid){
return
}
getUserPendingLoanList(uid).then(res => {
console.log('RES', res)
console.log('RES', userLoanList)
if(!res || !res.data.loans){
setUserLoanList({loading:false, data:[]})
return
}
setUserLoanList({loading:false, data:res?.data?.loans})
}).catch(err => {
console.log(err)
setUserLoanList({loading:false, data:[]})
})
},[])
return ( return (
<> <div className='w-full'>
{step == 1 ? {step == 1 ?
<> <>
<h1 className="font-bold my-5 text-2xl">Hello, Olanrewaju</h1> <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 "> <div className="group w-full lg:w-[27.8125rem] h-[12.75rem] mt-7 ">
<DashBoardCard <DashBoardCard
cardClass="bg-[#5C2684] relative" cardClass="bg-[#5C2684] relative"
@@ -89,13 +118,13 @@ const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({ handleNextStep, step
btnTextClass="w-[11.125rem] h-[2.8125rem] flex justify-center item-center btn-W text-[#FBB700]" btnTextClass="w-[11.125rem] h-[2.8125rem] flex justify-center item-center btn-W text-[#FBB700]"
image={NairaBag} image={NairaBag}
imgClass="translate-y-4 -rotate-6" imgClass="translate-y-4 -rotate-6"
onClick={handleNextStep} onClick={()=>handleNextStep({})}
/> />
</div> </div>
</> </>
: :
<> <>
<h1 className="font-bold my-5 text-2xl">Welcome Back, Olanrewaju</h1> <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 "> <div className="group w-full lg:w-[27.8125rem] h-[12.75rem] mt-7 ">
<DashBoardCard <DashBoardCard
cardClass="bg-[#5C2684] relative" cardClass="bg-[#5C2684] relative"
@@ -112,7 +141,50 @@ const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({ handleNextStep, step
</div> </div>
</> </>
} }
</> {userLoanList.loading ?
null
:
<div className='mt-5 w-full'>
<PendingList
data={userLoanList.data}
itemsPerPage={5}
tableTitle='Current Applications'
>
{(data:any)=>(
<div className="w-full p-4 rounded-lg shadow-lg bg-white overflow-x-auto min-h-[250px] max-h-[450px]">
<table className="text-[12px] sm:text-base w-full table-auto">
<thead>
<tr className='text-left border-b-2'>
<th className='px-1 py-4'>Date</th>
<th className='px-1 py-4 text-right'>Amount</th>
<th className='px-1 py-4 text-center min-w-[110px]'>Payment Term</th>
<th className='px-1 py-4 text-center'>Status</th>
<th className='px-1 py-4'>Action</th>
</tr>
</thead>
<tbody>
{data.map((item:any, index:any) =>(
<tr key={index || item} className='even:bg-slate-100'>
<td className='px-1 py-2'>{NewDateTimeFormatter(item?.added)}</td>
<td className='px-1 py-2 text-right'>{item?.loan_amount}</td>
<td className='px-1 py-2 text-center'>{item?.payment_month}</td>
<td className='px-1 py-2 text-center'>{item?.status}</td>
<td className='px-1 py-2'>
<button className='px-2 py-1 border-2 border-black flex gap-2 items-center'>
View
<Icons name='arrow-right' />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</PendingList>
</div>
}
</div>
); );
}; };
@@ -2,34 +2,49 @@ import { Button, InputCompOne, Stepper } from '../../shared/index';
import {Formik, Form} from 'formik' import {Formik, Form} from 'formik'
import * as Yup from "yup"; import * as Yup from "yup";
import { applyForLoan } from '../../../core/apiRequest';
// import { useNavigate } from "react-router-dom"; // import { useNavigate } from "react-router-dom";
// import { RouteHandler } from '../../../router/routes'; // import { RouteHandler } from '../../../router/routes';
type Props = { type Props = {
handleNextStep:()=>any handleNextStep:(value:{})=>any
applicationDetails: {}
} }
const initialValues = { const initialValues = {
account_number: "", account: "",
checked: false checked: false
}; };
// To get the validation schema // To get the validation schema
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
account_number: Yup.string() account: Yup.string()
.required("Required"), .required("Required"),
checked: Yup.bool() // use bool instead of boolean checked: Yup.bool() // use bool instead of boolean
.oneOf([true], "You must accept the terms and conditions") .oneOf([true], "You must accept the terms and conditions")
}); });
export default function DashboardHomeAttestation({handleNextStep}:Props) { export default function DashboardHomeAttestation({handleNextStep, applicationDetails}:Props) {
// let navigate = useNavigate(); // let navigate = useNavigate();
// const navigateToProfile = () => navigate(RouteHandler.dashboardProfile); // const navigateToProfile = () => navigate(RouteHandler.dashboardProfile);
//FUNCTION TO HANDLE SUBMIT //FUNCTION TO HANDLE LOAN APPLICATION
const handleSubmit = (values:any) => { const handleSubmit = (values:any) => {
console.log(values) delete values.checked
handleNextStep() applyForLoan({...applicationDetails, disbursement: values}).then(res=>{
console.log('APPLY FOR LOAN', res)
handleNextStep({disbursement: values})
console.log('ApplicationDetails', {...applicationDetails, disbursement: values})
}).catch(err=>{
console.log(err)
})
// applyForLoan(payload).then(res=>{
// console.log('APPLY FOR LOAN', res)
// // handleNextStep({disbursement: values})
// }).catch(err=>{
// console.log(err)
// })
}; };
return ( return (
@@ -50,15 +65,15 @@ export default function DashboardHomeAttestation({handleNextStep}:Props) {
<div className="flex items-center gap-[4.125rem]"> <div className="flex items-center gap-[4.125rem]">
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="account_number" name="account"
floatLabel="Disbursement account number" floatLabel="Disbursement account number"
// labelClass="font-bold text-[1.125rem]" // labelClass="font-bold text-[1.125rem]"
input input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="0102547896" placeholder="0102547896"
value={props.values.account_number} value={props.values.account}
onChange={props.handleChange} onChange={props.handleChange}
error={(props.errors.account_number && props.touched.account_number) ? props.errors.account_number : ''} error={(props.errors.account && props.touched.account) ? props.errors.account : ''}
/> />
</div> </div>
<div className='max-w-[25.875rem]'> <div className='max-w-[25.875rem]'>
@@ -87,3 +102,46 @@ export default function DashboardHomeAttestation({handleNextStep}:Props) {
</div> </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'}
// }
@@ -4,7 +4,7 @@ import {Formik, Form} from 'formik'
import * as Yup from "yup"; import * as Yup from "yup";
type Props = { type Props = {
handleNextStep:()=>any handleNextStep:(value:{})=>any
} }
const initialValues = { const initialValues = {
@@ -12,7 +12,8 @@ const initialValues = {
address: "", address: "",
marital_status: "", marital_status: "",
state: "", state: "",
email:"" email:"",
country:""
}; };
// To get the validation schema // To get the validation schema
@@ -28,14 +29,15 @@ const validationSchema = Yup.object().shape({
email: Yup.string() email: Yup.string()
.email("Invalid") .email("Invalid")
.required("Required"), .required("Required"),
country: Yup.string()
.required("Required"),
}); });
export default function DashboardHomeDetail({handleNextStep}:Props) { export default function DashboardHomeDetail({handleNextStep}:Props) {
//FUNCTION TO HANDLE SUBMIT //FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => { const handleSubmit = (values:any) => {
console.log(values) handleNextStep(values)
handleNextStep()
}; };
return ( return (
@@ -103,18 +105,32 @@ export default function DashboardHomeDetail({handleNextStep}:Props) {
error={(props.errors.state && props.touched.state) ? props.errors.state : ''} error={(props.errors.state && props.touched.state) ? props.errors.state : ''}
/> />
</div> </div>
<InputCompOne <div className="flex items-center gap-[4.125rem]">
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" <InputCompOne
name="email" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
label="Email address" name="email"
labelClass="font-bold text-[1.125rem]" label="Email address"
input labelClass="font-bold text-[1.125rem]"
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" input
placeholder="johndoe@gmail.com" inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
value={props.values.email} placeholder="johndoe@gmail.com"
onChange={props.handleChange} value={props.values.email}
error={(props.errors.email && props.touched.email) ? props.errors.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 <Button
className="my-8 max-w-[25.875rem] btn-Y text-black w-full h-11" className="my-8 max-w-[25.875rem] btn-Y text-black w-full h-11"
text="Next" text="Next"
@@ -163,4 +179,12 @@ const state: SelectOption = {
{ value: "imo", label: "Imo" }, { value: "imo", label: "Imo" },
{ value: "lagos", label: "Lagos" }, { value: "lagos", label: "Lagos" },
] ]
}
const country: SelectOption = {
loading: false,
data: [
{ value: "", label: "Please Select" },
{ value: "NG", label: "Nigeria" },
]
} }
@@ -4,36 +4,36 @@ import {Formik, Form} from 'formik'
import * as Yup from "yup"; import * as Yup from "yup";
type Props = { type Props = {
handleNextStep:()=>any handleNextStep:(value:{})=>any
} }
const initialValues = { const initialValues = {
job_title: "", job_title: "",
employer_name: "", name: "",
job_sector: "", sector: "",
industry: "", industry: "",
date_of_resumption: "", resumption_date: "",
employer_email:"", email:"",
annual_income: "", annual_income: "",
monthly_salary: "", monthly_salary: "",
salary_payment_date: "", salary_payment_date: "",
employee_id: "", employment_id: "",
qualification: "" highest_eductaion: ""
}; };
// To get the validation schema // To get the validation schema
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
job_title: Yup.string() job_title: Yup.string()
.required("Required"), .required("Required"),
employer_name: Yup.string() name: Yup.string()
.required("Required"), .required("Required"),
job_sector: Yup.string() sector: Yup.string()
.required("Required"), .required("Required"),
industry: Yup.string() industry: Yup.string()
.required("Required"), .required("Required"),
date_of_resumption: Yup.string() resumption_date: Yup.string()
.required("Required"), .required("Required"),
employer_email: Yup.string() email: Yup.string()
.email("Invalid") .email("Invalid")
.required("Required"), .required("Required"),
annual_income: Yup.string() annual_income: Yup.string()
@@ -54,9 +54,9 @@ const validationSchema = Yup.object().shape({
}), }),
salary_payment_date: Yup.string() salary_payment_date: Yup.string()
.required("Required"), .required("Required"),
employee_id: Yup.string() employment_id: Yup.string()
.required("Required"), .required("Required"),
qualification: Yup.string() highest_eductaion: Yup.string()
.required("Required"), .required("Required"),
}); });
@@ -65,8 +65,7 @@ export default function DashboardHomeEmploymentInfo({handleNextStep}:Props) {
//FUNCTION TO HANDLE SUBMIT //FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => { const handleSubmit = (values:any) => {
console.log(values) handleNextStep({employment: values})
handleNextStep()
}; };
@@ -99,29 +98,29 @@ export default function DashboardHomeEmploymentInfo({handleNextStep}:Props) {
/> />
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="employer_name" name="name"
floatLabel="Employer name" floatLabel="Employer name"
// labelClass="font-bold text-[1.125rem]" // labelClass="font-bold text-[1.125rem]"
input input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Mr. Mark John" placeholder="Mr. Mark John"
value={props.values.employer_name} value={props.values.name}
onChange={props.handleChange} onChange={props.handleChange}
error={(props.errors.employer_name && props.touched.employer_name) ? props.errors.employer_name : ''} error={(props.errors.name && props.touched.name) ? props.errors.name : ''}
/> />
</div> </div>
<div className="flex items-center gap-[4.125rem]"> <div className="flex items-center gap-[4.125rem]">
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="job_sector" name="sector"
floatLabel="Job Sector" floatLabel="Job Sector"
// labelClass="font-bold text-[1.125rem]" // labelClass="font-bold text-[1.125rem]"
select={true} select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={jobSector} selectOptions={jobSector}
selectValue={props.values.job_sector} selectValue={props.values.sector}
onChange={props.handleChange} onChange={props.handleChange}
error={(props.errors.job_sector && props.touched.job_sector) ? props.errors.job_sector : ''} error={(props.errors.sector && props.touched.sector) ? props.errors.sector : ''}
/> />
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
@@ -139,28 +138,28 @@ export default function DashboardHomeEmploymentInfo({handleNextStep}:Props) {
<div className="flex items-center gap-[4.125rem]"> <div className="flex items-center gap-[4.125rem]">
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="date_of_resumption" name="resumption_date"
floatLabel="Date of resumption" floatLabel="Date of resumption"
// labelClass="font-bold text-[1.125rem]" // labelClass="font-bold text-[1.125rem]"
input input
inputType='date' inputType='date'
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="12/12/2015" placeholder="12/12/2015"
value={props.values.date_of_resumption} value={props.values.resumption_date}
onChange={props.handleChange} onChange={props.handleChange}
error={(props.errors.date_of_resumption && props.touched.date_of_resumption) ? props.errors.date_of_resumption : ''} error={(props.errors.resumption_date && props.touched.resumption_date) ? props.errors.resumption_date : ''}
/> />
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="employer_email" name="email"
floatLabel="Employers official email" floatLabel="Employers official email"
// labelClass="font-bold text-[1.125rem]" // labelClass="font-bold text-[1.125rem]"
input input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="example@gmail.com" placeholder="example@gmail.com"
value={props.values.employer_email} value={props.values.email}
onChange={props.handleChange} onChange={props.handleChange}
error={(props.errors.employer_email && props.touched.employer_email) ? props.errors.employer_email : ''} error={(props.errors.email && props.touched.email) ? props.errors.email : ''}
/> />
</div> </div>
<div className="flex items-center gap-[4.125rem]"> <div className="flex items-center gap-[4.125rem]">
@@ -205,28 +204,28 @@ export default function DashboardHomeEmploymentInfo({handleNextStep}:Props) {
/> />
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="employee_id" name="employment_id"
floatLabel="Employee ID" floatLabel="Employee ID"
// labelClass="font-bold text-[1.125rem]" // labelClass="font-bold text-[1.125rem]"
input input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="LS/001/005" placeholder="LS/001/005"
value={props.values.employee_id} value={props.values.employment_id}
onChange={props.handleChange} onChange={props.handleChange}
error={(props.errors.employee_id && props.touched.employee_id) ? props.errors.employee_id : ''} error={(props.errors.employment_id && props.touched.employment_id) ? props.errors.employment_id : ''}
/> />
</div> </div>
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="qualification" name="highest_eductaion"
floatLabel="Highest level of education" floatLabel="Highest level of education"
// labelClass="font-bold text-[1.125rem]" // labelClass="font-bold text-[1.125rem]"
select={true} select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={qualification} selectOptions={highestEductaion}
selectValue={props.values.qualification} selectValue={props.values.highest_eductaion}
onChange={props.handleChange} onChange={props.handleChange}
error={(props.errors.qualification && props.touched.qualification) ? props.errors.qualification : ''} error={(props.errors.highest_eductaion && props.touched.highest_eductaion) ? props.errors.highest_eductaion : ''}
/> />
<Button <Button
className="my-8 max-w-[25.875rem] btn-Y text-black w-full h-11" className="my-8 max-w-[25.875rem] btn-Y text-black w-full h-11"
@@ -266,7 +265,7 @@ const industry: SelectOption = {
] ]
} }
const qualification: SelectOption = { const highestEductaion: SelectOption = {
loading: false, loading: false,
data: [ data: [
{ value: "", label: "Please Select" }, { value: "", label: "Please Select" },
@@ -4,18 +4,18 @@ import {Formik, Form} from 'formik'
import * as Yup from "yup"; import * as Yup from "yup";
type Props = { type Props = {
handleNextStep:()=>any handleNextStep:(value:{})=>any
} }
const initialValues = { const initialValues = {
ref_name: "", ref_name: "",
ref_email: "", ref_email: "",
ref_number: "", ref_phone_number: "",
ref_relationship: "", ref_relationship: "",
ref_bvn: "", ref_bvn: "",
ref_two_name: "", ref_two_name: "",
ref_two_email: "", ref_two_email: "",
ref_two_number: "", ref_two_phone_number: "",
ref_two_relationship: "", ref_two_relationship: "",
ref_two_bvn: "", ref_two_bvn: "",
}; };
@@ -27,7 +27,7 @@ const validationSchema = Yup.object().shape({
ref_email: Yup.string() ref_email: Yup.string()
.email("Invalid") .email("Invalid")
.required("Required"), .required("Required"),
ref_number: Yup.string() ref_phone_number: Yup.string()
.required("Required"), .required("Required"),
ref_relationship: Yup.string() ref_relationship: Yup.string()
.required("Required"), .required("Required"),
@@ -46,7 +46,7 @@ const validationSchema = Yup.object().shape({
ref_two_email: Yup.string() ref_two_email: Yup.string()
.email("Invalid") .email("Invalid")
.required("Required"), .required("Required"),
ref_two_number: Yup.string() ref_two_phone_number: Yup.string()
.required("Required"), .required("Required"),
ref_two_relationship: Yup.string() ref_two_relationship: Yup.string()
.required("Required"), .required("Required"),
@@ -66,8 +66,21 @@ export default function DashboardHomeRefereeInfo({handleNextStep}:Props) {
//FUNCTION TO HANDLE SUBMIT //FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => { const handleSubmit = (values:any) => {
console.log(values) let refOne = {
handleNextStep() 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]})
}; };
@@ -115,15 +128,15 @@ export default function DashboardHomeRefereeInfo({handleNextStep}:Props) {
/> />
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="ref_number" name="ref_phone_number"
floatLabel="Phone number" floatLabel="Phone number"
// labelClass="font-bold text-[1.125rem]" // labelClass="font-bold text-[1.125rem]"
input input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="07000000000" placeholder="07000000000"
value={props.values.ref_number} value={props.values.ref_phone_number}
onChange={props.handleChange} onChange={props.handleChange}
error={(props.errors.ref_number && props.touched.ref_number) ? props.errors.ref_number : ''} error={(props.errors.ref_phone_number && props.touched.ref_phone_number) ? props.errors.ref_phone_number : ''}
/> />
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
@@ -180,15 +193,15 @@ export default function DashboardHomeRefereeInfo({handleNextStep}:Props) {
/> />
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="ref_two_number" name="ref_two_phone_number"
floatLabel="Phone number" floatLabel="Phone number"
// labelClass="font-bold text-[1.125rem]" // labelClass="font-bold text-[1.125rem]"
input input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="07000000000" placeholder="07000000000"
value={props.values.ref_two_number} value={props.values.ref_two_phone_number}
onChange={props.handleChange} onChange={props.handleChange}
error={(props.errors.ref_two_number && props.touched.ref_two_number) ? props.errors.ref_two_number : ''} error={(props.errors.ref_two_phone_number && props.touched.ref_two_phone_number) ? props.errors.ref_two_phone_number : ''}
/> />
<InputCompOne <InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
+1 -1
View File
@@ -52,7 +52,7 @@ const Header: React.FC<HiddenMenuItems> = ({
<div className="flex flex-col-reverse lg:flex-col grow lg:grow-0 justify-between items-end"> <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"> <ul className="flex gap-0 lg:gap-[10px] items-center justify-end w-full flex-wrap">
{[ {[
{ text: "Open An Account", href: RouteHandler.getStarted }, { text: "Open An Account", href: RouteHandler.letsGetStarted },
{ {
text: "Internet Banking", text: "Internet Banking",
href: RouteHandler.businessBanking, href: RouteHandler.businessBanking,
+3 -1
View File
@@ -1,4 +1,4 @@
import { FaCaretDown } from "react-icons/fa"; import { FaCaretDown, FaCaretRight } from "react-icons/fa";
import dashIcon from "../../assets/images/dashboard/dashDefault.svg"; import dashIcon from "../../assets/images/dashboard/dashDefault.svg";
type Props = { type Props = {
@@ -111,6 +111,8 @@ export default function Icons({ name, fillColor, className }: Props) {
</svg> </svg>
) :name == 'arrow-down'? ) :name == 'arrow-down'?
<FaCaretDown className={`text-xl ${className && className}`} /> <FaCaretDown className={`text-xl ${className && className}`} />
:name == 'arrow-right'?
<FaCaretRight className={`text-xl ${className && className}`} />
:name == "dash-icon" ? ( :name == "dash-icon" ? (
<img src={dashIcon} alt="dash-icon" /> <img src={dashIcon} alt="dash-icon" />
) : null} ) : null}
+88 -49
View File
@@ -5,6 +5,12 @@ import { InputCompOne } from "..";
import {useNavigate} from 'react-router-dom' import {useNavigate} from 'react-router-dom'
import { RouteHandler } from "../../router/routes"; 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 // To get the validation schema
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
bvn: Yup.string() bvn: Yup.string()
@@ -18,7 +24,11 @@ const validationSchema = Yup.object().shape({
.min(11, "must be 11 digits") .min(11, "must be 11 digits")
.max(11, "must be 11 digits"), .max(11, "must be 11 digits"),
otp: Yup.string() otp: Yup.string()
.required("OTP is required") // .when('require_otp', {
// is: true,
// then: Yup.string().required("OTP is required")
// })
// .required("OTP is required")
.test("no-e", "Invalid number", (value:any) => { .test("no-e", "Invalid number", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) { if (value && /^[0-9]*$/.test(value) == false) {
return false; return false;
@@ -41,50 +51,80 @@ let initialValues = {
otp: '', otp: '',
}; };
type ValidBVN = {
verification_id:string
valid: undefined | boolean
}
const LetsGetStarted: React.FC = () => { const LetsGetStarted: React.FC = () => {
const dispatch = useDispatch()
const navigate = useNavigate() const navigate = useNavigate()
// const [pinValues, setPinValues] = React.useState({ // const [pinValues, setPinValues] = React.useState({
// bvn: "", // bvn: "",
// otp: "", // otp: "",
// }); // });
// const otpInputRef = React.useRef<HTMLInputElement>(null);
const [hideOTPComponent, setHideOTPComponent] = React.useState<boolean>(true); const [requestStatusBVN, setRequestStatusBVN] = React.useState<RequestStatus>({loading:false, status:undefined, message:''});
const firstInputRef = React.useRef<HTMLInputElement>(null);
const secondInputRef = React.useRef<HTMLInputElement>(null);
// const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const [requestStatusOTP, setRequestStatusOTP] = React.useState<RequestStatus>({loading:false, status:undefined, message:''});
// let { name, value } = e.target as HTMLInputElement;
// setPinValues((prev) => ({ ...prev, [name]: value })); const [bvnIsValid, setBvnIsValid] = React.useState<ValidBVN>({
// }; verification_id: '',
valid: undefined
});
const handleInput = (e: React.FormEvent<HTMLInputElement>) => {
let { name, value } = e.target as HTMLInputElement;
if (name === "bvn") { // e: React.FormEvent<HTMLInputElement>
const regex = /^[0-9]+$/; // let { value } = e.target as HTMLInputElement;
const bvnValidation = (values:any) => { // Function to Validate BVN
if (regex.test(value)) { let bvn = values.bvn
if (value?.length == 11) { setRequestStatusBVN({loading:true, status:false, message:''})
setHideOTPComponent(false); validateBVN({bvn}).then(res => {
// secondInputRef.current?.focus(); if(!res || !res.data.call_return){
} else setHideOTPComponent(true); setBvnIsValid({verification_id:'', valid: false})
} else { setRequestStatusBVN({loading:false, status:false, message:'unable to verify BVN'})
console.log("object not found"); 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) => { const handleSubmit = (values:any) => { // Function to VERIFY OTP AND LOGIN USER
console.log('values', values) setRequestStatusOTP({loading:true, status:false, message:''})
navigate(RouteHandler.dashboardHome, {replace:true}) // 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 ( return (
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
validationSchema={validationSchema} validationSchema={validationSchema}
onSubmit={handleSubmit} onSubmit={bvnIsValid.valid ? handleSubmit : bvnValidation}
> >
{(props:any) => ( {(props:any) => (
<Form className=""> <Form className="">
@@ -96,25 +136,25 @@ const LetsGetStarted: React.FC = () => {
</h1> </h1>
</div> </div>
<div className="mx-auto flex flex-col gap-8 max-w-[31.625rem] "> <div className="mx-auto flex flex-col gap-8 max-w-[31.625rem] ">
<InputCompOne <div className='w-full'>
parentClass="flex flex-col gap-2" <InputCompOne
label="Enter Your BVN " parentClass="flex flex-col gap-2"
name="bvn" label="Enter Your BVN "
parentInputClass="w-full" name="bvn"
labelSpan="( To get your BVN, dial *565*0# )" parentInputClass="w-full"
labelSpanClass="text-[13px] text-[#5a5a5a] font-semibold" labelSpan="( To get your BVN, dial *565*0# )"
placeholder="Enter your BVN" labelSpanClass="text-[13px] text-[#5a5a5a] font-semibold"
labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#282828] mb-[2px] flex item-center gap-[4px]" placeholder="Enter your BVN"
input labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#282828] mb-[2px] flex item-center gap-[4px]"
inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4" input
value={props.values.bvn} inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4"
onChange={props.handleChange} value={props.values.bvn}
onInput={handleInput} onChange={props.handleChange}
ref={firstInputRef} error={(props.errors.bvn && props.touched.bvn) && props.errors.bvn}
maxLength={11} />
error={(props.errors.bvn && props.touched.bvn) && props.errors.bvn} <p className={`p-2 ${!requestStatusBVN.status ? 'text-red-500' : 'text-emerald-500'}`}>{requestStatusBVN.loading ? 'verifying...' : requestStatusBVN.message}</p>
/> </div>
{!hideOTPComponent && ( {bvnIsValid.valid && (
<InputCompOne <InputCompOne
parentClass="flex flex-col gap-2" parentClass="flex flex-col gap-2"
label="Enter OTP " label="Enter OTP "
@@ -128,21 +168,20 @@ const LetsGetStarted: React.FC = () => {
inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4" inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4"
value={props.values.otp} value={props.values.otp}
onChange={props.handleChange} onChange={props.handleChange}
onInput={handleInput}
ref={secondInputRef}
maxLength={11}
error={(props.errors.otp && props.touched.otp) && props.errors.otp} error={(props.errors.otp && props.touched.otp) && props.errors.otp}
/> />
)} )}
<button <button
type='submit' 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" 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={!props.values.otp} disabled={requestStatusBVN.loading || (!props.values.otp && bvnIsValid.valid)}
> >
Enter Enter
</button> </button>
<p className={`p-2 ${!requestStatusOTP.status ? 'text-red-500' : 'text-emerald-500'}`}>{requestStatusOTP.message}</p>
{hideOTPComponent ? ( {bvnIsValid.valid || bvnIsValid.valid == undefined ? (
<p className="text-[#5C2684] mt-[1.5625rem] w-fit"> <p className="text-[#5C2684] mt-[1.5625rem] w-fit">
***Every personal information attached to your BVN is safe and ***Every personal information attached to your BVN is safe and
secure. It is only important for us to verify your information and secure. It is only important for us to verify your information and
@@ -0,0 +1,134 @@
import { ReactNode, useEffect, useState } from "react";
import { PendingTableList } from "../../core/models";
type PaginatedListProps = {
data: PendingTableList,
itemsPerPage?: number,
filterItem?: string[],
tableTitle?: string,
titleClass?:string,
children: (data:PendingTableList) => ReactNode;
}
export default function PendingList({
data,
itemsPerPage = 5,
filterItem,
tableTitle,
titleClass,
children,
}:PaginatedListProps) {
const [searchTerm, setSearchTerm] = useState("");
const [filteredData, setFilteredData] = useState(data);
const [currentPage, setCurrentPage] = useState(0);
const [newData, setNewData] = useState<any>([]);
const numberOfSelection = itemsPerPage;
const handlePrev = () => {
if (currentPage != 0) {
setCurrentPage((prev) => prev - numberOfSelection);
}
};
const handleNext = () => {
if (currentPage < data.length) {
setCurrentPage((prev) => prev + numberOfSelection);
}
};
const handleSearch = ({ target: { value } }:{target: {value:string}}, name:string) => {
setSearchTerm(value);
let newFilteredData:any = data.filter((item:any) =>
item[name].toLowerCase().startsWith(value.toLowerCase())
);
setFilteredData(newFilteredData);
setCurrentPage(0);
};
useEffect(() => {
setNewData(
filteredData?.slice(currentPage, numberOfSelection + currentPage)
);
}, [currentPage, filteredData]);
useEffect(()=>{
setCurrentPage(0)
},[itemsPerPage])
return (
<div className="w-full">
<h1 className={`text-2xl mb-5 font-semibold ${titleClass && titleClass}`}>{tableTitle}</h1>
{data.length > 0 && filterItem && (
<div className="mb-10 flex justify-end items-center gap-2">
{filterItem.map((item, index) => (
<label
key={index}
className="flex flex-col sm:flex-row items-center gap-2 text-slate-600 dark:text-slate-100 transition-all duration-500"
>
Search by {item[0].toUpperCase() + item.slice(1)}
<input
name={item}
type="text"
className="py-1 px-2 text-sm min-w-[100px] text-black dark:text-white bg-white dark:bg-slate-800 rounded-full border-0 outline-none ring-1 ring-slate-300 dark:ring-white transition-all duration-500"
value={searchTerm}
onChange={(e) => {
handleSearch(e, item);
}}
/>
</label>
))}
</div>
)}
{children(newData)}
{/* show prev and next button if data exist */}
{(data.length > 0 && data.length > itemsPerPage) && (
<div className="mt-5 md:mt-10 w-full flex gap-4 justify-center items-center">
<button
onClick={handlePrev}
className={`w-6 h-6 md:w-12 md:h-12 text-sm md:text-lg rounded-full flex justify-center items-center transition-all duration-300 ${
currentPage == 0
? "text-slate-400 border-slate-400 dark:text-slate-400 dark:border-slate-400 pointer-events-none"
: "text-slate-600 border-slate-600 dark:text-white dark:border-white"
}`}
>
&lt;
</button>
{data.length && data.map((item, index)=>{
item = item
if(index%itemsPerPage == 0 && index >= currentPage && index <= currentPage+itemsPerPage){
return (
<button
key={index}
onClick={handleNext}
className={`w-6 h-6 md:w-12 md:h-12 text-sm md:text-lg rounded-full flex justify-center items-center border transition-all duration-300 ${
currentPage != index
? "text-slate-400 border-slate-400 dark:text-slate-400 dark:border-slate-400"
: "text-slate-600 border-slate-600 dark:text-white dark:border-white pointer-events-none"
}`}
>
{index/itemsPerPage +1}
</button>
)
}
})}
<button
onClick={handleNext}
className={`w-6 h-6 md:w-12 md:h-12 text-sm md:text-lg rounded-full flex justify-center items-center transition-all duration-300 ${
currentPage + numberOfSelection >= data.length
? "text-slate-400 border-slate-400 dark:text-slate-400 dark:border-slate-400 pointer-events-none"
: "text-slate-600 border-slate-600 dark:text-white dark:border-white"
}`}
>
&gt;
</button>
</div>
)}
</div>
);
}
+1 -1
View File
@@ -9,7 +9,7 @@ const Stepper: React.FC<StepperProps> = ({ step = 0 }) => {
return ( return (
<div className="flex justify-between items-center gap-5"> <div className="flex justify-between items-center gap-5">
{[...Array(6)].map((_, index) => ( {[...Array(5)].map((_, index) => (
<div <div
key={index} key={index}
className={`w-[1.875rem] border-[.1875rem] rounded-sm ${(step === index className={`w-[1.875rem] border-[.1875rem] rounded-sm ${(step === index
+43
View File
@@ -0,0 +1,43 @@
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)
}
+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
);
});
}
+27
View File
@@ -0,0 +1,27 @@
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
}[];
+50 -39
View File
@@ -1,27 +1,27 @@
import { useState } from "react"; import { useState } from 'react';
import { Link, useLocation, useNavigate } from "react-router-dom"; import { Link, useLocation } from 'react-router-dom';
import Logo from "../../assets/icons/logo.svg"; import Logo from '../../assets/icons/logo.svg';
import { Icons } from "../../components"; import { Icons } from '../../components';
import { contactDetails } from '../../utils/data';
type Props = { type Props = {
asideDisplay?: () => void; asideDisplay?: () => void;
logoutUser: () => void;
}; };
export default function Aside({ asideDisplay }: Props) { export default function Aside({ asideDisplay, logoutUser }: Props) {
const { pathname } = useLocation(); const { pathname } = useLocation();
const navigate = useNavigate();
const [openNestedLink, setOpenNestedLink] = useState<{ name: string | null }>( const [openNestedLink, setOpenNestedLink] = useState<{ name: string | null }>(
{ name: "" } { name: '' }
); );
const handleOpenNestedLink = (e: any) => { const handleOpenNestedLink = (e: any) => {
if (!e || !e.target) { if (!e || !e.target) {
return setOpenNestedLink({ name: "" }); return setOpenNestedLink({ name: '' });
} }
if (openNestedLink.name && openNestedLink.name == e.target.name) { if (openNestedLink.name && openNestedLink.name == e.target.name) {
setOpenNestedLink({ name: "" }); setOpenNestedLink({ name: '' });
} else { } else {
setOpenNestedLink({ name: e.target.name }); setOpenNestedLink({ name: e.target.name });
} }
@@ -46,8 +46,8 @@ export default function Aside({ asideDisplay }: Props) {
onClick={(e) => handleOpenNestedLink(e)} 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 ${ 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) allNestedLinks.includes(pathname)
? " text-[#5C2684]" ? ' text-[#5C2684]'
: " text-[#585858]" : ' text-[#585858]'
}`} }`}
> >
{link.name} {link.name}
@@ -61,8 +61,8 @@ export default function Aside({ asideDisplay }: Props) {
<div <div
className={`transition-all duration-300 w-full z-1 ${ className={`transition-all duration-300 w-full z-1 ${
openNestedLink.name == link.name openNestedLink.name == link.name
? "relative top-0" ? 'relative top-0'
: "absolute -top-[500px]" : 'absolute -top-[500px]'
}`} }`}
> >
{link.nestedLink.map((nextLink, index) => ( {link.nestedLink.map((nextLink, index) => (
@@ -71,17 +71,17 @@ export default function Aside({ asideDisplay }: Props) {
asideDisplay && asideDisplay(); asideDisplay && asideDisplay();
}} }}
key={index} key={index}
to={nextLink.link ? nextLink.link : "#"} to={nextLink.link ? nextLink.link : '#'}
className={`w-full my-1 flex items-center gap-2 py-2 pl-5 text-base font-medium ${ className={`w-full my-1 flex items-center gap-2 py-2 pl-5 text-base font-medium ${
pathname == nextLink.link pathname == nextLink.link
? " text-[#5C2684]" ? ' text-[#5C2684]'
: "text-[#585858]" : 'text-[#585858]'
}`} }`}
> >
<Icons <Icons
name={nextLink.icon} name={nextLink.icon}
fillColor={`${ fillColor={`${
pathname == nextLink.link ? "#5C2684" : "#585858" pathname == nextLink.link ? '#5C2684' : '#585858'
}`} }`}
/> />
{nextLink.name} {nextLink.name}
@@ -97,14 +97,14 @@ export default function Aside({ asideDisplay }: Props) {
asideDisplay && asideDisplay(); asideDisplay && asideDisplay();
}} }}
key={index} key={index}
to={link.link ? link.link : "#"} 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 ${ 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]" pathname == link.link ? 'text-[#5C2684]' : 'text-[#585858]'
}`} }`}
> >
<Icons <Icons
name={link.icon} name={link.icon}
fillColor={`${pathname == link.link ? "#5C2684" : "#585858"}`} fillColor={`${pathname == link.link ? '#5C2684' : '#585858'}`}
/> />
{link.name} {link.name}
</Link> </Link>
@@ -115,24 +115,30 @@ export default function Aside({ asideDisplay }: Props) {
<div className="w-full flex justify-center items-center flex-col gap-3"> <div className="w-full flex justify-center items-center flex-col gap-3">
<button <button
className="py-3 px-6 bg-red-100 text-red-500 font-medium rounded-md w-full" className="py-3 px-6 bg-red-100 text-red-500 font-medium rounded-md w-full"
onClick={() => navigate("/login", { replace: true })} onClick={() => logoutUser()}
> >
Log out Log out
</button> </button>
<div className="flex flex-col gap-[.4375rem] text-[.75rem]"> <div className="flex flex-col gap-[.4375rem] text-[.75rem]">
<p className="font-extrabold tracking-[3%] text-[#FBB700] underline"> <p className="font-extrabold tracking-[3%] text-[#FBB700] underline">
For more enquiries and support For more enquiries and support
</p> </p>
<p className="font-extrabold tracking-[3%] text-[#5A5A5A]"> {/* <p className="font-extrabold tracking-[3%] text-[#5A5A5A]">
Call: 09099000000 Call: 09099000000
</p> </p>
<p className="font-extrabold tracking-[3%] text-[#5A5A5A]"> <p className="font-extrabold tracking-[3%] text-[#5A5A5A]">
Email: fcmbloan@support.com Email: fcmbloan@support.com
</p> </p> */}
{contactDetails.map(({ name, value }) => (
<p
key={name}
className="font-extrabold tracking-[3%] text-[#5A5A5A]"
>
{name}: {value}
</p>
))}
</div> </div>
</div> </div>
</div> </div>
); );
@@ -150,29 +156,34 @@ type AsideLinksType = {
}[]; }[];
const asideLinks: AsideLinksType = [ const asideLinks: AsideLinksType = [
{ name: "Dashboard", link: "/dashboard/home", icon: "dash-icon", nestedLink: [] },
{ {
name: "Your Profile", name: 'Dashboard',
link: "/dashboard/profile", link: '/dashboard/home',
icon: "dash-icon", icon: 'dash-icon',
nestedLink: [], nestedLink: [],
}, },
{ {
name: "Employment Details", name: 'Your Profile',
link: "/dashboard/verification", link: '/dashboard/profile',
icon: "dash-icon", icon: 'dash-icon',
nestedLink: [], nestedLink: [],
}, },
{ {
name: "Reference Details", name: 'Employment Details',
link: "/dashboard/payments", link: '/dashboard/verification',
icon: "dash-icon", icon: 'dash-icon',
nestedLink: [], nestedLink: [],
}, },
{ {
name: "Agreements", name: 'Reference Details',
link: "/dashboard/legals", link: '/dashboard/payments',
icon: "dash-icon", icon: 'dash-icon',
nestedLink: [],
},
{
name: 'Agreements',
link: '/dashboard/legals',
icon: 'dash-icon',
nestedLink: [], nestedLink: [],
}, },
// {name: 'Nested Link', icon: 'home', nestedLink:[ // {name: 'Nested Link', icon: 'home', nestedLink:[
+54 -4
View File
@@ -1,11 +1,61 @@
import {useState, useEffect} from 'react'
import DashboardLayout from "./DashboardLayout"; import DashboardLayout from "./DashboardLayout";
import { Outlet } from "react-router-dom"; 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() { 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 ( return (
<DashboardLayout> <>
<Outlet /> {loading && !Object.keys(userDetails).length ?
</DashboardLayout> <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>
}
</>
) )
} }
@@ -1,8 +1,12 @@
import { ReactNode, useState, useEffect } from "react"; import { ReactNode, useState, useEffect } from "react";
import { RouteHandler } from "../../router/routes";
import { useNavigate } from "react-router-dom";
import Aside from "./Aside"; import Aside from "./Aside";
export default function DashboardLayout({ children }: { children: ReactNode }) { export default function DashboardLayout({ children }: { children: ReactNode }) {
const navigate = useNavigate();
const [showAside, setShowAside] = useState<boolean>(false); const [showAside, setShowAside] = useState<boolean>(false);
const asideDisplay = (): void => { const asideDisplay = (): void => {
setShowAside((prev) => !prev); setShowAside((prev) => !prev);
@@ -30,17 +34,22 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
// return child; // return child;
// }); // });
const logoutUser = () => {
localStorage.clear()
navigate(RouteHandler.letsGetStarted, {replace:true})
}
return ( return (
<div className="w-full max-w-[2000px] mx-auto h-screen flex bg-[#020202] text-black"> <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 className="max-w-[18.75rem] w-full bg-white hidden md:block border-r-2 border-[#E6E6E6]">
<Aside /> <Aside logoutUser={logoutUser} />
</aside> </aside>
<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 ${ 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%]" showAside ? "left-0" : "-left-[200%]"
}`} }`}
> >
<Aside asideDisplay={asideDisplay} /> <Aside logoutUser={logoutUser} asideDisplay={asideDisplay} />
</aside> </aside>
<main className="dash-bg-image bg-[#F9F9F9] relative w-full overflow-y-auto overflow-x-hidden"> <main className="dash-bg-image bg-[#F9F9F9] relative w-full overflow-y-auto overflow-x-hidden">
@@ -74,8 +83,8 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
</div> </div>
</div> </div>
</header> </header>
<div className="flex p-5 relative"> <div className="flex p-2 md:p-5 relative">
<div className="w-full p-5">{children}</div> <div className="w-full p-2 md:p-5">{children}</div>
</div> </div>
</main> </main>
</div> </div>
+18
View File
@@ -0,0 +1,18 @@
export function NewDateTimeFormatter(isoDateString:any, addHour = true) {
const date = new Date(isoDateString);
if (addHour) {
date.setTime(date.getTime() + 1 * 60 * 60 * 1000);
}
const formattedDate = date.toLocaleDateString("en-US", {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
// second: "2-digit",
hour12: true,
timeZone: "UTC",
});
return formattedDate;
}
+6 -1
View File
@@ -4,10 +4,15 @@ import { BrowserRouter } from "react-router-dom";
import App from "./App.tsx"; import App from "./App.tsx";
import "./index.css"; import "./index.css";
import { Provider } from "react-redux";
import store from "./store/store";
ReactDOM.createRoot(document.getElementById("root")!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode> <React.StrictMode>
<BrowserRouter> <BrowserRouter>
<App /> <Provider store={store}>
<App />
</Provider>
</BrowserRouter> </BrowserRouter>
</React.StrictMode> </React.StrictMode>
); );
+20
View File
@@ -0,0 +1,20 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
userDetails: {},
};
export const userSlice = createSlice({
name: "userDetails",
initialState,
reducers: {
updateUserDetails: (state, action) => {
state.userDetails = { ...action.payload };
},
},
});
// Action creators are generated for each case reducer function
export const { updateUserDetails } = userSlice.actions;
export default userSlice.reducer;
+9
View File
@@ -0,0 +1,9 @@
import { configureStore } from "@reduxjs/toolkit";
import userDetailReducer from "./UserDetails";
export default configureStore({
reducer: {
userDetails: userDetailReducer,
},
});
+291 -286
View File
@@ -1,556 +1,561 @@
import FBook from "../assets/icons/facebook.svg"; import FBook from '../assets/icons/facebook.svg';
import Twitter from "../assets/icons/twitter.svg"; import Twitter from '../assets/icons/twitter.svg';
import Instagram from "../assets/icons/instagram.svg"; import Instagram from '../assets/icons/instagram.svg';
import FBookWhite from "../assets/images/socials/facebook.svg"; import FBookWhite from '../assets/images/socials/facebook.svg';
import LinkedInWhite from "../assets/images/socials/linkedin.svg"; import LinkedInWhite from '../assets/images/socials/linkedin.svg';
import XWhite from "../assets/images/socials/twitterx.svg"; import XWhite from '../assets/images/socials/twitterx.svg';
import WhatsappWhite from "../assets/images/socials/whatsapp.svg"; import WhatsappWhite from '../assets/images/socials/whatsapp.svg';
import YoutubeWhite from "../assets/images/socials/youtube.svg"; import YoutubeWhite from '../assets/images/socials/youtube.svg';
import InstagramWhite from "../assets/images/socials/instagram.svg"; import InstagramWhite from '../assets/images/socials/instagram.svg';
import { RouteHandler } from "../router/routes"; import { RouteHandler } from '../router/routes';
let process = import.meta.env; let process = import.meta.env;
export const top_header_data = [ export const top_header_data = [
{ id: 1, name: "HOME", href: RouteHandler.homepage }, { id: 1, name: 'HOME', href: RouteHandler.homepage },
{ id: 2, name: "PERSONAL", href: RouteHandler.personalBanking }, { id: 2, name: 'PERSONAL', href: RouteHandler.personalBanking },
{ id: 3, name: "BUSINESS", href: RouteHandler.businessBanking }, { id: 3, name: 'BUSINESS', href: RouteHandler.businessBanking },
{ id: 4, name: "CORPORATE", href: RouteHandler.cooperateBanking }, { id: 4, name: 'CORPORATE', href: RouteHandler.cooperateBanking },
]; ];
export const lowerMenuItems = [ export const lowerMenuItems = [
{ {
id: 1, id: 1,
name: "PERSONAL BANKING", name: 'PERSONAL BANKING',
linkPath: "/personal-banking", linkPath: '/personal-banking',
}, },
{ {
id: 2, id: 2,
name: "BUSINESS BANKING", name: 'BUSINESS BANKING',
linkPath: "/business-banking", linkPath: '/business-banking',
}, },
{ {
id: 3, id: 3,
name: "CORPORATE BANKING", name: 'CORPORATE BANKING',
linkPath: "/cooperate-banking", linkPath: '/cooperate-banking',
}, },
{ {
id: 4, id: 4,
name: "GROUP & SUBSIDIARIES", name: 'GROUP & SUBSIDIARIES',
linkPath: "", linkPath: '',
}, },
{ {
id: 5, id: 5,
name: "ABOUT US", name: 'ABOUT US',
linkPath: "/about-us", linkPath: '/about-us',
}, },
{ {
id: 6, id: 6,
name: "MY BANK AND I", name: 'MY BANK AND I',
linkPath: "", linkPath: '',
}, },
]; ];
export const _lowerMenuItems = [ export const _lowerMenuItems = [
{ {
name: "PERSONAL BANKING", name: 'PERSONAL BANKING',
linkPath: "/personal-banking", linkPath: '/personal-banking',
subItems: [ subItems: [
{ {
name: "CURRENT ACCOUNTS", name: 'CURRENT ACCOUNTS',
linkPath: "/current-accounts", linkPath: '/current-accounts',
subItems: [ subItems: [
{ {
name: "CLASSIC CURRENT ACCOUNT", name: 'CLASSIC CURRENT ACCOUNT',
linkPath: "/personal-classic-current-account", linkPath: '/personal-classic-current-account',
}, },
{ {
name: "PREMIUM CURRENT ACCOUNT", name: 'PREMIUM CURRENT ACCOUNT',
linkPath: "/premium-current-account", linkPath: '/premium-current-account',
}, },
{ {
name: "PERSONAL BUSINESS ACCOUNT", name: 'PERSONAL BUSINESS ACCOUNT',
linkPath: "/personal-business-account", linkPath: '/personal-business-account',
}, },
{ {
name: "DOMICILIARY CURRENT ACCOUNT", name: 'DOMICILIARY CURRENT ACCOUNT',
linkPath: "/domiciliary-current-account", linkPath: '/domiciliary-current-account',
}, },
], ],
}, },
{ {
name: "SAVINGS ACCOUNTS", name: 'SAVINGS ACCOUNTS',
linkPath: "/savings-accounts", linkPath: '/savings-accounts',
subItems: [ subItems: [
{ name: "FCMB EASY ACCOUNT", linkPath: "/easy-account" }, { name: 'FCMB EASY ACCOUNT', linkPath: '/easy-account' },
{ name: "KIDS (0-17 YRS)", linkPath: "/kids" }, { name: 'KIDS (0-17 YRS)', linkPath: '/kids' },
{ name: "FLEXX (18-30 YRS)", linkPath: "/flexx-account" }, { name: 'FLEXX (18-30 YRS)', linkPath: '/flexx-account' },
{ {
name: "CLASSIC SAVINGS ACCOUNT", name: 'CLASSIC SAVINGS ACCOUNT',
linkPath: "/classic-savings-account", linkPath: '/classic-savings-account',
}, },
{ {
name: "PREMIUM SAVINGS ACCOUNT", name: 'PREMIUM SAVINGS ACCOUNT',
linkPath: "/fcmb-premium-savings-account", linkPath: '/fcmb-premium-savings-account',
}, },
{ {
name: "FCMB SALARY SAVINGS ACCOUNT", name: 'FCMB SALARY SAVINGS ACCOUNT',
linkPath: "/fcmb-salary-savings-account", linkPath: '/fcmb-salary-savings-account',
}, },
{ {
name: "DOMICILIARY SAVINGS ACCOUNT", name: 'DOMICILIARY SAVINGS ACCOUNT',
linkPath: "/domiciliary-savings-account", linkPath: '/domiciliary-savings-account',
}, },
], ],
}, },
{ {
name: "INVESTMENTS", name: 'INVESTMENTS',
linkPath: "/investment-accounts", linkPath: '/investment-accounts',
subItems: [ subItems: [
{ name: "FIXED DEPOSITS", linkPath: "/fixed-deposits" }, { name: 'FIXED DEPOSITS', linkPath: '/fixed-deposits' },
{ name: "GRO (App)", linkPath: "/GRO" }, { name: 'GRO (App)', linkPath: '/GRO' },
{ name: "GRO (Web)", linkPath: "https://www.investwithgro.com/" }, { name: 'GRO (Web)', linkPath: 'https://www.investwithgro.com/' },
{ {
name: "I-NEST SAVING", name: 'I-NEST SAVING',
linkPath: "https://i-nest.fcmb.com/#/welcome", linkPath: 'https://i-nest.fcmb.com/#/welcome',
}, },
{ name: "CALL DEPOSITS", linkPath: "/call-deposits" }, { name: 'CALL DEPOSITS', linkPath: '/call-deposits' },
{ {
name: "EDUCATION INVESTMENT PLAN", name: 'EDUCATION INVESTMENT PLAN',
linkPath: "/education-investment", linkPath: '/education-investment',
}, },
{ {
name: "MUTUAL FUNDS", name: 'MUTUAL FUNDS',
linkPath: "https://www.fcmbassetmanagement.com/mutual-funds/", linkPath: 'https://www.fcmbassetmanagement.com/mutual-funds/',
}, },
], ],
}, },
{ {
name: "LOANS AND CREDIT CARDS", name: 'LOANS AND CREDIT CARDS',
linkPath: "/loans-and-credit-cards", linkPath: '/loans-and-credit-cards',
subItems: [ subItems: [
{ name: "FASTCASH", linkPath: "/fastcash" }, { name: 'FASTCASH', linkPath: '/fastcash' },
{ name: "SALARY PLUS LOAN", linkPath: "/salary-plus-loan" }, { name: 'SALARY PLUS LOAN', linkPath: '/salary-plus-loan' },
{ name: "PREMIUM SALARY LOAN", linkPath: "/premium-salary-loan" }, { name: 'PREMIUM SALARY LOAN', linkPath: '/premium-salary-loan' },
{ name: "AUTO LOAN", linkPath: "/auto-loan" }, { name: 'AUTO LOAN', linkPath: '/auto-loan' },
{ name: "AIRTIME LOAN", linkPath: "/airtime-loan" }, { name: 'AIRTIME LOAN', linkPath: '/airtime-loan' },
{ name: "CREDIT CARDS", linkPath: "/credit-cards" }, { name: 'CREDIT CARDS', linkPath: '/credit-cards' },
{ {
name: "EASYLIFT LOAN", name: 'EASYLIFT LOAN',
linkPath: "https://easyliftloanform.fcmb.com", linkPath: 'https://easyliftloanform.fcmb.com',
}, },
{ name: "MORTGAGE", linkPath: "http://mortgage.fcmb.com/" }, { name: 'MORTGAGE', linkPath: 'http://mortgage.fcmb.com/' },
], ],
}, },
{ {
name: "NON-RESIDENT NIGERIAN (NRN) BANKING", name: 'NON-RESIDENT NIGERIAN (NRN) BANKING',
linkPath: "https://www.fcmb.com/non-resident-nigerian", linkPath: 'https://www.fcmb.com/non-resident-nigerian',
}, },
{ {
name: "WAYS TO BANK", name: 'WAYS TO BANK',
linkPath: "/ways-to-bank", linkPath: '/ways-to-bank',
subItems: [ subItems: [
{ name: "FCMB MOBILE", linkPath: "/fcmb-mobile" }, { name: 'FCMB MOBILE', linkPath: '/fcmb-mobile' },
{ {
name: "RETAIL INTERNET BANKING", name: 'RETAIL INTERNET BANKING',
linkPath: "https://ibank.fcmb.com/", linkPath: 'https://ibank.fcmb.com/',
}, },
{ name: "USSD BANKING", linkPath: "/ussd" }, { name: 'USSD BANKING', linkPath: '/ussd' },
{ name: "OUR BRANCH NETWORK", linkPath: "/branch-network" }, { name: 'OUR BRANCH NETWORK', linkPath: '/branch-network' },
{ name: "OUR ATM NETWORK", linkPath: "/atm-network" }, { name: 'OUR ATM NETWORK', linkPath: '/atm-network' },
{ name: "FLASHMECASH", linkPath: "/flashme-cash" }, { name: 'FLASHMECASH', linkPath: '/flashme-cash' },
{ name: "OUR CARDS", linkPath: "/our-cards" }, { name: 'OUR CARDS', linkPath: '/our-cards' },
{ name: "FCMB ELECTRONIC CHANNELS", linkPath: "/e-channels" }, { name: 'FCMB ELECTRONIC CHANNELS', linkPath: '/e-channels' },
{ name: "AGENT BANKING", linkPath: "/agent-banking" }, { name: 'AGENT BANKING', linkPath: '/agent-banking' },
{ {
name: "FCMB SECURE COMMUNICATION", name: 'FCMB SECURE COMMUNICATION',
linkPath: "/secure-communication", linkPath: '/secure-communication',
}, },
{ name: "TEMI", linkPath: "/temi" }, { name: 'TEMI', linkPath: '/temi' },
], ],
}, },
{ name: "BANCASSURANCE", linkPath: "/bancassurance" }, { name: 'BANCASSURANCE', linkPath: '/bancassurance' },
], ],
}, },
{ {
name: "BUSINESS BANKING", name: 'BUSINESS BANKING',
linkPath: "/business-banking", linkPath: '/business-banking',
subItems: [ subItems: [
{ {
name: "PROPOSITIONS", name: 'PROPOSITIONS',
linkPath: "", linkPath: '',
subItems: [ subItems: [
{ name: "SHEVENTURES", linkPath: "/she-ventures" }, { name: 'SHEVENTURES', linkPath: '/she-ventures' },
{ name: "BUSINESS ZONE", linkPath: "https://businesszone.fcmb.com/" }, { name: 'BUSINESS ZONE', linkPath: 'https://businesszone.fcmb.com/' },
{ {
name: "FOOD BUSINESS SUPPORT", name: 'FOOD BUSINESS SUPPORT',
linkPath: "https://www.fcmb.com/food-business-support/", linkPath: 'https://www.fcmb.com/food-business-support/',
}, },
{ name: "SCHOOL BUSINESS SUPPORT", linkPath: "/school-support" }, { name: 'SCHOOL BUSINESS SUPPORT', linkPath: '/school-support' },
], ],
}, },
{ {
name: "CURRENT ACCOUNTS", name: 'CURRENT ACCOUNTS',
linkPath: "/business-current-accounts", linkPath: '/business-current-accounts',
subItems: [ subItems: [
{ {
name: "CORPORATE CURRENT ACCOUNT", name: 'CORPORATE CURRENT ACCOUNT',
linkPath: "/corporate-current-account", linkPath: '/corporate-current-account',
}, },
{ name: "FCMB BUSINESS ACCOUNT", linkPath: "/fcmb-business-account" }, { name: 'FCMB BUSINESS ACCOUNT', linkPath: '/fcmb-business-account' },
{ {
name: "DOMICILIARY CURRENT ACCOUNT", name: 'DOMICILIARY CURRENT ACCOUNT',
linkPath: "/business-domiciliary-current-account", linkPath: '/business-domiciliary-current-account',
}, },
], ],
}, },
{ {
name: "SAVINGS ACCOUNTS", name: 'SAVINGS ACCOUNTS',
linkPath: "/node/178", linkPath: '/node/178',
subItems: [ subItems: [
{ {
name: "BUSINESS SAVINGS ACCOUNT", name: 'BUSINESS SAVINGS ACCOUNT',
linkPath: "/business-savings-account", linkPath: '/business-savings-account',
}, },
], ],
}, },
{ {
name: "LOANS", name: 'LOANS',
linkPath: "/business-loans", linkPath: '/business-loans',
subItems: [ subItems: [
{ {
name: "SME DEVELOPMENT FINANCE FACILITY", name: 'SME DEVELOPMENT FINANCE FACILITY',
linkPath: "/sme-development-finance-facility", linkPath: '/sme-development-finance-facility',
}, },
{ {
name: "SME ASSET FINANCE FACILITY", name: 'SME ASSET FINANCE FACILITY',
linkPath: "/sme-asset-finance-facility", linkPath: '/sme-asset-finance-facility',
}, },
{ {
name: "SME WORKING CAPITAL FACILITY", name: 'SME WORKING CAPITAL FACILITY',
linkPath: "/sme-working-capital-facility", linkPath: '/sme-working-capital-facility',
}, },
{ name: "QUICK LOAN", linkPath: "/quick-loan" }, { name: 'QUICK LOAN', linkPath: '/quick-loan' },
{ {
name: "SME INVOICE DISCOUNTING FINANCE (IDF) FACILITY", name: 'SME INVOICE DISCOUNTING FINANCE (IDF) FACILITY',
linkPath: "/invoice-discounting-loan", linkPath: '/invoice-discounting-loan',
}, },
{ {
name: "SME LOCAL PURCHASE ORDER (LPO) FINANCE FACILITY", name: 'SME LOCAL PURCHASE ORDER (LPO) FINANCE FACILITY',
linkPath: "/local-purchase-order-loan", linkPath: '/local-purchase-order-loan',
}, },
], ],
}, },
{ name: "BONDS & GUARANTEES", linkPath: "/bonds-and-guarantees" }, { name: 'BONDS & GUARANTEES', linkPath: '/bonds-and-guarantees' },
{ name: "INTERVENTION FUNDS", linkPath: "/intervention-funds" }, { name: 'INTERVENTION FUNDS', linkPath: '/intervention-funds' },
{ name: "TRADE SERVICE", linkPath: "/trade-service" }, { name: 'TRADE SERVICE', linkPath: '/trade-service' },
{ {
name: "PAYMENT & COLLECTION", name: 'PAYMENT & COLLECTION',
linkPath: "/payment-and-collection", linkPath: '/payment-and-collection',
subItems: [ subItems: [
{ name: "COLLECTION SOLUTIONS", linkPath: "/collection-solutions" }, { name: 'COLLECTION SOLUTIONS', linkPath: '/collection-solutions' },
{ name: "PAYMENT SOLUTIONS", linkPath: "/payment-solutions" }, { name: 'PAYMENT SOLUTIONS', linkPath: '/payment-solutions' },
], ],
}, },
{ {
name: "WAYS TO BANK", name: 'WAYS TO BANK',
linkPath: "/business-ways-to-bank", linkPath: '/business-ways-to-bank',
subItems: [ subItems: [
{ {
name: "BUSINESS INTERNET BANKING", name: 'BUSINESS INTERNET BANKING',
linkPath: "https://ibank.fcmb.com/corporate/BbgLoginScreenUI.aspx", linkPath: 'https://ibank.fcmb.com/corporate/BbgLoginScreenUI.aspx',
}, },
{ {
name: "CORPORATE INTERNET BANKING", name: 'CORPORATE INTERNET BANKING',
linkPath: "https://www.fcmbonline.com/", linkPath: 'https://www.fcmbonline.com/',
}, },
{ name: "OUR ATM NETWORK", linkPath: "/atm-network2" }, { name: 'OUR ATM NETWORK', linkPath: '/atm-network2' },
{ name: "FCMB ONLINE", linkPath: "/fcmb-online-business" }, { name: 'FCMB ONLINE', linkPath: '/fcmb-online-business' },
{ {
name: "FCMB ELECTRONIC CHANNELS", name: 'FCMB ELECTRONIC CHANNELS',
linkPath: "https://www.fcmb.com/e-channels", linkPath: 'https://www.fcmb.com/e-channels',
}, },
{ name: "FCMB BUSINESS APP", linkPath: "/FCMB-business-app" }, { name: 'FCMB BUSINESS APP', linkPath: '/FCMB-business-app' },
], ],
}, },
], ],
}, },
{ {
name: "CORPORATE BANKING", name: 'CORPORATE BANKING',
linkPath: "/cooperate-banking", linkPath: '/cooperate-banking',
subItems: [ subItems: [
{ {
name: "FOREIGN EXCHANGE SERVICES", name: 'FOREIGN EXCHANGE SERVICES',
linkPath: "/foreign-exchange-services", linkPath: '/foreign-exchange-services',
}, },
{ name: "TRADE SERVICES", linkPath: "/node/166" }, { name: 'TRADE SERVICES', linkPath: '/node/166' },
{ name: "CASH MANAGEMENT SOLUTIONS", linkPath: "/cash-management" }, { name: 'CASH MANAGEMENT SOLUTIONS', linkPath: '/cash-management' },
{ name: "CORPORATE FINANCE", linkPath: "/corporate-finance" }, { name: 'CORPORATE FINANCE', linkPath: '/corporate-finance' },
], ],
}, },
{ {
name: "GROUP & SUBSIDIARIES", name: 'GROUP & SUBSIDIARIES',
linkPath: "", linkPath: '',
subItems: [ subItems: [
{ name: "FCMB GROUP PLC", linkPath: "https://www.fcmbgroup.com/" }, { name: 'FCMB GROUP PLC', linkPath: 'https://www.fcmbgroup.com/' },
{ {
name: "CSL STOCKBROKERS", name: 'CSL STOCKBROKERS',
linkPath: "https://www.cslstockbrokers.com/", linkPath: 'https://www.cslstockbrokers.com/',
}, },
{ {
name: "FCMB CAPITAL MARKETS", name: 'FCMB CAPITAL MARKETS',
linkPath: "https://www.fcmbcapitalmarketsng.com/", linkPath: 'https://www.fcmbcapitalmarketsng.com/',
}, },
{ {
name: "FCMB ASSET MANAGEMENT", name: 'FCMB ASSET MANAGEMENT',
linkPath: "https://www.fcmbassetmanagement.com/index/", linkPath: 'https://www.fcmbassetmanagement.com/index/',
}, },
{ {
name: "FCMB MICROFINANCE BANK", name: 'FCMB MICROFINANCE BANK',
linkPath: "/fcmb-microfinance-initiative", linkPath: '/fcmb-microfinance-initiative',
}, },
{ name: "FCMB UK", linkPath: "http://www.fcmbuk.com/" }, { name: 'FCMB UK', linkPath: 'http://www.fcmbuk.com/' },
{ {
name: "FCMB PENSIONS LIMITED", name: 'FCMB PENSIONS LIMITED',
linkPath: "https://www.fcmbpensions.com/", linkPath: 'https://www.fcmbpensions.com/',
}, },
{ {
name: "CREDIT DIRECT LIMITED", name: 'CREDIT DIRECT LIMITED',
linkPath: "https://www.creditdirect.ng", linkPath: 'https://www.creditdirect.ng',
}, },
{ name: "FCMB TRUSTEES", linkPath: "http://fcmbtrustees.com/" }, { name: 'FCMB TRUSTEES', linkPath: 'http://fcmbtrustees.com/' },
], ],
}, },
{ {
name: "ABOUT US", name: 'ABOUT US',
linkPath: "/about-us", linkPath: '/about-us',
subItems: [ subItems: [
{ {
name: "OUR VISION/MISSION/VALUES", name: 'OUR VISION/MISSION/VALUES',
linkPath: "/about-us/vision-mision-core-values", linkPath: '/about-us/vision-mision-core-values',
}, },
{ name: "OUR HISTORY", linkPath: "/about-us/our-history" }, { name: 'OUR HISTORY', linkPath: '/about-us/our-history' },
{ {
name: "INVESTOR RELATIONS", name: 'INVESTOR RELATIONS',
linkPath: "http://www.fcmbgroup.com/investor-relations", linkPath: 'http://www.fcmbgroup.com/investor-relations',
}, },
{ name: "CSR", linkPath: "/corporate-social-responsibility/index.html" }, { name: 'CSR', linkPath: '/corporate-social-responsibility/index.html' },
{ name: "SUSTAINABILITY", linkPath: "/sustainability/index.html" }, { name: 'SUSTAINABILITY', linkPath: '/sustainability/index.html' },
{ name: "OUR LEADERSHIP", linkPath: "/about-us/our-leadership" }, { name: 'OUR LEADERSHIP', linkPath: '/about-us/our-leadership' },
{ name: "BOARD OF DIRECTORS", linkPath: "/about-us/board-of-directors" }, { name: 'BOARD OF DIRECTORS', linkPath: '/about-us/board-of-directors' },
{ {
name: "MEDIA RELATIONS", name: 'MEDIA RELATIONS',
linkPath: "/media-relations", linkPath: '/media-relations',
subItems: [ subItems: [
{ name: "PRESS RELEASES", linkPath: "/press-releases" }, { name: 'PRESS RELEASES', linkPath: '/press-releases' },
{ name: "MEDIA STATEMENTS", linkPath: "/press-statements" }, { name: 'MEDIA STATEMENTS', linkPath: '/press-statements' },
], ],
}, },
{ name: "AWARDS AND RECOGNITION", linkPath: "/awards" }, { name: 'AWARDS AND RECOGNITION', linkPath: '/awards' },
{ {
name: "OUR POLICY", name: 'OUR POLICY',
linkPath: "/our-policies", linkPath: '/our-policies',
subItems: [ subItems: [
{ name: "BUSINESS CONTINUITY", linkPath: "/business-continuity" }, { name: 'BUSINESS CONTINUITY', linkPath: '/business-continuity' },
{ {
name: "CORPORATE GOVERNANCE POLICY", name: 'CORPORATE GOVERNANCE POLICY',
linkPath: "/corporate-governance", linkPath: '/corporate-governance',
}, },
{ name: "PRIVACY POLICY", linkPath: "/privacy-policy" }, { name: 'PRIVACY POLICY', linkPath: '/privacy-policy' },
{ name: "QUALITY POLICY", linkPath: "/quality-policy" }, { name: 'QUALITY POLICY', linkPath: '/quality-policy' },
], ],
}, },
], ],
}, },
{ {
name: "MY BANK AND I", name: 'MY BANK AND I',
linkPath: "", linkPath: '',
subItems: [ subItems: [
{ name: "TELEPHONE SELF SERVICE", linkPath: "/telephone-self-service" }, { name: 'TELEPHONE SELF SERVICE', linkPath: '/telephone-self-service' },
{ {
name: "CURRENT CAMPAIGNS/PROMOS", name: 'CURRENT CAMPAIGNS/PROMOS',
linkPath: "/current-campaigns-promos", linkPath: '/current-campaigns-promos',
subItems: [ subItems: [
{ {
name: "BOOK YOUR FLIGHTS AND PAY IN INSTALMENTS WITH FCMB", name: 'BOOK YOUR FLIGHTS AND PAY IN INSTALMENTS WITH FCMB',
linkPath: "/247travels", linkPath: '/247travels',
}, },
{ name: "REFER AND WIN", linkPath: "/refer-and-win" }, { name: 'REFER AND WIN', linkPath: '/refer-and-win' },
{ name: "CARD DISCOUNTS", linkPath: "/card-discounts" }, { name: 'CARD DISCOUNTS', linkPath: '/card-discounts' },
{ {
name: "FLEXX WRITING CHALLENGE", name: 'FLEXX WRITING CHALLENGE',
linkPath: "https://flexxzone.fcmb.com/writing-challenge/", linkPath: 'https://flexxzone.fcmb.com/writing-challenge/',
}, },
{ {
name: "FLEXXPRENEUR", name: 'FLEXXPRENEUR',
linkPath: linkPath:
"https://flexxzone.fcmb.com/2020/07/flexxpreneur-is-back-2/", 'https://flexxzone.fcmb.com/2020/07/flexxpreneur-is-back-2/',
}, },
{ {
name: "3-MONTH FREE BANKING", name: '3-MONTH FREE BANKING',
linkPath: "https://www.fcmb.com/campaign/smebanking", linkPath: 'https://www.fcmb.com/campaign/smebanking',
}, },
], ],
}, },
{ name: "CAREERS", linkPath: "/career" }, { name: 'CAREERS', linkPath: '/career' },
{ name: "CUSTOMER FEEDBACK", linkPath: "/customer-feedback" }, { name: 'CUSTOMER FEEDBACK', linkPath: '/customer-feedback' },
{ name: "CUSTOMER SERVICE", linkPath: "/customer-service" }, { name: 'CUSTOMER SERVICE', linkPath: '/customer-service' },
], ],
}, },
]; ];
export const socialsIcons = [ export const socialsIcons = [
{ name: "facebook", image: FBook, link: process.VITE_FACEBOOK_URL }, { name: 'facebook', image: FBook, link: process.VITE_FACEBOOK_URL },
{ name: "twitter", image: Twitter, link: process.VITE_TWITTER_URL }, { name: 'twitter', image: Twitter, link: process.VITE_TWITTER_URL },
{ name: "instagram", image: Instagram, link: process.VITE_INSTAGRAM_URL }, { name: 'instagram', image: Instagram, link: process.VITE_INSTAGRAM_URL },
]; ];
export const footerItems = [ export const footerItems = [
{ {
category: "PERSONAL BANKING", category: 'PERSONAL BANKING',
subItems: [ subItems: [
{ text: "CURRENT ACCOUNTS", href: "/current-accounts" }, { text: 'CURRENT ACCOUNTS', href: '/current-accounts' },
{ text: "SAVINGS ACCOUNTS", href: "/savings-accounts" }, { text: 'SAVINGS ACCOUNTS', href: '/savings-accounts' },
{ text: "INVESTMENTS", href: "/investment-accounts" }, { text: 'INVESTMENTS', href: '/investment-accounts' },
{ text: "LOANS AND CREDIT CARDS", href: "/loans-and-credit-cards" }, { text: 'LOANS AND CREDIT CARDS', href: '/loans-and-credit-cards' },
{ {
text: "NON-RESIDENT NIGERIAN (NRN) BANKING", text: 'NON-RESIDENT NIGERIAN (NRN) BANKING',
href: "https://www.fcmb.com/non-resident-nigerian", href: 'https://www.fcmb.com/non-resident-nigerian',
}, },
{ text: "WAYS TO BANK", href: "/ways-to-bank" }, { text: 'WAYS TO BANK', href: '/ways-to-bank' },
{ text: "BANCASSURANCE", href: "/bancassurance" }, { text: 'BANCASSURANCE', href: '/bancassurance' },
], ],
}, },
{ {
category: "BUSINESS BANKING", category: 'BUSINESS BANKING',
subItems: [ subItems: [
{ text: "PROPOSITIONS", href: "" }, { text: 'PROPOSITIONS', href: '' },
{ text: "CURRENT ACCOUNTS", href: "/business-current-accounts" }, { text: 'CURRENT ACCOUNTS', href: '/business-current-accounts' },
{ text: "SAVINGS ACCOUNTS", href: "/node/178" }, { text: 'SAVINGS ACCOUNTS', href: '/node/178' },
{ text: "LOANS", href: "/business-loans" }, { text: 'LOANS', href: '/business-loans' },
{ text: "BONDS & GUARANTEES", href: "/bonds-and-guarantees" }, { text: 'BONDS & GUARANTEES', href: '/bonds-and-guarantees' },
{ text: "INTERVENTION FUNDS", href: "/intervention-funds" }, { text: 'INTERVENTION FUNDS', href: '/intervention-funds' },
{ text: "TRADE SERVICE", href: "/trade-service" }, { text: 'TRADE SERVICE', href: '/trade-service' },
{ text: "PAYMENT & COLLECTION", href: "/payment-and-collection" }, { text: 'PAYMENT & COLLECTION', href: '/payment-and-collection' },
{ text: "WAYS TO BANK", href: "/business-ways-to-bank" }, { text: 'WAYS TO BANK', href: '/business-ways-to-bank' },
], ],
}, },
{ {
category: "COOPORATE BANKING", category: 'COOPORATE BANKING',
subItems: [ subItems: [
{ text: "FOREIGN EXCHANGE SERVICES", href: "/foreign-exchange-services" }, { text: 'FOREIGN EXCHANGE SERVICES', href: '/foreign-exchange-services' },
{ text: "TRADE SERVICES", href: "/node/166" }, { text: 'TRADE SERVICES', href: '/node/166' },
{ text: "CASH MANAGEMENT SOLUTIONS", href: "/cash-management" }, { text: 'CASH MANAGEMENT SOLUTIONS', href: '/cash-management' },
{ text: "COOPORATE FINANCE", href: "/corporate-finance" }, { text: 'COOPORATE FINANCE', href: '/corporate-finance' },
], ],
}, },
{ {
category: "GROUP & SUBSIDIARIES", category: 'GROUP & SUBSIDIARIES',
subItems: [ subItems: [
{ text: "FCMB GROUP PLC", href: "https://www.fcmbgroup.com/" }, { text: 'FCMB GROUP PLC', href: 'https://www.fcmbgroup.com/' },
{ text: "CSL STOCKBROKERS", href: "https://www.cslstockbrokers.com/" }, { text: 'CSL STOCKBROKERS', href: 'https://www.cslstockbrokers.com/' },
{ {
text: "FCMB CAPITAL MARKETS", text: 'FCMB CAPITAL MARKETS',
href: "https://www.fcmbcapitalmarketsng.com/", href: 'https://www.fcmbcapitalmarketsng.com/',
}, },
{ {
text: "FCMB ASSET MANAGEMENT", text: 'FCMB ASSET MANAGEMENT',
href: "https://www.fcmbassetmanagement.com/index/", href: 'https://www.fcmbassetmanagement.com/index/',
}, },
{ text: "FCMB MICROFINANCE BANK", href: "/fcmb-microfinance-initiative" }, { text: 'FCMB MICROFINANCE BANK', href: '/fcmb-microfinance-initiative' },
{ text: "FCMB UK", href: "http://www.fcmbuk.com/" }, { text: 'FCMB UK', href: 'http://www.fcmbuk.com/' },
{ text: "FCMB PENSIONS LIMITED", href: "https://www.fcmbpensions.com/" }, { text: 'FCMB PENSIONS LIMITED', href: 'https://www.fcmbpensions.com/' },
{ text: "CREDIT DIRECT LIMITED", href: "https://www.creditdirect.ng" }, { text: 'CREDIT DIRECT LIMITED', href: 'https://www.creditdirect.ng' },
{ text: "FCMB TRUSTEES", href: "http://fcmbtrustees.com/" }, { text: 'FCMB TRUSTEES', href: 'http://fcmbtrustees.com/' },
], ],
}, },
{ {
category: "ABOUT US", category: 'ABOUT US',
subItems: [ subItems: [
{ {
text: "OUR VISION/MISSION/VALUES", text: 'OUR VISION/MISSION/VALUES',
href: "/about-us/vision-mision-core-values", href: '/about-us/vision-mision-core-values',
}, },
{ text: "OUR HISTORY", href: "/about-us/our-history" }, { text: 'OUR HISTORY', href: '/about-us/our-history' },
{ {
text: "INVESTOR RELATIONS", text: 'INVESTOR RELATIONS',
href: "http://www.fcmbgroup.com/investor-relations", href: 'http://www.fcmbgroup.com/investor-relations',
}, },
{ text: "CSR", href: "/corporate-social-responsibility/index.html" }, { text: 'CSR', href: '/corporate-social-responsibility/index.html' },
{ text: "SUSTAINABILITY", href: "/sustainability/index.html" }, { text: 'SUSTAINABILITY', href: '/sustainability/index.html' },
{ text: "OUR LEADERSHIP", href: "/about-us/our-leadership" }, { text: 'OUR LEADERSHIP', href: '/about-us/our-leadership' },
{ text: "BOARD OF DIRECTORS", href: "/about-us/board-of-directors" }, { text: 'BOARD OF DIRECTORS', href: '/about-us/board-of-directors' },
{ text: "MEDIA RELATIONS", href: "/media-relations" }, { text: 'MEDIA RELATIONS', href: '/media-relations' },
{ text: "AWARDS AND RECOGNITION", href: "/awards" }, { text: 'AWARDS AND RECOGNITION', href: '/awards' },
{ text: "OUR POLICY", href: "/our-policies" }, { text: 'OUR POLICY', href: '/our-policies' },
], ],
}, },
{ {
category: "MY BANK AND I", category: 'MY BANK AND I',
subItems: [ subItems: [
{ text: "TELEPHONE SELF SERVICE", href: "/telephone-self-service" }, { text: 'TELEPHONE SELF SERVICE', href: '/telephone-self-service' },
{ text: "CURRENT CAMPAIGNS/PROMOS", href: "/current-campaigns-promos" }, { text: 'CURRENT CAMPAIGNS/PROMOS', href: '/current-campaigns-promos' },
{ text: "CAREERS", href: "/career" }, { text: 'CAREERS', href: '/career' },
{ text: "CUSTOMER FEEDBACK", href: "/customer-feedback" }, { text: 'CUSTOMER FEEDBACK', href: '/customer-feedback' },
{ text: "CUSTOMER SERVICE", href: "/customer-service" }, { text: 'CUSTOMER SERVICE', href: '/customer-service' },
], ],
}, },
]; ];
export const footerSocialLinks = [ export const footerSocialLinks = [
{ {
href: "https://www.facebook.com/FcmbMyBank/", href: 'https://www.facebook.com/FcmbMyBank/',
icon: FBookWhite, icon: FBookWhite,
}, },
{ {
href: "https://twitter.com/myfcmb/", href: 'https://twitter.com/myfcmb/',
icon: XWhite, icon: XWhite,
}, },
{ {
href: "https://www.linkedin.com/company/first-city-monument-bank-ltd/", href: 'https://www.linkedin.com/company/first-city-monument-bank-ltd/',
icon: LinkedInWhite, icon: LinkedInWhite,
}, },
{ {
href: "https://www.youtube.com/user/fcmbplc", href: 'https://www.youtube.com/user/fcmbplc',
icon: YoutubeWhite, icon: YoutubeWhite,
}, },
{ {
href: "https://www.instagram.com/myfcmb/", href: 'https://www.instagram.com/myfcmb/',
icon: InstagramWhite, icon: InstagramWhite,
}, },
{ {
href: "https://api.whatsapp.com/send?phone=09099999814", href: 'https://api.whatsapp.com/send?phone=09099999814',
icon: WhatsappWhite, icon: WhatsappWhite,
}, },
]; ];
export const footerCustomerLinks = [ export const footerCustomerLinks = [
{ {
text: "PRIVACY POLICY", text: 'PRIVACY POLICY',
href: "https://www.fcmb.com/privacy-policy", href: 'https://www.fcmb.com/privacy-policy',
}, },
{ {
text: "PRESS RELEASES", text: 'PRESS RELEASES',
href: "/press-releases", href: '/press-releases',
}, },
{ {
text: "SHARE PRICE", text: 'SHARE PRICE',
href: "/", href: '/',
}, },
{ {
text: "WHISTLE BLOWER", text: 'WHISTLE BLOWER',
href: "/fcmb-whistle-blower-form", href: '/fcmb-whistle-blower-form',
}, },
{ {
text: "FRAUD PREVENTION", text: 'FRAUD PREVENTION',
href: "/customer-service", href: '/customer-service',
}, },
{ {
text: "AML", text: 'AML',
href: "/customer-service", href: '/customer-service',
}, },
{ {
text: "CAREERS", text: 'CAREERS',
href: "/career", href: '/career',
}, },
]; ];
export const contactDetails = [
{ name: 'Call', value: import.meta.env.VITE_CALL_ENDPOINT },
{ name: 'Email', value: import.meta.env.VITE_EMAIL_ENDPOINT },
];