Compare commits

..

3 Commits

Author SHA1 Message Date
Elias 7b9d648ae9 automatic user logout after inactivity. Dafault to 7 mins 2024-05-14 13:54:19 +01:00
tokslaw 4d258a965b Merge branch 'layout-padding' of DigiFi/digifi-www into master 2024-05-09 17:29:13 +00:00
victorAnumudu e20b7e32f1 adjusted layout padding on mobile view 2024-05-06 19:00:56 +01:00
4 changed files with 220 additions and 242 deletions
@@ -152,12 +152,12 @@ const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({ handleNextStep, step
> >
{(data:any)=>( {(data:any)=>(
<div className="w-full p-4 rounded-lg shadow-lg bg-white overflow-x-auto min-h-[250px] max-h-[450px]"> <div className="w-full p-4 rounded-lg shadow-lg bg-white overflow-x-auto min-h-[250px] max-h-[450px]">
<table className="w-full table-auto"> <table className="text-[12px] sm:text-base w-full table-auto">
<thead> <thead>
<tr className='text-left border-b-2'> <tr className='text-left border-b-2'>
<th className='px-1 py-4'>Date</th> <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-right'>Amount</th>
<th className='px-1 py-4 text-center'>Payment Term</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-center'>Status</th>
<th className='px-1 py-4'>Action</th> <th className='px-1 py-4'>Action</th>
</tr> </tr>
+64 -120
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,100 +52,72 @@ 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>( const [requestStatusBVN, setRequestStatusBVN] = React.useState<RequestStatus>({loading:false, status:undefined, message:''});
{ loading: false, status: undefined, message: '' }
);
const [requestStatusOTP, setRequestStatusOTP] = React.useState<RequestStatus>( const [requestStatusOTP, setRequestStatusOTP] = React.useState<RequestStatus>({loading:false, status:undefined, message:''});
{ 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) => { const bvnValidation = (values:any) => { // Function to Validate BVN
// Function to Validate BVN let bvn = values.bvn
let bvn = values.bvn; setRequestStatusBVN({loading:true, status:false, message:''})
setRequestStatusBVN({ loading: true, status: false, message: '' }); validateBVN({bvn}).then(res => {
validateBVN({ bvn })
.then((res) => {
if(!res || !res.data.call_return){ if(!res || !res.data.call_return){
setBvnIsValid({ verification_id: '', valid: false }); setBvnIsValid({verification_id:'', valid: false})
setRequestStatusBVN({ setRequestStatusBVN({loading:false, status:false, message:'unable to verify BVN'})
loading: false,
status: false,
message: 'unable to verify BVN',
});
return setTimeout(()=>{ return setTimeout(()=>{
setRequestStatusBVN({ loading: false, status: false, message: '' }); setRequestStatusBVN({loading:false, status:false, message:''})
}, 4000); }, 4000)
} }
setBvnIsValid({ setBvnIsValid({verification_id:res.data.verification_id, valid: true})
verification_id: res.data.verification_id, setRequestStatusBVN({loading:false, status:true, message:'verified'})
valid: true, }).catch(err => {
}); setBvnIsValid({verification_id:'', valid: false})
setRequestStatusBVN({ console.log(err)
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
// Function to VERIFY OTP AND LOGIN USER setRequestStatusOTP({loading:true, status:false, message:''})
setRequestStatusOTP({ loading: true, status: false, message: '' });
// console.log('values', values) // console.log('values', values)
verifyOTP({ ...values, verification_id: bvnIsValid.verification_id }) verifyOTP({...values, verification_id:bvnIsValid.verification_id}).then(res=>{
.then((res) => {
if(!res || !res.data.call_return){ if(!res || !res.data.call_return){
setRequestStatusOTP({ setRequestStatusOTP({loading:false, status:false, message:'wrong otp'})
loading: false,
status: false,
message: 'wrong otp',
});
return setTimeout(()=>{ return setTimeout(()=>{
setRequestStatusOTP({ loading: false, status: false, message: '' }); setRequestStatusOTP({loading:false, status:false, message:''})
}, 4000); },4000)
} }
// console.log(res.data) // console.log(res.data)
localStorage.setItem('token', res.data?.token); localStorage.setItem('token', res.data?.token)
localStorage.setItem('uid', res?.data?.customer[0]?.uid); localStorage.setItem('uid', res?.data?.customer[0]?.uid)
dispatch(updateUserDetails({ ...res?.data?.customer[0] })); dispatch(updateUserDetails({ ...res?.data?.customer[0] }));
navigate(RouteHandler.dashboardHome, { replace: true }); navigate(RouteHandler.dashboardHome, {replace:true})
}) }).catch(err => {
.catch((err) => { setRequestStatusOTP({loading:false, status:false, message:'something went wrong, try again'})
setRequestStatusOTP({ console.log(err)
loading: false,
status: false,
message: 'something went wrong, try again',
});
console.log(err);
return setTimeout(()=>{ return setTimeout(()=>{
setRequestStatusOTP({ loading: false, status: false, message: '' }); setRequestStatusOTP({loading:false, status:false, message:''})
}, 4000); },4000)
}); })
}; };
return ( return (
@@ -164,7 +136,7 @@ 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] ">
<div className="w-full"> <div className='w-full'>
<InputCompOne <InputCompOne
parentClass="flex flex-col gap-2" parentClass="flex flex-col gap-2"
label="Enter Your BVN " label="Enter Your BVN "
@@ -178,22 +150,9 @@ 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.bvn} value={props.values.bvn}
onChange={props.handleChange} onChange={props.handleChange}
error={ error={(props.errors.bvn && props.touched.bvn) && props.errors.bvn}
props.errors.bvn && props.touched.bvn && props.errors.bvn
}
maxLength={11}
/> />
<p <p className={`p-2 ${!requestStatusBVN.status ? 'text-red-500' : 'text-emerald-500'}`}>{requestStatusBVN.loading ? 'verifying...' : requestStatusBVN.message}</p>
className={`p-2 ${
!requestStatusBVN.status
? 'text-red-500'
: 'text-emerald-500'
}`}
>
{requestStatusBVN.loading
? 'verifying...'
: requestStatusBVN.message}
</p>
</div> </div>
{bvnIsValid.valid && ( {bvnIsValid.valid && (
<InputCompOne <InputCompOne
@@ -209,39 +168,24 @@ 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}
error={ error={(props.errors.otp && props.touched.otp) && props.errors.otp}
props.errors.otp && props.touched.otp && props.errors.otp
}
maxLength={5}
/> />
)} )}
<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={ disabled={requestStatusBVN.loading || (!props.values.otp && bvnIsValid.valid)}
requestStatusBVN.loading ||
(!props.values.otp && bvnIsValid.valid)
}
> >
Enter Enter
</button> </button>
<p <p className={`p-2 ${!requestStatusOTP.status ? 'text-red-500' : 'text-emerald-500'}`}>{requestStatusOTP.message}</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 ***Every personal information attached to your BVN is safe and
and secure. It is only important for us to verify your secure. It is only important for us to verify your information and
information and also give you access to your application also give you access to your application profile/account.
profile/account.
</p> </p>
) : ( ) : (
<p className="text-[#5C2684] mt-[1.5625rem] w-fit"> <p className="text-[#5C2684] mt-[1.5625rem] w-fit">
+68 -34
View File
@@ -1,31 +1,62 @@
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';
type Props = { type Props = {
asideDisplay?: () => void; asideDisplay?: () => void;
logoutUser: () => void logoutUser: () => void;
}; };
export default function Aside({ asideDisplay, logoutUser }: Props) { export default function Aside({ asideDisplay, logoutUser }: Props) {
const { pathname } = useLocation(); const { pathname } = useLocation();
const [openNestedLink, setOpenNestedLink] = useState<{ name: string | null }>( const [openNestedLink, setOpenNestedLink] = useState<{ name: string | null }>(
{ name: "" } { name: '' }
); );
const handleOpenNestedLink = (e: any) => { const handleOpenNestedLink = (e: any) => {
if (!e || !e.target) { if (!e || !e.target) {
return setOpenNestedLink({ name: "" }); return setOpenNestedLink({ name: '' });
} }
if (openNestedLink.name && openNestedLink.name == e.target.name) { if (openNestedLink.name && openNestedLink.name == e.target.name) {
setOpenNestedLink({ name: "" }); setOpenNestedLink({ name: '' });
} else { } else {
setOpenNestedLink({ name: e.target.name }); setOpenNestedLink({ name: e.target.name });
} }
}; };
// 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="/">
@@ -45,8 +76,8 @@ export default function Aside({ asideDisplay, logoutUser }: Props) {
onClick={(e) => handleOpenNestedLink(e)} onClick={(e) => handleOpenNestedLink(e)}
className={`py-2 pl-2 text-left relative w-full overflow-hidden rounded-lg flex justify-between items-center z-10 bg-inherit ${ className={`py-2 pl-2 text-left relative w-full overflow-hidden rounded-lg flex justify-between items-center z-10 bg-inherit ${
allNestedLinks.includes(pathname) allNestedLinks.includes(pathname)
? " text-[#5C2684]" ? ' text-[#5C2684]'
: " text-[#585858]" : ' text-[#585858]'
}`} }`}
> >
{link.name} {link.name}
@@ -60,8 +91,8 @@ export default function Aside({ asideDisplay, logoutUser }: Props) {
<div <div
className={`transition-all duration-300 w-full z-1 ${ className={`transition-all duration-300 w-full z-1 ${
openNestedLink.name == link.name openNestedLink.name == link.name
? "relative top-0" ? 'relative top-0'
: "absolute -top-[500px]" : 'absolute -top-[500px]'
}`} }`}
> >
{link.nestedLink.map((nextLink, index) => ( {link.nestedLink.map((nextLink, index) => (
@@ -70,17 +101,17 @@ export default function Aside({ asideDisplay, logoutUser }: Props) {
asideDisplay && asideDisplay(); asideDisplay && asideDisplay();
}} }}
key={index} key={index}
to={nextLink.link ? nextLink.link : "#"} to={nextLink.link ? nextLink.link : '#'}
className={`w-full my-1 flex items-center gap-2 py-2 pl-5 text-base font-medium ${ className={`w-full my-1 flex items-center gap-2 py-2 pl-5 text-base font-medium ${
pathname == nextLink.link pathname == nextLink.link
? " text-[#5C2684]" ? ' text-[#5C2684]'
: "text-[#585858]" : 'text-[#585858]'
}`} }`}
> >
<Icons <Icons
name={nextLink.icon} name={nextLink.icon}
fillColor={`${ fillColor={`${
pathname == nextLink.link ? "#5C2684" : "#585858" pathname == nextLink.link ? '#5C2684' : '#585858'
}`} }`}
/> />
{nextLink.name} {nextLink.name}
@@ -96,14 +127,14 @@ export default function Aside({ asideDisplay, logoutUser }: Props) {
asideDisplay && asideDisplay(); asideDisplay && asideDisplay();
}} }}
key={index} key={index}
to={link.link ? link.link : "#"} to={link.link ? link.link : '#'}
className={`w-full my-4 flex items-center gap-2 py-2 pl-5 rounded-lg text-base font-medium ${ className={`w-full my-4 flex items-center gap-2 py-2 pl-5 rounded-lg text-base font-medium ${
pathname == link.link ? "text-[#5C2684]" : "text-[#585858]" pathname == link.link ? 'text-[#5C2684]' : 'text-[#585858]'
}`} }`}
> >
<Icons <Icons
name={link.icon} name={link.icon}
fillColor={`${pathname == link.link ? "#5C2684" : "#585858"}`} fillColor={`${pathname == link.link ? '#5C2684' : '#585858'}`}
/> />
{link.name} {link.name}
</Link> </Link>
@@ -119,7 +150,6 @@ export default function Aside({ asideDisplay, logoutUser }: Props) {
Log out Log out
</button> </button>
<div className="flex flex-col gap-[.4375rem] text-[.75rem]"> <div className="flex flex-col gap-[.4375rem] text-[.75rem]">
<p className="font-extrabold tracking-[3%] text-[#FBB700] underline"> <p className="font-extrabold tracking-[3%] text-[#FBB700] underline">
For more enquiries and support For more enquiries and support
@@ -131,7 +161,6 @@ export default function Aside({ asideDisplay, logoutUser }: Props) {
Email: fcmbloan@support.com Email: fcmbloan@support.com
</p> </p>
</div> </div>
</div> </div>
</div> </div>
); );
@@ -149,29 +178,34 @@ type AsideLinksType = {
}[]; }[];
const asideLinks: AsideLinksType = [ const asideLinks: AsideLinksType = [
{ name: "Dashboard", link: "/dashboard/home", icon: "dash-icon", nestedLink: [] },
{ {
name: "Your Profile", name: 'Dashboard',
link: "/dashboard/profile", link: '/dashboard/home',
icon: "dash-icon", icon: 'dash-icon',
nestedLink: [], nestedLink: [],
}, },
{ {
name: "Employment Details", name: 'Your Profile',
link: "/dashboard/verification", link: '/dashboard/profile',
icon: "dash-icon", icon: 'dash-icon',
nestedLink: [], nestedLink: [],
}, },
{ {
name: "Reference Details", name: 'Employment Details',
link: "/dashboard/payments", link: '/dashboard/verification',
icon: "dash-icon", icon: 'dash-icon',
nestedLink: [], nestedLink: [],
}, },
{ {
name: "Agreements", name: 'Reference Details',
link: "/dashboard/legals", link: '/dashboard/payments',
icon: "dash-icon", icon: 'dash-icon',
nestedLink: [],
},
{
name: 'Agreements',
link: '/dashboard/legals',
icon: 'dash-icon',
nestedLink: [], nestedLink: [],
}, },
// {name: 'Nested Link', icon: 'home', nestedLink:[ // {name: 'Nested Link', icon: 'home', nestedLink:[
@@ -83,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>