480 lines
17 KiB
TypeScript
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>
|
|
);
|
|
}
|