4 Commits

7 changed files with 174 additions and 109 deletions
@@ -6,50 +6,44 @@ import {Formik, Form} from 'formik'
import * as Yup from "yup";
const initialValues = {
title: "",
marital_status: "",
agent_id: "",
bvn: "",
first_name: "",
phone: "",
email: "",
surname: "",
dob: "",
second_name: "",
spouse_bvn: "",
salary_acct: "",
confirm_salary_acct: false,
qualification: "",
doe: "",
gl: "",
ippis: "",
employer_name: "",
designation: "",
checked: false
};
// To get the validation schema
const validationSchema = Yup.object().shape({
title: Yup.string()
.required("Required"),
marital_status: Yup.string()
.required("Required"),
agent_id: Yup.string()
.required("Required"),
bvn: Yup.string()
.required("BVN is required")
salary_acct: Yup.string()
.required("Required")
.test("no-e", "Invalid number", (value:any) => {
if (value && /^[0-9]*$/.test(value) == false) {
return false;
}
return true;
})
.min(11, "must be 11 digits")
.max(11, "must be 11 digits"),
first_name: Yup.string()
.min(10, "must be 10 digits")
.max(10, "must be 10 digits"),
confirm_salary_acct: Yup.bool() // use bool instead of boolean
.oneOf([true], "You must check the box"),
qualification: Yup.string()
.required("Required"),
phone: Yup.string()
doe: Yup.string()
.required("BVN is required"),
gl: Yup.string()
.required("Required"),
email: Yup.string()
.required("Required")
.email("Wrong email format"),
surname: Yup.string()
ippis: Yup.string(),
employer_name: Yup.string()
.required("Required"),
dob: Yup.string()
designation: Yup.string()
.required("Required"),
checked: Yup.bool(),
checked: Yup.bool() // use bool instead of boolean
.oneOf([true], "You must accept that the information here is correct"),
});
@@ -103,27 +97,30 @@ const EmployerValidation: React.FC= () => {
</p>
</div>
<div className="w-full my-10">
<p className="mb-1 text-[12px] font-bold">Confirms Employee's salary account number</p>
<p className="mb-1 text-[12px] font-bold flex items-center gap-4">Confirms Employee's salary account number
{(props.errors.salary_acct && props.touched.salary_acct) && <span className='text-[10px] text-red-500'>{props.errors.salary_acct}</span> }
</p>
<div className="w-full flex items-center gap-8">
<InputCompOne
parentClass="w-full max-w-[25rem]"
// label="First Name"
name="first_name"
name="salary_acct"
parentInputClass="w-full"
labelClass="font-bold text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.first_name}
onChange={props.handleChange}
error={(props.errors.first_name && props.touched.first_name) ? props.errors.first_name : ''}
// error={(props.errors.first_name && props.touched.first_name) ? props.errors.first_name : ''}
/>
<input
type='checkbox'
name="checked"
name="confirm_salary_acct"
className='w-6 h-6 p-2 accent-purple-600 text-purple-600 bg-gray-100 border-gray-300 rounded-lg focus:ring-purple-500'
onChange={props.handleChange}
/>
</div>
{(props.errors.confirm_salary_acct && props.touched.confirm_salary_acct) && <span className='text-[10px] text-red-500'>{props.errors.confirm_salary_acct}</span> }
</div>
<div className="my-10">
@@ -146,7 +143,7 @@ const EmployerValidation: React.FC= () => {
<InputCompOne
parentInputClass="w-full"
parentClass="w-full md:max-w-[25rem]"
name="title"
name="qualification"
label="Applicant's Educational Qualification"
labelClass="font-bold text-base md:text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
labelSpan={`(Please select the emplyee's highest qualification)`}
@@ -154,22 +151,22 @@ const EmployerValidation: React.FC= () => {
select={true}
selectClass="w-full h-[36px] rounded-[6px]"
selectOptions={titleOptions}
selectValue={props.values.title}
selectValue={props.values.qualification}
onChange={props.handleChange}
error={(props.errors.title && props.touched.title) ? props.errors.title : ''}
error={(props.errors.qualification && props.touched.qualification) ? props.errors.qualification : ''}
/>
<InputCompOne
parentClass="w-full md:max-w-[25rem]"
label="Applicant's Date of Employment"
name="dob"
name="doe"
parentInputClass="w-full"
labelClass="font-bold text-base md:text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputType='date'
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem] px-3"
value={props.values.dob}
value={props.values.doe}
onChange={props.handleChange}
error={(props.errors.dob && props.touched.dob) ? props.errors.dob : ''}
error={(props.errors.doe && props.touched.doe) ? props.errors.doe : ''}
/>
</div>
@@ -178,26 +175,26 @@ const EmployerValidation: React.FC= () => {
<InputCompOne
parentClass="w-full md:max-w-[25rem]"
label="Applicant's Grade Level"
name="first_name"
name="gl"
parentInputClass="w-full"
labelClass="font-bold text-base md:text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.first_name}
value={props.values.gl}
onChange={props.handleChange}
error={(props.errors.first_name && props.touched.first_name) ? props.errors.first_name : ''}
error={(props.errors.gl && props.touched.gl) ? props.errors.gl : ''}
/>
<InputCompOne
parentClass="w-full md:max-w-[25rem]"
label="Applicant's IPPIS Number (optional)"
name="phone"
name="ippis"
parentInputClass="w-full"
labelClass="font-bold text-base md:text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.phone}
value={props.values.ippis}
onChange={props.handleChange}
error={(props.errors.phone && props.touched.phone) ? props.errors.phone : ''}
error={(props.errors.ippis && props.touched.ippis) ? props.errors.ippis : ''}
/>
</div>
@@ -219,30 +216,30 @@ const EmployerValidation: React.FC= () => {
<InputCompOne
parentClass="w-full md:max-w-[25rem]"
label="Employer's Name"
name="first_name"
name="employer_name"
parentInputClass="w-full"
labelClass="font-bold text-base md:text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
labelSpan={`(your full name)`}
labelSpanClass='text-[10px]'
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.first_name}
value={props.values.employer_name}
onChange={props.handleChange}
error={(props.errors.first_name && props.touched.first_name) ? props.errors.first_name : ''}
error={(props.errors.employer_name && props.touched.employer_name) ? props.errors.employer_name : ''}
/>
<InputCompOne
parentClass="w-full md:max-w-[25rem]"
label="Desgination"
name="phone"
label="Designation"
name="designation"
parentInputClass="w-full"
labelClass="font-bold text-base md:text-[1.125rem] leading-[1.3613rem] tracking-[2%] text-[#5C2684] mb-[.125rem]"
labelSpan={`(your position)`}
labelSpanClass='text-[10px]'
input
inputClass="w-full h-[2.25rem] bg-[#EFEFEF] rounded-[.375rem]"
value={props.values.phone}
value={props.values.designation}
onChange={props.handleChange}
error={(props.errors.phone && props.touched.phone) ? props.errors.phone : ''}
error={(props.errors.designation && props.touched.designation) ? props.errors.designation : ''}
/>
</div>
+68 -47
View File
@@ -1,10 +1,11 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import { Button, CustomSpinner, FloatLabelInput } from "..";
import CustomModal from "../modal/CustomModal";
import { useNavigate, useLocation } from "react-router-dom";
import { RouteHandler } from "../../router/routes";
import { RequestStatus } from "../../core/models";
import { employerLogin } from "../../core/apiRequest";
import ErrorMsg from "../shared/ErrorMsg";
type FormType = {
@@ -23,6 +24,7 @@ export default function Login() {
const {state} = useLocation()
const [modal, setModal] = useState<boolean>(false)
const [expiredLinkModal, setExpiredLinkModal] = useState<boolean>(false)
const [requestStatus, setRequestStatus] = useState<RequestStatus>({loading:false, status:null, message:'', data:{}})
@@ -50,7 +52,7 @@ export default function Login() {
employerLogin(reqData).then(res => {
// console.log('RES', res)
if(!res?.data?.call_return){
setRequestStatus({loading:false, status:false, message:'Email/Password is wrong', data:{}})
setRequestStatus({loading:false, status:false, message:res?.data?.status_message || 'Invalid Details', data:{}})
return setTimeout(()=>{
setRequestStatus({loading:false, status:null, message:'', data:{}})
},4000)
@@ -66,6 +68,12 @@ export default function Login() {
})
}
useEffect(()=>{
if(!state?.application_uid){
setExpiredLinkModal(true)
}
},[])
return (
<>
<div className={`w-full overflow-y-auto bg-top bg-cover`}>
@@ -73,57 +81,70 @@ export default function Login() {
<div className="w-full md:max-w-[570px]">
<div className="bg-white w-full rounded-2xl border-2 border-black">
<div className="w-full p-5 sm:p-10 lg:p-20 flex flex-col justify-between items-center h-full">
<div className="mb-4">
<h1 className="text-2xl text-center font-bold leading-3 tracking-wide text-black dark:text-black">
Welcome!
</h1>
<p className="text-xl mt-4 text-center font-medium text-black dark:text-black">
Please login with your email and default password provided to you
{expiredLinkModal &&
<p className="text-xl mb-4 text-center font-medium text-red-500 dark:text-black">
Invalid Link, Please proceed to your email and click on the link to continue
</p>
</div>
}
<div className={`${expiredLinkModal && 'hidden'} w-full`}>
<div className="mb-4">
<h1 className="text-2xl text-center font-bold leading-3 tracking-wide text-black dark:text-black">
Welcome!
</h1>
<p className="text-xl mt-4 text-center font-medium text-black dark:text-black">
Please login with your email and default password provided to you
</p>
</div>
<div className="w-full">
{/* INPUTS */}
<div className="w-full">
<div className="relative my-2 py-2">
<FloatLabelInput
id="email"
name="email"
type="email"
placeHolder="Email"
labelName="Email"
value={formDetails.email}
inputClass=""
onChange={handleFormChange}
/>
{/* INPUTS */}
<div className="w-full">
<div className="relative my-2 py-2">
<FloatLabelInput
id="email"
name="email"
type="email"
placeHolder="Email"
labelName="Email"
value={formDetails.email}
inputClass=""
onChange={handleFormChange}
/>
</div>
<div className="relative my-2 py-2">
<FloatLabelInput
id="password"
name="password"
type="password"
placeHolder="Password"
labelName="Password"
value={formDetails.password}
inputClass=""
onChange={handleFormChange}
/>
</div>
</div>
<div className="relative my-2 py-2">
<FloatLabelInput
id="password"
name="password"
type="password"
placeHolder="Password"
labelName="Password"
value={formDetails.password}
inputClass=""
onChange={handleFormChange}
/>
</div>
</div>
<div className="mt-10 w-full flex items-center gap-2">
<Button
text="Enter"
className="rounded-md w-full sm:w-2/5 text-xl capitalize font-bold"
onClick={loginFxn}
disabled={!formDetails.password || !formDetails.email || requestStatus.loading}
/>
{requestStatus.loading &&
<CustomSpinner />
}
{/* <Link to='' className='text-black text-sm'>Forget your password?</Link> */}
<div className="mt-10 w-full flex items-center gap-2">
<Button
text="Enter"
className="rounded-md w-full sm:w-2/5 text-xl capitalize font-bold"
onClick={loginFxn}
disabled={!formDetails.password || !formDetails.email || requestStatus.loading}
/>
{requestStatus.loading &&
<CustomSpinner />
}
{/* <Link to='' className='text-black text-sm'>Forget your password?</Link> */}
</div>
</div>
</div>
<div className='w-full'>
<ErrorMsg
message={requestStatus.message}
status={requestStatus.status}
/>
</div>
</div>
</div>
</div>
@@ -136,7 +157,7 @@ export default function Login() {
<div className="px-5 md:px-10 w-full flex justify-end">
<button
className="font-bold text-[11px] lg:text-[13px] text-[#5A2C82]"
onClick={()=>{navigate(RouteHandler.otppage, { state: {verify_uid: requestStatus?.data?.verify_uid, application_uid: requestStatus?.data?.records?.application_uid }, replace:true })}}
onClick={()=>{navigate(RouteHandler.otppage, { state: {verify_uid: requestStatus?.data?.verify_uid, application_uid: state?.application_uid }, replace:true })}}
>
Ok
</button>
+30 -1
View File
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import React, { useEffect, useState } from "react";
import { Button, CustomSpinner } from "..";
import { useLocation, useNavigate } from "react-router-dom";
import { RouteHandler } from "../../router/routes";
@@ -6,6 +6,7 @@ import { updateUserDetails } from "../../store/UserDetails";
import { useDispatch } from "react-redux";
import { verifyOTP } from "../../core/apiRequest";
import { RequestStatus } from "../../core/models";
import ErrorMsg from "../shared/ErrorMsg";
type FormType = {
[index: string] : string
@@ -38,6 +39,23 @@ export default function Login() {
setValues((prev:FormType) => ({ ...prev, [name]: value }));
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Backspace") {
const { name } = e.target as HTMLInputElement;
if(values[name]){
setValues((prev: FormType) => ({ ...prev, [name]: "" }));
}else{
const keys = Object.keys(values)
for(let i=keys.length-1; i>=0; i--){
if(values[keys[i]]){
setValues((prev: FormType) => ({ ...prev, [keys[i]]: "" }));
break
}
}
}
}
};
const handleSubmit = () => {
let reqData = {
verify_uid: state?.verify_uid,
@@ -110,6 +128,7 @@ export default function Login() {
value={values.otp1}
onChange={handleChange}
maxLength={1}
onKeyDown={handleKeyDown}
/>
<input
id='otp2'
@@ -120,6 +139,7 @@ export default function Login() {
value={values.otp2}
onChange={handleChange}
maxLength={1}
onKeyDown={handleKeyDown}
/>
<input
id='otp3'
@@ -130,6 +150,7 @@ export default function Login() {
value={values.otp3}
onChange={handleChange}
maxLength={1}
onKeyDown={handleKeyDown}
/>
<input
id='otp4'
@@ -140,6 +161,7 @@ export default function Login() {
value={values.otp4}
onChange={handleChange}
maxLength={1}
onKeyDown={handleKeyDown}
/>
</div>
@@ -159,6 +181,13 @@ export default function Login() {
{/* <Link to='#' className='text-black text-sm'>Forget your password?</Link> */}
</div>
</div>
<div className='w-full'>
<ErrorMsg
message={requestStatus.message}
status={requestStatus.status}
/>
</div>
</div>
</div>
</div>
+4 -6
View File
@@ -1,12 +1,10 @@
import React from 'react'
type Props = {
size?: string
width?: string
height?: string
}
export default function CustomSpinner({size='8'}:Props) {
let width = `w-${size}`
let height = `h-${size}`
export default function CustomSpinner({width='w-6', height='h-6'}:Props) {
return (
<div role="status">
<svg aria-hidden="true" className={`inline ${width} ${height} text-gray-200 animate-spin dark:text-gray-600 fill-blue-600`} viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
+15
View File
@@ -0,0 +1,15 @@
type Error = {
message?: string
status?: boolean
}
export default function ErrorMsg({message, status}:Error) {
return (
<div className={`${!message && 'hidden'} w-full`}>
<p className={`${status ? 'text-green-600' : 'text-red-500'} pt-2 text-base text-center`}>
{message && message}
</p>
</div>
)
}
@@ -45,6 +45,10 @@ export default function DashboardAuth() {
navigate(RouteHandler.loginpage, {state:{application_uid}, replace:true})
return
}
if(!application_uid){ // IF NO TOKEN || UID RETURN TO LOGIN PAGE
navigate(RouteHandler.loginpage, {replace:true})
return
}
const getUser = () => { // FUNCTION TO GET USER BY ID
let data = {firstname:'firstname', lastname:'lastname', uid:'28273737646466464'}
setLoading(false)
+4 -3
View File
@@ -1,4 +1,4 @@
import { Route, Routes } from "react-router-dom";
import { Route, Routes, Navigate } from "react-router-dom";
import { RouteHandler } from "./routes";
import { DashboardAuth, Layout } from "../layouts";
@@ -12,7 +12,8 @@ import {
const Routers = () => {
return (
<Routes>
<Route path='auth' element={<Layout />}>
<Route path='/auth' element={<Layout />}>
<Route exact path={'auth'} element={<LoginPage />} />
<Route path={RouteHandler.loginpage} element={<LoginPage />} />
<Route path={RouteHandler.otppage} element={<OTPPage />} />
</Route>
@@ -22,7 +23,7 @@ const Routers = () => {
<Route element={<DashboardAuth />}>
<Route path={RouteHandler.homepage} element={<StartValidationPage />} />
</Route>
<Route path="*" element={<>Error Page</>} />
<Route path="*" element={<Navigate to={RouteHandler.loginpage} />} />
</Routes>
);
};