Compare commits
51 Commits
login-page
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 10acd16efe | |||
| 1dc8e25cc8 | |||
| 9e25e73923 | |||
| 9b0ad3c886 | |||
| b72fd8e939 | |||
| 944fd5c4c1 | |||
| 6d7db4d733 | |||
| eb340cfc1b | |||
| 61b7a62e1f | |||
| eab8745468 | |||
| 0064f5e4c3 | |||
| 9d7cb31094 | |||
| df7ca95653 | |||
| 348532d7cf | |||
| 979d6d5c9e | |||
| e1b2eec88d | |||
| d263a4e3df | |||
| f1985ae180 | |||
| f74e0ef436 | |||
| 20231b65c0 | |||
| f360056f03 | |||
| ab1192a508 | |||
| fcd72e401a | |||
| 8b45dd409f | |||
| 4bf728a38d | |||
| dc2c1e7eab | |||
| 9d86a67d98 | |||
| c96fc2710c | |||
| baa0920be6 | |||
| bb26537920 | |||
| 2e3902019b | |||
| b844a402c9 | |||
| 29c33aca62 | |||
| fb8d598d43 | |||
| b248cce3a5 | |||
| 5533797d24 | |||
| ab7657d07f | |||
| ec210390b3 | |||
| d148cc24f4 | |||
| f7530e17eb | |||
| 411d44d4e8 | |||
| 15a598872e | |||
| b185419ba7 | |||
| 44656bdfde | |||
| e0d51a2ce3 | |||
| 60dd820f6e | |||
| b827c69415 | |||
| 114df2eca8 | |||
| 53090ed96f | |||
| 6d71eb13c1 | |||
| c9f80c5f41 |
@@ -6,7 +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'
|
||||
REACT_APP_MAIN_API0='https://devcore.digifi.chiefsoft.net'
|
||||
REACT_APP_MAIN_API='http://simbrellang.net:6335'
|
||||
|
||||
# ENQUIRIES CONTACTS
|
||||
VITE_CALL_ENDPOINT='09099000000'
|
||||
|
||||
+2
-1
@@ -6,7 +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'
|
||||
REACT_APP_MAIN_API0='https://devcore.digifi.chiefsoft.net'
|
||||
REACT_APP_MAIN_API='http://simbrellang.net:6335'
|
||||
|
||||
# ENQUIRIES CONTACTS
|
||||
VITE_CALL_ENDPOINT='09099000000'
|
||||
|
||||
+2
-1
@@ -6,7 +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'
|
||||
REACT_APP_MAIN_API0='https://devcore.digifi.chiefsoft.net'
|
||||
REACT_APP_MAIN_API='http://simbrellang.net:6335'
|
||||
|
||||
# ENQUIRIES CONTACTS
|
||||
VITE_CALL_ENDPOINT='09099000000'
|
||||
|
||||
+2
-1
@@ -14,7 +14,8 @@ services:
|
||||
- "5173"
|
||||
extra_hosts:
|
||||
- digifi-apidev.chiefsoft.net:10.10.33.15
|
||||
- backend.wrenchboard.api.test:10.10.33.15
|
||||
- devcore.digifi.chiefsoft.net:10.10.33.15
|
||||
- core.digifi.chiefsoft.net:10.10.33.15
|
||||
environment:
|
||||
- PORT=${DIGIFI_PORT}
|
||||
tty: true
|
||||
|
||||
+8
-1
@@ -3,12 +3,19 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^2.5.1",
|
||||
"@tanstack/react-query": "^5.66.0",
|
||||
"axios": "^1.7.9",
|
||||
"cra-template": "1.2.0",
|
||||
"formik": "^2.4.6",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.1.5",
|
||||
"react-scripts": "5.0.1"
|
||||
"react-scripts": "5.0.1",
|
||||
"redux": "^5.0.1",
|
||||
"yup": "^1.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 15 KiB |
+2
-2
@@ -7,7 +7,7 @@
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
content="digiFi global back office systems"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
@@ -24,7 +24,7 @@
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>digiFi</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
+4
-20
@@ -2,32 +2,12 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/*@media (prefers-reduced-motion: no-preference) {*/
|
||||
/* .App-logo {*/
|
||||
/* animation: App-logo-spin infinite 20s linear;*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
@@ -36,3 +16,7 @@
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
button, a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
+16
@@ -1,10 +1,26 @@
|
||||
import { QueryClientProvider, QueryClient } from '@tanstack/react-query'
|
||||
import MyRoutes from './MyRoutes';
|
||||
|
||||
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
retry: 3,
|
||||
// refetchOnMount: false,
|
||||
staleTime: Infinity // can also be a number in millisecond
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MyRoutes />
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+10
-1
@@ -1,17 +1,26 @@
|
||||
import React from 'react'
|
||||
import {Routes, Route} from 'react-router-dom'
|
||||
|
||||
import UserExists from './authorization/UserExists'
|
||||
|
||||
import GetStartedPage from './pages/GetStartedPage'
|
||||
import LoginPage from './pages/LoginPage'
|
||||
import HomePage from './pages/HomePage'
|
||||
import myLinks from './myLinks'
|
||||
import GetLoanPage from './pages/GetLoanPage'
|
||||
|
||||
export default function MyRoutes() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path={myLinks.getStarted} element={<GetStartedPage />} />
|
||||
<Route path={myLinks.login} element={<LoginPage />} />
|
||||
<Route path={myLinks.home} element={<HomePage />} />
|
||||
|
||||
<Route element={<UserExists />}>
|
||||
<Route path={myLinks.home} element={<HomePage />} />
|
||||
<Route path={myLinks.getLoan} element={<GetLoanPage />} />
|
||||
</Route>
|
||||
|
||||
<Route path={myLinks.error} element={<LoginPage />} />
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useLocation, useNavigate, Outlet } from 'react-router-dom'
|
||||
import Layout from '../components/layout/Layout'
|
||||
|
||||
import myLinks from '../myLinks'
|
||||
import PageLoader from '../components/PageLoader'
|
||||
|
||||
export default function UserExists() {
|
||||
|
||||
const {state} = useLocation()
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(()=>{
|
||||
if(!state || localStorage.getItem('active') != 'true'){
|
||||
return navigate(myLinks.getStarted, {replace:true})
|
||||
}
|
||||
setTimeout(()=>{
|
||||
setLoading(false)
|
||||
},2000)
|
||||
},[])
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
loading ?
|
||||
<PageLoader />
|
||||
:
|
||||
<Layout>
|
||||
<Outlet />
|
||||
</Layout>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
+104
-45
@@ -1,49 +1,94 @@
|
||||
// import React from 'react'
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import { CiPhone } from "react-icons/ci"
|
||||
import { IoIosPhonePortrait } from "react-icons/io"
|
||||
import { MdOutlineEmail } from "react-icons/md"
|
||||
import { BsCash } from "react-icons/bs";
|
||||
import { MdUpdate } from "react-icons/md"
|
||||
import { FaUser, FaMapPin } from "react-icons/fa";
|
||||
|
||||
import { demoUsersList } from "../services/siteServices"
|
||||
import queryKeys from "../services/queryKeys"
|
||||
import myLinks from "../myLinks";
|
||||
import TableWrapper from "./TableWrapper";
|
||||
import formatNumber from "../helpers/formatNumber";
|
||||
|
||||
export default function HomeCom() {
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const {data:users, isFetching, isError, error} = useQuery({
|
||||
queryKey: queryKeys.demoUsers,
|
||||
queryFn: () => demoUsersList()
|
||||
})
|
||||
|
||||
const demoUsers = users?.data?.demo_data?.list // LOAN USERS LIST
|
||||
|
||||
const getLoanPage = (user) => {
|
||||
navigate(myLinks.getLoan, {state:{user}})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full h-screen flex flex-col gap-2 overflow-y-auto text-black bg-slate-100 p-5 sm:p-[40px]">
|
||||
<div className="py-4 text-3xl text-black font-bold">Users</div>
|
||||
<div className="grid gap-5 sm:gap-[40px] grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
||||
|
||||
{data.map((item, index) => {
|
||||
let color = item.place == 'Friends' ? 'text-emerald-500 bg-emerald-100/90' : item.place == 'Office' ? 'text-blue-500 bg-blue-100/90' : 'text-orange-500 bg-orange-100/90'
|
||||
return (
|
||||
<div key={index} className="w-full p-3 sm:p-5 bg-white shadow flex flex-col gap-5">
|
||||
<div className="mb-5 card-title w-[70%] mx-auto">
|
||||
<h1 className="mb-[1px] text-2xl font-bold">{item.name}</h1>
|
||||
<span className={` ${color} text-sm font-bold p-1 rounded`}>{item.place}</span>
|
||||
<div className="w-full flex flex-col gap-2 text-black">
|
||||
<div className="py-3 text-3xl text-black font-bold">Users</div>
|
||||
{isFetching ?
|
||||
<>
|
||||
<div className="w-full py-4">
|
||||
<p className='text-slate-800'>Loading...</p>
|
||||
</div>
|
||||
</>
|
||||
: isError ?
|
||||
<div className="w-full py-4">
|
||||
<p className='text-red-500'>{error.message}</p>
|
||||
</div>
|
||||
<div className="card-body flex flex-col gap-2">
|
||||
<div className="contact text-slate-400 flex gap-3 items-center">
|
||||
<span className="w-[40px] h-[40px] rounded-full flex flex-col justify-center items-center bg-slate-100/90 text-slate-400">
|
||||
<IoIosPhonePortrait />
|
||||
</span>
|
||||
<span>{item.contact}</span>
|
||||
</div>
|
||||
<div className="contact text-slate-400 flex gap-3 items-center">
|
||||
<span className="w-[40px] h-[40px] rounded-full flex flex-col justify-center items-center bg-slate-100/90 text-slate-400">
|
||||
<CiPhone />
|
||||
</span>
|
||||
<span>{item.contact}</span>
|
||||
</div>
|
||||
<div className="contact text-slate-400 flex gap-3 items-center">
|
||||
<span className="w-[40px] h-[40px] rounded-full flex flex-col justify-center items-center bg-slate-100/90 text-slate-400">
|
||||
<MdOutlineEmail />
|
||||
</span>
|
||||
<span>{item.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
:
|
||||
<TableWrapper data={demoUsers} itemsPerPage={16}>
|
||||
{({ data }) => (
|
||||
<div className="grid gap-5 sm:gap-[40px] grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{data.map((user, index) => {
|
||||
let hasSalaryAcct = user.salary_account === 0 ? false : true
|
||||
return (
|
||||
<div onClick={()=>getLoanPage(user)} key={user?.uid || index} className={`${hasSalaryAcct ? 'bg-white' : 'bg-red-50'} w-full rounded p-3 sm:p-5 shadow flex flex-col gap-5 hover:scale-105 hover:cursor-pointer`}>
|
||||
<div className="mb-5 card-title w-full md:w-4/5 mx-auto text-center">
|
||||
<h1 className="mb-[1px] text-base md:text-xl lg:text-2xl font-bold">{user.name}</h1>
|
||||
<span className={`text-sm font-bold p-1 rounded`}>ID: {user.bvn}</span>
|
||||
</div>
|
||||
<div className="card-body flex flex-col gap-2">
|
||||
<div className="contact text-slate-700 flex gap-2 items-center">
|
||||
<span className="min-w-[30px] min-h-[30px] max-w-[30px] max-h-[30px] rounded-full flex flex-col justify-center items-center bg-slate-100/90 text-slate-700">
|
||||
<FaUser />
|
||||
</span>
|
||||
<span>{user.customerid}</span>
|
||||
</div>
|
||||
<div className="contact text-slate-700 flex gap-3 items-center">
|
||||
<span className="min-w-[30px] min-h-[30px] max-w-[30px] max-h-[30px] rounded-full flex flex-col justify-center items-center bg-slate-100/90 text-slate-700">
|
||||
<BsCash />
|
||||
</span>
|
||||
<span>{formatNumber(user.balance)} Naira</span>
|
||||
</div>
|
||||
<div className="contact text-slate-700 flex gap-3 items-center">
|
||||
<span className="min-w-[30px] min-h-[30px] max-w-[30px] max-h-[30px] rounded-full flex flex-col justify-center items-center bg-slate-100/90 text-slate-700">
|
||||
<FaMapPin />
|
||||
</span>
|
||||
<span className="break-words">{user.pin}</span>
|
||||
</div>
|
||||
<div className="contact text-slate-700 flex gap-3 items-center">
|
||||
<span className="min-w-[30px] min-h-[30px] max-w-[30px] max-h-[30px] rounded-full flex flex-col justify-center items-center bg-slate-100/90 text-slate-700">
|
||||
<MdUpdate />
|
||||
</span>
|
||||
<span>{user.updated}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
</div>
|
||||
)}
|
||||
</TableWrapper>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@@ -51,12 +96,26 @@ export default function HomeCom() {
|
||||
|
||||
|
||||
|
||||
const data = [
|
||||
{name:'Jerry Eze', place: 'Home', contact: '021-025-0325', email: 'jerry@example.com'},
|
||||
{name:'Mark John', place: 'Office', contact: '011-025-0311', email: 'mark@example.com'},
|
||||
{name:'Larry Bon', place: 'Friends', contact: '033-025-0333', email: 'larry@example.com'},
|
||||
{name:'Jeff Henry', place: 'Home', contact: '044-025-0344', email: 'jeff@example.com'},
|
||||
{name:'Rose Ordor', place: 'Office', contact: '055-025-0355', email: 'rose@example.com'},
|
||||
{name:'Mike Timothy', place: 'Friends', contact: '066-025-0366', email: 'mike@example.com'},
|
||||
// const data = [
|
||||
// {name:'Jerry Eze', place: 'Home', contact: '021-025-0325', email: 'jerry@example.com'},
|
||||
// {name:'Mark John', place: 'Office', contact: '011-025-0311', email: 'mark@example.com'},
|
||||
// {name:'Larry Bon', place: 'Friends', contact: '033-025-0333', email: 'larry@example.com'},
|
||||
// {name:'Jeff Henry', place: 'Home', contact: '044-025-0344', email: 'jeff@example.com'},
|
||||
// {name:'Rose Ordor', place: 'Office', contact: '055-025-0355', email: 'rose@example.com'},
|
||||
// {name:'Mike Timothy', place: 'Friends', contact: '066-025-0366', email: 'mike@example.com'},
|
||||
|
||||
]
|
||||
// {name:'Jerry Eze', place: 'Home', contact: '021-025-0325', email: 'jerry@example.com'},
|
||||
// {name:'Mark John', place: 'Office', contact: '011-025-0311', email: 'mark@example.com'},
|
||||
// {name:'Larry Bon', place: 'Friends', contact: '033-025-0333', email: 'larry@example.com'},
|
||||
// {name:'Jeff Henry', place: 'Home', contact: '044-025-0344', email: 'jeff@example.com'},
|
||||
// {name:'Rose Ordor', place: 'Office', contact: '055-025-0355', email: 'rose@example.com'},
|
||||
// {name:'Mike Timothy', place: 'Friends', contact: '066-025-0366', email: 'mike@example.com'},
|
||||
|
||||
// {name:'Jerry Eze', place: 'Home', contact: '021-025-0325', email: 'jerry@example.com'},
|
||||
// {name:'Mark John', place: 'Office', contact: '011-025-0311', email: 'mark@example.com'},
|
||||
// {name:'Larry Bon', place: 'Friends', contact: '033-025-0333', email: 'larry@example.com'},
|
||||
// {name:'Jeff Henry', place: 'Home', contact: '044-025-0344', email: 'jeff@example.com'},
|
||||
// {name:'Rose Ordor', place: 'Office', contact: '055-025-0355', email: 'rose@example.com'},
|
||||
// {name:'Mike Timothy', place: 'Friends', contact: '066-025-0366', email: 'mike@example.com'},
|
||||
|
||||
// ]
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function InputText({id, name, type='text'}) {
|
||||
export default function InputText({id, name, type='text', value, handleChange}) {
|
||||
return (
|
||||
<div className='w-full h-12 round overflow-hidden'>
|
||||
<input id={id} name={name} type={type} className='p-2 w-full h-full text-black outline-0 ring-0 border border-black rounded' />
|
||||
<input id={id} name={name} type={type} value={value} onChange={handleChange} className='p-2 w-full h-full text-black outline-0 ring-0 border border-slate-400 focus:border-purple-600 rounded' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function Label({name, htmlfor}) {
|
||||
export default function Label({name, htmlfor, error}) {
|
||||
return (
|
||||
<label className='text-black' htmlFor={htmlfor}>{name}</label>
|
||||
<label className='text-black flex gap-1 items-center' htmlFor={htmlfor}>{name} {error && <span className='text-red-500 text-sm'>{error}</span>}</label>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useLocation, useNavigate, Link } from 'react-router-dom'
|
||||
|
||||
import { IoIosArrowBack } from "react-icons/io";
|
||||
|
||||
import myLinks from '../myLinks'
|
||||
import PayloanScreens from './loan_screen/PayloanScreens';
|
||||
import GetLoanScreens from './loan_screen/GetLoanScreens';
|
||||
import LoanInfoScreens from './loan_screen/LoanInfoScreens';
|
||||
|
||||
export default function LoanScreens() {
|
||||
|
||||
const {state} = useLocation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [step, setStep] = useState({
|
||||
details: {},
|
||||
screen: [],
|
||||
activeUser: state.user
|
||||
})
|
||||
|
||||
const typeToShow = {
|
||||
getloan: 'getloan',
|
||||
payloan: 'payloan',
|
||||
loaninfo: 'loaninfo',
|
||||
}
|
||||
|
||||
const [showtype, setShowType] = useState(typeToShow.getloan)
|
||||
|
||||
const handleStep = (detailsToAdd, screen) => {
|
||||
setStep(prev => ({...prev, details: {...prev.details, ...detailsToAdd}, screen: [...prev.screen, screen]}))
|
||||
}
|
||||
|
||||
const handleBackBtn = () => {
|
||||
// if(step?.screen?.length <= 0 || step?.screen[step.screen.length -1 ] == screens.finished){
|
||||
// setStep({details: {}, screen: []})
|
||||
// return navigate(myLinks.home, {state:{proceed:'true'}})
|
||||
// }
|
||||
if(step?.screen[step.screen.length -1 ] == screens.finished){
|
||||
setStep({details: {}, screen: []})
|
||||
return navigate(myLinks.home, {state:{proceed:'true'}})
|
||||
}
|
||||
setStep(prev => ({...prev, screen: prev.screen.slice(0, -1)}))
|
||||
}
|
||||
|
||||
let screens = showtype == typeToShow.getloan ? {
|
||||
products: 'products-screen',
|
||||
terms_conditions: 'terms_conditions',
|
||||
getLoan: 'get-loan',
|
||||
pin: 'pin-screen',
|
||||
loan_details: 'loan_details',
|
||||
offers: 'offers-screen',
|
||||
selected_offer: 'selected-offer-screen',
|
||||
loan_pin: 'loan-pin-screen',
|
||||
not_eligible: 'not-eligible',
|
||||
finished: 'finished'
|
||||
} : showtype == typeToShow.payloan ? {
|
||||
loan_list: 'loan_list',
|
||||
repay_pin: 'repay_pin',
|
||||
finished: 'finished'
|
||||
}: showtype == typeToShow.loaninfo ? {
|
||||
loan_info: 'loan_info',
|
||||
finished: 'finished'
|
||||
}: null
|
||||
|
||||
useEffect(()=>{
|
||||
if(!state?.user){
|
||||
return navigate(myLinks.getStarted, {replace:true})
|
||||
}
|
||||
},[])
|
||||
|
||||
useEffect(()=>{ // reset set details whenever the type of screen route to display changes
|
||||
setStep({ details: {}, screen: [], activeUser: state.user})
|
||||
},[showtype])
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col items-center justify-center`}>
|
||||
<div className='relative flex flex-col gap-4 w-[80%] sm:w-[400px] h-[600px] bg-white rounded-xl p-4 sm:p-8 shadow'>
|
||||
<div className="relative pb-3 card-title w-full border-b-2 flex gap-4 items-center">
|
||||
<div className={` ${!step.screen.length && 'hidden'} absolute left-2 top-1/2 -translate-y-1/2 p-1 cursor-pointer`} onClick={handleBackBtn}>
|
||||
<IoIosArrowBack className='text-3xl sm:text-5xl font-bold text-orange-500 cursor-pointer' />
|
||||
</div>
|
||||
<div className='w-full text-center'>
|
||||
<h1 className="mb-[1px] text-2xl font-bold">{state?.user.name}</h1>
|
||||
<span className={`text-base font-bold p-1 rounded`}>ID: {state?.user.bvn}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showtype == typeToShow.getloan &&
|
||||
<>
|
||||
<GetLoanScreens step={step} screens={screens} typeToShow={typeToShow} setShowType={setShowType} handleStep={handleStep} />
|
||||
</>
|
||||
}
|
||||
|
||||
{showtype == typeToShow.payloan &&
|
||||
<>
|
||||
<PayloanScreens step={step} screens={screens} handleStep={handleStep} />
|
||||
</>
|
||||
}
|
||||
|
||||
{showtype == typeToShow.loaninfo &&
|
||||
<>
|
||||
<LoanInfoScreens step={step} screens={screens} handleStep={handleStep} />
|
||||
</>
|
||||
}
|
||||
|
||||
<div className='absolute left-0 bottom-1 w-full flex justify-center items-center'>
|
||||
<Link to={myLinks.home} className='font-semibold text-center border-b border-transparent hover:border-purple-400'>Back to <span className='text-purple-400 '>Home</span></Link>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const data1 = [];
|
||||
|
||||
// type PaginatedListProps = {
|
||||
// data: { name: string; email: string; status: string; location: string; }[],
|
||||
// itemsPerPage: number,
|
||||
// filterItem?: string[],
|
||||
// titleClass?:string,
|
||||
// children: (data:any) => ReactNode;
|
||||
// }
|
||||
|
||||
export default function TableWrapper({
|
||||
data = data1,
|
||||
itemsPerPage = 5,
|
||||
filterItem,
|
||||
children,
|
||||
}) {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [filteredData, setFilteredData] = useState(data);
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
const [newData, setNewData] = useState([]);
|
||||
|
||||
const numberOfSelection = itemsPerPage;
|
||||
|
||||
const handlePrev = () => {
|
||||
if (currentPage != 0) {
|
||||
setCurrentPage((prev) => prev - numberOfSelection);
|
||||
}
|
||||
};
|
||||
const handleNext = () => {
|
||||
if (currentPage < data.length) {
|
||||
setCurrentPage((prev) => prev + numberOfSelection);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = ({ target: { value } }, name) => {
|
||||
setSearchTerm(value);
|
||||
let newFilteredData = data.filter((item) =>
|
||||
item[name].toLowerCase().startsWith(value.toLowerCase())
|
||||
);
|
||||
setFilteredData(newFilteredData);
|
||||
setCurrentPage(0);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true)
|
||||
setTimeout(()=>{
|
||||
setNewData(
|
||||
filteredData?.slice(currentPage, numberOfSelection + currentPage)
|
||||
);
|
||||
setIsLoading(false)
|
||||
},1000)
|
||||
|
||||
}, [currentPage, filteredData]);
|
||||
|
||||
useEffect(()=>{
|
||||
setCurrentPage(0)
|
||||
},[itemsPerPage])
|
||||
|
||||
return (
|
||||
<div className="py-4 w-full bg-transparent rounded-md">
|
||||
{data.length > 0 && filterItem && (
|
||||
<div className="mb-10 flex justify-end items-center gap-2">
|
||||
{filterItem.map((item, index) => (
|
||||
<label
|
||||
key={index}
|
||||
className="flex flex-col sm:flex-row items-center gap-2 text-slate-600 dark:text-slate-100 transition-all duration-500"
|
||||
>
|
||||
Search by {item[0].toUpperCase() + item.slice(1)}
|
||||
<input
|
||||
name={item}
|
||||
type="text"
|
||||
className="py-1 px-2 text-sm min-w-[100px] text-black dark:text-white bg-white dark:bg-slate-800 rounded-full border-0 outline-none ring-1 ring-slate-300 dark:ring-white transition-all duration-500"
|
||||
value={searchTerm}
|
||||
onChange={(e) => {
|
||||
handleSearch(e, item);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full">
|
||||
{children({ data: newData })}
|
||||
</div>
|
||||
|
||||
{/* PAGINATION BUTTON */}
|
||||
<div className='mt-6 p-2 w-full flex flex-col lg:flex-row justify-center items-center gap-3 md:gap-6'>
|
||||
<div className="text-sm text-center lg:text-left font-normal text-gray-500 dark:text-gray-400 block w-full">Showing <span className="font-semibold text-gray-900 dark:text-white">
|
||||
{isLoading ? '----' : `${currentPage + 1}-${currentPage + numberOfSelection >= data.length ? data.length : (currentPage + numberOfSelection)}`}</span> of <span className="font-semibold text-gray-900 dark:text-white">{data.length}</span>
|
||||
</div>
|
||||
<div className='flex items-center gap-3 md:gap-6'>
|
||||
<button
|
||||
onClick={handlePrev}
|
||||
className={`px-4 py-2 rounded ${currentPage == 0 ? 'bg-sky-600/50 pointer-events-none' : 'bg-sky-600'} text-white-light`}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Prev
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNext}
|
||||
className={`px-4 py-2 rounded ${currentPage + numberOfSelection >= data.length ? 'bg-sky-600/50 pointer-events-none' : 'bg-sky-600'} text-white-light`}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* show prev and next button if data exist */}
|
||||
{/* {data.length > 0 && (
|
||||
{data.length && data.map((item, index)=>{
|
||||
item = item
|
||||
if(index%itemsPerPage == 0 && index >= currentPage && index <= currentPage+itemsPerPage){
|
||||
return (
|
||||
<button
|
||||
key={index}
|
||||
onClick={handleNext}
|
||||
className={`w-6 h-6 md:w-12 md:h-12 text-sm md:text-lg rounded-full flex justify-center items-center border transition-all duration-300 ${
|
||||
currentPage != index
|
||||
? "text-slate-400 border-slate-400 dark:text-slate-400 dark:border-slate-400"
|
||||
: "text-slate-600 border-slate-600 dark:text-white dark:border-white pointer-events-none"
|
||||
}`}
|
||||
>
|
||||
{index/itemsPerPage +1}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
)} */}
|
||||
{isLoading && <TableIsLoading />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const TableIsLoading = () => {
|
||||
return (
|
||||
<div className="w-full fixed z-[999] inset-0 bg-slate-600/30 flex justify-center items-center">
|
||||
<p className="rounded-md shadow-md p-4 bg-white/80 dark:bg-gray-900 text-brown dark:text-white">Loading...</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -5,14 +5,14 @@ import myLinks from '../../myLinks';
|
||||
|
||||
export default function GetStarted() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo2} className="App-logo" alt="logo" />
|
||||
<p className=''>
|
||||
<div className={`h-screen bg-sky-300 flex flex-col items-center justify-center bg-[url('./assets/first-background.jpg')] bg-cover bg-center bg-no-repeat`}>
|
||||
<header className="flex flex-col gap-2 justify-center items-center text-lg sm:text-2xl font-bold">
|
||||
<img src={logo2} className="h-32 sm:h-72 w-auto" alt="logo" />
|
||||
<p className='w-full text-center'>
|
||||
digiFi - Banking Offers Emulator Systems.
|
||||
</p>
|
||||
<Link
|
||||
className="App-link"
|
||||
className="mt-2 px-2 py-1 text-emerald-600 bg-emerald-100/90 rounded"
|
||||
to={myLinks.login}
|
||||
href="https://reactjs.org"
|
||||
// target="_blank"
|
||||
|
||||
@@ -1,15 +1,52 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
|
||||
import myLinks from '../../myLinks'
|
||||
import Label from '../Label'
|
||||
import InputText from '../InputText'
|
||||
import PageLoader from '../PageLoader'
|
||||
import { loginUser } from '../../services/siteServices'
|
||||
|
||||
export default function LoginCom() {
|
||||
|
||||
const [loading, setLoading] = useState(true)
|
||||
const {state} = useLocation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [fields, setFields] = useState({
|
||||
username: '',
|
||||
password: '',
|
||||
})
|
||||
|
||||
const handleChange = ({target:{name, value}}) => {
|
||||
setFields(prev => ({...prev, [name]:value}))
|
||||
}
|
||||
|
||||
const login = useMutation({
|
||||
mutationFn: (fields) => {
|
||||
if(!fields.username || !fields.password){
|
||||
throw new Error('Please provide all fields marked *')
|
||||
}
|
||||
return loginUser(fields)
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log(error)
|
||||
},
|
||||
onSuccess: (res) => {
|
||||
// const {token, room} = res?.data?.data
|
||||
// if(token){
|
||||
// localStorage.setItem('token', token)
|
||||
// localStorage.setItem('room', room)
|
||||
// // const data = {token}
|
||||
// // dispatch(updateUserDetails({ ...data }));
|
||||
// }
|
||||
navigate(myLinks.home, {state:{proceed:'true'}}) // later add redux to dispatch state
|
||||
localStorage.setItem('active', 'true')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(state?.proceed != 'true'){
|
||||
return navigate(myLinks.getStarted, {replace:true})
|
||||
@@ -25,18 +62,26 @@ export default function LoginCom() {
|
||||
<PageLoader />
|
||||
:
|
||||
<div className={`h-screen bg-sky-300 flex flex-col items-center justify-center bg-[url('./assets/first-background.jpg')] bg-cover bg-center bg-no-repeat`}>
|
||||
<div className='flex flex-col gap-4 w-[80%] sm:w-[500px] bg-white rounded-xl p-3 sm:p-5 shadow'>
|
||||
<div className='flex flex-col gap-4 w-[80%] sm:w-[500px] bg-white rounded p-5 sm:p-[30px] shadow'>
|
||||
<div className='text-input'>
|
||||
<Label name='Username' htmlfor='username' />
|
||||
<InputText id='username' name='username' />
|
||||
<InputText id='username' name='username' value={fields.username} handleChange={handleChange} />
|
||||
</div>
|
||||
<div className='text-input'>
|
||||
<Label name='Password' htmlfor='password' />
|
||||
<InputText id='password' name='password' type='password' />
|
||||
<InputText id='password' name='password' type='password' value={fields.password} handleChange={handleChange} />
|
||||
</div>
|
||||
|
||||
{login.error &&
|
||||
<>
|
||||
<div className="w-full text-center p-2">
|
||||
<p className='text-red-500 text-sm'>{login.error.message}</p>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
<div className='mt-5 flex justify-end items-center'>
|
||||
<button onClick={()=>navigate(myLinks.home, {state:{proceed:'true'}})} className='px-3 py-2 bg-purple-800 text-white font-bold rounded'>Login</button>
|
||||
<button onClick={()=>{login.mutate(fields)}} disabled={login.isPending} className='px-4 py-2 bg-purple-800 text-white font-bold rounded'>{login.isPending ? 'loading...' : 'Login'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import {Outlet, useNavigate} from 'react-router-dom'
|
||||
import myLinks from '../../myLinks'
|
||||
|
||||
export default function Layout() {
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleLogout = () => {
|
||||
navigate(myLinks.login, {state:{proceed:'false'}, replace:true})
|
||||
localStorage.clear()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`w-full h-screen overflow-y-auto flex flex-col gap-4 bg-[url('./assets/first-background.jpg')] bg-cover bg-center bg-no-repeat`}>
|
||||
<div className='sticky top-0 w-full flex justify-end p-5 pb-0 sm:p-[30px] sm:pb-0'>
|
||||
<button onClick={handleLogout} className='bg-white px-4 py-2 rounded text-red-500 font-bold text-xl'>Logout</button>
|
||||
</div>
|
||||
<div className='p-5 pb-0 sm:p-[30px] sm:pb-0'>
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react'
|
||||
|
||||
import Pin from './getloan/Pin';
|
||||
import Offers from './getloan/Offers';
|
||||
import LoanPin from './getloan/LoanPin';
|
||||
import SelectedOffer from './getloan/SelectedOffer';
|
||||
import GetLoan from './getloan/GetLoan';
|
||||
import TermsAndConditions from './getloan/TermsAndConditions';
|
||||
import LoanDetails from './getloan/LoanDetails';
|
||||
import ProductDetails from './getloan/ProductDetails';
|
||||
import NotEligible from './getloan/NotEligible';
|
||||
|
||||
export default function GetLoanScreens({step, screens, typeToShow, setShowType, handleStep}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{!step?.screen?.length || step?.screen[step.screen.length -1 ] == screens.products ?
|
||||
<ProductDetails step={step} handleStep={handleStep} screens={screens} />
|
||||
: step?.screen[step.screen.length -1 ] == screens.getLoan ?
|
||||
<>
|
||||
<GetLoan step={step} handleStep={handleStep} screens={screens} typeToShow={typeToShow} setShowType={setShowType} />
|
||||
</>
|
||||
:step?.screen[step.screen.length -1 ] == screens.not_eligible?
|
||||
<>
|
||||
<NotEligible step={step} handleStep={handleStep} screens={screens} />
|
||||
</>
|
||||
:step?.screen[step.screen.length -1 ] == screens.terms_conditions ?
|
||||
<>
|
||||
<TermsAndConditions step={step} handleStep={handleStep} screens={screens} />
|
||||
</>
|
||||
:step?.screen[step.screen.length -1 ] == screens.pin ?
|
||||
<>
|
||||
<Pin step={step} handleStep={handleStep} screens={screens} />
|
||||
</>
|
||||
: step?.screen[step.screen.length -1 ] == screens.offers ?
|
||||
<>
|
||||
<Offers step={step} handleStep={handleStep} screens={screens} />
|
||||
</>
|
||||
: step?.screen[step.screen.length -1 ] == screens.selected_offer ?
|
||||
<>
|
||||
<SelectedOffer step={step} handleStep={handleStep} screens={screens} />
|
||||
</>
|
||||
: step?.screen[step.screen.length -1 ] == screens.loan_details?
|
||||
<>
|
||||
<LoanDetails step={step} handleStep={handleStep} screens={screens} />
|
||||
</>
|
||||
:step?.screen[step.screen.length -1 ] == screens.loan_pin ?
|
||||
<>
|
||||
<LoanPin step={step} handleStep={handleStep} screens={screens} />
|
||||
</>
|
||||
:step?.screen[step.screen.length -1 ] == screens.finished ?
|
||||
<>
|
||||
<LoanPin step={step} handleStep={handleStep} screens={screens} />
|
||||
</>
|
||||
: null
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
|
||||
import LoanInfoList from './loaninfo/LoanInfoList';
|
||||
|
||||
|
||||
export default function LoanInfoScreens({step, screens, typeToShow, setShowType, handleStep}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{!step?.screen?.length || step?.screen[step.screen.length -1 ] == screens.loan_list ?
|
||||
<LoanInfoList step={step} handleStep={handleStep} screens={screens} />
|
||||
: step?.screen[step.screen.length -1 ] == screens.repay_pin ?
|
||||
<>
|
||||
{/* <GetLoan step={step} handleStep={handleStep} screens={screens} typeToShow={typeToShow} setShowType={setShowType} /> */}
|
||||
{null}
|
||||
</>
|
||||
: null
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react'
|
||||
|
||||
import RepayLoanList from './payloan/RepayLoanList';
|
||||
import PinRepayment from './payloan/PinRepayment';
|
||||
|
||||
|
||||
export default function PayloanScreens({step, screens, typeToShow, setShowType, handleStep}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{!step?.screen?.length || step?.screen[step.screen.length -1 ] == screens.loan_list ?
|
||||
<RepayLoanList step={step} handleStep={handleStep} screens={screens} />
|
||||
: step?.screen[step.screen.length -1 ] == screens.repay_pin ?
|
||||
<>
|
||||
{/* <GetLoan step={step} handleStep={handleStep} screens={screens} typeToShow={typeToShow} setShowType={setShowType} /> */}
|
||||
<PinRepayment step={step} handleStep={handleStep} screens={screens} />
|
||||
</>
|
||||
: step?.screen[step.screen.length -1 ] == screens.finished ?
|
||||
<>
|
||||
<PinRepayment step={step} handleStep={handleStep} screens={screens} />
|
||||
</>
|
||||
:null
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function GetLoan({step, handleStep, screens, typeToShow, setShowType}) {
|
||||
|
||||
const handleGetLoan = () => {
|
||||
// handleStep({...step.details}, screens.pin)
|
||||
handleStep({...step.details}, screens.terms_conditions)
|
||||
}
|
||||
|
||||
const handlePayLoan = () => {
|
||||
setShowType(typeToShow.payloan)
|
||||
}
|
||||
|
||||
const handleLoanInfo = () => {
|
||||
setShowType(typeToShow.loaninfo)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-3 h-full flex flex-col gap-3'>
|
||||
<div className='mt-3 flex justify-center'>
|
||||
<button onClick={handleGetLoan} className='p-3 bg-purple-800 text-lg sm:text-2xl text-white font-bold rounded'>Get Loan</button>
|
||||
</div>
|
||||
|
||||
<div className='mt-3 w-full h-full flex flex-col gap-2 justify-end'>
|
||||
<button onClick={handlePayLoan} className='w-full p-3 bg-orange-500 text-lg sm:text-2xl text-white font-bold rounded'>Pay Loan</button>
|
||||
<button onClick={handleLoanInfo} className='w-full p-3 bg-orange-500 text-lg sm:text-2xl text-white font-bold rounded'>Loan Information</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function LoanDetails({step, handleStep, screens}) {
|
||||
|
||||
const handleGetLoan = () => {
|
||||
handleStep({...step.details}, screens.loan_pin)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-3 h-full flex flex-col gap-3'>
|
||||
<p className='font-semibold text-base md:text-xl'>Loan Details</p>
|
||||
<div className='text-input w-full flex flex-col gap-2 justify-center items-center'>
|
||||
<p className='w-full text-base md:text-xl'>Loan Amount: ₦{step.details.typedAmount}</p>
|
||||
<p className='w-full text-base md:text-xl'>Interest 5%: ₦{Math.round((step.details.typedAmount*0.05 + Number.EPSILON) * 100) / 100}</p>
|
||||
<p className='w-full text-base md:text-xl'>Mgt Fee 1%: ₦{Math.round((step.details.typedAmount*0.01 + Number.EPSILON) * 100) / 100}</p>
|
||||
<p className='w-full text-base md:text-xl'>Insurance 1%: ₦{Math.round((step.details.typedAmount*0.01 + Number.EPSILON) * 100) / 100}</p>
|
||||
<p className='w-full text-base md:text-xl'>VAT(3.75% Int)%: ₦{Math.round((0.0375 * step.details.typedAmount*0.05 + Number.EPSILON) * 100) / 100}</p>
|
||||
</div>
|
||||
<div className='mt-3 flex justify-center'>
|
||||
<button onClick={handleGetLoan} className='p-3 bg-purple-800 text-lg sm:text-2xl text-white font-bold rounded'>Accept</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import Label from '../../Label'
|
||||
import InputText from '../../InputText'
|
||||
import { verifyLoan } from '../../../services/siteServices'
|
||||
|
||||
export default function LoanPin({step, handleStep, screens}) {
|
||||
|
||||
const [isSuccess, setIsSuccess] = useState(false)
|
||||
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const [pin, setPin] = useState('')
|
||||
|
||||
const handlePinChange = ({target:{value}}) => {
|
||||
setError('')
|
||||
setPin(value)
|
||||
}
|
||||
|
||||
const proceed = useMutation({
|
||||
mutationFn: () => {
|
||||
let fields = {pin, bvn:step.activeUser.bvn, loan_application_id:step.details.loan_application_id}
|
||||
if(!fields.pin){
|
||||
throw ({message:'Please enter pin'})
|
||||
}
|
||||
// if(isNaN(fields.pin)){
|
||||
// throw ({message:'Amount must be a valid figure'})
|
||||
// }
|
||||
if(fields.pin.length != 4){
|
||||
throw ({message:'Pin must be 4 digits'})
|
||||
}
|
||||
return verifyLoan(fields)
|
||||
},
|
||||
onError: (error) => {
|
||||
setError(error.message)
|
||||
setTimeout(()=>{setError('')},3000)
|
||||
},
|
||||
onSuccess: (res) => {
|
||||
setIsSuccess(true)
|
||||
handleStep({...step}, screens.finished)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isSuccess ?
|
||||
<div className='mt-3 flex flex-col gap-4'>
|
||||
<div className='text-input font-semibold w-full flex flex-col gap-4 justify-center items-center'>
|
||||
<p className='text-center text-base md:text-xl'>{`Your loan of ₦${step.details.typedAmount} would be set up. T&C's apply.`}</p>
|
||||
<Label name='Enter your 4 Digit pin' htmlfor='pin' />
|
||||
<InputText id='pin' name='pin' type='text' value={pin} handleChange={handlePinChange} />
|
||||
<button onClick={()=>{proceed.mutate()}} disabled={proceed.isPending} className='p-3 bg-purple-800 text-base sm:text-xl text-white font-bold rounded'>{proceed.isPending ? 'loading...' : 'Continue'}</button>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div className='mt-3 flex flex-col gap-4'>
|
||||
<div className='text-input font-semibold w-full flex flex-col gap-4 justify-center items-center'>
|
||||
<p className='text-center text-base md:text-xl'>Your request is being processed. You will receive a confirmation SMS shortly.</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
{error &&
|
||||
<>
|
||||
<div className="w-full text-center p-2">
|
||||
<p className='text-red-500 text-sm'>{error}</p>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function NotEligible({step, handleStep, screens}) {
|
||||
|
||||
// const handleGetLoan = () => {
|
||||
// handleStep({...step.details}, screens.getloan)
|
||||
// }
|
||||
|
||||
return (
|
||||
<div className='mt-3 h-full flex flex-col gap-3'>
|
||||
<p className='text-center text-base sm:text-lg text-red-500 font-medium'>Selected profile not qualified for this product</p>
|
||||
|
||||
{/* <div className='mt-3 w-full h-full flex flex-col gap-2 justify-end'>
|
||||
<button className='w-full p-3 bg-orange-500 text-lg sm:text-2xl text-white font-bold rounded'>Pay Loan</button>
|
||||
<button className='w-full p-3 bg-orange-500 text-lg sm:text-2xl text-white font-bold rounded'>Loan Information</button>
|
||||
</div> */}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { IoIosArrowForward } from "react-icons/io";
|
||||
|
||||
import { getOffers, loanSelect } from '../../../services/siteServices'
|
||||
import queryKeys from '../../../services/queryKeys'
|
||||
|
||||
export default function Offers({step, handleStep, screens}) {
|
||||
|
||||
const [selectedAmount, setSelectedAmount] = useState('')
|
||||
|
||||
const {data, isFetching, isError, error} = useQuery({
|
||||
queryKey: queryKeys.offers,
|
||||
queryFn: () => getOffers()
|
||||
})
|
||||
|
||||
const offers = data?.data?.product_data?.offers // OFFERS LIST
|
||||
|
||||
const selectedLoan = useMutation({
|
||||
mutationFn: (details) => {
|
||||
let fields = {bvn:step.activeUser.bvn, loan: details.loan}
|
||||
// if(!fields.bvn){
|
||||
// throw new Error('*')
|
||||
// }
|
||||
setSelectedAmount(details.amount)
|
||||
return loanSelect(fields)
|
||||
},
|
||||
onError: (error) => {
|
||||
// setError('*')
|
||||
},
|
||||
onSuccess: (res) => {
|
||||
const selectedOffer = res.data.loan
|
||||
handleStep({selectedAmount, selectedOffer, ...res.data}, screens.selected_offer)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='mt-3 flex flex-col gap-4'>
|
||||
<div className='text-input font-semibold w-full flex flex-col gap-4 justify-center items-center'>
|
||||
{isFetching ?
|
||||
<>
|
||||
<div className="w-full py-4">
|
||||
<p className='text-slate-800 text-center'>Loading...</p>
|
||||
</div>
|
||||
</>
|
||||
: isError ?
|
||||
<div className="w-full py-4">
|
||||
<p className='text-red-500 text-center'>{error.message}</p>
|
||||
</div>
|
||||
:
|
||||
<>
|
||||
{offers.map(item => {
|
||||
let isDisabled = item.active == '0' ? true : false
|
||||
return (
|
||||
<button
|
||||
key={item?.cid}
|
||||
disabled={isDisabled || selectedLoan.isPending}
|
||||
// value={item.loan}
|
||||
onClick={()=>selectedLoan.mutate(item)}
|
||||
className={`w-full flex gap-2 justify-between items-center p-2 bg-purple-800 ${selectedLoan.isPending && 'bg-purple-800/50'} text-base sm:text-xl text-white font-bold rounded ${isDisabled && 'opacity-50'}`}
|
||||
>
|
||||
{item?.description}
|
||||
<IoIosArrowForward />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
}
|
||||
<>
|
||||
</>
|
||||
|
||||
{selectedLoan.isPending &&
|
||||
<div className="w-full text-center p-2">
|
||||
<p className='text-sm'>loading...</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
{selectedLoan.error &&
|
||||
<>
|
||||
<div className="w-full text-center p-2">
|
||||
<p className='text-red-500 text-sm'>{selectedLoan.error.message}</p>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
|
||||
import Label from '../../Label'
|
||||
import InputText from '../../InputText'
|
||||
import { verifyPin } from '../../../services/siteServices'
|
||||
|
||||
export default function Pin({step, handleStep, screens}) {
|
||||
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const [pin, setPin] = useState('')
|
||||
|
||||
const handlePinChange = ({target:{value}}) => {
|
||||
setError('')
|
||||
setPin(value)
|
||||
}
|
||||
|
||||
const proceed = useMutation({
|
||||
mutationFn: () => {
|
||||
let fields = {pin, bvn:step.activeUser.bvn}
|
||||
if(!fields.pin){
|
||||
throw new Error('*')
|
||||
}
|
||||
return verifyPin(fields)
|
||||
},
|
||||
onError: (error) => {
|
||||
setError('*')
|
||||
},
|
||||
onSuccess: (res) => {
|
||||
handleStep(step.details, screens.offers)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='mt-3 flex flex-col gap-4'>
|
||||
{(!proceed.error || proceed?.error?.message == '*') &&
|
||||
<div className='text-input font-semibold w-full flex flex-col gap-4 justify-center items-center'>
|
||||
<Label name='Enter your pin' htmlfor='pin' error={error} />
|
||||
<InputText id='pin' name='pin' type='text' value={pin} handleChange={handlePinChange} />
|
||||
<button onClick={()=>{proceed.mutate()}} disabled={proceed.isPending} className='p-3 bg-purple-800 text-base sm:text-xl text-white font-bold rounded'>{proceed.isPending ? 'loading...' : 'Continue'}</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
{(proceed.error && proceed.error.message != '*') &&
|
||||
<>
|
||||
<div className="w-full text-center p-2">
|
||||
<p className='text-red-500 font-semibold text-base sm:text-3xl'>{proceed.error.message}</p>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react'
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import queryKeys from '../../../services/queryKeys';
|
||||
import { getProducts } from '../../../services/siteServices';
|
||||
import { IoIosArrowForward } from "react-icons/io";
|
||||
|
||||
export default function ProductDetails({step, handleStep, screens}) {
|
||||
|
||||
const {data, isFetching, isError, error} = useQuery({
|
||||
queryKey: queryKeys.products,
|
||||
queryFn: () => getProducts()
|
||||
})
|
||||
|
||||
const products = data?.data?.product_data?.products // PRODUCTS LIST
|
||||
|
||||
const handleClick = (product) => {
|
||||
if(step?.activeUser?.salary_account){
|
||||
handleStep(product, screens.getLoan)
|
||||
}else{
|
||||
handleStep({...step.details}, screens.not_eligible)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='mt-3 flex flex-col gap-3'>
|
||||
{isFetching ?
|
||||
<>
|
||||
<div className="w-full py-4">
|
||||
<p className='text-slate-800 text-center'>Loading...</p>
|
||||
</div>
|
||||
</>
|
||||
: isError ?
|
||||
<div className="w-full py-4">
|
||||
<p className='text-red-500 text-center'>{error.message}</p>
|
||||
</div>
|
||||
:
|
||||
<>
|
||||
{products && products.map(product => {
|
||||
let isDisabled = product.active == '0' ? true : false
|
||||
return (
|
||||
<button
|
||||
key={product?.cid}
|
||||
disabled={isDisabled}
|
||||
onClick={()=>handleClick(product)}
|
||||
className={`w-full flex gap-2 justify-between items-center p-2 bg-purple-800 text-base sm:text-xl text-white font-bold rounded ${isDisabled && 'opacity-50'}`}
|
||||
>
|
||||
<span>{product?.description}</span>
|
||||
<IoIosArrowForward />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import Label from '../../Label'
|
||||
import InputText from '../../InputText'
|
||||
import { loanApply } from '../../../services/siteServices'
|
||||
|
||||
export default function SelectedOffer({step, handleStep, screens}) {
|
||||
|
||||
const [amount, setAmount] = useState('')
|
||||
|
||||
const [error, setError] = useState('')
|
||||
|
||||
|
||||
const handleAmountChange = ({target:{value}}) => {
|
||||
setError('')
|
||||
setAmount(value)
|
||||
}
|
||||
|
||||
const apply = useMutation({
|
||||
mutationFn: () => {
|
||||
let fields = {amount: Number(amount), bvn:step.activeUser.bvn, loan:step.details.loan}
|
||||
if(!fields.amount){
|
||||
// throw new Error('*')
|
||||
throw ({message:'Please enter amount'})
|
||||
}
|
||||
if(fields.amount > Number(step.details.selectedAmount)){
|
||||
// throw new Error('*')
|
||||
throw ({message:`Please enter amount not more than ${step.details.selectedAmount}`})
|
||||
}
|
||||
if(isNaN(amount)){
|
||||
throw ({message:'Amount must be a valid figure'})
|
||||
}
|
||||
return loanApply(fields)
|
||||
},
|
||||
onError: (error) => {
|
||||
setError(error.message)
|
||||
setTimeout(()=>{setError('')},3000)
|
||||
},
|
||||
onSuccess: (res) => {
|
||||
// const selectedOffer = res.data.loan[0].loan
|
||||
handleStep({typedAmount: Number(amount),loan_application_id:res.data.loan_application_id[0], ...step.details}, screens.loan_details)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='mt-3 flex flex-col gap-4'>
|
||||
<div className='text-input font-semibold w-full flex flex-col gap-4 justify-center items-center'>
|
||||
<p className='text-center text-base'>Your eligible loan amount is ₦{step.details.selectedAmount}</p>
|
||||
<Label name='Please enter your desired loan amount' htmlfor='amount' />
|
||||
<InputText id='amount' name='amount' type='text' value={amount} handleChange={handleAmountChange} />
|
||||
{/* <div className='flex flex-col gap-4'>
|
||||
<p className='text-center text-base'>Upfront Interest 200 Naira</p>
|
||||
<p className='text-center text-base'>Due Date: Next Salary or 30 Days</p>
|
||||
</div> */}
|
||||
<button onClick={()=>{apply.mutate()}} disabled={apply.isPending} className='p-3 bg-purple-800 text-base sm:text-xl text-white font-bold rounded'>{apply.isPending ? 'loading...' : 'Continue'}</button>
|
||||
</div>
|
||||
|
||||
{error &&
|
||||
<>
|
||||
<div className="w-full text-center p-2">
|
||||
<p className='text-red-500 text-sm'>{error}</p>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function TermsAndCondition({step, handleStep, screens}) {
|
||||
|
||||
const handleGetLoan = () => {
|
||||
handleStep({...step.details}, screens.offers)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-3 h-full flex flex-col gap-3'>
|
||||
<div className='text-input font-semibold w-full flex flex-col gap-4 justify-center items-center'>
|
||||
<p className='text-center text-base md:text-xl'>I accept the Terms and Conditions</p>
|
||||
</div>
|
||||
<div className='mt-3 flex justify-center'>
|
||||
<button onClick={handleGetLoan} className='p-3 bg-purple-800 text-lg sm:text-2xl text-white font-bold rounded'>Accept</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { IoIosArrowForward } from "react-icons/io";
|
||||
|
||||
import { getLoanInfo, loanSelect } from '../../../services/siteServices'
|
||||
import queryKeys from '../../../services/queryKeys'
|
||||
|
||||
export default function LoanInfoList({step, handleStep, screens}) {
|
||||
|
||||
const [selectedAmount, setSelectedAmount] = useState('')
|
||||
|
||||
const {data, isFetching, isError, error} = useQuery({
|
||||
queryKey: queryKeys.offers,
|
||||
queryFn: () => getLoanInfo()
|
||||
})
|
||||
|
||||
const loanInfoList = data?.data // LOAN INFO LIST
|
||||
|
||||
const selectedLoan = useMutation({
|
||||
mutationFn: (details) => {
|
||||
let fields = {bvn:step.activeUser.bvn, loan: details.loan}
|
||||
// if(!fields.bvn){
|
||||
// throw new Error('*')
|
||||
// }
|
||||
setSelectedAmount(details.amount)
|
||||
return loanSelect(fields)
|
||||
},
|
||||
onError: (error) => {
|
||||
// setError('*')
|
||||
},
|
||||
onSuccess: (res) => {
|
||||
const selectedOffer = res.data.loan
|
||||
handleStep({selectedAmount, selectedOffer, ...res.data}, screens.repay_pin)
|
||||
}
|
||||
})
|
||||
|
||||
const handleClick = (selectedAmount) => { // remove later
|
||||
handleStep({selectedAmount}, screens.repay_pin)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-3 flex flex-col gap-4'>
|
||||
<div className='text-input font-semibold w-full flex flex-col gap-4 justify-center items-center'>
|
||||
{isFetching ?
|
||||
<>
|
||||
<div className="w-full py-4">
|
||||
<p className='text-slate-800 text-center'>Loading...</p>
|
||||
</div>
|
||||
</>
|
||||
: isError ?
|
||||
<div className="w-full py-4">
|
||||
<p className='text-red-500 text-center'>{error.message}</p>
|
||||
</div>
|
||||
:
|
||||
<>
|
||||
<p className='text-center text-base md:text-xl'>Loan to repay</p>
|
||||
{loanInfoList.map(item => {
|
||||
let isDisabled = item.active == '0' ? true : false
|
||||
return (
|
||||
<button
|
||||
key={item?.cid}
|
||||
disabled={true}
|
||||
// disabled={isDisabled || selectedLoan.isPending}
|
||||
// value={item.loan}
|
||||
// onClick={()=>selectedLoan.mutate(item)}
|
||||
onClick={()=>handleClick(item?.amount)}
|
||||
className={`w-full flex gap-2 justify-between items-center p-2 bg-purple-800 ${selectedLoan.isPending && 'bg-purple-800/50'} text-base sm:text-xl text-white font-bold rounded ${isDisabled && 'opacity-50'}`}
|
||||
>
|
||||
{item?.description}
|
||||
{/* <IoIosArrowForward /> */}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
}
|
||||
<>
|
||||
</>
|
||||
|
||||
{selectedLoan.isPending &&
|
||||
<div className="w-full text-center p-2">
|
||||
<p className='text-sm'>loading...</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
{selectedLoan.error &&
|
||||
<>
|
||||
<div className="w-full text-center p-2">
|
||||
<p className='text-red-500 text-sm'>{selectedLoan.error.message}</p>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import Label from '../../Label'
|
||||
import InputText from '../../InputText'
|
||||
import { repayLoan } from '../../../services/siteServices'
|
||||
|
||||
export default function PinRepayment({step, handleStep, screens}) {
|
||||
|
||||
console.log('step', step.activeUser, step.details)
|
||||
|
||||
const [isSuccess, setIsSuccess] = useState(false)
|
||||
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const [pin, setPin] = useState('')
|
||||
|
||||
const handlePinChange = ({target:{value}}) => {
|
||||
setError('')
|
||||
setPin(value)
|
||||
}
|
||||
|
||||
const proceed = useMutation({
|
||||
mutationFn: () => {
|
||||
let fields = {pin, loan_id:step.details.selected_loan_id, customerID:step.activeUser.customerid}
|
||||
if(!fields.pin){
|
||||
throw ({message:'Please enter pin'})
|
||||
}
|
||||
|
||||
if(fields.pin.length != 4){
|
||||
throw ({message:'Pin must be 4 digits'})
|
||||
}
|
||||
return repayLoan(fields)
|
||||
},
|
||||
onError: (error) => {
|
||||
setError(error.message)
|
||||
setTimeout(()=>{setError('')},3000)
|
||||
},
|
||||
onSuccess: (res) => {
|
||||
setIsSuccess(true)
|
||||
handleStep({...step}, screens.finished)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isSuccess ?
|
||||
<div className='mt-3 flex flex-col gap-4'>
|
||||
<div className='text-input font-semibold w-full flex flex-col gap-4 justify-center items-center'>
|
||||
<p className='text-center text-base md:text-xl'>{`Your loan amount due is ${step.details.selectedAmount}`}</p>
|
||||
<Label name='Enter your 4 Digit pin' htmlfor='pin' />
|
||||
<InputText id='pin' name='pin' type='text' value={pin} handleChange={handlePinChange} />
|
||||
<button onClick={()=>{proceed.mutate()}} disabled={proceed.isPending} className='p-3 bg-purple-800 text-base sm:text-xl text-white font-bold rounded'>{proceed.isPending ? 'loading...' : 'Continue'}</button>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div className='mt-3 flex flex-col gap-4'>
|
||||
<div className='text-input font-semibold w-full flex flex-col gap-4 justify-center items-center'>
|
||||
<p className='text-center text-base md:text-xl'>Your loan repayment is being processed. You will receive a confirmation SMS shortly.</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
{error &&
|
||||
<>
|
||||
<div className="w-full text-center p-2">
|
||||
<p className='text-red-500 text-sm'>{error}</p>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { IoIosArrowForward } from "react-icons/io";
|
||||
|
||||
import { getLoanInfo, loanSelect } from '../../../services/siteServices'
|
||||
import queryKeys from '../../../services/queryKeys'
|
||||
|
||||
export default function RepayLoanList({step, handleStep, screens}) {
|
||||
|
||||
const [selectedAmount, setSelectedAmount] = useState('')
|
||||
|
||||
const {data, isFetching, isError, error} = useQuery({
|
||||
queryKey: queryKeys.offers,
|
||||
queryFn: () => getLoanInfo()
|
||||
})
|
||||
|
||||
const loanInfoList = data?.data // LOAN INFO LIST
|
||||
|
||||
// const selectedLoan = useMutation({
|
||||
// mutationFn: (details) => {
|
||||
// let fields = {bvn:step.activeUser.bvn, loan: details.loan}
|
||||
// setSelectedAmount(details.amount)
|
||||
// return loanSelect(fields)
|
||||
// },
|
||||
// onError: (error) => {
|
||||
// },
|
||||
// onSuccess: (res) => {
|
||||
// const selectedOffer = res.data.loan
|
||||
// handleStep({selectedAmount, selectedOffer, ...res.data}, screens.repay_pin)
|
||||
// }
|
||||
// })
|
||||
|
||||
const handleClick = (selected_loan_id, selectedAmount) => {
|
||||
handleStep({selected_loan_id, selectedAmount}, screens.repay_pin)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-3 flex flex-col gap-4'>
|
||||
<div className='text-input font-semibold w-full flex flex-col gap-4 justify-center items-center'>
|
||||
{isFetching ?
|
||||
<>
|
||||
<div className="w-full py-4">
|
||||
<p className='text-slate-800 text-center'>Loading...</p>
|
||||
</div>
|
||||
</>
|
||||
: isError ?
|
||||
<div className="w-full py-4">
|
||||
<p className='text-red-500 text-center'>{error.message}</p>
|
||||
</div>
|
||||
:
|
||||
<>
|
||||
<p className='text-center text-base md:text-xl'>Select Loan to repay</p>
|
||||
{loanInfoList.map(item => {
|
||||
let isDisabled = item.active == '0' ? true : false
|
||||
let amount = item?.description.split(' ')[item?.description.split(' ').length -1] //remove later
|
||||
return (
|
||||
<button
|
||||
key={item?.cid}
|
||||
// disabled={isDisabled || selectedLoan.isPending}
|
||||
// value={item.loan}
|
||||
// onClick={()=>selectedLoan.mutate(item)}
|
||||
// className={`w-full flex gap-2 justify-between items-center p-2 bg-purple-800 ${selectedLoan.isPending && 'bg-purple-800/50'} text-base sm:text-xl text-white font-bold rounded ${isDisabled && 'opacity-50'}`}
|
||||
className={`w-full flex gap-2 justify-between items-center p-2 bg-purple-800 text-base sm:text-xl text-white font-bold rounded`}
|
||||
onClick={()=>handleClick(item.loan_id, amount)}
|
||||
>
|
||||
{item?.description}
|
||||
<IoIosArrowForward />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
}
|
||||
<>
|
||||
</>
|
||||
|
||||
{/* {selectedLoan.isPending &&
|
||||
<div className="w-full text-center p-2">
|
||||
<p className='text-sm'>loading...</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
{selectedLoan.error &&
|
||||
<>
|
||||
<div className="w-full text-center p-2">
|
||||
<p className='text-red-500 text-sm'>{selectedLoan.error.message}</p>
|
||||
</div>
|
||||
</>
|
||||
} */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
const formatNumber = (number = 0) => {
|
||||
// return new Intl.NumberFormat().format(number);
|
||||
return number.toFixed(2);
|
||||
};
|
||||
|
||||
export default formatNumber
|
||||
@@ -2,6 +2,14 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html{
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
*{
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client';
|
||||
import {BrowserRouter as Router} from 'react-router-dom'
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
//import reportWebVitals from './reportWebVitals';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
@@ -17,4 +17,4 @@ root.render(
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
//reportWebVitals();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
const myLinks = {
|
||||
error: '*',
|
||||
getStarted: '/',
|
||||
login: '/login',
|
||||
home: '/home',
|
||||
getLoan: '/get-loan'
|
||||
}
|
||||
|
||||
export default myLinks
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import LoanScreens from '../components/LoanScreens'
|
||||
|
||||
export default function GetLoanPage() {
|
||||
return (
|
||||
<LoanScreens />
|
||||
)
|
||||
}
|
||||
+1
-29
@@ -1,37 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
import myLinks from '../myLinks'
|
||||
import HomeCom from '../components/HomeCom'
|
||||
import PageLoader from '../components/PageLoader'
|
||||
|
||||
export default function HomePage() {
|
||||
|
||||
|
||||
const {state} = useLocation()
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(()=>{
|
||||
if(state?.proceed != 'true'){
|
||||
return navigate(myLinks.getStarted, {replace:true})
|
||||
}
|
||||
setTimeout(()=>{
|
||||
setLoading(false)
|
||||
},2000)
|
||||
},[])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
loading ?
|
||||
<PageLoader />
|
||||
:
|
||||
<HomeCom />
|
||||
}
|
||||
<HomeCom />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
const queryKeys = {
|
||||
demoUsers: ['demo-users'],
|
||||
products: ['products'],
|
||||
offers: ['offers'],
|
||||
}
|
||||
|
||||
export default queryKeys
|
||||
@@ -0,0 +1,115 @@
|
||||
import axios from "axios"
|
||||
|
||||
|
||||
axios.interceptors.request.use(
|
||||
config => {
|
||||
config.headers = {
|
||||
Accept: "application/json",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
// "Access-Control-Expose-Headers": "Access-Control-Allow-Origin",
|
||||
// "Access-Control-Allow-Headers": "Origin, X-API-KEY, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method, Access-Control-Allow-Headers, Authorization, observe, enctype, Content-Length, X-Csrf-Token",
|
||||
"Content-Type": "application/json;charset=UTF-8",
|
||||
// 'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
};
|
||||
// config.headers['Authorization'] = `Bearer ${localStorage.getItem('token')}`;
|
||||
// config.baseURL = process.env.REACT_APP_MAIN_API
|
||||
return config;
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
const postAuxEnd = (path, postData, media=false) => {
|
||||
const basePath = media ? process.env.REACT_APP_MAIN_API : process.env.REACT_APP_MAIN_API
|
||||
return axios.post(`${basePath}${path}`, postData).then(res => {
|
||||
return res
|
||||
}).catch(err => {
|
||||
throw new Error(err.response.data.message);
|
||||
})
|
||||
}
|
||||
|
||||
const getAuxEnd = (path, reqData= null) => {
|
||||
const basePath = process.env.REACT_APP_MAIN_API
|
||||
return axios.get(`${basePath}${path}`,{ params: reqData }).then(res => {
|
||||
return res
|
||||
// localStorage.clear();
|
||||
// window.location.href = `/login?sessionExpired=true`;
|
||||
}).catch(err => {
|
||||
throw new Error(err);
|
||||
// throw new Error(err.response.data.message);
|
||||
// return err
|
||||
})
|
||||
}
|
||||
|
||||
// FUNCTION TO LOGIN USER IN
|
||||
export const loginUser = (reqData) => {
|
||||
let postData = {
|
||||
...reqData
|
||||
}
|
||||
return postAuxEnd('/salary/login', postData, false)
|
||||
}
|
||||
|
||||
// FUNCTION TO LOGIN USER IN
|
||||
export const verifyPin = (reqData) => {
|
||||
let postData = {
|
||||
...reqData
|
||||
}
|
||||
return postAuxEnd('/salary/verifypin', postData, false)
|
||||
}
|
||||
|
||||
// FUNCTION TO VERIFY LOAN
|
||||
export const verifyLoan = (reqData) => {
|
||||
let postData = {
|
||||
...reqData
|
||||
}
|
||||
return postAuxEnd('/salary/verifloan', postData, false)
|
||||
}
|
||||
|
||||
// FUNCTION TO REPAY LOAN
|
||||
export const repayLoan = (reqData) => {
|
||||
let postData = {
|
||||
...reqData
|
||||
}
|
||||
return postAuxEnd('/loan/repay', postData, false)
|
||||
}
|
||||
|
||||
// FUNCTION TO RUN WHEN USER SELECTS A LOAN
|
||||
export const loanSelect = (reqData) => {
|
||||
let postData = {
|
||||
...reqData
|
||||
}
|
||||
return postAuxEnd('/salary/loanselect', postData, false)
|
||||
}
|
||||
|
||||
// FUNCTION TO APPLY FOR LOAN
|
||||
export const loanApply = (reqData) => {
|
||||
let postData = {
|
||||
...reqData
|
||||
}
|
||||
return postAuxEnd('/salary/loanapply', postData, false)
|
||||
}
|
||||
|
||||
// FUNCTION TO GET DEMO USERS
|
||||
export const demoUsersList = (reqData) => {
|
||||
const postData = { ...reqData }
|
||||
return getAuxEnd(`/salary/demousers`, postData)
|
||||
}
|
||||
|
||||
// FUNCTION TO GET MY PRODUCTS DATA
|
||||
export const getProducts = (reqData) => {
|
||||
const postData = { ...reqData }
|
||||
return getAuxEnd(`/salary/products`, postData)
|
||||
}
|
||||
|
||||
// FUNCTION TO GET MY OFFERS DATA
|
||||
export const getOffers = (reqData) => {
|
||||
const postData = { ...reqData }
|
||||
return getAuxEnd(`/salary/loanoffers`, postData)
|
||||
}
|
||||
|
||||
// FUNCTION TO GET LOAN INFO
|
||||
export const getLoanInfo = (reqData) => {
|
||||
const postData = { ...reqData }
|
||||
return getAuxEnd(`/loan/info`, postData)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const initialState = {
|
||||
userDetails: {},
|
||||
};
|
||||
|
||||
export const userSlice = createSlice({
|
||||
name: "userDetails",
|
||||
initialState,
|
||||
reducers: {
|
||||
updateUserDetails: (state, action) => {
|
||||
state.userDetails = { ...action.payload };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Action creators are generated for each case reducer function
|
||||
export const { updateUserDetails } = userSlice.actions;
|
||||
|
||||
export default userSlice.reducer;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
|
||||
import userDetailReducer from "./UserDetails";
|
||||
|
||||
|
||||
export default configureStore({
|
||||
reducer: {
|
||||
userDetails: userDetailReducer,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user