Files
IB/src/app/eMandate/mandate_page/page.tsx
tomosa.sarkar 6258080848 fix: Change bank name
fix: otp filled null after complete the process
2025-11-18 16:13:23 +05:30

480 lines
17 KiB
TypeScript

"use client";
import React, { useEffect, useState } from "react";
import {
Text,
Title,
Box,
Image,
Card,
Checkbox,
Button,
Group,
Grid,
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, IconX } from "@tabler/icons-react";
import { notifications } from "@mantine/notifications";
import { useMediaQuery } from "@mantine/hooks";
type Mandate = {
id: string;
category: string;
amount: number;
maxAmount: number;
name: string;
frequency: string;
firstCollection: string;
finalCollection: string;
};
const MandateCard = ({
mandate,
onAction,
}: {
mandate: Mandate;
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>
<Text size="sm" mt="xs">
Amount: {mandate.amount}
</Text>
<Text size="sm">Max Amount: {mandate.maxAmount}</Text>
<Text size="sm">Name: {mandate.name}</Text>
<Text size="sm">Frequency: {mandate.frequency}</Text>
<Text size="sm">First Collection: {mandate.firstCollection}</Text>
<Text size="sm">Final Collection: {mandate.finalCollection}</Text>
<Checkbox
mt="md"
label="I agree for the debit of mandate processing charges"
checked={agreed}
onChange={(e) => setAgreed(e.currentTarget.checked)}
/>
<Group mt="md" grow>
<Button variant="outline" color="red" onClick={() => handleClick("reject")}>
Reject
</Button>
<Button color="green" onClick={() => handleClick("accept")}>
Accept
</Button>
</Group>
</Card>
);
};
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");
localStorage.removeItem("Emendate_data");
localStorage.removeItem("Emendate_req_doc");
localStorage.removeItem("Emendate_type");
router.push("/eMandate/logout");
};
const handleFetchUserName = async () => {
try {
const token = localStorage.getItem("mandate_token");
const response = await fetch("/api/customer", {
method: "GET",
headers: {
"Content-Type": "application/json",
'X-Login-Type': 'eMandate',
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",
category: "Insurance Premium",
amount: 11743,
maxAmount: 11743,
name: "LIFE INSURANCE CORPORATION",
frequency: "YEAR",
firstCollection: "2025-09-20",
finalCollection: "2038-03-28",
},
{
id: "2",
category: "Loan EMI",
amount: 8500,
maxAmount: 8500,
name: "XYZ BANK",
frequency: "MONTH",
firstCollection: "2025-10-01",
finalCollection: "2030-10-01",
},
]);
// 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",
'X-Login-Type': 'eMandate',
},
body: JSON.stringify({
mobileNumber: localStorage.getItem("userMobNo"),
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,
});
}
};
// Resend OTP
const handleResendOtp = async () => {
try {
const response = await fetch("/api/otp/send", {
method: "POST",
headers: { "Content-Type": "application/json",'X-Login-Type': 'eMandate' },
body: JSON.stringify({
mobileNumber: localStorage.getItem("userMobNo"),
type: "EMandate",
}),
});
if (!response.ok) throw new Error("Failed to resend OTP");
notifications.show({
withBorder: true,
color: "blue",
title: "OTP Resent",
message: "A new OTP has been sent to your registered mobile number.",
autoClose: 4000,
});
} catch {
notifications.show({
withBorder: true,
color: "red",
title: "Error",
message: "Failed to resend OTP. Please try again.",
autoClose: 5000,
});
}
};
// 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=${localStorage.getItem("userMobNo")}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
'X-Login-Type': 'eMandate',
},
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
style={{
backgroundColor: "#f8f9fa",
minHeight: "100vh",
display: "flex",
flexDirection: "column",
}}
>
{/* HEADER */}
<Box
style={{
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "70px",
zIndex: 100,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: isMobile ? "0 0.5rem" : "0 1.5rem",
background:
"linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
}}
>
<Box style={{ display: "flex", alignItems: "center", flex: 1, minWidth: 0 }}>
<Image
src={logo}
component={NextImage}
fit="contain"
alt="ebanking"
style={{
width: isMobile ? "45px" : "70px",
height: isMobile ? "45px" : "70px",
flexShrink: 0,
}}
/>
{!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>
{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",
left: 0,
width: "100%",
padding: "0.3rem 1rem",
backgroundColor: "#e6ffff",
zIndex: 90,
}}
>
<Text fw={500} style={{ fontFamily: "inter", fontSize: "20px" }}>
Welcome, {custname ?? ""}
</Text>
<Text size="xs" c="dimmed" style={{ marginBottom: "4px" }}>
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" />
</Box>
{/* CONTENT */}
<Box
style={{
flex: 1,
marginTop: "160px",
marginBottom: "40px",
backgroundColor: "#e6ffff",
overflowY: "auto",
}}
>
<Container size="lg">
<Grid>
{mandates.map((mandate) => (
<Grid.Col span={{ base: 12, sm: 6 }} key={mandate.id}>
<MandateCard mandate={mandate} onAction={handleMandateAction} />
</Grid.Col>
))}
</Grid>
</Container>
</Box>
{/* FOOTER */}
<Box
style={{
position: "fixed",
bottom: 0,
left: 0,
width: "100%",
height: "40px",
backgroundColor: "#f8f9fa",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderTop: "1px solid #ddd",
}}
>
<Text c="dimmed" size="xs">
© 2025 The Kangra Central Co-Operative Bank Ltd.
</Text>
</Box>
{/* OTP MODAL */}
<Modal opened={otpModalOpen} onClose={() => setOtpModalOpen(false)} title="OTP Verification" centered>
<Text mb="sm" size="sm" ta="center" c="green">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 mt="md" size="xs" c="dimmed">
If you did not receive the OTP in SMS, you can{" "}
<Text
span
c="blue"
td="underline"
style={{ cursor: "pointer" }}
onClick={handleResendOtp}
>
click here to resend the SMS
</Text>
.
</Text>
<Group mt="md" grow>
<Button color="gray" onClick={() => setOtpModalOpen(false)}>
Cancel
</Button>
<Button color="green" onClick={handleOtpSubmit}>
Submit
</Button>
</Group>
</Modal>
</div>
</Providers>
);
}