Compare commits

...

5 Commits

Author SHA1 Message Date
Elias f63171273e max input length: bvn & code 2024-05-07 15:19:40 +01:00
ameye 6bd533c7ca Merge branch 'pending-loan-list' of DigiFi/digifi-www into master 2024-05-06 16:51:12 +00:00
victorAnumudu 408777353d pending loan list added 2024-05-06 16:52:00 +01:00
victorAnumudu a2e039eab4 initial commit 2024-05-06 14:44:42 +01:00
ameye 8d6cc5861e Merge branch 'link-path' of DigiFi/digifi-www into master 2024-05-06 12:23:07 +00:00
7 changed files with 451 additions and 155 deletions
@@ -1,7 +1,11 @@
import React, { FC } from "react"; import React, { FC, useState, useEffect } from "react";
import NairaBag from "../../assets/images/dashboard/naira-bag.png"; import NairaBag from "../../assets/images/dashboard/naira-bag.png";
import { Button } from "../"; import { Button, Icons } from "../";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import PendingList from "../paginated-list/PendingList";
import { PendingTableList } from "../../core/models";
import { NewDateTimeFormatter } from "../../lib/NewDateTimeFormatter";
import { getUserPendingLoanList } from "../../core/apiRequest";
export interface DashBoardCardProps { export interface DashBoardCardProps {
title?: string; title?: string;
@@ -75,8 +79,31 @@ interface DashboardHomeIntroProps {
const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({ handleNextStep, step }) => { const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({ handleNextStep, step }) => {
const { userDetails } = useSelector((state:any) => state?.userDetails); // CHECKS IF USER Details are avaliable const { userDetails } = useSelector((state:any) => state?.userDetails); // CHECKS IF USER Details are avaliable
const [userLoanList, setUserLoanList] = useState<{loading:boolean, data:PendingTableList}>({loading: true, data:[]})
useEffect(()=>{
let token = localStorage.getItem('token')
let uid = localStorage.getItem('uid')
if(!token || !uid){
return
}
getUserPendingLoanList(uid).then(res => {
console.log('RES', res)
console.log('RES', userLoanList)
if(!res || !res.data.loans){
setUserLoanList({loading:false, data:[]})
return
}
setUserLoanList({loading:false, data:res?.data?.loans})
}).catch(err => {
console.log(err)
setUserLoanList({loading:false, data:[]})
})
},[])
return ( return (
<> <div className='w-full'>
{step == 1 ? {step == 1 ?
<> <>
<h1 className="font-bold my-5 text-2xl">Hello, {userDetails.firstname}</h1> <h1 className="font-bold my-5 text-2xl">Hello, {userDetails.firstname}</h1>
@@ -114,7 +141,50 @@ const DashboardHomeIntro: FC<DashboardHomeIntroProps> = ({ handleNextStep, step
</div> </div>
</> </>
} }
</> {userLoanList.loading ?
null
:
<div className='mt-5 w-full'>
<PendingList
data={userLoanList.data}
itemsPerPage={5}
tableTitle='Current Applications'
>
{(data:any)=>(
<div className="w-full p-4 rounded-lg shadow-lg bg-white overflow-x-auto min-h-[250px] max-h-[450px]">
<table className="w-full table-auto">
<thead>
<tr className='text-left border-b-2'>
<th className='px-1 py-4'>Date</th>
<th className='px-1 py-4 text-right'>Amount</th>
<th className='px-1 py-4 text-center'>Payment Term</th>
<th className='px-1 py-4 text-center'>Status</th>
<th className='px-1 py-4'>Action</th>
</tr>
</thead>
<tbody>
{data.map((item:any, index:any) =>(
<tr key={index || item} className='even:bg-slate-100'>
<td className='px-1 py-2'>{NewDateTimeFormatter(item?.added)}</td>
<td className='px-1 py-2 text-right'>{item?.loan_amount}</td>
<td className='px-1 py-2 text-center'>{item?.payment_month}</td>
<td className='px-1 py-2 text-center'>{item?.status}</td>
<td className='px-1 py-2'>
<button className='px-2 py-1 border-2 border-black flex gap-2 items-center'>
View
<Icons name='arrow-right' />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</PendingList>
</div>
}
</div>
); );
}; };
+3 -1
View File
@@ -1,4 +1,4 @@
import { FaCaretDown } from "react-icons/fa"; import { FaCaretDown, FaCaretRight } from "react-icons/fa";
import dashIcon from "../../assets/images/dashboard/dashDefault.svg"; import dashIcon from "../../assets/images/dashboard/dashDefault.svg";
type Props = { type Props = {
@@ -111,6 +111,8 @@ export default function Icons({ name, fillColor, className }: Props) {
</svg> </svg>
) :name == 'arrow-down'? ) :name == 'arrow-down'?
<FaCaretDown className={`text-xl ${className && className}`} /> <FaCaretDown className={`text-xl ${className && className}`} />
:name == 'arrow-right'?
<FaCaretRight className={`text-xl ${className && className}`} />
:name == "dash-icon" ? ( :name == "dash-icon" ? (
<img src={dashIcon} alt="dash-icon" /> <img src={dashIcon} alt="dash-icon" />
) : null} ) : null}
+204 -148
View File
@@ -1,42 +1,42 @@
import React from "react"; import React from 'react';
import * as Yup from "yup"; import * as Yup from 'yup';
import { Form, Formik } from "formik"; import { Form, Formik } from 'formik';
import { InputCompOne } from ".."; import { InputCompOne } from '..';
import {useNavigate} from 'react-router-dom' import { useNavigate } from 'react-router-dom';
import { RouteHandler } from "../../router/routes"; import { RouteHandler } from '../../router/routes';
import { useDispatch } from "react-redux"; import { useDispatch } from 'react-redux';
import { updateUserDetails } from "../../store/UserDetails"; import { updateUserDetails } from '../../store/UserDetails';
import { validateBVN, verifyOTP } from "../../core/apiRequest"; import { validateBVN, verifyOTP } from '../../core/apiRequest';
import { RequestStatus } from "../../core/models"; import { RequestStatus } from '../../core/models';
// To get the validation schema // To get the validation schema
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
bvn: Yup.string() bvn: Yup.string()
.required("BVN is required") .required('BVN is required')
.test("no-e", "Invalid number", (value:any) => { .test('no-e', 'Invalid number', (value: any) => {
if (value && /^[0-9]*$/.test(value) == false) { if (value && /^[0-9]*$/.test(value) == false) {
return false; return false;
} }
return true; return true;
}) })
.min(11, "must be 11 digits") .min(11, 'must be 11 digits')
.max(11, "must be 11 digits"), .max(11, 'must be 11 digits'),
otp: Yup.string() otp: Yup.string()
// .when('require_otp', { // .when('require_otp', {
// is: true, // is: true,
// then: Yup.string().required("OTP is required") // then: Yup.string().required("OTP is required")
// }) // })
// .required("OTP is required") // .required("OTP is required")
.test("no-e", "Invalid number", (value:any) => { .test('no-e', 'Invalid number', (value: any) => {
if (value && /^[0-9]*$/.test(value) == false) { if (value && /^[0-9]*$/.test(value) == false) {
return false; return false;
} }
return true; return true;
}) })
.min(5, "must be 5 digits") .min(5, 'must be 5 digits')
.max(5, "must be 5 digits"), .max(5, 'must be 5 digits'),
// .test("no-e", "must be 11 characters", (value:any) => { // .test("no-e", "must be 11 characters", (value:any) => {
// if (value.length < 11) { // if (value.length < 11) {
// return false; // return false;
@@ -52,151 +52,207 @@ let initialValues = {
}; };
type ValidBVN = { type ValidBVN = {
verification_id:string verification_id: string;
valid: undefined | boolean valid: undefined | boolean;
} };
const LetsGetStarted: React.FC = () => { const LetsGetStarted: React.FC = () => {
const dispatch = useDispatch() const dispatch = useDispatch();
const navigate = useNavigate() const navigate = useNavigate();
// const [pinValues, setPinValues] = React.useState({ // const [pinValues, setPinValues] = React.useState({
// bvn: "", // bvn: "",
// otp: "", // otp: "",
// }); // });
// const otpInputRef = React.useRef<HTMLInputElement>(null); // const otpInputRef = React.useRef<HTMLInputElement>(null);
const [requestStatusBVN, setRequestStatusBVN] = React.useState<RequestStatus>({loading:false, status:undefined, message:''}); const [requestStatusBVN, setRequestStatusBVN] = React.useState<RequestStatus>(
{ loading: false, status: undefined, message: '' }
);
const [requestStatusOTP, setRequestStatusOTP] = React.useState<RequestStatus>({loading:false, status:undefined, message:''}); const [requestStatusOTP, setRequestStatusOTP] = React.useState<RequestStatus>(
{ loading: false, status: undefined, message: '' }
);
const [bvnIsValid, setBvnIsValid] = React.useState<ValidBVN>({ const [bvnIsValid, setBvnIsValid] = React.useState<ValidBVN>({
verification_id: '', verification_id: '',
valid: undefined valid: undefined,
}); });
// e: React.FormEvent<HTMLInputElement> // e: React.FormEvent<HTMLInputElement>
// let { value } = e.target as HTMLInputElement; // let { value } = e.target as HTMLInputElement;
const bvnValidation = (values:any) => { // Function to Validate BVN const bvnValidation = (values: any) => {
let bvn = values.bvn // Function to Validate BVN
setRequestStatusBVN({loading:true, status:false, message:''}) let bvn = values.bvn;
validateBVN({bvn}).then(res => { setRequestStatusBVN({ loading: true, status: false, message: '' });
if(!res || !res.data.call_return){ validateBVN({ bvn })
setBvnIsValid({verification_id:'', valid: false}) .then((res) => {
setRequestStatusBVN({loading:false, status:false, message:'unable to verify BVN'}) if (!res || !res.data.call_return) {
return setTimeout(()=>{ setBvnIsValid({ verification_id: '', valid: false });
setRequestStatusBVN({loading:false, status:false, message:''}) setRequestStatusBVN({
}, 4000) loading: false,
} status: false,
setBvnIsValid({verification_id:res.data.verification_id, valid: true}) message: 'unable to verify BVN',
setRequestStatusBVN({loading:false, status:true, message:'verified'}) });
}).catch(err => { return setTimeout(() => {
setBvnIsValid({verification_id:'', valid: false}) setRequestStatusBVN({ loading: false, status: false, message: '' });
console.log(err) }, 4000);
}) }
setBvnIsValid({
verification_id: res.data.verification_id,
valid: true,
});
setRequestStatusBVN({
loading: false,
status: true,
message: 'verified',
});
})
.catch((err) => {
setBvnIsValid({ verification_id: '', valid: false });
console.log(err);
});
}; };
const handleSubmit = (values:any) => { // Function to VERIFY OTP AND LOGIN USER const handleSubmit = (values: any) => {
setRequestStatusOTP({loading:true, status:false, message:''}) // Function to VERIFY OTP AND LOGIN USER
setRequestStatusOTP({ loading: true, status: false, message: '' });
// console.log('values', values) // console.log('values', values)
verifyOTP({...values, verification_id:bvnIsValid.verification_id}).then(res=>{ verifyOTP({ ...values, verification_id: bvnIsValid.verification_id })
if(!res || !res.data.call_return){ .then((res) => {
setRequestStatusOTP({loading:false, status:false, message:'wrong otp'}) if (!res || !res.data.call_return) {
return setTimeout(()=>{ setRequestStatusOTP({
setRequestStatusOTP({loading:false, status:false, message:''}) loading: false,
},4000) status: false,
} message: 'wrong otp',
// console.log(res.data) });
localStorage.setItem('token', res.data?.token) return setTimeout(() => {
localStorage.setItem('uid', res?.data?.customer[0]?.uid) setRequestStatusOTP({ loading: false, status: false, message: '' });
dispatch(updateUserDetails({ ...res?.data?.customer[0] })); }, 4000);
navigate(RouteHandler.dashboardHome, {replace:true}) }
}).catch(err => { // console.log(res.data)
setRequestStatusOTP({loading:false, status:false, message:'something went wrong, try again'}) localStorage.setItem('token', res.data?.token);
console.log(err) localStorage.setItem('uid', res?.data?.customer[0]?.uid);
return setTimeout(()=>{ dispatch(updateUserDetails({ ...res?.data?.customer[0] }));
setRequestStatusOTP({loading:false, status:false, message:''}) navigate(RouteHandler.dashboardHome, { replace: true });
},4000) })
}) .catch((err) => {
setRequestStatusOTP({
loading: false,
status: false,
message: 'something went wrong, try again',
});
console.log(err);
return setTimeout(() => {
setRequestStatusOTP({ loading: false, status: false, message: '' });
}, 4000);
});
}; };
return ( return (
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
validationSchema={validationSchema} validationSchema={validationSchema}
onSubmit={bvnIsValid.valid ? handleSubmit : bvnValidation} onSubmit={bvnIsValid.valid ? handleSubmit : bvnValidation}
> >
{(props:any) => ( {(props: any) => (
<Form className=""> <Form className="">
<div className="w-full"> <div className="w-full">
<div className="containerMode flex justify-between gap-1 xl:gap-8 flex-col"> <div className="containerMode flex justify-between gap-1 xl:gap-8 flex-col">
<div className="my-[4rem] flex items-center justify-center w-full"> <div className="my-[4rem] flex items-center justify-center w-full">
<h1 className="font-bold text-[2.375rem] text-[#5C2684] my-[.5rem] text-center"> <h1 className="font-bold text-[2.375rem] text-[#5C2684] my-[.5rem] text-center">
Lets Get You Started Lets Get You Started
</h1> </h1>
</div>
<div className="mx-auto flex flex-col gap-8 max-w-[31.625rem] ">
<div className='w-full'>
<InputCompOne
parentClass="flex flex-col gap-2"
label="Enter Your BVN "
name="bvn"
parentInputClass="w-full"
labelSpan="( To get your BVN, dial *565*0# )"
labelSpanClass="text-[13px] text-[#5a5a5a] font-semibold"
placeholder="Enter your BVN"
labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#282828] mb-[2px] flex item-center gap-[4px]"
input
inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4"
value={props.values.bvn}
onChange={props.handleChange}
error={(props.errors.bvn && props.touched.bvn) && props.errors.bvn}
/>
<p className={`p-2 ${!requestStatusBVN.status ? 'text-red-500' : 'text-emerald-500'}`}>{requestStatusBVN.loading ? 'verifying...' : requestStatusBVN.message}</p>
</div> </div>
{bvnIsValid.valid && ( <div className="mx-auto flex flex-col gap-8 max-w-[31.625rem] ">
<InputCompOne <div className="w-full">
parentClass="flex flex-col gap-2" <InputCompOne
label="Enter OTP " parentClass="flex flex-col gap-2"
name="otp" label="Enter Your BVN "
parentInputClass="w-full" name="bvn"
labelSpan="( Please check your BVN phone number for verification pin )" parentInputClass="w-full"
labelSpanClass="text-[13px] text-[#5a5a5a] font-semibold" labelSpan="( To get your BVN, dial *565*0# )"
placeholder="Enter your OTP" labelSpanClass="text-[13px] text-[#5a5a5a] font-semibold"
labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#282828] mb-[2px] flex item-center gap-[4px]" placeholder="Enter your BVN"
input labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#282828] mb-[2px] flex item-center gap-[4px]"
inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4" input
value={props.values.otp} inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4"
onChange={props.handleChange} value={props.values.bvn}
error={(props.errors.otp && props.touched.otp) && props.errors.otp} onChange={props.handleChange}
/> error={
)} props.errors.bvn && props.touched.bvn && props.errors.bvn
<button }
type='submit' maxLength={11}
className="w-full h-[3.625rem] rounded bg-[#FBB700] rounded-2 px-4 text-[18px] text-[#282828] font-semibold disabled:text-[#282828] disabled:text-opacity-50" />
disabled={requestStatusBVN.loading || (!props.values.otp && bvnIsValid.valid)} <p
> className={`p-2 ${
Enter !requestStatusBVN.status
</button> ? 'text-red-500'
: 'text-emerald-500'
}`}
>
{requestStatusBVN.loading
? 'verifying...'
: requestStatusBVN.message}
</p>
</div>
{bvnIsValid.valid && (
<InputCompOne
parentClass="flex flex-col gap-2"
label="Enter OTP "
name="otp"
parentInputClass="w-full"
labelSpan="( Please check your BVN phone number for verification pin )"
labelSpanClass="text-[13px] text-[#5a5a5a] font-semibold"
placeholder="Enter your OTP"
labelClass="font-bold text-[18px] leading-[21.78px] tracking-[2%] text-[#282828] mb-[2px] flex item-center gap-[4px]"
input
inputClass="w-full h-[3.625rem] rounded bg-[#EFEFEF] px-4"
value={props.values.otp}
onChange={props.handleChange}
error={
props.errors.otp && props.touched.otp && props.errors.otp
}
maxLength={5}
/>
)}
<button
type="submit"
className="w-full h-[3.625rem] rounded bg-[#FBB700] rounded-2 px-4 text-[18px] text-[#282828] font-semibold disabled:text-[#282828] disabled:text-opacity-50"
disabled={
requestStatusBVN.loading ||
(!props.values.otp && bvnIsValid.valid)
}
>
Enter
</button>
<p className={`p-2 ${!requestStatusOTP.status ? 'text-red-500' : 'text-emerald-500'}`}>{requestStatusOTP.message}</p> <p
className={`p-2 ${
!requestStatusOTP.status
? 'text-red-500'
: 'text-emerald-500'
}`}
>
{requestStatusOTP.message}
</p>
{bvnIsValid.valid || bvnIsValid.valid == undefined ? ( {bvnIsValid.valid || bvnIsValid.valid == undefined ? (
<p className="text-[#5C2684] mt-[1.5625rem] w-fit"> <p className="text-[#5C2684] mt-[1.5625rem] w-fit">
***Every personal information attached to your BVN is safe and ***Every personal information attached to your BVN is safe
secure. It is only important for us to verify your information and and secure. It is only important for us to verify your
also give you access to your application profile/account. information and also give you access to your application
</p> profile/account.
) : ( </p>
<p className="text-[#5C2684] mt-[1.5625rem] w-fit"> ) : (
***Did not receive OTP? Click to resend <p className="text-[#5C2684] mt-[1.5625rem] w-fit">
</p> ***Did not receive OTP? Click to resend
)} </p>
)}
</div>
</div> </div>
</div> </div>
</div> </Form>
</Form> )}
)}
</Formik> </Formik>
); );
}; };
@@ -0,0 +1,134 @@
import { ReactNode, useEffect, useState } from "react";
import { PendingTableList } from "../../core/models";
type PaginatedListProps = {
data: PendingTableList,
itemsPerPage?: number,
filterItem?: string[],
tableTitle?: string,
titleClass?:string,
children: (data:PendingTableList) => ReactNode;
}
export default function PendingList({
data,
itemsPerPage = 5,
filterItem,
tableTitle,
titleClass,
children,
}:PaginatedListProps) {
const [searchTerm, setSearchTerm] = useState("");
const [filteredData, setFilteredData] = useState(data);
const [currentPage, setCurrentPage] = useState(0);
const [newData, setNewData] = useState<any>([]);
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 } }:{target: {value:string}}, name:string) => {
setSearchTerm(value);
let newFilteredData:any = data.filter((item:any) =>
item[name].toLowerCase().startsWith(value.toLowerCase())
);
setFilteredData(newFilteredData);
setCurrentPage(0);
};
useEffect(() => {
setNewData(
filteredData?.slice(currentPage, numberOfSelection + currentPage)
);
}, [currentPage, filteredData]);
useEffect(()=>{
setCurrentPage(0)
},[itemsPerPage])
return (
<div className="w-full">
<h1 className={`text-2xl mb-5 font-semibold ${titleClass && titleClass}`}>{tableTitle}</h1>
{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>
)}
{children(newData)}
{/* show prev and next button if data exist */}
{(data.length > 0 && data.length > itemsPerPage) && (
<div className="mt-5 md:mt-10 w-full flex gap-4 justify-center items-center">
<button
onClick={handlePrev}
className={`w-6 h-6 md:w-12 md:h-12 text-sm md:text-lg rounded-full flex justify-center items-center transition-all duration-300 ${
currentPage == 0
? "text-slate-400 border-slate-400 dark:text-slate-400 dark:border-slate-400 pointer-events-none"
: "text-slate-600 border-slate-600 dark:text-white dark:border-white"
}`}
>
&lt;
</button>
{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>
)
}
})}
<button
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 transition-all duration-300 ${
currentPage + numberOfSelection >= data.length
? "text-slate-400 border-slate-400 dark:text-slate-400 dark:border-slate-400 pointer-events-none"
: "text-slate-600 border-slate-600 dark:text-white dark:border-white"
}`}
>
&gt;
</button>
</div>
)}
</div>
);
}
+8 -1
View File
@@ -26,7 +26,6 @@ export const applyForLoan = (postData:any) => {
return postAuxEnd('/loan/apply', reqData) return postAuxEnd('/loan/apply', reqData)
} }
// FUNCTION TO GET USER BY CUSTOMER UID // FUNCTION TO GET USER BY CUSTOMER UID
export const getUserByID = (uid:string) => { export const getUserByID = (uid:string) => {
let reqData = { let reqData = {
@@ -34,3 +33,11 @@ export const getUserByID = (uid:string) => {
} }
return getAuxEnd(`/profile?uid=${uid}`, reqData) return getAuxEnd(`/profile?uid=${uid}`, reqData)
} }
// FUNCTION TO GET USER BY CUSTOMER UID
export const getUserPendingLoanList = (uid:string) => {
let reqData = {
// customer_uid: localStorage.getItem('uid'),
}
return getAuxEnd(`/dash?uid=${uid}`, reqData)
}
+9
View File
@@ -16,3 +16,12 @@ export interface User {
customer_uid?:string customer_uid?:string
call_return?:string call_return?:string
} }
export type PendingTableList = {
status?: string | boolean;
application_uid?: string
added?: string
loan_amount?: string
payment_month?: string
}[];
+18
View File
@@ -0,0 +1,18 @@
export function NewDateTimeFormatter(isoDateString:any, addHour = true) {
const date = new Date(isoDateString);
if (addHour) {
date.setTime(date.getTime() + 1 * 60 * 60 * 1000);
}
const formattedDate = date.toLocaleDateString("en-US", {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
// second: "2-digit",
hour12: true,
timeZone: "UTC",
});
return formattedDate;
}