Compare commits

...

14 Commits

18 changed files with 578 additions and 299 deletions
+5 -1
View File
@@ -10,4 +10,8 @@ VITE_USERS_ENDPOINT='https://digifi-apidev.chiefsoft.net/digiusers/v1'
# ENQUIRIES CONTACTS
VITE_CALL_ENDPOINT='09099000000'
VITE_EMAIL_ENDPOINT='fcmbloan@support.com'
VITE_EMAIL_ENDPOINT='fcmbloan@support.com'
#BANK NAME
VITE_BANK_NAME='First City Monument Bank'
VITE_BANK_NAME_SHORT='FCMB'
+5 -1
View File
@@ -6,4 +6,8 @@ VITE_TWITTER_URL=https://twitter.com
VITE_INSTAGRAM_URL=https://www.instagram.com
# BACKEND END POINTS
VITE_USERS_ENDPOINT='https://digifi-apidev.chiefsoft.net/digiusers/v1'
VITE_USERS_ENDPOINT='https://digifi-apidev.chiefsoft.net/digiusers/v1'
#BANK NAME
VITE_BANK_NAME='First City Monument Bank'
VITE_BANK_NAME_SHORT='FCMB'
+5 -1
View File
@@ -6,4 +6,8 @@ TWITTER_URL=https://twitter.com
INSTAGRAM_URL=https://www.instagram.com
# BACKEND END POINTS
VITE_USERS_ENDPOINT='https://digifi-apidev.chiefsoft.net/digiusers/v1'
VITE_USERS_ENDPOINT='https://digifi-apidev.chiefsoft.net/digiusers/v1'
#BANK NAME
VITE_BANK_NAME='First City Monument Bank'
VITE_BANK_NAME_SHORT='FCMB'
+114 -93
View File
@@ -6,6 +6,8 @@ import PendingList from '../paginated-list/PendingList';
import { PendingTableList } from '../../core/models';
import { NewDateTimeFormatter } from '../../lib/NewDateTimeFormatter';
import { getUserPendingLoanList } from '../../core/apiRequest';
import {FormatAmount} from '../../lib/FormatAmount'
import PendingLoanPopout from './PendingLoanPopout';
export interface DashBoardCardProps {
title?: string;
@@ -77,12 +79,22 @@ interface DashboardHomeIntroProps {
step?: number | string;
}
interface PopoutProps<T> {
show?: boolean
data?: T
}
const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({
handleNextStep,
step,
}) => {
const { userDetails } = useSelector((state: any) => state?.userDetails); // CHECKS IF USER Details are avaliable
const [loanPopout, setLoanPopout] = useState<PopoutProps<PendingTableList>>({show:false, data:{}})
const closePopout = () => {
setLoanPopout({show:false, data:{}})
}
const [userLoanList, setUserLoanList] = useState<{
loading: boolean;
data: PendingTableList;
@@ -111,100 +123,109 @@ const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({
}, []);
return (
<div className="w-full">
{step == 1 ? (
<>
<h1 className="font-bold my-5 text-2xl">
Hello, {userDetails.firstname}
</h1>
<div className="group w-full lg:w-[27.8125rem] h-[12.75rem] mt-7 ">
<DashBoardCard
cardClass="bg-[#5C2684] relative"
desc="Begin your application and get up to "
descSpan="5 million naira loan."
descClass="leading-[1.5625rem] text-lg text-white"
descSpanClass="font-bold"
btnTitle="Apply here"
btnTextClass="w-[11.125rem] h-[2.8125rem] flex justify-center item-center btn-W text-[#FBB700]"
image={NairaBag}
imgClass="translate-y-4 -rotate-6"
onClick={() => handleNextStep({})}
/>
</div>
</>
) : (
<>
<h1 className="font-bold my-5 text-2xl">
Welcome Back, {userDetails.firstname}
</h1>
<div className="group w-full lg:w-[27.8125rem] h-[12.75rem] mt-7 ">
<DashBoardCard
cardClass="bg-[#5C2684] relative"
desc="Your loan application has been reviewed and accepted, please confirm for disbursement."
// descSpan="5 million naira loan."
descClass="leading-[1.5625rem] text-lg text-white"
// descSpanClass="font-bold"
btnTitle="View and accept"
btnTextClass="w-[11.125rem] h-[2.8125rem] flex justify-center item-center btn-W text-[#FBB700]"
image={NairaBag}
imgClass="translate-y-4 -rotate-6"
// onClick={handleNextStep}
/>
</div>
</>
)}
{userLoanList.loading ? null : (
<div className="mt-5 w-full">
<PendingList
data={userLoanList.data}
itemsPerPage={5}
tableTitle="Current Applications"
>
{(data: any) => (
<div className="w-full p-4 rounded-lg shadow-lg bg-white overflow-x-auto min-h-[250px] max-h-[450px]">
<table className="text-[12px] sm:text-base w-full table-auto">
<thead>
<tr className="text-left border-b-2">
<th className="px-1 py-4">Date</th>
<th className="px-1 py-4 text-right">Amount</th>
<th className="px-1 py-4 text-center min-w-[110px]">
Payment Term
</th>
<th className="px-1 py-4 text-center">Status</th>
<th className="px-1 py-4 text-right">Action</th>
</tr>
</thead>
<tbody>
{data.map((item: any, index: any) => (
<tr key={index || item} className="even:bg-slate-100">
<td className="px-1 py-2">
{NewDateTimeFormatter(item?.added)}
</td>
<td className="px-1 py-2 text-right">
{item?.loan_amount}
</td>
<td className="px-1 py-2 text-center">
{item?.payment_month}
</td>
<td className="px-1 py-2 text-center">
{item?.status}
</td>
<td className="px-1 py-2 text-right">
<button className="px-2 py-1 border-2 border-black">
View
<Icons name="arrow-right" />
</button>
</td>
<>
<div className="w-full">
{step == 1 ? (
<>
<h1 className="font-bold my-5 text-2xl">
Hello, {userDetails.firstname}
</h1>
<div className="group w-full lg:w-[27.8125rem] h-[12.75rem] mt-7 ">
<DashBoardCard
cardClass="bg-[#5C2684] relative"
desc="Begin your application and get up to "
descSpan="5 million naira loan."
descClass="leading-[1.5625rem] text-lg text-white"
descSpanClass="font-bold"
btnTitle="Apply here"
btnTextClass="w-[11.125rem] h-[2.8125rem] flex justify-center item-center btn-W text-[#FBB700]"
image={NairaBag}
imgClass="translate-y-4 -rotate-6"
onClick={() => handleNextStep({})}
/>
</div>
</>
) : (
<>
<h1 className="font-bold my-5 text-2xl">
Welcome Back, {userDetails.firstname}
</h1>
<div className="group w-full lg:w-[27.8125rem] h-[12.75rem] mt-7 ">
<DashBoardCard
cardClass="bg-[#5C2684] relative"
desc="Your loan application has been reviewed and accepted, please confirm for disbursement."
// descSpan="5 million naira loan."
descClass="leading-[1.5625rem] text-lg text-white"
// descSpanClass="font-bold"
btnTitle="View and accept"
btnTextClass="w-[11.125rem] h-[2.8125rem] flex justify-center item-center btn-W text-[#FBB700]"
image={NairaBag}
imgClass="translate-y-4 -rotate-6"
// onClick={handleNextStep}
/>
</div>
</>
)}
{userLoanList.loading ? null : (
<div className="mt-5 w-full">
<PendingList
data={userLoanList.data}
itemsPerPage={5}
tableTitle="Current Applications"
>
{(data: any) => (
<div className="w-full p-4 rounded-lg shadow-lg bg-white overflow-x-auto min-h-[250px] max-h-[450px]">
<table className="text-[12px] sm:text-base w-full table-auto">
<thead>
<tr className="text-left border-b-2">
<th className="px-1 py-4">Date</th>
<th className="px-1 py-4 text-right">Amount</th>
<th className="px-1 py-4 text-center min-w-[110px]">
Payment Term
</th>
<th className="px-1 py-4 text-center">Status</th>
<th className="px-1 py-4 text-right">Action</th>
</tr>
))}
</tbody>
</table>
</div>
)}
</PendingList>
</div>
)}
</div>
</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">
{FormatAmount(item?.loan_amount)}
</td>
<td className="px-1 py-2 text-center">
{item?.payment_month}
</td>
<td className="px-1 py-2 text-center">
<button
className={`${!item?.status_text?.button && 'pointer-events-none border-0'} border p-2`}
onClick={()=>setLoanPopout({show:true, data:item})}
>
{item?.status_text?.text || 'Pending'}
</button>
</td>
<td className="flex justify-end px-1 py-2 text-right">
<button className="flex flex-nowrap items-center px-2 py-1 border-2 border-black">
View
<Icons name="arrow-right" />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</PendingList>
</div>
)}
</div>
{loanPopout.show && <PendingLoanPopout data={loanPopout.data} action={closePopout} />}
</>
);
};
@@ -0,0 +1,75 @@
import React from 'react'
import ModalWrapper from '../modal/ModalWrapper'
import { PendingTableList } from '../../core/models'
import { NewDateTimeFormatter } from '../../lib/NewDateTimeFormatter'
interface Props<T> {
action?: ()=>void
data?: T
}
export default function PendingLoanPopout({data, action}:Props<PendingTableList>) {
const addCard = () => {
console.log('good')
}
return (
<ModalWrapper>
<div className='modal-container'>
<div className='modal-header'>
<h1 className='modal-title'>Add Card</h1>
<button
type="button"
className="modal-close-btn"
name='cancel'
onClick={action}
>
<svg
width="36"
height="36"
viewBox="0 0 36 36"
fill="none"
className="fill-current"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M36 16.16C36 17.4399 36 18.7199 36 20.0001C35.7911 20.0709 35.8636 20.2554 35.8385 20.4001C34.5321 27.9453 30.246 32.9248 22.9603 35.2822C21.9006 35.6251 20.7753 35.7657 19.6802 35.9997C18.4003 35.9997 17.1204 35.9997 15.8401 35.9997C15.5896 35.7086 15.2189 35.7732 14.9034 35.7093C7.77231 34.2621 3.08728 30.0725 0.769671 23.187C0.435002 22.1926 0.445997 21.1199 0 20.1599C0 18.7198 0 17.2798 0 15.8398C0.291376 15.6195 0.214408 15.2656 0.270759 14.9808C1.71321 7.69774 6.02611 2.99691 13.0428 0.700951C14.0118 0.383805 15.0509 0.386897 15.9999 0C17.2265 0 18.4532 0 19.6799 0C19.7156 0.124041 19.8125 0.136067 19.9225 0.146719C27.3 0.868973 33.5322 6.21922 35.3801 13.427C35.6121 14.3313 35.7945 15.2484 36 16.16ZM33.011 18.0787C33.0433 9.77105 26.3423 3.00309 18.077 2.9945C9.78479 2.98626 3.00344 9.658 2.98523 17.8426C2.96667 26.1633 9.58859 32.9601 17.7602 33.0079C26.197 33.0577 32.9787 26.4186 33.011 18.0787Z"
fill=""
fillOpacity="0.6"
/>
<path
d="M15.9309 18.023C13.9329 16.037 12.007 14.1207 10.0787 12.2072C9.60071 11.733 9.26398 11.2162 9.51996 10.506C9.945 9.32677 11.1954 9.0811 12.1437 10.0174C13.9067 11.7585 15.6766 13.494 17.385 15.2879C17.9108 15.8401 18.1633 15.7487 18.6375 15.258C20.3586 13.4761 22.1199 11.7327 23.8822 9.99096C24.8175 9.06632 26.1095 9.33639 26.4967 10.517C26.7286 11.2241 26.3919 11.7413 25.9133 12.2178C24.1757 13.9472 22.4477 15.6855 20.7104 17.4148C20.5228 17.6018 20.2964 17.7495 20.0466 17.9485C22.0831 19.974 24.0372 21.8992 25.9689 23.8468C26.9262 24.8119 26.6489 26.1101 25.4336 26.4987C24.712 26.7292 24.2131 26.3441 23.7455 25.8757C21.9945 24.1227 20.2232 22.3892 18.5045 20.6049C18.0698 20.1534 17.8716 20.2269 17.4802 20.6282C15.732 22.4215 13.9493 24.1807 12.1777 25.951C11.7022 26.4262 11.193 26.7471 10.4738 26.4537C9.31345 25.9798 9.06881 24.8398 9.98589 23.8952C11.285 22.5576 12.6138 21.2484 13.9387 19.9355C14.5792 19.3005 15.2399 18.6852 15.9309 18.023Z"
fill="#"
fillOpacity="0.6"
/>
</svg>
</button>
</div>
<div className='modal-body'>
<div className='w-full flex flex-col gap-2'>
<div className='py-2 w-full flex gap-1'>
<p className='font-semibold'>ID :</p>
<p>{data?.application_uid}</p>
</div>
<div className='py-2 w-full flex gap-1'>
<p className='font-semibold'>Loan Amount :</p>
<p>{data?.loan_amount}</p>
</div>
<div className='py-2 w-full flex gap-1'>
<p className='font-semibold'>Payment Month :</p>
<p>{data?.payment_month}</p>
</div>
<div className='py-2 w-full flex gap-1'>
<p className='font-semibold'>Added :</p>
<p>{NewDateTimeFormatter(data?.added)}</p>
</div>
</div>
</div>
<div className='modal-footer'>
<button onClick={action} name='cancel' className='custom-btn text-red-500 border border-red-500 hover:text-red-700'>Cancel</button>
<button onClick={addCard} name='proceed' className='custom-btn border border-sky-500 text-sky-500 hover:text-sky-700'>Add Card</button>
</div>
</div>
</ModalWrapper>
)
}
@@ -32,10 +32,9 @@ export default function DashboardHomeAttestation({handleNextStep, applicationDet
//FUNCTION TO HANDLE LOAN APPLICATION
const handleSubmit = (values:any) => {
delete values.checked
applyForLoan({...applicationDetails, disbursement: values}).then(res=>{
applyForLoan({...applicationDetails,disbursement_account: values?.account, disbursement: values}).then(res=>{
console.log('APPLY FOR LOAN', res)
handleNextStep({disbursement: values})
console.log('ApplicationDetails', {...applicationDetails, disbursement: values})
}).catch(err=>{
console.log(err)
})
@@ -53,7 +52,7 @@ export default function DashboardHomeAttestation({handleNextStep, applicationDet
<Stepper step={4} />
</div>
<p className='my-10 text-red-500 text-lg md:text-2xl'>Applicant's Attestation and Debit Instruction</p>
<p className='text-red-500 text-base'>NB: Must be your FCMB account number</p>
<p className='text-red-500 text-base'>NB: Must be your {import.meta.env.VITE_BANK_NAME_SHORT} account number</p>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
@@ -84,7 +83,7 @@ export default function DashboardHomeAttestation({handleNextStep, applicationDet
className='w-4 h-4 p-2 accent-purple-600 text-purple-600 bg-gray-100 border-gray-300 rounded focus:ring-purple-500'
onChange={props.handleChange}
/>
<p className='text-[12px] text-justify'>By pressing, you agree that you have read, understood and accept the <span className='text-blue-600'>applicatant's attestation</span> and <span className='text-blue-600'>terms and conditions</span> for FCMB
<p className='text-[12px] text-justify'>By pressing, you agree that you have read, understood and accept the <span className='text-blue-600'>applicatant's attestation</span> and <span className='text-blue-600'>terms and conditions</span> for {import.meta.env.VITE_BANK_NAME_SHORT}
premium salary loan. You also give us permission to collect financial information and run credit checks on the account provided through our partners
</p>
</div>
@@ -1,12 +1,20 @@
import {useState, useEffect} from 'react'
import { Button, InputCompOne, Stepper } from '../../shared/index';
import {Formik, Form} from 'formik'
import * as Yup from "yup";
import { getEmployersList } from '../../../core/apiRequest';
type Props = {
handleNextStep:(value:{})=>any
}
// type EmployerProps = {
// loading?: boolean,
// data?: Array<{[index:string]: string}> | {[index:string]: Array<{[index:string]: string}> }
// }
const initialValues = {
job_title: "",
name: "",
@@ -18,24 +26,37 @@ const initialValues = {
monthly_salary: "",
salary_payment_date: "",
employment_id: "",
highest_eductaion: ""
highest_eductaion: "",
employer_uid: "",
isChecked: false
};
// To get the validation schema
const validationSchema = Yup.object().shape({
isChecked: Yup.bool(), // use bool instead of boolean
// .oneOf([true, false], "You must accept the terms and conditions"),
job_title: Yup.string()
.required("Required"),
name: Yup.string()
.required("Required"),
sector: Yup.string()
.required("Required"),
industry: Yup.string()
.required("Required"),
name: Yup.string().when('isChecked', {
is: true,
then: () => Yup.string().required('required'),
otherwise: () => Yup.string(),
}),
sector: Yup.string().when('isChecked', {
is: true,
then: () => Yup.string().required('required'),
}),
industry: Yup.string().when('isChecked', {
is: true,
then: () => Yup.string().required('required'),
}),
resumption_date: Yup.string()
.required("Required"),
email: Yup.string()
.email("Invalid")
.required("Required"),
email: Yup.string().when('isChecked', {
is: true,
then: () => Yup.string().required('required'),
})
.email("Invalid"),
annual_income: Yup.string()
.required("Required")
.test("no-e", "Invalid", (value:any) => {
@@ -58,16 +79,41 @@ const validationSchema = Yup.object().shape({
.required("Required"),
highest_eductaion: Yup.string()
.required("Required"),
employer_uid: Yup.string().when('isChecked', {
is: false,
then: () => Yup.string().required('required'),
}),
});
export default function DashboardHomeEmploymentInfo({handleNextStep}:Props) {
const [employersList, setEmployersList] = useState<any>({
loading: true,
data: []
})
//FUNCTION TO HANDLE SUBMIT
const handleSubmit = (values:any) => {
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 (
<div className="w-full">
@@ -83,155 +129,188 @@ export default function DashboardHomeEmploymentInfo({handleNextStep}:Props) {
<Form>
<div className="mt-[3.25rem] flex flex-col gap-9">
<p className='text-red-500 text-lg md:text-2xl'>Employment Informaton</p>
<div className="flex items-center gap-[4.125rem]">
<InputCompOne
parentClass="max-w-[25.875rem] w-full flex flex-col gap-4"
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="max-w-[25.875rem] w-full flex flex-col gap-4"
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 : ''}
/>
<div className="flex flex-col lg:flex-row items-start gap-[2rem]">
<div className='w-full lg:max-w-[30rem] flex flex-col'>
<div className='w-full gap-[2rem]'>
<InputCompOne
parentClass="w-full"
name="employer_uid"
floatLabel="Employer Name"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={{loading:employersList?.loading, data: employersList?.data?.records}}
selectValue={props.values.employer_uid}
onChange={props.handleChange}
error={(props.errors.employer_uid && props.touched.employer_uid) ? props.errors.employer_uid : ''}
disabled={props.values.isChecked}
/>
<div className='flex gap-4 items-start my-2'>
<input
type='checkbox'
name="isChecked"
className='w-4 h-4 p-2 accent-purple-600 text-purple-600 bg-gray-100 border-gray-300 rounded focus:ring-purple-500'
onChange={props.handleChange}
checked={props.values.isChecked}
/>
<p className='text-[12px] text-justify'>Check here if employer is not on the list</p>
</div>
<div className={`hidden p-4 ${props.values.isChecked && 'hidden'}`}>
Name: {'Name'}
</div>
</div>
<div className={`w-full flex flex-col gap-[2rem] ${!props.values.isChecked && 'hidden'}`}>
<InputCompOne
parentClass="w-full"
name="name"
floatLabel="Employer name"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Mr. Mark John"
value={props.values.name}
onChange={props.handleChange}
error={(props.errors.name && props.touched.name) ? props.errors.name : ''}
/>
<InputCompOne
parentClass="w-full"
name="email"
floatLabel="Employers official email"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="example@gmail.com"
value={props.values.email}
onChange={props.handleChange}
error={(props.errors.email && props.touched.email) ? props.errors.email : ''}
/>
<InputCompOne
parentClass="w-full"
name="industry"
floatLabel="Select your industry"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={industry}
selectValue={props.values.industry}
onChange={props.handleChange}
error={(props.errors.industry && props.touched.industry) ? props.errors.industry : ''}
/>
<InputCompOne
parentClass="w-full"
name="sector"
floatLabel="Job Sector"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={jobSector}
selectValue={props.values.sector}
onChange={props.handleChange}
error={(props.errors.sector && props.touched.sector) ? props.errors.sector : ''}
/>
</div>
</div>
<div className='w-full lg:max-w-[30rem] flex flex-col gap-[2rem]'>
<InputCompOne
parentClass="w-full"
name="job_title"
floatLabel="Job Title"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="Software Engineer"
value={props.values.job_title}
onChange={props.handleChange}
error={(props.errors.job_title && props.touched.job_title) ? props.errors.job_title : ''}
/>
<InputCompOne
parentClass="w-full"
name="highest_eductaion"
floatLabel="Highest level of education"
// labelClass="font-bold text-[1.125rem]"
select={true}
selectClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
selectOptions={highestEductaion}
selectValue={props.values.highest_eductaion}
onChange={props.handleChange}
error={(props.errors.highest_eductaion && props.touched.highest_eductaion) ? props.errors.highest_eductaion : ''}
/>
<div className="w-full flex flex-col sm:flex-row items-center gap-4">
<InputCompOne
parentClass="w-full"
name="resumption_date"
floatLabel="Date of resumption"
// labelClass="font-bold text-[1.125rem]"
input
inputType='date'
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="12/12/2015"
value={props.values.resumption_date}
onChange={props.handleChange}
error={(props.errors.resumption_date && props.touched.resumption_date) ? props.errors.resumption_date : ''}
/>
<InputCompOne
parentClass="w-full"
name="salary_payment_date"
floatLabel="Salary payment date"
// labelClass="font-bold text-[1.125rem]"
input
inputType='date'
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="30th of every month"
value={props.values.salary_payment_date}
onChange={props.handleChange}
error={(props.errors.salary_payment_date && props.touched.salary_payment_date) ? props.errors.salary_payment_date : ''}
/>
</div>
<div className="w-full flex flex-col sm:flex-row items-center gap-4">
<InputCompOne
parentClass="w-full"
name="annual_income"
floatLabel="Annual Income"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem] text-right"
placeholder="1,200,000"
value={props.values.annual_income}
onChange={props.handleChange}
error={(props.errors.annual_income && props.touched.annual_income) ? props.errors.annual_income : ''}
/>
<InputCompOne
parentClass="w-full"
name="monthly_salary"
floatLabel="Net monthly salary"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem] text-right"
placeholder="100,000"
value={props.values.monthly_salary}
onChange={props.handleChange}
error={(props.errors.monthly_salary && props.touched.monthly_salary) ? props.errors.monthly_salary : ''}
/>
</div>
<InputCompOne
parentClass="w-full"
name="employment_id"
floatLabel="Employee ID"
// labelClass="font-bold text-[1.125rem]"
input
inputClass="w-full h-[3.625rem] bg-[#EFEFEF] px-4 rounded-[.375rem]"
placeholder="LS/001/005"
value={props.values.employment_id}
onChange={props.handleChange}
error={(props.errors.employment_id && props.touched.employment_id) ? props.errors.employment_id : ''}
/>
<div className="w-full">
<Button
className="my-4 btn-Y text-black w-full h-11"
text="Next"
type="submit"
/>
</div>
</div>
</div>
<div 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>
</Form>
)}
+1 -1
View File
@@ -17,7 +17,7 @@ const BottomFooterOne = () => {
<CustomerLinks />
</div>
<p className="text-[.8125rem] text-[#333] leading-[1.42857]">
© <span>{date}</span> First City Monument Bank (Licensed by the
© <span>{date}</span> {import.meta.env.VITE_BANK_NAME} (Licensed by the
Central Bank of Nigeria)
</p>
</div>
+1 -1
View File
@@ -8,7 +8,7 @@ export default function Footer() {
<div className="w-full h-[5.4375rem] bg-[F7F7F7] flex items-center">
<div className="containerMode flex justify-center md:justify-between items-center flex-wrap gap-2">
<p className="text-[.9375rem] tracking-[2%] font-semibold text-[#969696]">
{date} @ First City Monument Bank Limited
{date} @ {import.meta.env.VITE_BANK_NAME} Limited
</p>
<div className="footer-social-icons flex justify-end items-center gap-2">
{renderSocialLinks()}
@@ -12,7 +12,7 @@ const EligiblityBox = () => {
You must have a valid BVN
</li>
<li className="text-base leading-[1.5625rem]">
Must have a salary or current bank account with FCMB
Must have a salary or current bank account with {import.meta.env.VITE_BANK_NAME_SHORT}
</li>
</ul>
</div>
+14
View File
@@ -0,0 +1,14 @@
import { ReactNode } from "react"
type Props = {
children: ReactNode
}
export default function ModalWrapper({children}:Props) {
return (
<div className="z-50 fixed inset-0">
<div className="bg-black/50 fixed inset-0"></div>
<div className="relative h-full flex flex-col justify-center items-center">{children}</div>
</div>
)
}
+22 -9
View File
@@ -17,7 +17,7 @@ export interface InputCompOneProps {
selectValue?: string;
input?: boolean;
select?: boolean;
selectOptions?: {loading:boolean, data:{ value: string; label: string }[]};
selectOptions?: {loading:boolean, data:{ [index: string]: string; }[]};
inputType?: string;
inputClass?: string;
parentInputClass?: string;
@@ -25,6 +25,7 @@ export interface InputCompOneProps {
parentClass?: string;
maxLength?: number;
error?: string;
disabled?: boolean
}
const InputCompOne = forwardRef<HTMLInputElement, InputCompOneProps>(
@@ -52,6 +53,7 @@ const InputCompOne = forwardRef<HTMLInputElement, InputCompOneProps>(
parentClass,
maxLength,
error,
disabled=false
},
forwardedRef
) => {
@@ -78,6 +80,7 @@ const InputCompOne = forwardRef<HTMLInputElement, InputCompOneProps>(
className={`px-4 ${floatLabel && 'peer pt-4 placeholder:text-transparent'} ${inputClass}`}
maxLength={maxLength}
id={label ? label : floatLabel}
disabled={disabled}
/>
{floatLabel &&
<label
@@ -96,17 +99,27 @@ const InputCompOne = forwardRef<HTMLInputElement, InputCompOneProps>(
name={name}
id={label ? label : floatLabel}
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}
disabled={disabled}
>
{selectOptions.loading ?
<option value=''>Loading</option>
: selectOptions.data.length ?
selectOptions.data.map(({ value, label }) => (
<option key={value} value={value}>
{label}
</option>
))
<option value=''>Loading...</option>
: selectOptions.data.length && name == 'employer_uid' ?
<>
<option value=''>Please Select</option>
{selectOptions.data.map(({ uid, name }) => (
<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>
}
+8
View File
@@ -40,4 +40,12 @@ export const getUserPendingLoanList = (uid:string) => {
// customer_uid: localStorage.getItem('uid'),
}
return getAuxEnd(`/dash?uid=${uid}`, reqData)
}
// FUNCTION TO GET LIST OF EMPLOYERS
export const getEmployersList = () => {
let reqData = {
// customer_uid: localStorage.getItem('uid'),
}
return getAuxEnd(`/employers`, reqData)
}
+2 -2
View File
@@ -10,8 +10,8 @@ export function postAuxEnd(uri: string, reqData: any): Promise<any> {
// console.log('SAMPLE',innerReqData)
// formData.append(reqData[value][innerValue], reqData[value][innerValue]);
// }
// formData.append(value, JSON.stringify(reqData[value]));
formData.append(value, reqData[value]);
formData.append(value, JSON.stringify(reqData[value]));
// formData.append(value, reqData[value]);
} else {
formData.append(value, reqData[value]);
}
+7 -2
View File
@@ -19,9 +19,14 @@ export interface User {
export type PendingTableList = {
status?: string | boolean;
status?: string | boolean
application_uid?: string
added?: string
loan_amount?: string
payment_month?: string
payment_month?: string
status_text?: {
text?: string
button?: boolean
advise?: string
}
}[];
+29
View File
@@ -17,4 +17,33 @@ body {
/* @apply container mx-auto px-5 xxs:max-w-full sm:max-w-[98%] lg:max-w-[1100px]; */
@apply container mx-auto px-5 max-w-[1500px]
}
.custom-btn{@apply min-w-[100px] transition-all duration-300 p-2 rounded-full}
/* MODAL COMPONENT */
.modal-container{
animation-name: zoom;
animation-duration: .2s;
animation-timing-function: linear;
@apply w-4/5 max-w-[600px] bg-white shadow-md rounded-2xl overflow-hidden
}
.modal-header {
@apply w-full flex items-center justify-between p-5 py-8 border-b bg-sky-500
}
.modal-title {
@apply text-2xl leading-8 font-bold text-white dark:text-white tracking-wide flex items-center
}
.modal-close-btn {
@apply text-white dark:text-red-500
}
.modal-body {
@apply w-full p-5 min-h-[150px] max-h-[500px] overflow-y-auto break-words flex flex-col justify-center items-center
}
.modal-footer {
@apply w-full p-5 border-t flex justify-between items-center gap-4
}
/* MODAL COMPONENT */
}
+24
View File
@@ -0,0 +1,24 @@
// FUNCTION TO RETURN AMOUNT TO TWO DECIMAL PLACES
export const FormatAmount = (
amount = "00",
) => {
// Convert the number to a string
let numStr = String(amount);
// Split the string into integer and decimal parts
let parts = numStr.split(".");
let integerPart = parts[0] || "";
let decimalPart = parts[1] || "";
// Add thousands separators to the integer part
let formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
// Truncate or pad the decimal part to two decimal points
let formattedDecimal = decimalPart.slice(0, 2).padEnd(2, "0");
// Combine the formatted integer and decimal parts
let formattedNumber = '₦' + formattedInteger + '.' + formattedDecimal;
// return formattedNumber;
return formattedNumber;
};
+24 -24
View File
@@ -82,7 +82,7 @@ export const _lowerMenuItems = [
name: 'SAVINGS ACCOUNTS',
linkPath: '/savings-accounts',
subItems: [
{ name: 'FCMB EASY ACCOUNT', linkPath: '/easy-account' },
{ name: `${import.meta.env.VITE_BANK_NAME_SHORT} EASY ACCOUNT`, linkPath: '/easy-account' },
{ name: 'KIDS (0-17 YRS)', linkPath: '/kids' },
{ name: 'FLEXX (18-30 YRS)', linkPath: '/flexx-account' },
{
@@ -94,7 +94,7 @@ export const _lowerMenuItems = [
linkPath: '/fcmb-premium-savings-account',
},
{
name: 'FCMB SALARY SAVINGS ACCOUNT',
name: `${import.meta.env.VITE_BANK_NAME_SHORT} SALARY SAVINGS ACCOUNT`,
linkPath: '/fcmb-salary-savings-account',
},
{
@@ -150,7 +150,7 @@ export const _lowerMenuItems = [
name: 'WAYS TO BANK',
linkPath: '/ways-to-bank',
subItems: [
{ name: 'FCMB MOBILE', linkPath: '/fcmb-mobile' },
{ name: `${import.meta.env.VITE_BANK_NAME_SHORT} MOBILE`, linkPath: '/fcmb-mobile' },
{
name: 'RETAIL INTERNET BANKING',
linkPath: 'https://ibank.fcmb.com/',
@@ -160,10 +160,10 @@ export const _lowerMenuItems = [
{ name: 'OUR ATM NETWORK', linkPath: '/atm-network' },
{ name: 'FLASHMECASH', linkPath: '/flashme-cash' },
{ name: 'OUR CARDS', linkPath: '/our-cards' },
{ name: 'FCMB ELECTRONIC CHANNELS', linkPath: '/e-channels' },
{ name: `${import.meta.env.VITE_BANK_NAME_SHORT} ELECTRONIC CHANNELS`, linkPath: '/e-channels' },
{ name: 'AGENT BANKING', linkPath: '/agent-banking' },
{
name: 'FCMB SECURE COMMUNICATION',
name: `${import.meta.env.VITE_BANK_NAME_SHORT} SECURE COMMUNICATION`,
linkPath: '/secure-communication',
},
{ name: 'TEMI', linkPath: '/temi' },
@@ -197,7 +197,7 @@ export const _lowerMenuItems = [
name: 'CORPORATE CURRENT ACCOUNT',
linkPath: '/corporate-current-account',
},
{ name: 'FCMB BUSINESS ACCOUNT', linkPath: '/fcmb-business-account' },
{ name: `${import.meta.env.VITE_BANK_NAME_SHORT} BUSINESS ACCOUNT`, linkPath: '/fcmb-business-account' },
{
name: 'DOMICILIARY CURRENT ACCOUNT',
linkPath: '/business-domiciliary-current-account',
@@ -265,12 +265,12 @@ export const _lowerMenuItems = [
linkPath: 'https://www.fcmbonline.com/',
},
{ name: 'OUR ATM NETWORK', linkPath: '/atm-network2' },
{ name: 'FCMB ONLINE', linkPath: '/fcmb-online-business' },
{ name: `${import.meta.env.VITE_BANK_NAME_SHORT} ONLINE`, linkPath: '/fcmb-online-business' },
{
name: 'FCMB ELECTRONIC CHANNELS',
name: `${import.meta.env.VITE_BANK_NAME_SHORT} ELECTRONIC CHANNELS`,
linkPath: 'https://www.fcmb.com/e-channels',
},
{ name: 'FCMB BUSINESS APP', linkPath: '/FCMB-business-app' },
{ name: `${import.meta.env.VITE_BANK_NAME_SHORT} BUSINESS APP`, linkPath: '/FCMB-business-app' },
],
},
],
@@ -292,33 +292,33 @@ export const _lowerMenuItems = [
name: 'GROUP & SUBSIDIARIES',
linkPath: '',
subItems: [
{ name: 'FCMB GROUP PLC', linkPath: 'https://www.fcmbgroup.com/' },
{ name: `${import.meta.env.VITE_BANK_NAME_SHORT} GROUP PLC`, linkPath: 'https://www.fcmbgroup.com/' },
{
name: 'CSL STOCKBROKERS',
linkPath: 'https://www.cslstockbrokers.com/',
},
{
name: 'FCMB CAPITAL MARKETS',
name: `${import.meta.env.VITE_BANK_NAME_SHORT} CAPITAL MARKETS`,
linkPath: 'https://www.fcmbcapitalmarketsng.com/',
},
{
name: 'FCMB ASSET MANAGEMENT',
name: `${import.meta.env.VITE_BANK_NAME_SHORT} ASSET MANAGEMENT`,
linkPath: 'https://www.fcmbassetmanagement.com/index/',
},
{
name: 'FCMB MICROFINANCE BANK',
name: `${import.meta.env.VITE_BANK_NAME_SHORT} MICROFINANCE BANK`,
linkPath: '/fcmb-microfinance-initiative',
},
{ name: 'FCMB UK', linkPath: 'http://www.fcmbuk.com/' },
{ name: `${import.meta.env.VITE_BANK_NAME_SHORT} UK`, linkPath: 'http://www.fcmbuk.com/' },
{
name: 'FCMB PENSIONS LIMITED',
name: `${import.meta.env.VITE_BANK_NAME_SHORT} PENSIONS LIMITED`,
linkPath: 'https://www.fcmbpensions.com/',
},
{
name: 'CREDIT DIRECT LIMITED',
linkPath: 'https://www.creditdirect.ng',
},
{ name: 'FCMB TRUSTEES', linkPath: 'http://fcmbtrustees.com/' },
{ name: `${import.meta.env.VITE_BANK_NAME_SHORT} TRUSTEES`, linkPath: 'http://fcmbtrustees.com/' },
],
},
{
@@ -372,7 +372,7 @@ export const _lowerMenuItems = [
linkPath: '/current-campaigns-promos',
subItems: [
{
name: 'BOOK YOUR FLIGHTS AND PAY IN INSTALMENTS WITH FCMB',
name: `BOOK YOUR FLIGHTS AND PAY IN INSTALMENTS WITH ${import.meta.env.VITE_BANK_NAME_SHORT}`,
linkPath: '/247travels',
},
{ name: 'REFER AND WIN', linkPath: '/refer-and-win' },
@@ -447,21 +447,21 @@ export const footerItems = [
{
category: 'GROUP & SUBSIDIARIES',
subItems: [
{ text: 'FCMB GROUP PLC', href: 'https://www.fcmbgroup.com/' },
{ text: `${import.meta.env.VITE_BANK_NAME_SHORT} GROUP PLC`, href: 'https://www.fcmbgroup.com/' },
{ text: 'CSL STOCKBROKERS', href: 'https://www.cslstockbrokers.com/' },
{
text: 'FCMB CAPITAL MARKETS',
text: `${import.meta.env.VITE_BANK_NAME_SHORT} CAPITAL MARKETS`,
href: 'https://www.fcmbcapitalmarketsng.com/',
},
{
text: 'FCMB ASSET MANAGEMENT',
text: `${import.meta.env.VITE_BANK_NAME_SHORT} ASSET MANAGEMENT`,
href: 'https://www.fcmbassetmanagement.com/index/',
},
{ text: 'FCMB MICROFINANCE BANK', href: '/fcmb-microfinance-initiative' },
{ text: 'FCMB UK', href: 'http://www.fcmbuk.com/' },
{ text: 'FCMB PENSIONS LIMITED', href: 'https://www.fcmbpensions.com/' },
{ text: `${import.meta.env.VITE_BANK_NAME_SHORT} MICROFINANCE BANK`, href: '/fcmb-microfinance-initiative' },
{ text: `${import.meta.env.VITE_BANK_NAME_SHORT} UK`, href: 'http://www.fcmbuk.com/' },
{ text: `${import.meta.env.VITE_BANK_NAME_SHORT} PENSIONS LIMITED`, href: 'https://www.fcmbpensions.com/' },
{ text: 'CREDIT DIRECT LIMITED', href: 'https://www.creditdirect.ng' },
{ text: 'FCMB TRUSTEES', href: 'http://fcmbtrustees.com/' },
{ text: `${import.meta.env.VITE_BANK_NAME_SHORT} TRUSTEES`, href: 'http://fcmbtrustees.com/' },
],
},
{