Compare commits
46 Commits
| 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 |
@@ -6,7 +6,8 @@ TWITTER_URL=https://twitter.com
|
|||||||
INSTAGRAM_URL=https://www.instagram.com
|
INSTAGRAM_URL=https://www.instagram.com
|
||||||
|
|
||||||
# BACKEND END POINTS
|
# 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
|
# ENQUIRIES CONTACTS
|
||||||
VITE_CALL_ENDPOINT='09099000000'
|
VITE_CALL_ENDPOINT='09099000000'
|
||||||
|
|||||||
+2
-1
@@ -6,7 +6,8 @@ VITE_TWITTER_URL=https://twitter.com
|
|||||||
VITE_INSTAGRAM_URL=https://www.instagram.com
|
VITE_INSTAGRAM_URL=https://www.instagram.com
|
||||||
|
|
||||||
# BACKEND END POINTS
|
# 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
|
# ENQUIRIES CONTACTS
|
||||||
VITE_CALL_ENDPOINT='09099000000'
|
VITE_CALL_ENDPOINT='09099000000'
|
||||||
|
|||||||
+2
-1
@@ -6,7 +6,8 @@ TWITTER_URL=https://twitter.com
|
|||||||
INSTAGRAM_URL=https://www.instagram.com
|
INSTAGRAM_URL=https://www.instagram.com
|
||||||
|
|
||||||
# BACKEND END POINTS
|
# 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
|
# ENQUIRIES CONTACTS
|
||||||
VITE_CALL_ENDPOINT='09099000000'
|
VITE_CALL_ENDPOINT='09099000000'
|
||||||
|
|||||||
+2
-1
@@ -14,7 +14,8 @@ services:
|
|||||||
- "5173"
|
- "5173"
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- digifi-apidev.chiefsoft.net:10.10.33.15
|
- 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:
|
environment:
|
||||||
- PORT=${DIGIFI_PORT}
|
- PORT=${DIGIFI_PORT}
|
||||||
tty: true
|
tty: true
|
||||||
|
|||||||
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="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
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" />
|
<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.
|
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`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>React App</title>
|
<title>digiFi</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
-20
@@ -2,32 +2,12 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-logo {
|
|
||||||
height: 40vmin;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*@media (prefers-reduced-motion: no-preference) {*/
|
/*@media (prefers-reduced-motion: no-preference) {*/
|
||||||
/* .App-logo {*/
|
/* .App-logo {*/
|
||||||
/* animation: App-logo-spin infinite 20s linear;*/
|
/* 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 {
|
@keyframes App-logo-spin {
|
||||||
from {
|
from {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
|
|||||||
+16
@@ -1,10 +1,26 @@
|
|||||||
|
import { QueryClientProvider, QueryClient } from '@tanstack/react-query'
|
||||||
import MyRoutes from './MyRoutes';
|
import MyRoutes from './MyRoutes';
|
||||||
|
|
||||||
|
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
retry: 3,
|
||||||
|
// refetchOnMount: false,
|
||||||
|
staleTime: Infinity // can also be a number in millisecond
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
<MyRoutes />
|
<MyRoutes />
|
||||||
|
</QueryClientProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import GetStartedPage from './pages/GetStartedPage'
|
|||||||
import LoginPage from './pages/LoginPage'
|
import LoginPage from './pages/LoginPage'
|
||||||
import HomePage from './pages/HomePage'
|
import HomePage from './pages/HomePage'
|
||||||
import myLinks from './myLinks'
|
import myLinks from './myLinks'
|
||||||
|
import GetLoanPage from './pages/GetLoanPage'
|
||||||
|
|
||||||
export default function MyRoutes() {
|
export default function MyRoutes() {
|
||||||
return (
|
return (
|
||||||
@@ -16,7 +17,10 @@ export default function MyRoutes() {
|
|||||||
|
|
||||||
<Route element={<UserExists />}>
|
<Route element={<UserExists />}>
|
||||||
<Route path={myLinks.home} element={<HomePage />} />
|
<Route path={myLinks.home} element={<HomePage />} />
|
||||||
|
<Route path={myLinks.getLoan} element={<GetLoanPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
<Route path={myLinks.error} element={<LoginPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useLocation, useNavigate, Outlet } from 'react-router-dom'
|
import { useLocation, useNavigate, Outlet } from 'react-router-dom'
|
||||||
|
import Layout from '../components/layout/Layout'
|
||||||
|
|
||||||
import myLinks from '../myLinks'
|
import myLinks from '../myLinks'
|
||||||
import PageLoader from '../components/PageLoader'
|
import PageLoader from '../components/PageLoader'
|
||||||
@@ -13,7 +14,7 @@ export default function UserExists() {
|
|||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
if(state?.proceed != 'true'){
|
if(!state || localStorage.getItem('active') != 'true'){
|
||||||
return navigate(myLinks.getStarted, {replace:true})
|
return navigate(myLinks.getStarted, {replace:true})
|
||||||
}
|
}
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
@@ -27,7 +28,9 @@ export default function UserExists() {
|
|||||||
loading ?
|
loading ?
|
||||||
<PageLoader />
|
<PageLoader />
|
||||||
:
|
:
|
||||||
<Outlet />
|
<Layout>
|
||||||
|
<Outlet />
|
||||||
|
</Layout>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
+102
-57
@@ -1,49 +1,94 @@
|
|||||||
// import React from 'react'
|
// import React from 'react'
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
import { CiPhone } from "react-icons/ci"
|
import { BsCash } from "react-icons/bs";
|
||||||
import { IoIosPhonePortrait } from "react-icons/io"
|
import { MdUpdate } from "react-icons/md"
|
||||||
import { MdOutlineEmail } 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() {
|
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 (
|
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 lg:grid-cols-3 xl:grid-cols-4">
|
|
||||||
|
|
||||||
{data.map((item, index) => {
|
<div className="w-full flex flex-col gap-2 text-black">
|
||||||
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'
|
<div className="py-3 text-3xl text-black font-bold">Users</div>
|
||||||
return (
|
{isFetching ?
|
||||||
<div key={index} className="w-full rounded p-3 sm:p-5 bg-white shadow flex flex-col gap-5 hover:scale-105 hover:cursor-pointer">
|
<>
|
||||||
<div className="mb-5 card-title w-[70%] mx-auto">
|
<div className="w-full py-4">
|
||||||
<h1 className="mb-[1px] text-2xl font-bold">{item.name}</h1>
|
<p className='text-slate-800'>Loading...</p>
|
||||||
<span className={` ${color} text-sm font-bold p-1 rounded`}>{item.place}</span>
|
</div>
|
||||||
|
</>
|
||||||
|
: isError ?
|
||||||
|
<div className="w-full py-4">
|
||||||
|
<p className='text-red-500'>{error.message}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-body flex flex-col gap-2">
|
:
|
||||||
<div className="contact text-slate-700 flex gap-3 items-center">
|
<TableWrapper data={demoUsers} itemsPerPage={16}>
|
||||||
<span className="w-[40px] h-[40px] rounded-full flex flex-col justify-center items-center bg-slate-100/90 text-slate-700">
|
{({ data }) => (
|
||||||
<IoIosPhonePortrait />
|
<div className="grid gap-5 sm:gap-[40px] grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
</span>
|
{data.map((user, index) => {
|
||||||
<span>{item.contact}</span>
|
let hasSalaryAcct = user.salary_account === 0 ? false : true
|
||||||
</div>
|
return (
|
||||||
<div className="contact text-slate-700 flex gap-3 items-center">
|
<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`}>
|
||||||
<span className="w-[40px] h-[40px] rounded-full flex flex-col justify-center items-center bg-slate-100/90 text-slate-700">
|
<div className="mb-5 card-title w-full md:w-4/5 mx-auto text-center">
|
||||||
<CiPhone />
|
<h1 className="mb-[1px] text-base md:text-xl lg:text-2xl font-bold">{user.name}</h1>
|
||||||
</span>
|
<span className={`text-sm font-bold p-1 rounded`}>ID: {user.bvn}</span>
|
||||||
<span>{item.contact}</span>
|
</div>
|
||||||
</div>
|
<div className="card-body flex flex-col gap-2">
|
||||||
<div className="contact text-slate-700 flex gap-3 items-center">
|
<div className="contact text-slate-700 flex gap-2 items-center">
|
||||||
<span className="w-[40px] h-[40px] rounded-full flex flex-col justify-center items-center bg-slate-100/90 text-slate-700">
|
<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">
|
||||||
<MdOutlineEmail />
|
<FaUser />
|
||||||
</span>
|
</span>
|
||||||
<span>{item.email}</span>
|
<span>{user.customerid}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="contact text-slate-700 flex gap-3 items-center">
|
||||||
</div>
|
<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>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,26 +96,26 @@ export default function HomeCom() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const data = [
|
// const data = [
|
||||||
{name:'Jerry Eze', place: 'Home', contact: '021-025-0325', email: 'jerry@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:'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:'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:'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:'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:'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:'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:'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:'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:'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:'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:'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:'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:'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:'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:'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:'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:'Mike Timothy', place: 'Friends', contact: '066-025-0366', email: 'mike@example.com'},
|
||||||
|
|
||||||
]
|
// ]
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export default function InputText({id, name, type='text'}) {
|
export default function InputText({id, name, type='text', value, handleChange}) {
|
||||||
return (
|
return (
|
||||||
<div className='w-full h-12 round overflow-hidden'>
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export default function Label({name, htmlfor}) {
|
export default function Label({name, htmlfor, error}) {
|
||||||
return (
|
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() {
|
export default function GetStarted() {
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<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="App-header">
|
<header className="flex flex-col gap-2 justify-center items-center text-lg sm:text-2xl font-bold">
|
||||||
<img src={logo2} className="App-logo" alt="logo" />
|
<img src={logo2} className="h-32 sm:h-72 w-auto" alt="logo" />
|
||||||
<p className=''>
|
<p className='w-full text-center'>
|
||||||
digiFi - Banking Offers Emulator Systems.
|
digiFi - Banking Offers Emulator Systems.
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
className="App-link"
|
className="mt-2 px-2 py-1 text-emerald-600 bg-emerald-100/90 rounded"
|
||||||
to={myLinks.login}
|
to={myLinks.login}
|
||||||
href="https://reactjs.org"
|
href="https://reactjs.org"
|
||||||
// target="_blank"
|
// target="_blank"
|
||||||
|
|||||||
@@ -1,15 +1,52 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
import { useMutation } from '@tanstack/react-query'
|
||||||
|
|
||||||
import myLinks from '../../myLinks'
|
import myLinks from '../../myLinks'
|
||||||
import Label from '../Label'
|
import Label from '../Label'
|
||||||
import InputText from '../InputText'
|
import InputText from '../InputText'
|
||||||
import PageLoader from '../PageLoader'
|
import PageLoader from '../PageLoader'
|
||||||
|
import { loginUser } from '../../services/siteServices'
|
||||||
|
|
||||||
export default function LoginCom() {
|
export default function LoginCom() {
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const {state} = useLocation()
|
const {state} = useLocation()
|
||||||
const navigate = useNavigate()
|
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(()=>{
|
useEffect(()=>{
|
||||||
if(state?.proceed != 'true'){
|
if(state?.proceed != 'true'){
|
||||||
return navigate(myLinks.getStarted, {replace:true})
|
return navigate(myLinks.getStarted, {replace:true})
|
||||||
@@ -25,18 +62,26 @@ export default function LoginCom() {
|
|||||||
<PageLoader />
|
<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={`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'>
|
<div className='text-input'>
|
||||||
<Label name='Username' htmlfor='username' />
|
<Label name='Username' htmlfor='username' />
|
||||||
<InputText id='username' name='username' />
|
<InputText id='username' name='username' value={fields.username} handleChange={handleChange} />
|
||||||
</div>
|
</div>
|
||||||
<div className='text-input'>
|
<div className='text-input'>
|
||||||
<Label name='Password' htmlfor='password' />
|
<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>
|
</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'>
|
<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>
|
</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,10 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
html{
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
*{
|
*{
|
||||||
transition: all .3s;
|
transition: all .3s;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client';
|
|||||||
import {BrowserRouter as Router} from 'react-router-dom'
|
import {BrowserRouter as Router} from 'react-router-dom'
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import reportWebVitals from './reportWebVitals';
|
//import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
root.render(
|
root.render(
|
||||||
@@ -17,4 +17,4 @@ root.render(
|
|||||||
// If you want to start measuring performance in your app, pass a function
|
// If you want to start measuring performance in your app, pass a function
|
||||||
// to log results (for example: reportWebVitals(console.log))
|
// to log results (for example: reportWebVitals(console.log))
|
||||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||||
reportWebVitals();
|
//reportWebVitals();
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
const myLinks = {
|
const myLinks = {
|
||||||
|
error: '*',
|
||||||
getStarted: '/',
|
getStarted: '/',
|
||||||
login: '/login',
|
login: '/login',
|
||||||
home: '/home',
|
home: '/home',
|
||||||
|
getLoan: '/get-loan'
|
||||||
}
|
}
|
||||||
|
|
||||||
export default myLinks
|
export default myLinks
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import LoanScreens from '../components/LoanScreens'
|
||||||
|
|
||||||
|
export default function GetLoanPage() {
|
||||||
|
return (
|
||||||
|
<LoanScreens />
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
const queryKeys = {
|
const queryKeys = {
|
||||||
dummy: ['dummy']
|
demoUsers: ['demo-users'],
|
||||||
|
products: ['products'],
|
||||||
|
offers: ['offers'],
|
||||||
}
|
}
|
||||||
|
|
||||||
export default queryKeys
|
export default queryKeys
|
||||||
@@ -4,12 +4,12 @@ import axios from "axios"
|
|||||||
axios.interceptors.request.use(
|
axios.interceptors.request.use(
|
||||||
config => {
|
config => {
|
||||||
config.headers = {
|
config.headers = {
|
||||||
// Accept: "application/json",
|
Accept: "application/json",
|
||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": "*",
|
||||||
// "Access-Control-Expose-Headers": "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",
|
// "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",
|
"Content-Type": "application/json;charset=UTF-8",
|
||||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
// 'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||||
};
|
};
|
||||||
// config.headers['Authorization'] = `Bearer ${localStorage.getItem('token')}`;
|
// config.headers['Authorization'] = `Bearer ${localStorage.getItem('token')}`;
|
||||||
// config.baseURL = process.env.REACT_APP_MAIN_API
|
// config.baseURL = process.env.REACT_APP_MAIN_API
|
||||||
@@ -47,5 +47,69 @@ export const loginUser = (reqData) => {
|
|||||||
let postData = {
|
let postData = {
|
||||||
...reqData
|
...reqData
|
||||||
}
|
}
|
||||||
return postAuxEnd('/panel/auth/login', postData, false)
|
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)
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user