login endpoint added

This commit is contained in:
victorAnumudu
2025-02-05 21:23:45 +01:00
parent b185419ba7
commit 15a598872e
9 changed files with 326 additions and 63 deletions
+4
View File
@@ -7,6 +7,7 @@ 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 (
@@ -16,7 +17,10 @@ export default function MyRoutes() {
<Route element={<UserExists />}>
<Route path={myLinks.home} element={<HomePage />} />
<Route path={myLinks.getLoan} element={<GetLoanPage />} />
</Route>
<Route path={myLinks.error} element={<LoginPage />} />
</Routes>
)
}
+1 -1
View File
@@ -13,7 +13,7 @@ export default function UserExists() {
const [loading, setLoading] = useState(true)
useEffect(()=>{
if(state?.proceed != 'true'){
if(!state){
return navigate(myLinks.getStarted, {replace:true})
}
setTimeout(()=>{
+32
View File
@@ -0,0 +1,32 @@
import React, { useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import myLinks from '../myLinks'
import Label from './Label'
import InputText from './InputText'
export default function LoginCom() {
const {state} = useLocation()
const navigate = useNavigate()
useEffect(()=>{
if(!state?.user){
return navigate(myLinks.getStarted, {replace:true})
}
},[])
return (
<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-[400px] min-h-[500px] bg-white rounded-xl p-4 sm:p-8 shadow'>
<div className="pb-3 card-title w-full text-center border-b-2">
<h1 className="mb-[1px] text-2xl font-bold">{state?.user.name}</h1>
<span className={`text-base font-bold p-1 rounded`}>BVN: {state?.user.bvn}</span>
</div>
<div className='mt-3 flex justify-center items-center'>
<button onClick={()=>console.log('working')} className='p-4 bg-purple-800 text-lg sm:text-2xl text-white font-bold rounded'>Get Loan</button>
</div>
</div>
</div>
)
}
+82 -57
View File
@@ -1,59 +1,84 @@
// 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 { CiBank } from "react-icons/ci";
import { MdOutlineEmail } from "react-icons/md"
import { demoUsersList } from "../services/siteServices"
import queryKeys from "../services/queryKeys"
import myLinks from "../myLinks";
import TableWrapper from "./TableWrapper";
export default function HomeCom() {
const navigate = useNavigate()
const {data:users, isFetching, isError, error} = useQuery({
queryKey: queryKeys.demoUsers,
queryFn: () => demoUsersList()
})
const demoUsers = users?.data
console.log(demoUsers)
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 lg:grid-cols-3 xl: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 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">
<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={`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="w-full h-screen flex flex-col gap-2 overflow-y-auto text-black p-5 sm:p-[40px]">
<div className="py-4 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-700 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-700">
<IoIosPhonePortrait />
</span>
<span>{item.contact}</span>
</div>
<div className="contact text-slate-700 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-700">
<CiPhone />
</span>
<span>{item.contact}</span>
</div>
<div className="contact text-slate-700 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-700">
<MdOutlineEmail />
</span>
<span>{item.email}</span>
</div>
</div>
</div>
)
})}
:
<TableWrapper data={demoUsers} itemsPerPage={8}>
{({ 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 color = user.place == 'Friends' ? 'text-emerald-500 bg-emerald-100/90' : user.place == 'Office' ? 'text-blue-500 bg-blue-100/90' : 'text-orange-500 bg-orange-100/90'
return (
<div onClick={()=>getLoanPage(user)} key={user?.uid || 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">
<h1 className="mb-[1px] text-2xl font-bold">{user.name}</h1>
{/* <span className={` ${color} text-sm font-bold p-1 rounded`}>{user.place}</span> */}
</div>
<div className="card-body flex flex-col gap-2">
<div className="contact text-slate-700 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-700">
<CiBank />
</span>
<span>{user.bvn}</span>
</div>
<div className="contact text-slate-700 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-700">
<CiPhone />
</span>
<span>{user.mobile}</span>
</div>
<div className="contact text-slate-700 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-700">
<MdOutlineEmail />
</span>
<span>{user.email}</span>
</div>
</div>
</div>
)
})}
</div>
)}
</TableWrapper>
}
</div>
</div>
)
@@ -63,26 +88,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'},
{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'},
]
// ]
+2 -2
View File
@@ -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-black rounded' />
</div>
)
}
+148
View File
@@ -0,0 +1,148 @@
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="p-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>
)
}
+47 -3
View File
@@ -1,15 +1,51 @@
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
}
})
useEffect(()=>{
if(state?.proceed != 'true'){
return navigate(myLinks.getStarted, {replace:true})
@@ -28,15 +64,23 @@ export default function LoginCom() {
<div className='flex flex-col gap-4 w-[80%] sm:w-[500px] bg-white rounded-xl p-3 sm:p-5 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)}} className='px-3 py-2 bg-purple-800 text-white font-bold rounded'>{login.isPending ? 'loading...' : 'Login'}</button>
</div>
</div>
</div>
+2
View File
@@ -1,7 +1,9 @@
const myLinks = {
error: '*',
getStarted: '/',
login: '/login',
home: '/home',
getLoan: '/get-loan'
}
export default myLinks
+8
View File
@@ -0,0 +1,8 @@
import React from 'react'
import GetLoan from '../components/GetLoan'
export default function GetLoanPage() {
return (
<GetLoan />
)
}