Compare commits

..

24 Commits

Author SHA1 Message Date
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
28 changed files with 794 additions and 178 deletions
+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'
+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:
+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
}[];
+4 -5
View File
@@ -1,17 +1,16 @@
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";
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: "" }
); );
@@ -115,7 +114,7 @@ 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>
+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,
},
});