wip: Add logic for e mandate
This commit is contained in:
@@ -88,6 +88,7 @@ export default function Login() {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Login-Type': 'emandate',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
customerNo: CIF,
|
||||
@@ -103,33 +104,34 @@ export default function Login() {
|
||||
message: data?.error || "Internal Server Error",
|
||||
autoClose: 5000,
|
||||
});
|
||||
localStorage.removeItem("access_token");
|
||||
regenerateCaptcha()
|
||||
localStorage.removeItem("mandate_token");
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
return;
|
||||
|
||||
}
|
||||
setIsLogging(true);
|
||||
if (response.ok) {
|
||||
console.log(data);
|
||||
const token = data.token;
|
||||
localStorage.setItem("emandate_token", token);
|
||||
localStorage.setItem("mandate_token", token);
|
||||
// localStorage.setItem("pswExpiryDate", data.loginPswExpiry);
|
||||
if (data.FirstTimeLogin === true) {
|
||||
notifications.show({
|
||||
withBorder: true,
|
||||
color: "red",
|
||||
title: "Error",
|
||||
message: "Please set your credential into Internet Banking before login.",
|
||||
message: "Please go to Internet Banking, set your credentials, and then try logging in here again.",
|
||||
autoClose: 5000,
|
||||
});
|
||||
}
|
||||
else {
|
||||
router.push("/mandate_page");
|
||||
router.push("/eMandate/mandate_page");
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
regenerateCaptcha();
|
||||
setIsLogging(false);
|
||||
notifications.show({
|
||||
withBorder: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Text,
|
||||
Title,
|
||||
@@ -13,12 +13,16 @@ import {
|
||||
Container,
|
||||
ActionIcon,
|
||||
Divider,
|
||||
Modal,
|
||||
TextInput,
|
||||
} from "@mantine/core";
|
||||
import { Providers } from "@/app/providers";
|
||||
import { useRouter } from "next/navigation";
|
||||
import NextImage from "next/image";
|
||||
import logo from "@/app/image/logo1.jpg";
|
||||
import { IconLogout } from "@tabler/icons-react";
|
||||
import { IconLogout, IconX } from "@tabler/icons-react";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
|
||||
type Mandate = {
|
||||
id: string;
|
||||
@@ -33,15 +37,29 @@ type Mandate = {
|
||||
|
||||
const MandateCard = ({
|
||||
mandate,
|
||||
onAccept,
|
||||
onReject,
|
||||
onAction,
|
||||
}: {
|
||||
mandate: Mandate;
|
||||
onAccept: (id: string) => void;
|
||||
onReject: (id: string) => void;
|
||||
onAction: (id: string, action: "accept" | "reject") => void;
|
||||
}) => {
|
||||
const [agreed, setAgreed] = useState(false);
|
||||
|
||||
const handleClick = (action: "accept" | "reject") => {
|
||||
if (!agreed) {
|
||||
notifications.show({
|
||||
withBorder: true,
|
||||
icon: <IconX size={18} />,
|
||||
color: "red",
|
||||
title: "Error",
|
||||
message:
|
||||
"Please agree to the debit of mandate processing charges first.",
|
||||
autoClose: 4000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
onAction(mandate.id, action);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card shadow="sm" radius="md" p="lg" withBorder>
|
||||
<Text fw={600}>{mandate.category}</Text>
|
||||
@@ -62,19 +80,10 @@ const MandateCard = ({
|
||||
/>
|
||||
|
||||
<Group mt="md" grow>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="red"
|
||||
disabled={!agreed}
|
||||
onClick={() => onReject(mandate.id)}
|
||||
>
|
||||
<Button variant="outline" color="red" onClick={() => handleClick("reject")}>
|
||||
Reject
|
||||
</Button>
|
||||
<Button
|
||||
color="green"
|
||||
disabled={!agreed}
|
||||
onClick={() => onAccept(mandate.id)}
|
||||
>
|
||||
<Button color="green" onClick={() => handleClick("accept")}>
|
||||
Accept
|
||||
</Button>
|
||||
</Group>
|
||||
@@ -84,6 +93,64 @@ const MandateCard = ({
|
||||
|
||||
export default function MandatePage() {
|
||||
const router = useRouter();
|
||||
const [authorized, setAuthorized] = useState<boolean | null>(null);
|
||||
const [custname, setCustname] = useState<string | null>(null);
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
// OTP Modal states
|
||||
const [otpModalOpen, setOtpModalOpen] = useState(false);
|
||||
const [otp, setOtp] = useState("");
|
||||
const [pendingAction, setPendingAction] = useState<{ id: string; action: "accept" | "reject" } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("mandate_token");
|
||||
if (!token) {
|
||||
setAuthorized(false);
|
||||
router.push("/eMandate/login");
|
||||
} else {
|
||||
setAuthorized(true);
|
||||
handleFetchUserName();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem("mandate_token");
|
||||
localStorage.removeItem("user_name");
|
||||
localStorage.removeItem("userMobNo");
|
||||
router.push("/eMandate/login");
|
||||
};
|
||||
|
||||
const handleFetchUserName = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem("mandate_token");
|
||||
const response = await fetch("/api/customer", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
if (!response.ok) throw new Error();
|
||||
const data = await response.json();
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const name = data[0].custname;
|
||||
const mobileNumber = data[0].mobileno;
|
||||
localStorage.setItem("user_name", name);
|
||||
localStorage.setItem("userMobNo", mobileNumber);
|
||||
setCustname(name);
|
||||
}
|
||||
} catch {
|
||||
notifications.show({
|
||||
withBorder: true,
|
||||
color: "red",
|
||||
title: "Please try again later",
|
||||
message: "Unable to Fetch, Please try again later",
|
||||
autoClose: 5000,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Dummy mandates
|
||||
const [mandates] = useState<Mandate[]>([
|
||||
{
|
||||
id: "1",
|
||||
@@ -105,21 +172,95 @@ export default function MandatePage() {
|
||||
firstCollection: "2025-10-01",
|
||||
finalCollection: "2030-10-01",
|
||||
},
|
||||
|
||||
]);
|
||||
|
||||
const handleAccept = (id: string) => {
|
||||
alert(`Accepted mandate ${id}`);
|
||||
// STEP 1: When Accept/Reject pressed → call send OTP API
|
||||
const handleMandateAction = async (id: string, action: "accept" | "reject") => {
|
||||
try {
|
||||
const response = await fetch("/api/otp/send", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
mobileNumber: '7890544527',
|
||||
type: 'EMandate'
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log(data)
|
||||
if (!response.ok) throw new Error("Failed to send OTP");
|
||||
|
||||
notifications.show({
|
||||
withBorder: true,
|
||||
color: "green",
|
||||
title: "OTP Sent",
|
||||
message: "An OTP has been sent to your registered mobile number.",
|
||||
autoClose: 4000,
|
||||
});
|
||||
|
||||
setPendingAction({ id, action });
|
||||
setOtp("");
|
||||
setOtpModalOpen(true);
|
||||
} catch (err) {
|
||||
notifications.show({
|
||||
withBorder: true,
|
||||
color: "red",
|
||||
title: "Error",
|
||||
message: "Failed to send OTP. Please try again.",
|
||||
autoClose: 5000,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleReject = (id: string) => {
|
||||
alert(`Rejected mandate ${id}`);
|
||||
};
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem("access_token");
|
||||
router.push("/eMandate/login"); // redirect to login page
|
||||
// STEP 2: Verify OTP and complete action
|
||||
const handleOtpSubmit = async () => {
|
||||
// if (!otp) {
|
||||
// notifications.show({
|
||||
// withBorder: true,
|
||||
// color: "red",
|
||||
// title: "Invalid Input",
|
||||
// message: "Please enter OTP before proceed",
|
||||
// autoClose: 5000,
|
||||
// });
|
||||
// }
|
||||
if (!pendingAction) return;
|
||||
try {
|
||||
const response = await fetch(`/api/otp/verify?mobileNumber=${7890544527}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
otp: otp,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("Invalid OTP");
|
||||
|
||||
notifications.show({
|
||||
withBorder: true,
|
||||
color: "green",
|
||||
title: "Success",
|
||||
message: `Mandate ${pendingAction.action}ed successfully!`,
|
||||
autoClose: 4000,
|
||||
});
|
||||
|
||||
setOtpModalOpen(false);
|
||||
setPendingAction(null);
|
||||
} catch {
|
||||
notifications.show({
|
||||
withBorder: true,
|
||||
color: "red",
|
||||
title: "Error",
|
||||
message: "Invalid OTP. Please try again.",
|
||||
autoClose: 4000,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (!authorized) return null;
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<div
|
||||
@@ -137,54 +278,67 @@ export default function MandatePage() {
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "12%",
|
||||
height: "70px",
|
||||
zIndex: 100,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "0 0.5rem",
|
||||
padding: isMobile ? "0 0.5rem" : "0 1.5rem",
|
||||
background:
|
||||
"linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
||||
borderBottom: "1px solid black",
|
||||
}}
|
||||
>
|
||||
<Box style={{ display: "flex", alignItems: "center" }}>
|
||||
<Box style={{ display: "flex", alignItems: "center", flex: 1, minWidth: 0 }}>
|
||||
<Image
|
||||
src={logo}
|
||||
component={NextImage}
|
||||
fit="contain"
|
||||
alt="ebanking"
|
||||
style={{ width: "70px", height: "70px", flexShrink: 0 }}
|
||||
/>
|
||||
<Title
|
||||
order={2}
|
||||
style={{
|
||||
fontFamily: "Roboto",
|
||||
marginLeft: "1rem",
|
||||
color: "white",
|
||||
width: isMobile ? "45px" : "70px",
|
||||
height: isMobile ? "45px" : "70px",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
/>
|
||||
{!isMobile && (
|
||||
<Title
|
||||
order={2}
|
||||
style={{
|
||||
fontFamily: "Roboto",
|
||||
marginLeft: "1rem",
|
||||
color: "white",
|
||||
fontSize: "1.2rem",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Logout Icon */}
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="white"
|
||||
size="lg"
|
||||
onClick={handleLogout}
|
||||
title="Logout"
|
||||
>
|
||||
<IconLogout size={28} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
{isMobile ? (
|
||||
<ActionIcon variant="subtle" color="white" size="lg" onClick={handleLogout} title="Logout">
|
||||
<IconLogout size={26} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
) : (
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="white"
|
||||
size="md"
|
||||
onClick={handleLogout}
|
||||
leftSection={<span style={{ fontSize: "14px", fontWeight: 500 }}>LOGOUT</span>}
|
||||
rightSection={<IconLogout size={25} stroke={1.5} />}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* WELCOME BAR */}
|
||||
<Box
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: "70px", // directly below header
|
||||
top: "70px",
|
||||
left: 0,
|
||||
width: "100%",
|
||||
padding: "0.3rem 1rem",
|
||||
@@ -193,24 +347,23 @@ export default function MandatePage() {
|
||||
}}
|
||||
>
|
||||
<Text fw={500} style={{ fontFamily: "inter", fontSize: "20px" }}>
|
||||
Welcome, John Doe
|
||||
Welcome, {custname ?? ""}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" style={{ marginBottom: "4px" }}>
|
||||
Login Time: 9/24/2025, 12:52:05 PM
|
||||
Last logged in at {new Date().toLocaleString()}
|
||||
</Text>
|
||||
<Title order={4} ta="center" style={{ margin: 0 }}>
|
||||
KCCB E-Mandate
|
||||
</Title>
|
||||
<Divider size="xs" color='#99c2ff' />
|
||||
<Divider size="xs" color="#99c2ff" />
|
||||
</Box>
|
||||
|
||||
{/* CONTENT */}
|
||||
<Box
|
||||
style={{
|
||||
flex: 1,
|
||||
marginTop: "160px", // header + welcome bar height
|
||||
marginBottom: "40px", // leave space for footer
|
||||
// padding: "1rem",
|
||||
marginTop: "160px",
|
||||
marginBottom: "40px",
|
||||
backgroundColor: "#e6ffff",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
@@ -219,11 +372,7 @@ export default function MandatePage() {
|
||||
<Grid>
|
||||
{mandates.map((mandate) => (
|
||||
<Grid.Col span={{ base: 12, sm: 6 }} key={mandate.id}>
|
||||
<MandateCard
|
||||
mandate={mandate}
|
||||
onAccept={handleAccept}
|
||||
onReject={handleReject}
|
||||
/>
|
||||
<MandateCard mandate={mandate} onAction={handleMandateAction} />
|
||||
</Grid.Col>
|
||||
))}
|
||||
</Grid>
|
||||
@@ -249,6 +398,31 @@ export default function MandatePage() {
|
||||
© 2025 The Kangra Central Co-Operative Bank
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* OTP MODAL */}
|
||||
<Modal opened={otpModalOpen} onClose={() => setOtpModalOpen(false)} title="Enter OTP" centered>
|
||||
<Text mb="sm">An OTP has been sent to your registered mobile number.</Text>
|
||||
<TextInput
|
||||
label="OTP"
|
||||
placeholder="Enter OTP"
|
||||
value={otp}
|
||||
withAsterisk
|
||||
onInput={(e) => {
|
||||
const input = e.currentTarget.value.replace(/\D/g, "");
|
||||
if (input.length <= 6) setOtp(input);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text size="xs">if you did not received the OTP in SMS,you can click click here to resend the SMS</Text>
|
||||
<Group mt="md" grow>
|
||||
<Button color="gray" onClick={() => setOtpModalOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button color="green" onClick={handleOtpSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Modal>
|
||||
</div>
|
||||
</Providers>
|
||||
);
|
||||
|
||||
@@ -129,6 +129,7 @@ export default function Login() {
|
||||
message: data?.error || "Internal Server Error",
|
||||
autoClose: 5000,
|
||||
});
|
||||
regenerateCaptcha();
|
||||
localStorage.removeItem("access_token");
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
@@ -150,6 +151,7 @@ export default function Login() {
|
||||
|
||||
}
|
||||
else {
|
||||
regenerateCaptcha();
|
||||
setIsLogging(false);
|
||||
notifications.show({
|
||||
withBorder: true,
|
||||
@@ -218,7 +220,7 @@ export default function Login() {
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
console.log(data)
|
||||
// console.log(data)
|
||||
if (!res.ok) {
|
||||
notifications.show({
|
||||
color: "red",
|
||||
|
||||
Reference in New Issue
Block a user