Compare commits

...

12 Commits

6 changed files with 634 additions and 427 deletions
+130 -110
View File
@@ -1,11 +1,11 @@
import React, { FC, useState, useEffect } 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, Icons } from "../"; import { Button, Icons } from '../';
import { useSelector } from "react-redux"; import { useSelector } from 'react-redux';
import PendingList from "../paginated-list/PendingList"; import PendingList from '../paginated-list/PendingList';
import { PendingTableList } from "../../core/models"; import { PendingTableList } from '../../core/models';
import { NewDateTimeFormatter } from "../../lib/NewDateTimeFormatter"; import { NewDateTimeFormatter } from '../../lib/NewDateTimeFormatter';
import { getUserPendingLoanList } from "../../core/apiRequest"; import { getUserPendingLoanList } from '../../core/apiRequest';
export interface DashBoardCardProps { export interface DashBoardCardProps {
title?: string; title?: string;
@@ -55,7 +55,7 @@ export const DashBoardCard: React.FC<DashBoardCardProps> = ({
)} )}
{desc && ( {desc && (
<p className={`text-lg text-left ${descClass && descClass}`}> <p className={`text-lg text-left ${descClass && descClass}`}>
{desc}{" "} {desc}{' '}
{descSpan && ( {descSpan && (
<span className={`${descSpanClass && descSpanClass}`}> <span className={`${descSpanClass && descSpanClass}`}>
{descSpan} {descSpan}
@@ -73,117 +73,137 @@ export const DashBoardCard: React.FC<DashBoardCardProps> = ({
}; };
interface DashboardHomeIntroProps { interface DashboardHomeIntroProps {
handleNextStep:(value:{})=>any handleNextStep: (value: {}) => any;
step?:number|string step?: number | string;
} }
const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({ handleNextStep, step }) => { const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({
const { userDetails } = useSelector((state:any) => state?.userDetails); // CHECKS IF USER Details are avaliable 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:[]}) const [userLoanList, setUserLoanList] = useState<{
loading: boolean;
data: PendingTableList;
}>({ loading: true, data: [] });
useEffect(()=>{ useEffect(() => {
let token = localStorage.getItem('token') let token = localStorage.getItem('token');
let uid = localStorage.getItem('uid') let uid = localStorage.getItem('uid');
if(!token || !uid){ if (!token || !uid) {
return return;
} }
getUserPendingLoanList(uid).then(res => { getUserPendingLoanList(uid)
console.log('RES', res) .then((res) => {
console.log('RES', userLoanList) console.log('RES', res);
if(!res || !res.data.loans){ console.log('RES', userLoanList);
setUserLoanList({loading:false, data:[]}) if (!res || !res.data.loans) {
return setUserLoanList({ loading: false, data: [] });
} return;
setUserLoanList({loading:false, data:res?.data?.loans}) }
}).catch(err => { setUserLoanList({ loading: false, data: res?.data?.loans });
console.log(err) })
setUserLoanList({loading:false, data:[]}) .catch((err) => {
}) console.log(err);
},[]) setUserLoanList({ loading: false, data: [] });
});
}, []);
return ( return (
<div className='w-full'> <div className="w-full">
{step == 1 ? {step == 1 ? (
<> <>
<h1 className="font-bold my-5 text-2xl">Hello, {userDetails.firstname}</h1> <h1 className="font-bold my-5 text-2xl">
<div className="group w-full lg:w-[27.8125rem] h-[12.75rem] mt-7 "> Hello, {userDetails.firstname}
<DashBoardCard </h1>
cardClass="bg-[#5C2684] relative" <div className="group w-full lg:w-[27.8125rem] h-[12.75rem] mt-7 ">
desc="Begin your application and get up to " <DashBoardCard
descSpan="5 million naira loan." cardClass="bg-[#5C2684] relative"
descClass="leading-[1.5625rem] text-lg text-white" desc="Begin your application and get up to "
descSpanClass="font-bold" descSpan="5 million naira loan."
btnTitle="Apply here" descClass="leading-[1.5625rem] text-lg text-white"
btnTextClass="w-[11.125rem] h-[2.8125rem] flex justify-center item-center btn-W text-[#FBB700]" descSpanClass="font-bold"
image={NairaBag} btnTitle="Apply here"
imgClass="translate-y-4 -rotate-6" btnTextClass="w-[11.125rem] h-[2.8125rem] flex justify-center item-center btn-W text-[#FBB700]"
onClick={()=>handleNextStep({})} image={NairaBag}
imgClass="translate-y-4 -rotate-6"
onClick={() => handleNextStep({})}
/> />
</div> </div>
</> </>
: ) : (
<> <>
<h1 className="font-bold my-5 text-2xl">Welcome Back, {userDetails.firstname}</h1> <h1 className="font-bold my-5 text-2xl">
<div className="group w-full lg:w-[27.8125rem] h-[12.75rem] mt-7 "> Welcome Back, {userDetails.firstname}
<DashBoardCard </h1>
cardClass="bg-[#5C2684] relative" <div className="group w-full lg:w-[27.8125rem] h-[12.75rem] mt-7 ">
desc="Your loan application has been reviewed and accepted, please confirm for disbursement." <DashBoardCard
// descSpan="5 million naira loan." cardClass="bg-[#5C2684] relative"
descClass="leading-[1.5625rem] text-lg text-white" desc="Your loan application has been reviewed and accepted, please confirm for disbursement."
// descSpanClass="font-bold" // descSpan="5 million naira loan."
btnTitle="View and accept" descClass="leading-[1.5625rem] text-lg text-white"
btnTextClass="w-[11.125rem] h-[2.8125rem] flex justify-center item-center btn-W text-[#FBB700]" // descSpanClass="font-bold"
image={NairaBag} btnTitle="View and accept"
imgClass="translate-y-4 -rotate-6" btnTextClass="w-[11.125rem] h-[2.8125rem] flex justify-center item-center btn-W text-[#FBB700]"
// onClick={handleNextStep} image={NairaBag}
imgClass="translate-y-4 -rotate-6"
// onClick={handleNextStep}
/> />
</div> </div>
</> </>
} )}
{userLoanList.loading ? {userLoanList.loading ? null : (
null <div className="mt-5 w-full">
: <PendingList
<div className='mt-5 w-full'> data={userLoanList.data}
<PendingList itemsPerPage={5}
data={userLoanList.data} tableTitle="Current Applications"
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]">
{(data:any)=>( <table className="text-[12px] sm:text-base w-full table-auto">
<div className="w-full p-4 rounded-lg shadow-lg bg-white overflow-x-auto min-h-[250px] max-h-[450px]"> <thead>
<table className="text-[12px] sm:text-base w-full table-auto"> <tr className="text-left border-b-2">
<thead> <th className="px-1 py-4">Date</th>
<tr className='text-left border-b-2'> <th className="px-1 py-4 text-right">Amount</th>
<th className='px-1 py-4'>Date</th> <th className="px-1 py-4 text-center min-w-[110px]">
<th className='px-1 py-4 text-right'>Amount</th> Payment Term
<th className='px-1 py-4 text-center min-w-[110px]'>Payment Term</th> </th>
<th className='px-1 py-4 text-center'>Status</th> <th className="px-1 py-4 text-center">Status</th>
<th className='px-1 py-4'>Action</th> <th className="px-1 py-4 text-right">Action</th>
</tr>
</thead>
<tbody>
{data.map((item:any, index:any) =>(
<tr key={index || item} className='even:bg-slate-100'>
<td className='px-1 py-2'>{NewDateTimeFormatter(item?.added)}</td>
<td className='px-1 py-2 text-right'>{item?.loan_amount}</td>
<td className='px-1 py-2 text-center'>{item?.payment_month}</td>
<td className='px-1 py-2 text-center'>{item?.status}</td>
<td className='px-1 py-2'>
<button className='px-2 py-1 border-2 border-black flex gap-2 items-center'>
View
<Icons name='arrow-right' />
</button>
</td>
</tr> </tr>
))} </thead>
</tbody> <tbody>
</table> {data.map((item: any, index: any) => (
</div> <tr key={index || item} className="even:bg-slate-100">
)} <td className="px-1 py-2">
</PendingList> {NewDateTimeFormatter(item?.added)}
</div> </td>
} <td className="px-1 py-2 text-right">
{item?.loan_amount}
</td>
<td className="px-1 py-2 text-center">
{item?.payment_month}
</td>
<td className="px-1 py-2 text-center">
{item?.status}
</td>
<td className="px-1 py-2 text-right">
<button className="px-2 py-1 border-2 border-black">
View
<Icons name="arrow-right" />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</PendingList>
</div>
)}
</div> </div>
); );
}; };
@@ -1,12 +1,20 @@
import {useState, useEffect} from 'react'
import { Button, InputCompOne, Stepper } from '../../shared/index'; 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 { getEmployersList } from '../../../core/apiRequest';
type Props = { type Props = {
handleNextStep:(value:{})=>any handleNextStep:(value:{})=>any
} }
// type EmployerProps = {
// loading?: boolean,
// data?: Array<{[index:string]: string}> | {[index:string]: Array<{[index:string]: string}> }
// }
const initialValues = { const initialValues = {
job_title: "", job_title: "",
name: "", name: "",
@@ -18,24 +26,37 @@ const initialValues = {
monthly_salary: "", monthly_salary: "",
salary_payment_date: "", salary_payment_date: "",
employment_id: "", employment_id: "",
highest_eductaion: "" highest_eductaion: "",
employer_uid: "",
isChecked: false
}; };
// To get the validation schema // To get the validation schema
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
isChecked: Yup.bool(), // use bool instead of boolean
// .oneOf([true, false], "You must accept the terms and conditions"),
job_title: Yup.string() job_title: Yup.string()
.required("Required"), .required("Required"),
name: Yup.string() name: Yup.string().when('isChecked', {
.required("Required"), is: true,
sector: Yup.string() then: () => Yup.string().required('required'),
.required("Required"), otherwise: () => Yup.string(),
industry: Yup.string() }),
.required("Required"), sector: Yup.string().when('isChecked', {
is: true,
then: () => Yup.string().required('required'),
}),
industry: Yup.string().when('isChecked', {
is: true,
then: () => Yup.string().required('required'),
}),
resumption_date: Yup.string() resumption_date: Yup.string()
.required("Required"), .required("Required"),
email: Yup.string() email: Yup.string().when('isChecked', {
.email("Invalid") is: true,
.required("Required"), then: () => Yup.string().required('required'),
})
.email("Invalid"),
annual_income: Yup.string() annual_income: Yup.string()
.required("Required") .required("Required")
.test("no-e", "Invalid", (value:any) => { .test("no-e", "Invalid", (value:any) => {
@@ -58,16 +79,41 @@ const validationSchema = Yup.object().shape({
.required("Required"), .required("Required"),
highest_eductaion: Yup.string() highest_eductaion: Yup.string()
.required("Required"), .required("Required"),
employer_uid: Yup.string().when('isChecked', {
is: false,
then: () => Yup.string().required('required'),
}),
}); });
export default function DashboardHomeEmploymentInfo({handleNextStep}:Props) { export default function DashboardHomeEmploymentInfo({handleNextStep}:Props) {
const [employersList, setEmployersList] = useState<any>({
loading: true,
data: []
})
//FUNCTION TO HANDLE SUBMIT //FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => { const handleSubmit = (values:any) => {
handleNextStep({employment: values}) // Remember to changed the checked value's name
if(values.employer_uid){
let employer_uid = values.employer_uid
delete values.employer_uid
handleNextStep({employer_uid, employment: values})
}else{
handleNextStep({employment: values})
}
}; };
useEffect(()=>{
getEmployersList().then(res => {
setEmployersList({loading:false, data:res?.data})
}).catch(err => {
console.log(err)
setEmployersList({loading:false, data:[]})
})
},[])
return ( return (
<div className="w-full"> <div className="w-full">
@@ -83,155 +129,188 @@ export default function DashboardHomeEmploymentInfo({handleNextStep}:Props) {
<Form> <Form>
<div className="mt-[3.25rem] flex flex-col gap-9"> <div className="mt-[3.25rem] flex flex-col gap-9">
<p className='text-red-500 text-lg md:text-2xl'>Employment Informaton</p> <p className='text-red-500 text-lg md:text-2xl'>Employment Informaton</p>
<div className="flex items-center gap-[4.125rem]"> <div className="flex flex-col lg:flex-row items-start gap-[2rem]">
<InputCompOne <div className='w-full lg:max-w-[30rem] flex flex-col'>
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" <div className='w-full gap-[2rem]'>
name="job_title" <InputCompOne
floatLabel="Job Title" parentClass="w-full"
// labelClass="font-bold text-[1.125rem]" name="employer_uid"
input floatLabel="Employer Name"
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" // labelClass="font-bold text-[1.125rem]"
placeholder="Software Engineer" select={true}
value={props.values.job_title} selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
onChange={props.handleChange} selectOptions={{loading:employersList?.loading, data: employersList?.data?.records}}
error={(props.errors.job_title && props.touched.job_title) ? props.errors.job_title : ''} selectValue={props.values.employer_uid}
/> onChange={props.handleChange}
<InputCompOne error={(props.errors.employer_uid && props.touched.employer_uid) ? props.errors.employer_uid : ''}
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4" disabled={props.values.isChecked}
name="name" />
floatLabel="Employer name" <div className='flex gap-4 items-start my-2'>
// labelClass="font-bold text-[1.125rem]" <input
input type='checkbox'
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]" name="isChecked"
placeholder="Mr. Mark John" className='w-4 h-4 p-2 accent-purple-600 text-purple-600 bg-gray-100 border-gray-300 rounded focus:ring-purple-500'
value={props.values.name} onChange={props.handleChange}
onChange={props.handleChange} checked={props.values.isChecked}
error={(props.errors.name && props.touched.name) ? props.errors.name : ''} />
/> <p className='text-[12px] text-justify'>Check here if employer is not on the list</p>
</div>
<div className={`hidden p-4 ${props.values.isChecked && 'hidden'}`}>
Name: {'Name'}
</div>
</div>
<div className={`w-full flex flex-col gap-[2rem] ${!props.values.isChecked && 'hidden'}`}>
<InputCompOne
parentClass="w-full"
name="name"
floatLabel="Employer name"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Mr. Mark John"
value={props.values.name}
onChange={props.handleChange}
error={(props.errors.name && props.touched.name) ? props.errors.name : ''}
/>
<InputCompOne
parentClass="w-full"
name="email"
floatLabel="Employers official email"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="example@gmail.com"
value={props.values.email}
onChange={props.handleChange}
error={(props.errors.email && props.touched.email) ? props.errors.email : ''}
/>
<InputCompOne
parentClass="w-full"
name="industry"
floatLabel="Select your industry"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={industry}
selectValue={props.values.industry}
onChange={props.handleChange}
error={(props.errors.industry && props.touched.industry) ? props.errors.industry : ''}
/>
<InputCompOne
parentClass="w-full"
name="sector"
floatLabel="Job Sector"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={jobSector}
selectValue={props.values.sector}
onChange={props.handleChange}
error={(props.errors.sector && props.touched.sector) ? props.errors.sector : ''}
/>
</div>
</div>
<div className='w-full lg:max-w-[30rem] flex flex-col gap-[2rem]'>
<InputCompOne
parentClass="w-full"
name="job_title"
floatLabel="Job Title"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Software Engineer"
value={props.values.job_title}
onChange={props.handleChange}
error={(props.errors.job_title && props.touched.job_title) ? props.errors.job_title : ''}
/>
<InputCompOne
parentClass="w-full"
name="highest_eductaion"
floatLabel="Highest level of education"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={highestEductaion}
selectValue={props.values.highest_eductaion}
onChange={props.handleChange}
error={(props.errors.highest_eductaion && props.touched.highest_eductaion) ? props.errors.highest_eductaion : ''}
/>
<div className="w-full flex flex-col sm:flex-row items-center gap-4">
<InputCompOne
parentClass="w-full"
name="resumption_date"
floatLabel="Date of resumption"
// labelClass="font-bold text-[1.125rem]"
input
inputType='date'
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="12/12/2015"
value={props.values.resumption_date}
onChange={props.handleChange}
error={(props.errors.resumption_date && props.touched.resumption_date) ? props.errors.resumption_date : ''}
/>
<InputCompOne
parentClass="w-full"
name="salary_payment_date"
floatLabel="Salary payment date"
// labelClass="font-bold text-[1.125rem]"
input
inputType='date'
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="30th of every month"
value={props.values.salary_payment_date}
onChange={props.handleChange}
error={(props.errors.salary_payment_date && props.touched.salary_payment_date) ? props.errors.salary_payment_date : ''}
/>
</div>
<div className="w-full flex flex-col sm:flex-row items-center gap-4">
<InputCompOne
parentClass="w-full"
name="annual_income"
floatLabel="Annual Income"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem] text-right"
placeholder="1,200,000"
value={props.values.annual_income}
onChange={props.handleChange}
error={(props.errors.annual_income && props.touched.annual_income) ? props.errors.annual_income : ''}
/>
<InputCompOne
parentClass="w-full"
name="monthly_salary"
floatLabel="Net monthly salary"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem] text-right"
placeholder="100,000"
value={props.values.monthly_salary}
onChange={props.handleChange}
error={(props.errors.monthly_salary && props.touched.monthly_salary) ? props.errors.monthly_salary : ''}
/>
</div>
<InputCompOne
parentClass="w-full"
name="employment_id"
floatLabel="Employee ID"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="LS/001/005"
value={props.values.employment_id}
onChange={props.handleChange}
error={(props.errors.employment_id && props.touched.employment_id) ? props.errors.employment_id : ''}
/>
<div className="w-full">
<Button
className="my-4 btn-Y text-black w-full h-11"
text="Next"
type="submit"
/>
</div>
</div>
</div> </div>
<div className="flex items-center gap-[4.125rem]">
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="sector"
floatLabel="Job Sector"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={jobSector}
selectValue={props.values.sector}
onChange={props.handleChange}
error={(props.errors.sector && props.touched.sector) ? props.errors.sector : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="industry"
floatLabel="Select your industry"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={industry}
selectValue={props.values.industry}
onChange={props.handleChange}
error={(props.errors.industry && props.touched.industry) ? props.errors.industry : ''}
/>
</div>
<div className="flex items-center gap-[4.125rem]">
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="resumption_date"
floatLabel="Date of resumption"
// labelClass="font-bold text-[1.125rem]"
input
inputType='date'
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="12/12/2015"
value={props.values.resumption_date}
onChange={props.handleChange}
error={(props.errors.resumption_date && props.touched.resumption_date) ? props.errors.resumption_date : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="email"
floatLabel="Employers official email"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="example@gmail.com"
value={props.values.email}
onChange={props.handleChange}
error={(props.errors.email && props.touched.email) ? props.errors.email : ''}
/>
</div>
<div className="flex items-center gap-[4.125rem]">
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="annual_income"
floatLabel="Annual Income"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem] text-right"
placeholder="1,200,000"
value={props.values.annual_income}
onChange={props.handleChange}
error={(props.errors.annual_income && props.touched.annual_income) ? props.errors.annual_income : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="monthly_salary"
floatLabel="Net monthly salary"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem] text-right"
placeholder="100,000"
value={props.values.monthly_salary}
onChange={props.handleChange}
error={(props.errors.monthly_salary && props.touched.monthly_salary) ? props.errors.monthly_salary : ''}
/>
</div>
<div className="flex items-center gap-[4.125rem]">
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="salary_payment_date"
floatLabel="Salary payment date"
// labelClass="font-bold text-[1.125rem]"
input
inputType='date'
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="30th of every month"
value={props.values.salary_payment_date}
onChange={props.handleChange}
error={(props.errors.salary_payment_date && props.touched.salary_payment_date) ? props.errors.salary_payment_date : ''}
/>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="employment_id"
floatLabel="Employee ID"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="LS/001/005"
value={props.values.employment_id}
onChange={props.handleChange}
error={(props.errors.employment_id && props.touched.employment_id) ? props.errors.employment_id : ''}
/>
</div>
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
name="highest_eductaion"
floatLabel="Highest level of education"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={highestEductaion}
selectValue={props.values.highest_eductaion}
onChange={props.handleChange}
error={(props.errors.highest_eductaion && props.touched.highest_eductaion) ? props.errors.highest_eductaion : ''}
/>
<Button
className="my-8 max-w-[25.875rem] btn-Y text-black w-full h-11"
text="Next"
type="submit"
/>
</div> </div>
</Form> </Form>
)} )}
+204 -148
View File
@@ -1,42 +1,42 @@
import React from "react"; import React from 'react';
import * as Yup from "yup"; import * as Yup from 'yup';
import { Form, Formik } from "formik"; import { Form, Formik } from 'formik';
import { InputCompOne } from ".."; 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 { useDispatch } from 'react-redux';
import { updateUserDetails } from "../../store/UserDetails"; import { updateUserDetails } from '../../store/UserDetails';
import { validateBVN, verifyOTP } from "../../core/apiRequest"; import { validateBVN, verifyOTP } from '../../core/apiRequest';
import { RequestStatus } from "../../core/models"; 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()
.required("BVN is required") .required('BVN 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;
} }
return true; return true;
}) })
.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()
// .when('require_otp', { // .when('require_otp', {
// is: true, // is: true,
// then: Yup.string().required("OTP is required") // then: Yup.string().required("OTP is required")
// }) // })
// .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;
} }
return true; return true;
}) })
.min(5, "must be 5 digits") .min(5, 'must be 5 digits')
.max(5, "must be 5 digits"), .max(5, 'must be 5 digits'),
// .test("no-e", "must be 11 characters", (value:any) => { // .test("no-e", "must be 11 characters", (value:any) => {
// if (value.length < 11) { // if (value.length < 11) {
// return false; // return false;
@@ -52,151 +52,207 @@ let initialValues = {
}; };
type ValidBVN = { type ValidBVN = {
verification_id:string verification_id: string;
valid: undefined | boolean valid: undefined | boolean;
} };
const LetsGetStarted: React.FC = () => { const LetsGetStarted: React.FC = () => {
const dispatch = useDispatch() 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 otpInputRef = React.useRef<HTMLInputElement>(null);
const [requestStatusBVN, setRequestStatusBVN] = React.useState<RequestStatus>({loading:false, status:undefined, message:''}); const [requestStatusBVN, setRequestStatusBVN] = React.useState<RequestStatus>(
{ loading: false, status: undefined, message: '' }
);
const [requestStatusOTP, setRequestStatusOTP] = React.useState<RequestStatus>({loading:false, status:undefined, message:''}); const [requestStatusOTP, setRequestStatusOTP] = React.useState<RequestStatus>(
{ loading: false, status: undefined, message: '' }
);
const [bvnIsValid, setBvnIsValid] = React.useState<ValidBVN>({ const [bvnIsValid, setBvnIsValid] = React.useState<ValidBVN>({
verification_id: '', verification_id: '',
valid: undefined valid: undefined,
}); });
// e: React.FormEvent<HTMLInputElement> // e: React.FormEvent<HTMLInputElement>
// let { value } = e.target as HTMLInputElement; // let { value } = e.target as HTMLInputElement;
const bvnValidation = (values:any) => { // Function to Validate BVN const bvnValidation = (values: any) => {
let bvn = values.bvn // Function to Validate BVN
setRequestStatusBVN({loading:true, status:false, message:''}) let bvn = values.bvn;
validateBVN({bvn}).then(res => { setRequestStatusBVN({ loading: true, status: false, message: '' });
if(!res || !res.data.call_return){ validateBVN({ bvn })
setBvnIsValid({verification_id:'', valid: false}) .then((res) => {
setRequestStatusBVN({loading:false, status:false, message:'unable to verify BVN'}) if (!res || !res.data.call_return) {
return setTimeout(()=>{ setBvnIsValid({ verification_id: '', valid: false });
setRequestStatusBVN({loading:false, status:false, message:''}) setRequestStatusBVN({
}, 4000) loading: false,
} status: false,
setBvnIsValid({verification_id:res.data.verification_id, valid: true}) message: 'unable to verify BVN',
setRequestStatusBVN({loading:false, status:true, message:'verified'}) });
}).catch(err => { return setTimeout(() => {
setBvnIsValid({verification_id:'', valid: false}) setRequestStatusBVN({ loading: false, status: false, message: '' });
console.log(err) }, 4000);
}) }
setBvnIsValid({
verification_id: res.data.verification_id,
valid: true,
});
setRequestStatusBVN({
loading: false,
status: true,
message: 'verified',
});
})
.catch((err) => {
setBvnIsValid({ verification_id: '', valid: false });
console.log(err);
});
}; };
const handleSubmit = (values:any) => { // Function to VERIFY OTP AND LOGIN USER const handleSubmit = (values: any) => {
setRequestStatusOTP({loading:true, status:false, message:''}) // Function to VERIFY OTP AND LOGIN USER
setRequestStatusOTP({ loading: true, status: false, message: '' });
// console.log('values', values) // console.log('values', values)
verifyOTP({...values, verification_id:bvnIsValid.verification_id}).then(res=>{ verifyOTP({ ...values, verification_id: bvnIsValid.verification_id })
if(!res || !res.data.call_return){ .then((res) => {
setRequestStatusOTP({loading:false, status:false, message:'wrong otp'}) if (!res || !res.data.call_return) {
return setTimeout(()=>{ setRequestStatusOTP({
setRequestStatusOTP({loading:false, status:false, message:''}) loading: false,
},4000) status: false,
} message: 'wrong otp',
// console.log(res.data) });
localStorage.setItem('token', res.data?.token) return setTimeout(() => {
localStorage.setItem('uid', res?.data?.customer[0]?.uid) setRequestStatusOTP({ loading: false, status: false, message: '' });
dispatch(updateUserDetails({ ...res?.data?.customer[0] })); }, 4000);
navigate(RouteHandler.dashboardHome, {replace:true}) }
}).catch(err => { // console.log(res.data)
setRequestStatusOTP({loading:false, status:false, message:'something went wrong, try again'}) localStorage.setItem('token', res.data?.token);
console.log(err) localStorage.setItem('uid', res?.data?.customer[0]?.uid);
return setTimeout(()=>{ dispatch(updateUserDetails({ ...res?.data?.customer[0] }));
setRequestStatusOTP({loading:false, status:false, message:''}) navigate(RouteHandler.dashboardHome, { replace: true });
},4000) })
}) .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={bvnIsValid.valid ? handleSubmit : bvnValidation} onSubmit={bvnIsValid.valid ? handleSubmit : bvnValidation}
> >
{(props:any) => ( {(props: any) => (
<Form className=""> <Form className="">
<div className="w-full"> <div className="w-full">
<div className="containerMode flex justify-between gap-1 xl:gap-8 flex-col"> <div className="containerMode flex justify-between gap-1 xl:gap-8 flex-col">
<div className="my-[4rem] flex items-center justify-center w-full"> <div className="my-[4rem] flex items-center justify-center w-full">
<h1 className="font-bold text-[2.375rem] text-[#5C2684] my-[.5rem] text-center"> <h1 className="font-bold text-[2.375rem] text-[#5C2684] my-[.5rem] text-center">
Lets Get You Started Lets Get You Started
</h1> </h1>
</div>
<div className="mx-auto flex flex-col gap-8 max-w-[31.625rem] ">
<div className='w-full'>
<InputCompOne
parentClass="flex flex-col gap-2"
label="Enter Your BVN "
name="bvn"
parentInputClass="w-full"
labelSpan="( To get your BVN, dial *565*0# )"
labelSpanClass="text-[13px] text-[#5a5a5a] font-semibold"
placeholder="Enter your BVN"
labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#282828] mb-[2px] flex item-center gap-[4px]"
input
inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4"
value={props.values.bvn}
onChange={props.handleChange}
error={(props.errors.bvn && props.touched.bvn) && props.errors.bvn}
/>
<p className={`p-2 ${!requestStatusBVN.status ? 'text-red-500' : 'text-emerald-500'}`}>{requestStatusBVN.loading ? 'verifying...' : requestStatusBVN.message}</p>
</div> </div>
{bvnIsValid.valid && ( <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 OTP " parentClass="flex flex-col gap-2"
name="otp" label="Enter Your BVN "
parentInputClass="w-full" name="bvn"
labelSpan="( Please check your BVN phone number for verification pin )" parentInputClass="w-full"
labelSpanClass="text-[13px] text-[#5a5a5a] font-semibold" labelSpan="( To get your BVN, dial *565*0# )"
placeholder="Enter your OTP" 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.otp} inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4"
onChange={props.handleChange} value={props.values.bvn}
error={(props.errors.otp && props.touched.otp) && props.errors.otp} onChange={props.handleChange}
/> error={
)} props.errors.bvn && props.touched.bvn && props.errors.bvn
<button }
type='submit' maxLength={11}
className="w-full h-[3.625rem] rounded bg-[#FBB700] rounded-2 px-4 text-[18px] text-[#282828] font-semibold disabled:text-[#282828] disabled:text-opacity-50" />
disabled={requestStatusBVN.loading || (!props.values.otp && bvnIsValid.valid)} <p
> className={`p-2 ${
Enter !requestStatusBVN.status
</button> ? 'text-red-500'
: 'text-emerald-500'
}`}
>
{requestStatusBVN.loading
? 'verifying...'
: requestStatusBVN.message}
</p>
</div>
{bvnIsValid.valid && (
<InputCompOne
parentClass="flex flex-col gap-2"
label="Enter OTP "
name="otp"
parentInputClass="w-full"
labelSpan="( Please check your BVN phone number for verification pin )"
labelSpanClass="text-[13px] text-[#5a5a5a] font-semibold"
placeholder="Enter your OTP"
labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#282828] mb-[2px] flex item-center gap-[4px]"
input
inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4"
value={props.values.otp}
onChange={props.handleChange}
error={
props.errors.otp && props.touched.otp && props.errors.otp
}
maxLength={5}
/>
)}
<button
type="submit"
className="w-full h-[3.625rem] rounded bg-[#FBB700] rounded-2 px-4 text-[18px] text-[#282828] font-semibold disabled:text-[#282828] disabled:text-opacity-50"
disabled={
requestStatusBVN.loading ||
(!props.values.otp && bvnIsValid.valid)
}
>
Enter
</button>
<p className={`p-2 ${!requestStatusOTP.status ? 'text-red-500' : 'text-emerald-500'}`}>{requestStatusOTP.message}</p> <p
className={`p-2 ${
!requestStatusOTP.status
? 'text-red-500'
: 'text-emerald-500'
}`}
>
{requestStatusOTP.message}
</p>
{bvnIsValid.valid || bvnIsValid.valid == undefined ? ( {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
secure. It is only important for us to verify your information and and secure. It is only important for us to verify your
also give you access to your application profile/account. information and also give you access to your application
</p> profile/account.
) : ( </p>
<p className="text-[#5C2684] mt-[1.5625rem] w-fit"> ) : (
***Did not receive OTP? Click to resend <p className="text-[#5C2684] mt-[1.5625rem] w-fit">
</p> ***Did not receive OTP? Click to resend
)} </p>
)}
</div>
</div> </div>
</div> </div>
</div> </Form>
</Form> )}
)}
</Formik> </Formik>
); );
}; };
+22 -9
View File
@@ -17,7 +17,7 @@ export interface InputCompOneProps {
selectValue?: string; selectValue?: string;
input?: boolean; input?: boolean;
select?: boolean; select?: boolean;
selectOptions?: {loading:boolean, data:{ value: string; label: string }[]}; selectOptions?: {loading:boolean, data:{ [index: string]: string; }[]};
inputType?: string; inputType?: string;
inputClass?: string; inputClass?: string;
parentInputClass?: string; parentInputClass?: string;
@@ -25,6 +25,7 @@ export interface InputCompOneProps {
parentClass?: string; parentClass?: string;
maxLength?: number; maxLength?: number;
error?: string; error?: string;
disabled?: boolean
} }
const InputCompOne = forwardRef<HTMLInputElement, InputCompOneProps>( const InputCompOne = forwardRef<HTMLInputElement, InputCompOneProps>(
@@ -52,6 +53,7 @@ const InputCompOne = forwardRef<HTMLInputElement, InputCompOneProps>(
parentClass, parentClass,
maxLength, maxLength,
error, error,
disabled=false
}, },
forwardedRef forwardedRef
) => { ) => {
@@ -78,6 +80,7 @@ const InputCompOne = forwardRef<HTMLInputElement, InputCompOneProps>(
className={`px-4 ${floatLabel && 'peer pt-4 placeholder:text-transparent'} ${inputClass}`} className={`px-4 ${floatLabel && 'peer pt-4 placeholder:text-transparent'} ${inputClass}`}
maxLength={maxLength} maxLength={maxLength}
id={label ? label : floatLabel} id={label ? label : floatLabel}
disabled={disabled}
/> />
{floatLabel && {floatLabel &&
<label <label
@@ -96,17 +99,27 @@ const InputCompOne = forwardRef<HTMLInputElement, InputCompOneProps>(
name={name} name={name}
id={label ? label : floatLabel} id={label ? label : floatLabel}
value={selectValue} value={selectValue}
className={`px-4 appearance-none ${floatLabel && 'peer pt-4'} ${selectClass}`} className={`px-4 appearance-none ${floatLabel && 'peer pt-4'} ${selectClass} ${disabled && 'opacity-50'}`}
onChange={onChange} onChange={onChange}
disabled={disabled}
> >
{selectOptions.loading ? {selectOptions.loading ?
<option value=''>Loading</option> <option value=''>Loading...</option>
: selectOptions.data.length ? : selectOptions.data.length && name == 'employer_uid' ?
selectOptions.data.map(({ value, label }) => ( <>
<option key={value} value={value}> <option value=''>Please Select</option>
{label} {selectOptions.data.map(({ uid, name }) => (
</option> <option key={uid} value={uid}>
)) {name}
</option>
))}
</>
: selectOptions.data.length && name != 'employer_uid' ?
selectOptions.data.map(({ value, label }) => (
<option key={value} value={value}>
{label}
</option>
))
: :
<option value=''>Not Found</option> <option value=''>Not Found</option>
} }
+8
View File
@@ -41,3 +41,11 @@ export const getUserPendingLoanList = (uid:string) => {
} }
return getAuxEnd(`/dash?uid=${uid}`, reqData) return getAuxEnd(`/dash?uid=${uid}`, reqData)
} }
// FUNCTION TO GET LIST OF EMPLOYERS
export const getEmployersList = () => {
let reqData = {
// customer_uid: localStorage.getItem('uid'),
}
return getAuxEnd(`/employers`, reqData)
}
+32 -1
View File
@@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { Link, useLocation } 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';
@@ -27,6 +27,37 @@ export default function Aside({ asideDisplay, logoutUser }: Props) {
} }
}; };
// Track user activity
useEffect(() => {
let timeout: number;
const resetTimeout = () => {
clearTimeout(timeout);
timeout = window.setTimeout(() => {
// Logout user after 7 minutes of inactivity
logoutUser();
}, 7 * 60 * 1000); // 7 minutes in milliseconds
};
const handleUserActivity = () => {
resetTimeout();
};
// Attach event listeners to track user activity
document.addEventListener('mousemove', handleUserActivity);
document.addEventListener('keypress', handleUserActivity);
// Initialize timeout
resetTimeout();
// Clear timeout and remove event listeners on component unmount
return () => {
clearTimeout(timeout);
document.removeEventListener('mousemove', handleUserActivity);
document.removeEventListener('keypress', handleUserActivity);
};
}, [logoutUser]);
return ( return (
<div className="py-5 px-10 flex flex-col h-full bg-inherit"> <div className="py-5 px-10 flex flex-col h-full bg-inherit">
<Link to="/"> <Link to="/">