Merge branch 'dev' of https://7o9o-lb-526275444.ap-south-1.elb.amazonaws.com/tomosa.sarkar/IB into admin_feature
934
package-lock.json
generated
@@ -27,6 +27,7 @@
|
||||
"date-fns": "^3.6.0",
|
||||
"dayjs": "^1.11.11",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"exceljs": "^4.4.0",
|
||||
"html2pdf.js": "^0.12.0",
|
||||
"ib": "file:",
|
||||
"IB": "file:",
|
||||
|
||||
BIN
public/kccb_watermark.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
@@ -2,16 +2,36 @@
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Group,
|
||||
Container,
|
||||
Paper,
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
Group,
|
||||
Badge,
|
||||
Divider,
|
||||
Loader,
|
||||
Center,
|
||||
Card,
|
||||
SimpleGrid,
|
||||
ThemeIcon,
|
||||
Box,
|
||||
rem,
|
||||
Grid,
|
||||
} from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import {
|
||||
IconCreditCard,
|
||||
IconWallet,
|
||||
IconTrendingUp,
|
||||
IconBuilding,
|
||||
IconCircleCheck,
|
||||
IconAlertCircle,
|
||||
IconUser,
|
||||
IconFileText,
|
||||
IconCircleDot,
|
||||
} from "@tabler/icons-react";
|
||||
|
||||
interface accountData {
|
||||
stAccountNo: string;
|
||||
@@ -19,23 +39,21 @@ interface accountData {
|
||||
stAvailableBalance: string;
|
||||
custname: string;
|
||||
stBookingNumber: string;
|
||||
stApprovedAmount?: string; // optional for loan accounts
|
||||
stApprovedAmount?: string;
|
||||
}
|
||||
|
||||
export default function AccountDetails() {
|
||||
const router = useRouter();
|
||||
export default function App() {
|
||||
const [accountOptions, setAccountOptions] = useState<{ value: string; label: string }[]>([]);
|
||||
const [selectedAccNo, setSelectedAccNo] = useState<string | null>(null);
|
||||
const [authorized, setAuthorized] = useState<boolean | null>(null);
|
||||
const [accountDetails, setAccountDetails] = useState<accountData | null>(null);
|
||||
const searchParams = useSearchParams();
|
||||
const passedAccNo = searchParams.get("accNo");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("access_token");
|
||||
if (!token) {
|
||||
setAuthorized(false);
|
||||
router.push("/login");
|
||||
// router.push("/login");
|
||||
} else {
|
||||
setAuthorized(true);
|
||||
}
|
||||
@@ -51,9 +69,6 @@ export default function AccountDetails() {
|
||||
value: acc.stAccountNo,
|
||||
}));
|
||||
setAccountOptions(options);
|
||||
if (passedAccNo) {
|
||||
handleAccountSelection(passedAccNo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [authorized]);
|
||||
@@ -62,6 +77,8 @@ export default function AccountDetails() {
|
||||
setSelectedAccNo(accNo);
|
||||
setAccountDetails(null);
|
||||
if (!accNo) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const token = localStorage.getItem("access_token");
|
||||
const response = await fetch("/api/customer", {
|
||||
@@ -77,11 +94,10 @@ export default function AccountDetails() {
|
||||
if (response.ok && Array.isArray(data)) {
|
||||
const matched = data.find((acc) => acc.stAccountNo === accNo);
|
||||
if (matched) {
|
||||
// Simulate approvedBalance for loan accounts
|
||||
if (matched.stAccountType.toUpperCase().includes("LN")) {
|
||||
matched.stApprovedAmount = (
|
||||
parseFloat(matched.stAvailableBalance) + 20000
|
||||
).toFixed(2); // dummy logic
|
||||
).toFixed(2);
|
||||
}
|
||||
setAccountDetails(matched);
|
||||
} else {
|
||||
@@ -102,79 +118,270 @@ export default function AccountDetails() {
|
||||
title: "Fetch failed",
|
||||
message: "Could not fetch account details. Try again.",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getAccountIcon = (accountType: string) => {
|
||||
const type = accountType.toUpperCase();
|
||||
if (type.includes("LN")) return IconTrendingUp;
|
||||
if (type.includes("SA") || type.includes("SB") || type.includes("CC") || type.includes("OD") || type.includes("CA")) return IconWallet;
|
||||
return IconBuilding;
|
||||
};
|
||||
|
||||
const getAccountColor = (accountType: string) => {
|
||||
const type = accountType.toUpperCase();
|
||||
if (type.includes("LN")) return "violet";
|
||||
if (type.includes("SA") || type.includes("SB") || type.includes("CC") || type.includes("OD") || type.includes("CA")) return "blue";
|
||||
return "cyan";
|
||||
};
|
||||
|
||||
const isLoanAccount = accountDetails?.stAccountType.toUpperCase().includes("LN");
|
||||
const AccountIcon = accountDetails ? getAccountIcon(accountDetails.stAccountType) : IconCreditCard;
|
||||
const accountColor = accountDetails ? getAccountColor(accountDetails.stAccountType) : "blue";
|
||||
|
||||
if (!authorized) return null;
|
||||
|
||||
return (
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={400}>
|
||||
<Title order={3} mb="md">
|
||||
Account Details
|
||||
</Title>
|
||||
|
||||
<Stack gap="md">
|
||||
<Select
|
||||
label="Select Account Number"
|
||||
placeholder="Choose account number"
|
||||
data={accountOptions}
|
||||
value={selectedAccNo}
|
||||
onChange={handleAccountSelection}
|
||||
searchable
|
||||
/>
|
||||
<Grid gutter="md">
|
||||
{/* Left side – Account Selector */}
|
||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
||||
<Title order={4} mb="md">
|
||||
Account Selector
|
||||
</Title>
|
||||
<Select
|
||||
label="Select Account Number"
|
||||
placeholder="Choose an account number"
|
||||
data={accountOptions}
|
||||
value={selectedAccNo}
|
||||
onChange={handleAccountSelection}
|
||||
searchable
|
||||
size="sm"
|
||||
leftSection={<IconCreditCard size={20} />}
|
||||
styles={{
|
||||
input: {
|
||||
borderRadius: rem(12),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Paper>
|
||||
</Grid.Col>
|
||||
|
||||
{accountDetails && (
|
||||
<Paper withBorder p="md" radius="md" bg="gray.0">
|
||||
<Stack gap="sm">
|
||||
<Group p="apart">
|
||||
<Text size="sm" fw={500} c="dimmed">Account Number</Text>
|
||||
<Text size="md">{accountDetails.stAccountNo}</Text>
|
||||
</Group>
|
||||
{/* Right side – Account Details */}
|
||||
<Grid.Col span={{ base: 12, md: 8 }}>
|
||||
<Paper
|
||||
shadow="sm"
|
||||
radius="md"
|
||||
p="md"
|
||||
withBorder
|
||||
h={500}
|
||||
style={{ display: "flex", flexDirection: "column", overflow: "auto" }}
|
||||
>
|
||||
<Title order={4} mb="md">
|
||||
Account Details
|
||||
</Title>
|
||||
|
||||
<Group p="apart">
|
||||
<Text size="sm" fw={500} c="dimmed">Account Type</Text>
|
||||
<Text size="md">{accountDetails.stAccountType}</Text>
|
||||
</Group>
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<Center style={{ flex: 1 }}>
|
||||
<Stack align="center" gap="md">
|
||||
<Loader size="lg" type="dots" />
|
||||
<Text c="dimmed">Loading account details...</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
)}
|
||||
|
||||
<Group p="apart">
|
||||
<Text size="sm" fw={500} c="dimmed">Description</Text>
|
||||
<Text size="md">{accountDetails.stBookingNumber}</Text>
|
||||
</Group>
|
||||
|
||||
{/* Show Loan-specific fields */}
|
||||
{accountDetails.stAccountType.toUpperCase().includes("LN") ? (
|
||||
<>
|
||||
<Group p="apart">
|
||||
<Text size="sm" fw={500} c="dimmed">Approved Balance</Text>
|
||||
<Text size="md" c="gray.8">
|
||||
₹{parseFloat(accountDetails.stApprovedAmount || "0").toLocaleString("en-IN", {
|
||||
minimumFractionDigits: 2,
|
||||
})}
|
||||
</Text>
|
||||
{/* Account Details */}
|
||||
{!loading && accountDetails && (
|
||||
<Stack gap="sm" style={{ flex: 1 }}>
|
||||
{/* Account Header Card */}
|
||||
<Paper
|
||||
shadow="md"
|
||||
p="xs"
|
||||
radius="md"
|
||||
style={{
|
||||
background: "linear-gradient(56deg, rgba(24,140,186,1) 0%, rgba(62,230,132,1) 86%)",
|
||||
}}
|
||||
>
|
||||
<Group justify="space-between" align="flex-start" wrap="wrap" mb="xs">
|
||||
<Group gap="sm">
|
||||
<ThemeIcon size={48} radius="md" variant="white" color={accountColor}>
|
||||
<AccountIcon size={28} />
|
||||
</ThemeIcon>
|
||||
<Box>
|
||||
<Text size="xs" c="white" opacity={0.8}>
|
||||
Account Number
|
||||
</Text>
|
||||
<Title order={3} c="white" style={{ letterSpacing: "0.5px" }}>
|
||||
{accountDetails.stAccountNo}
|
||||
</Title>
|
||||
</Box>
|
||||
</Group>
|
||||
<Group p="apart">
|
||||
<Text size="sm" fw={500} c="dimmed">Available Balance</Text>
|
||||
<Text size="md" c="red">
|
||||
– ₹{parseFloat(accountDetails.stAvailableBalance).toLocaleString("en-IN", {
|
||||
minimumFractionDigits: 2,
|
||||
})}
|
||||
</Text>
|
||||
</Group>
|
||||
</>
|
||||
<Badge size="md" variant="white" color={accountColor} radius="md">
|
||||
{accountDetails.stAccountType}
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
<Divider color="white" opacity={0.2} my="sm" />
|
||||
|
||||
<Box>
|
||||
<Text size="xs" c="white" opacity={0.8} mb={4}>
|
||||
Description
|
||||
</Text>
|
||||
<Text c="white">{accountDetails.stBookingNumber}</Text>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Balance Cards */}
|
||||
{isLoanAccount ? (
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="sm">
|
||||
{/* Approved Balance */}
|
||||
<Card shadow="sm" padding="md" radius="md" withBorder>
|
||||
<Group gap="xs" mb="xs">
|
||||
<ThemeIcon size={32} radius="md" variant="light" color="teal">
|
||||
<IconCircleCheck size={20} />
|
||||
</ThemeIcon>
|
||||
<Text size="xs" c="dimmed">
|
||||
Approved Balance
|
||||
</Text>
|
||||
</Group>
|
||||
<Title order={3} c="dark">
|
||||
₹
|
||||
{parseFloat(accountDetails.stApprovedAmount || "0").toLocaleString(
|
||||
"en-IN",
|
||||
{
|
||||
minimumFractionDigits: 2,
|
||||
}
|
||||
)}
|
||||
</Title>
|
||||
</Card>
|
||||
|
||||
{/* Outstanding Amount */}
|
||||
<Card shadow="sm" padding="md" radius="md" withBorder>
|
||||
<Group gap="xs" mb="xs">
|
||||
<ThemeIcon size={32} radius="md" variant="light" color="red">
|
||||
<IconAlertCircle size={20} />
|
||||
</ThemeIcon>
|
||||
<Text size="xs" c="dimmed">
|
||||
Outstanding Amount
|
||||
</Text>
|
||||
</Group>
|
||||
<Title order={3} c="red">
|
||||
– ₹
|
||||
{parseFloat(accountDetails.stAvailableBalance).toLocaleString(
|
||||
"en-IN",
|
||||
{
|
||||
minimumFractionDigits: 2,
|
||||
}
|
||||
)}
|
||||
</Title>
|
||||
</Card>
|
||||
</SimpleGrid>
|
||||
) : (
|
||||
<Group p="apart">
|
||||
<Text size="sm" fw={500} c="dimmed">Available Balance</Text>
|
||||
<Text size="md" c="green">
|
||||
₹{parseFloat(accountDetails.stAvailableBalance).toLocaleString("en-IN", {
|
||||
<Card shadow="sm" padding="md" radius="md" withBorder>
|
||||
<Group gap="xs" mb="xs">
|
||||
<ThemeIcon size={36} radius="md" variant="light" color="teal">
|
||||
<IconWallet size={24} />
|
||||
</ThemeIcon>
|
||||
<Text size="xs" c="dimmed">
|
||||
Available Balance
|
||||
</Text>
|
||||
</Group>
|
||||
<Title order={2} c="teal">
|
||||
₹
|
||||
{parseFloat(accountDetails.stAvailableBalance).toLocaleString("en-IN", {
|
||||
minimumFractionDigits: 2,
|
||||
})}
|
||||
</Text>
|
||||
</Group>
|
||||
</Title>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Account Information */}
|
||||
<Paper shadow="sm" p="md" radius="md" withBorder>
|
||||
<Title order={5} mb="md">
|
||||
Account Information
|
||||
</Title>
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
|
||||
<Stack gap={4}>
|
||||
<Group gap="xs">
|
||||
<ThemeIcon size={18} radius="xl" variant="light" color="gray">
|
||||
<IconUser size={12} />
|
||||
</ThemeIcon>
|
||||
<Text size="xs" c="dimmed">
|
||||
Account Holder
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="sm">{accountDetails.custname}</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack gap={4}>
|
||||
<Group gap="xs">
|
||||
<ThemeIcon size={18} radius="xl" variant="light" color="gray">
|
||||
<IconCreditCard size={12} />
|
||||
</ThemeIcon>
|
||||
<Text size="xs" c="dimmed">
|
||||
Account Type
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="sm">{accountDetails.stAccountType}</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack gap={4}>
|
||||
<Group gap="xs">
|
||||
<ThemeIcon size={18} radius="xl" variant="light" color="gray">
|
||||
<IconFileText size={12} />
|
||||
</ThemeIcon>
|
||||
<Text size="xs" c="dimmed">
|
||||
Booking Number
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="sm">{accountDetails.stBookingNumber}</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack gap={4}>
|
||||
<Group gap="xs">
|
||||
<ThemeIcon size={18} radius="xl" variant="light" color="gray">
|
||||
<IconCircleDot size={12} />
|
||||
</ThemeIcon>
|
||||
<Text size="xs" c="dimmed">
|
||||
Status
|
||||
</Text>
|
||||
</Group>
|
||||
<Badge
|
||||
color="teal"
|
||||
variant="light"
|
||||
size="md"
|
||||
leftSection={<IconCircleDot size={10} />}
|
||||
>
|
||||
Active
|
||||
</Badge>
|
||||
</Stack>
|
||||
</SimpleGrid>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{/* Empty State */}
|
||||
{!loading && !accountDetails && (
|
||||
<Center style={{ flex: 1 }}>
|
||||
<Stack align="center" gap="md">
|
||||
<ThemeIcon size={56} radius="xl" variant="light" color="gray">
|
||||
<IconCreditCard size={28} />
|
||||
</ThemeIcon>
|
||||
<Text c="dimmed" size="sm">
|
||||
{selectedAccNo
|
||||
? "No account details available"
|
||||
: "Please select an account to view details"}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
"use client";
|
||||
import { Paper, Select, Title, Button, Text, Grid, ScrollArea, Table, Divider, Center, Loader, Stack, Group, Card } from "@mantine/core";
|
||||
import { Paper, Select, Title, Button, Text, Grid, ScrollArea, Table, Divider, Center, Loader, Stack, Group, Card, ThemeIcon } from "@mantine/core";
|
||||
import { DateInput } from '@mantine/dates';
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import dayjs from 'dayjs';
|
||||
import { IconFileSpreadsheet, IconFileText, IconFileTypePdf } from "@tabler/icons-react";
|
||||
import { IconCopy, IconFileSpreadsheet, IconFileText, IconFileTypePdf } from "@tabler/icons-react";
|
||||
import { generatePDF } from "@/app/_components/statement_download/PdfGenerator";
|
||||
import { generateCSV } from "@/app/_components/statement_download/CsvGenerator";
|
||||
import { generateExcel } from "@/app/_components/statement_download/CsvGenerator";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
|
||||
export default function AccountStatementPage() {
|
||||
@@ -168,8 +168,8 @@ export default function AccountStatementPage() {
|
||||
<Grid gutter="md">
|
||||
{/* Left side – form */}
|
||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={400}>
|
||||
<Title order={4} mb="sm">Account Transactions</Title>
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
||||
<Title order={4} mb="sm">Transaction Filters</Title>
|
||||
<Select
|
||||
label="Select Account Number"
|
||||
placeholder="Choose account number"
|
||||
@@ -202,8 +202,8 @@ export default function AccountStatementPage() {
|
||||
|
||||
{/* Right side – transaction list */}
|
||||
<Grid.Col span={{ base: 12, md: 8 }}>
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={400} style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Title order={5} mb="xs">Account Transactions</Title>
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500} style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Title order={4} mb="xs">Account Transactions</Title>
|
||||
<Group justify="space-between" align="center" mt="sm">
|
||||
<div>
|
||||
<Text fw={500} ><strong>Account No :</strong> {selectedAccNo}</Text>
|
||||
@@ -223,17 +223,34 @@ export default function AccountStatementPage() {
|
||||
size={22}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() =>
|
||||
generatePDF(selectedAccNo || "", availableBalance || "0", transactions,
|
||||
generatePDF(
|
||||
selectedAccNo || "",
|
||||
availableBalance || "0",
|
||||
transactions,
|
||||
localStorage.getItem("remitter_name") || "",
|
||||
startDate ? dayjs(startDate).format("DD/MM/YYYY") : "",
|
||||
endDate ? dayjs(endDate).format("DD/MM/YYYY") : "")
|
||||
endDate ? dayjs(endDate).format("DD/MM/YYYY") : "",
|
||||
localStorage.getItem("remitter_branch_no") || "",
|
||||
localStorage.getItem("remitter_cif_no") || "",
|
||||
localStorage.getItem("remitter_address") || ""
|
||||
)
|
||||
}
|
||||
/>
|
||||
<IconFileSpreadsheet
|
||||
size={22}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() =>
|
||||
generateCSV(selectedAccNo || "NA", availableBalance || "0.00", transactions)
|
||||
generateExcel(
|
||||
selectedAccNo || "",
|
||||
availableBalance || "0",
|
||||
transactions,
|
||||
localStorage.getItem("remitter_name") || "",
|
||||
startDate ? dayjs(startDate).format("DD/MM/YYYY") : "",
|
||||
endDate ? dayjs(endDate).format("DD/MM/YYYY") : "",
|
||||
localStorage.getItem("remitter_branch_no") || "",
|
||||
localStorage.getItem("remitter_cif_no") || "",
|
||||
localStorage.getItem("remitter_address") || ""
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -261,7 +278,20 @@ export default function AccountStatementPage() {
|
||||
</Stack>
|
||||
</Center>
|
||||
) : transactions.length === 0 ? (
|
||||
<p>No transactions found.</p>
|
||||
// <p>No transactions found.</p>
|
||||
<Center style={{ flex: 1 }}>
|
||||
<Stack align="center" gap="md">
|
||||
<ThemeIcon size={56} radius="xl" variant="light" color="gray">
|
||||
<IconCopy
|
||||
size={28} />
|
||||
</ThemeIcon>
|
||||
<Text c="dimmed" size="sm">
|
||||
{selectedAccNo
|
||||
? "No account details available"
|
||||
: "Please select the filters to get the details"}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
) : isMobile ? (
|
||||
// ✅ Mobile View – Card Layout
|
||||
<Stack gap="sm">
|
||||
@@ -291,13 +321,68 @@ export default function AccountStatementPage() {
|
||||
) : (
|
||||
// ✅ Desktop View – Table Layout
|
||||
<Table style={{ borderCollapse: "collapse", width: "100%" }}>
|
||||
<thead style={{ backgroundColor: "#3385ff" }}>
|
||||
{/* <tr>
|
||||
<th style={{ ...cellStyle, textAlign: "left", color: "white" }}>Narration</th>
|
||||
<th style={{ ...cellStyle, textAlign: "left", color: "white" }}>Date</th>
|
||||
<th style={{ ...cellStyle, textAlign: "right", color: "white" }}>Amount (₹)</th>
|
||||
<th style={{ ...cellStyle, textAlign: "right", color: "white" }}>Balance (₹)</th>
|
||||
</tr> */}
|
||||
<thead
|
||||
style={{
|
||||
background: "linear-gradient(56deg, rgba(24,140,186,1) 0%, rgba(62,230,132,1) 86%)",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
zIndex: 5,
|
||||
}}
|
||||
>
|
||||
<tr>
|
||||
<th
|
||||
style={{
|
||||
...cellStyle,
|
||||
textAlign: "left",
|
||||
color: "white",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
// background: "linear-gradient(56deg, rgba(24,140,186,1) 0%, rgba(62,230,132,1) 86%)",
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
Narration
|
||||
</th>
|
||||
<th
|
||||
style={{
|
||||
...cellStyle,
|
||||
textAlign: "left",
|
||||
color: "white",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
// background: "linear-gradient(56deg, rgba(24,140,186,1) 0%, rgba(62,230,132,1) 86%)",
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
Date
|
||||
</th>
|
||||
<th
|
||||
style={{
|
||||
...cellStyle,
|
||||
textAlign: "right",
|
||||
color: "white",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
// background: "linear-gradient(56deg, rgba(24,140,186,1) 0%, rgba(62,230,132,1) 86%)",
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
Amount (₹)
|
||||
</th>
|
||||
<th
|
||||
style={{
|
||||
...cellStyle,
|
||||
textAlign: "right",
|
||||
color: "white",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
// background: "linear-gradient(56deg, rgba(24,140,186,1) 0%, rgba(62,230,132,1) 86%)",
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
Balance (₹)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{transactions.map((txn, i) => (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import { Box, Burger, Button, Divider, Drawer, Stack, Text } from '@mantine/core';
|
||||
import { Box, Burger, Button, Divider, Drawer, ScrollArea, SegmentedControl, Stack, Text } from '@mantine/core';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
@@ -16,7 +16,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const [drawerOpened, setDrawerOpened] = useState(false);
|
||||
|
||||
const links = [
|
||||
{ label: "Account Summary", href: "/accounts" },
|
||||
{ label: " Account Summary", href: "/accounts" },
|
||||
{ label: "Account Statement ", href: "/accounts/account_statement" },
|
||||
{ label: "Account Details", href: "/accounts/account_details" },
|
||||
];
|
||||
@@ -33,55 +33,62 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
if (authorized) {
|
||||
return (
|
||||
<Box style={{ display: "flex", height: "100%", flexDirection: isMobile ? "column" : "row" }}>
|
||||
{/* Desktop Sidebar */}
|
||||
<Box style={{ display: "flex", height: "100%", flexDirection: "column" }}>
|
||||
{/* ---------------- DESKTOP SIDEBAR ---------------- */}
|
||||
{!isMobile && (
|
||||
<Box
|
||||
style={{
|
||||
width: "16%",
|
||||
backgroundColor: "#c5e4f9",
|
||||
borderRight: "1px solid #ccc",
|
||||
}}
|
||||
>
|
||||
<Stack style={{ background: "#228be6", height: "10%", alignItems: "center" }}>
|
||||
<Text fw={700} c="white" style={{ textAlign: "center", marginTop: "10px" }}>
|
||||
My Accounts
|
||||
</Text>
|
||||
</Stack>
|
||||
<>
|
||||
{/* Segmented Tabs */}
|
||||
<Box mb="1rem">
|
||||
<ScrollArea type="never" offsetScrollbars>
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
value={pathname}
|
||||
onChange={(value) => router.push(value)}
|
||||
data={links.map((link) => ({
|
||||
label: link.label,
|
||||
value: link.href,
|
||||
}))}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "#e9ecef",
|
||||
borderRadius: 999,
|
||||
padding: 4,
|
||||
},
|
||||
control: {
|
||||
borderRadius: 999,
|
||||
transition: "0.3s",
|
||||
},
|
||||
|
||||
<Stack gap="sm" justify="flex-start" style={{ padding: "1rem" }}>
|
||||
{links.map((link) => {
|
||||
const isActive = pathname === link.href;
|
||||
return (
|
||||
<Text
|
||||
key={link.href}
|
||||
component={Link}
|
||||
href={link.href}
|
||||
c={isActive ? "darkblue" : "blue"}
|
||||
style={{
|
||||
textDecoration: isActive ? "underline" : "none",
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
}}
|
||||
>
|
||||
{link.label}
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Box>
|
||||
indicator: {
|
||||
borderRadius: 999,
|
||||
background: "linear-gradient(90deg, #02a355 0%, #5483c9ff 100%)",
|
||||
boxShadow: "0 3px 8px rgba(0,0,0,0.15)",
|
||||
},
|
||||
|
||||
label: {
|
||||
fontSize: 13,
|
||||
padding: "6px 10px",
|
||||
textAlign: "center",
|
||||
fontWeight: 600,
|
||||
color: "#000",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ScrollArea>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Mobile: Burger & Drawer */}
|
||||
{/* ---------------- MOBILE TOP BAR ---------------- */}
|
||||
{isMobile && (
|
||||
<>
|
||||
{/* Top header with burger and title */}
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: "#228be6",
|
||||
// padding: "0.5rem 1rem",
|
||||
background: "linear-gradient(135deg, #1e88e5 0%, #1565c0 100%)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "0.7rem 1rem",
|
||||
}}
|
||||
>
|
||||
<Burger
|
||||
@@ -90,11 +97,12 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
size="sm"
|
||||
color="white"
|
||||
/>
|
||||
<Text fw={500} c="white">
|
||||
My Accounts
|
||||
<Text fw={600} c="white">
|
||||
Accounts
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* MOBILE DRAWER */}
|
||||
<Drawer
|
||||
opened={drawerOpened}
|
||||
onClose={() => setDrawerOpened(false)}
|
||||
@@ -103,27 +111,31 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
overlayProps={{ color: "black", opacity: 0.55, blur: 3 }}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "#e6f5ff", // soft background for drawer
|
||||
// borderLeft: "4px solid #228be6",
|
||||
// borderRadius: "8px",
|
||||
backgroundColor: "#eaf4ff",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Logo and Drawer Header */}
|
||||
<Box style={{ display: "flex", alignItems: "center", marginBottom: "1rem" }}>
|
||||
<>
|
||||
<Image src={logo} alt="KCCB Logo" width={40} height={40} style={{ borderRadius: "50%" }} />
|
||||
<Text
|
||||
fw={700}
|
||||
ml="10px"
|
||||
style={{ fontSize: "18px", color: "#228be6" }}
|
||||
>
|
||||
My Accounts
|
||||
</Text>
|
||||
</>
|
||||
{/* Drawer Header */}
|
||||
<Box
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: "1rem",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={logo}
|
||||
alt="KCCB Logo"
|
||||
width={45}
|
||||
height={45}
|
||||
style={{ borderRadius: "50%" }}
|
||||
/>
|
||||
<Text fw={700} ml="10px" style={{ fontSize: "19px", color: "#1565c0" }}>
|
||||
Accounts
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Menu Items */}
|
||||
{/* Drawer Items */}
|
||||
<Stack gap="sm">
|
||||
{links.map((link) => {
|
||||
const isActive = pathname === link.href;
|
||||
@@ -138,22 +150,11 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
textDecoration: isActive ? "underline" : "none",
|
||||
color: isActive ? "#fff" : "#228be6",
|
||||
backgroundColor: isActive ? "#228be6" : "#dceeff",
|
||||
color: isActive ? "#fff" : "#1565c0",
|
||||
backgroundColor: isActive ? "#1565c0" : "#dceeff",
|
||||
borderRadius: "8px",
|
||||
padding: "10px 12px",
|
||||
transition: "0.3s",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
const target = e.currentTarget as unknown as HTMLElement;
|
||||
target.style.backgroundColor = "#228be6";
|
||||
target.style.color = "#fff";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
const target = e.currentTarget as unknown as HTMLElement;
|
||||
target.style.backgroundColor = isActive ? "#228be6" : "#dceeff";
|
||||
target.style.color = isActive ? "#fff" : "#228be6";
|
||||
transition: "0.2s",
|
||||
}}
|
||||
onClick={() => setDrawerOpened(false)}
|
||||
>
|
||||
@@ -166,9 +167,14 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
{/* Content Area */}
|
||||
<Box style={{ flex: 1, padding: isMobile ? "0.5rem" : "1rem", overflowY: "auto" }}>
|
||||
{/* ---------------- CONTENT AREA ---------------- */}
|
||||
<Box
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: isMobile ? "0.5rem" : "1rem",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -106,7 +106,7 @@ export default function AccountSummary() {
|
||||
<ScrollArea>
|
||||
<Table style={{ borderCollapse: "collapse", width: "100%" }}>
|
||||
<thead>
|
||||
<tr style={{ backgroundColor: "#3385ff" }}>
|
||||
<tr style={{ background: "linear-gradient(56deg, rgba(24,140,186,1) 0%, rgba(62,230,132,1) 86%)", }}>
|
||||
<th style={{ ...cellStyle, textAlign: "left" }}>Account Type</th>
|
||||
<th style={{ ...cellStyle, textAlign: "right" }}>Account No.</th>
|
||||
{title.includes("Deposit Accounts (INR)") ?
|
||||
@@ -124,16 +124,16 @@ export default function AccountSummary() {
|
||||
if (!authorized) return null;
|
||||
|
||||
return (
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder>
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
||||
<Title
|
||||
order={isMobile ? 4 : 3}
|
||||
order={isMobile ? 4 : 4}
|
||||
mb="md"
|
||||
style={{ textAlign: isMobile ? "center" : "left" }}
|
||||
>
|
||||
Account Summary
|
||||
</Title>
|
||||
|
||||
{/* ✅ Responsive layout: Group for desktop, Stack for mobile */}
|
||||
{/* Responsive layout: Group for desktop, Stack for mobile */}
|
||||
{isMobile ? (
|
||||
<Stack gap="md">
|
||||
{depositAccounts.length > 0 &&
|
||||
|
||||
@@ -135,7 +135,7 @@ export default function AddBeneficiaryOthers() {
|
||||
}, [ifsccode]);
|
||||
|
||||
const validateAndSendOtp = async () => {
|
||||
if (!bankName || !ifsccode || !branchName || !accountNo || !confirmAccountNo || !beneficiaryType) {
|
||||
if (!bankName || !ifsccode || !branchName || !accountNo || !confirmAccountNo || !beneficiaryType || !nickName) {
|
||||
notifications.show({
|
||||
withBorder: true,
|
||||
color: "red",
|
||||
@@ -145,20 +145,18 @@ export default function AddBeneficiaryOthers() {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmedIfsc = ifsccode.trim().toUpperCase();
|
||||
const isValidIfscCode = (code: string) => {
|
||||
return /^[A-Z]{4}0[0-9]{6}$/.test(code);
|
||||
};
|
||||
|
||||
if (!isValidIfscCode(trimmedIfsc)) {
|
||||
notifications.show({
|
||||
title: "Invalid IFSC Code",
|
||||
message: "Must be 11 characters: 4 uppercase letters, 0, then 6 digits (e.g., HDFC0123456)",
|
||||
color: "red",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// const isValidIfscCode = (code: string) => {
|
||||
// return /^[A-Z]{4}0[0-9]{6}$/.test(code);
|
||||
// };
|
||||
// if (!isValidIfscCode(trimmedIfsc)) {
|
||||
// notifications.show({
|
||||
// title: "Invalid IFSC Code",
|
||||
// message: "Must be 11 characters: 4 uppercase letters, 0, then 6 digits (e.g., HDFC0123456)",
|
||||
// color: "red",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (accountNo.length < 10 || accountNo.length > 17) {
|
||||
notifications.show({
|
||||
@@ -185,23 +183,46 @@ export default function AddBeneficiaryOthers() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const token = localStorage.getItem("access_token");
|
||||
const response = await fetch(`/api/beneficiary/validate/outside-bank?accountNo=${accountNo}&ifscCode=${ifsccode}&remitterName=""`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Login-Type": "IB",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
let response;
|
||||
// IF IFSC starts with "KACE" (within bank)
|
||||
if (ifsccode.startsWith("KACE")) {
|
||||
response = await fetch(
|
||||
`/api/beneficiary/validate/within-bank?accountNumber=${accountNo}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Login-Type": "IB",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
// ELSE: other banks → keep your old API
|
||||
else {
|
||||
response = await fetch(
|
||||
`/api/beneficiary/validate/outside-bank?accountNo=${accountNo}&ifscCode=${ifsccode}&remitterName=""`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Login-Type": "IB",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data?.name) {
|
||||
setBeneficiaryName(data.name);
|
||||
setValidationStatus("success");
|
||||
setIsVisibilityLocked(true);
|
||||
setOtpSent(true);
|
||||
const otp = await handleSendOtp();
|
||||
|
||||
await handleSendOtp();
|
||||
|
||||
notifications.show({
|
||||
withBorder: true,
|
||||
color: "green",
|
||||
@@ -309,6 +330,7 @@ export default function AddBeneficiaryOthers() {
|
||||
setConfirmAccountNo('');
|
||||
setBeneficiaryName('');
|
||||
setNickName('');
|
||||
setOtp('');
|
||||
setBeneficiaryType(null);
|
||||
setIsVisibilityLocked(false);
|
||||
setOtpSent(false);
|
||||
@@ -408,6 +430,16 @@ export default function AddBeneficiaryOthers() {
|
||||
withAsterisk
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4}>
|
||||
<TextInput
|
||||
label="Nick Name (Optional)"
|
||||
placeholder="Enter nickname (optional)"
|
||||
value={nickName}
|
||||
required
|
||||
onChange={(e) => setNickName(e.currentTarget.value)}
|
||||
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4}>
|
||||
<TextInput
|
||||
label="Beneficiary Name"
|
||||
@@ -417,15 +449,6 @@ export default function AddBeneficiaryOthers() {
|
||||
required
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={4}>
|
||||
<TextInput
|
||||
label="Nick Name (Optional)"
|
||||
placeholder="Enter nickname (optional)"
|
||||
value={nickName}
|
||||
onChange={(e) => setNickName(e.currentTarget.value)}
|
||||
|
||||
/>
|
||||
</Grid.Col>
|
||||
{!otpSent && (
|
||||
<Grid.Col >
|
||||
<Group gap="sm">
|
||||
@@ -473,7 +496,7 @@ export default function AddBeneficiaryOthers() {
|
||||
{!otpVerified ? (
|
||||
<Button onClick={verify_otp}>Validate OTP</Button>
|
||||
) : (
|
||||
<Button color="blue" size="sm" onClick={AddBen}>
|
||||
<Button size="sm" onClick={AddBen}>
|
||||
Add
|
||||
</Button>
|
||||
)}
|
||||
@@ -4,11 +4,11 @@ import React, { useEffect, useState } from 'react';
|
||||
import {TextInput,Button,Select,Title,Paper,Grid,Group,Radio,Text,PasswordInput} from '@mantine/core';
|
||||
import { useRouter } from "next/navigation";
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import AddBeneficiaryOthers from './addBeneficiaryOthers';
|
||||
import AddBeneficiaryOthers from '@/app/(main)/beneficiary/add_beneficiary/addBeneficiaryOthers';
|
||||
|
||||
|
||||
const bankOptions = [
|
||||
{ value: 'KCCB', label: 'KCCB - The Kangra Central Co-Operative Bank' },
|
||||
{ value: 'KCCB', label: 'KCCB - The Kangra Central Co-Operative Bank Ltd.' },
|
||||
];
|
||||
|
||||
const AddBeneficiary: React.FC = () => {
|
||||
@@ -189,8 +189,8 @@ const AddBeneficiary: React.FC = () => {
|
||||
if (!authorized) return null;
|
||||
|
||||
return (
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={400}>
|
||||
<Title order={3} mb="md">Add Beneficiary</Title>
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
||||
<Title order={4} mb="md">Add Beneficiary</Title>
|
||||
|
||||
{/* <Radio.Group value={bankType} onChange={setBankType} name="bankType" withAsterisk mb="md">
|
||||
<Group justify="center">
|
||||
182
src/app/(main)/beneficiary/layout.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Stack,
|
||||
Text,
|
||||
SegmentedControl,
|
||||
ScrollArea,
|
||||
Burger,
|
||||
Drawer,
|
||||
Button,
|
||||
} from "@mantine/core";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import Image from "next/image";
|
||||
import logo from "@/app/image/logo1.jpg";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const [authorized, SetAuthorized] = useState<boolean | null>(null);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const [drawerOpened, setDrawerOpened] = useState(false);
|
||||
|
||||
/* Beneficiary Options */
|
||||
const links = [
|
||||
{ label: "View Beneficiary", href: "/beneficiary" },
|
||||
{ label: "Add Beneficiary", href: "/beneficiary/add_beneficiary" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("access_token");
|
||||
if (!token) {
|
||||
SetAuthorized(false);
|
||||
router.push("/login");
|
||||
} else {
|
||||
SetAuthorized(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!authorized) return null;
|
||||
|
||||
return (
|
||||
<Box style={{ display: "flex", height: "100%", flexDirection: "column" }}>
|
||||
|
||||
{/* ---------------- DESKTOP HEADER ---------------- */}
|
||||
{!isMobile && (
|
||||
<>
|
||||
{/* Segmented Tabs */}
|
||||
<Box mb="1rem">
|
||||
<ScrollArea type="never" offsetScrollbars>
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
value={pathname}
|
||||
onChange={(value) => router.push(value)}
|
||||
data={links.map((link) => ({
|
||||
label: link.label,
|
||||
value: link.href,
|
||||
}))}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "#e9ecef",
|
||||
borderRadius: 999,
|
||||
padding: 4,
|
||||
},
|
||||
control: {
|
||||
borderRadius: 999,
|
||||
transition: "0.3s",
|
||||
},
|
||||
|
||||
indicator: {
|
||||
borderRadius: 999,
|
||||
background: "linear-gradient(90deg, #02a355 0%, #5483c9ff 100%)",
|
||||
boxShadow: "0 3px 8px rgba(0,0,0,0.15)",
|
||||
},
|
||||
|
||||
label: {
|
||||
fontSize: 13,
|
||||
padding: "6px 10px",
|
||||
textAlign: "center",
|
||||
fontWeight: 600,
|
||||
color: "#000",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ScrollArea>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ---------------- MOBILE HEADER ---------------- */}
|
||||
{isMobile && (
|
||||
<>
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: "#228be6",
|
||||
padding: "0.8rem 1rem",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Burger
|
||||
opened={drawerOpened}
|
||||
onClick={() => setDrawerOpened(true)}
|
||||
size="sm"
|
||||
color="white"
|
||||
/>
|
||||
<Text fw={600} c="white">
|
||||
Beneficiary
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Drawer for Mobile */}
|
||||
<Drawer
|
||||
opened={drawerOpened}
|
||||
onClose={() => setDrawerOpened(false)}
|
||||
padding="md"
|
||||
size="75%"
|
||||
overlayProps={{ color: "black", opacity: 0.55, blur: 3 }}
|
||||
>
|
||||
{/* Drawer Header */}
|
||||
<Box style={{ display: "flex", alignItems: "center", marginBottom: "1rem" }}>
|
||||
<Image
|
||||
src={logo}
|
||||
alt="KCCB Logo"
|
||||
width={40}
|
||||
height={40}
|
||||
style={{ borderRadius: "50%" }}
|
||||
/>
|
||||
<Text fw={700} ml="10px" style={{ color: "#228be6", fontSize: "18px" }}>
|
||||
Beneficiary
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Drawer Menu Items */}
|
||||
<Stack gap="sm">
|
||||
{links.map((link) => {
|
||||
const isActive = pathname === link.href;
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={link.href}
|
||||
component={Link}
|
||||
href={link.href}
|
||||
fullWidth
|
||||
variant="light"
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
backgroundColor: isActive ? "#228be6" : "#e2efff",
|
||||
color: isActive ? "#fff" : "#1b69c7",
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
borderRadius: 10,
|
||||
padding: "10px",
|
||||
}}
|
||||
onClick={() => setDrawerOpened(false)}
|
||||
>
|
||||
{link.label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Drawer>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ---------------- CONTENT BODY ---------------- */}
|
||||
<Box
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: isMobile ? "0.8rem" : "1rem",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Box >
|
||||
);
|
||||
}
|
||||
12
src/app/(main)/beneficiary/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import ViewBeneficiary from "./view_beneficiary/page";
|
||||
|
||||
|
||||
export default function Beneficiary() {
|
||||
|
||||
return (
|
||||
<ViewBeneficiary />
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Center, Group, Loader, Paper, ScrollArea, Table, Text, Title, TextInput, Button, Modal } from "@mantine/core";
|
||||
import { Center, Group, Loader, Paper, ScrollArea, Table, Text, Title, TextInput, Button, Modal, PasswordInput } from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
@@ -165,7 +165,7 @@ export default function ViewBeneficiary() {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Login-Type": "IB",
|
||||
"X-Login-Type": "IB",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
@@ -215,24 +215,42 @@ export default function ViewBeneficiary() {
|
||||
|
||||
|
||||
return (
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={400}>
|
||||
<Title order={3} mb="md">My Beneficiaries</Title>
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
||||
<Group justify="space-between" align="center" mb="md" wrap="wrap">
|
||||
<Title order={4}>My Beneficiaries</Title>
|
||||
|
||||
<Button
|
||||
color="green"
|
||||
variant="filled"
|
||||
size="sm"
|
||||
onClick={() => router.push("/beneficiary/add_beneficiary")}
|
||||
style={{ minWidth: "150px" }}
|
||||
>
|
||||
+ Add Beneficiary
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
{beneficiaries.length === 0 ? (
|
||||
<Text>No beneficiaries found.</Text>
|
||||
) : (
|
||||
<>
|
||||
<ScrollArea h={300} type="always">
|
||||
<ScrollArea h={400} type="always">
|
||||
<Table highlightOnHover withTableBorder stickyHeader style={{ borderCollapse: "collapse", width: "100%" }}>
|
||||
<Table.Thead>
|
||||
<Table.Tr style={{ backgroundColor: "#3385ff" }}>
|
||||
<Table.Th>Bank</Table.Th>
|
||||
<thead style={{
|
||||
background: "linear-gradient(56deg, rgba(24,140,186,1) 0%, rgba(62,230,132,1) 86%)",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
zIndex: 5,
|
||||
}}>
|
||||
<tr>
|
||||
<Table.Th >Bank</Table.Th>
|
||||
<Table.Th style={{ textAlign: "right" }}>Account No</Table.Th>
|
||||
<Table.Th>Name</Table.Th>
|
||||
<Table.Th>Type</Table.Th>
|
||||
<Table.Th >Name</Table.Th>
|
||||
<Table.Th >Type</Table.Th>
|
||||
<Table.Th style={{ textAlign: "center" }}>IFSC</Table.Th>
|
||||
<Table.Th style={{ textAlign: "center" }}>Action Icon</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
</tr>
|
||||
</thead>
|
||||
<Table.Tbody>
|
||||
{beneficiaries.map((b, i) => (
|
||||
<Table.Tr key={i}>
|
||||
@@ -295,7 +313,7 @@ export default function ViewBeneficiary() {
|
||||
) : (
|
||||
<>
|
||||
<Text mb="sm">Enter OTP sent to your registered number:</Text>
|
||||
<TextInput
|
||||
<PasswordInput
|
||||
value={otp}
|
||||
onChange={(e) => setOtp(e.currentTarget.value)}
|
||||
placeholder="Enter OTP"
|
||||
@@ -1,12 +1,12 @@
|
||||
"use client";
|
||||
import { Box, Burger, Button, Divider, Drawer, Stack, Text } from '@mantine/core';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Box, Burger, Button, Drawer, ScrollArea, SegmentedControl, Stack, Text } from "@mantine/core";
|
||||
import { usePathname } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
import logo from "@/app/image/logo1.jpg";
|
||||
import { useMediaQuery } from '@mantine/hooks';
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const [authorized, SetAuthorized] = useState<boolean | null>(null);
|
||||
@@ -16,163 +16,167 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const [drawerOpened, setDrawerOpened] = useState(false);
|
||||
|
||||
const links = [
|
||||
{ label: " Quick Pay", href: "/funds_transfer" },
|
||||
{ label: "Add Beneficiary", href: "/funds_transfer/add_beneficiary" },
|
||||
{ label: "View Beneficiary ", href: "/funds_transfer/view_beneficiary" },
|
||||
{ label: "Send to Beneficiary", href: "/funds_transfer/send_beneficiary" },
|
||||
{ label: "Quick Pay", href: "/funds_transfer" },
|
||||
{ label: "Bank Transfer", href: "/funds_transfer/send_beneficiary" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("access_token");
|
||||
if (!token) {
|
||||
SetAuthorized(false);
|
||||
router.push("/login");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
SetAuthorized(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (authorized) {
|
||||
return (
|
||||
<Box style={{ display: "flex", height: "100%", flexDirection: isMobile ? "column" : "row" }}>
|
||||
{/* Desktop Sidebar */}
|
||||
{!isMobile && (
|
||||
if (!authorized) return null;
|
||||
|
||||
return (
|
||||
<Box style={{ display: "flex", height: "100%", flexDirection: "column" }}>
|
||||
{/* ---------------- DESKTOP SIDEBAR ---------------- */}
|
||||
{!isMobile && (
|
||||
<>
|
||||
{/* Segmented Tabs */}
|
||||
<Box mb="1rem">
|
||||
<ScrollArea type="never" offsetScrollbars>
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
value={pathname}
|
||||
onChange={(value) => router.push(value)}
|
||||
data={links.map((link) => ({
|
||||
label: link.label,
|
||||
value: link.href,
|
||||
}))}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "#e9ecef",
|
||||
borderRadius: 999,
|
||||
padding: 4,
|
||||
},
|
||||
control: {
|
||||
borderRadius: 999,
|
||||
transition: "0.3s",
|
||||
},
|
||||
|
||||
indicator: {
|
||||
borderRadius: 999,
|
||||
background: "linear-gradient(90deg, #02a355 0%, #5483c9ff 100%)",
|
||||
boxShadow: "0 3px 8px rgba(0,0,0,0.15)",
|
||||
},
|
||||
|
||||
label: {
|
||||
fontSize: 13,
|
||||
padding: "6px 10px",
|
||||
textAlign: "center",
|
||||
fontWeight: 600,
|
||||
color: "#000",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ScrollArea>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ---------------- MOBILE TOP BAR ---------------- */}
|
||||
{isMobile && (
|
||||
<>
|
||||
<Box
|
||||
style={{
|
||||
width: "16%",
|
||||
backgroundColor: "#c5e4f9",
|
||||
borderRight: "1px solid #ccc",
|
||||
background: "linear-gradient(135deg, #1e88e5 0%, #1565c0 100%)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "0.7rem 1rem",
|
||||
}}
|
||||
>
|
||||
<Stack style={{ background: "#228be6", height: "10%", alignItems: "center" }}>
|
||||
<Text fw={700} c="white" style={{ textAlign: "center", marginTop: "10px" }}>
|
||||
Send Money
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack gap="sm" justify="flex-start" style={{ padding: "1rem" }}>
|
||||
{links.map((link) => {
|
||||
const isActive = pathname === link.href;
|
||||
return (
|
||||
<Text
|
||||
key={link.href}
|
||||
component={Link}
|
||||
href={link.href}
|
||||
c={isActive ? "darkblue" : "blue"}
|
||||
style={{
|
||||
textDecoration: isActive ? "underline" : "none",
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
}}
|
||||
>
|
||||
{link.label}
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
<Burger
|
||||
opened={drawerOpened}
|
||||
onClick={() => setDrawerOpened(!drawerOpened)}
|
||||
size="sm"
|
||||
color="white"
|
||||
/>
|
||||
<Text fw={600} c="white">
|
||||
Send Money
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Mobile: Burger & Drawer */}
|
||||
{isMobile && (
|
||||
<>
|
||||
{/* Top header with burger and title */}
|
||||
{/* MOBILE DRAWER */}
|
||||
<Drawer
|
||||
opened={drawerOpened}
|
||||
onClose={() => setDrawerOpened(false)}
|
||||
padding="md"
|
||||
size="75%"
|
||||
overlayProps={{ color: "black", opacity: 0.55, blur: 3 }}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "#eaf4ff",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Drawer Header */}
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: "#228be6",
|
||||
// padding: "0.5rem 1rem",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: "1rem",
|
||||
}}
|
||||
>
|
||||
<Burger
|
||||
opened={drawerOpened}
|
||||
onClick={() => setDrawerOpened(!drawerOpened)}
|
||||
size="sm"
|
||||
color="white"
|
||||
<Image
|
||||
src={logo}
|
||||
alt="KCCB Logo"
|
||||
width={45}
|
||||
height={45}
|
||||
style={{ borderRadius: "50%" }}
|
||||
/>
|
||||
<Text fw={500} c="white">
|
||||
Send Money
|
||||
<Text fw={700} ml="10px" style={{ fontSize: "19px", color: "#1565c0" }}>
|
||||
Send Money
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Drawer
|
||||
opened={drawerOpened}
|
||||
onClose={() => setDrawerOpened(false)}
|
||||
padding="md"
|
||||
size="75%"
|
||||
overlayProps={{ color: "black", opacity: 0.55, blur: 3 }}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "#e6f5ff", // soft background for drawer
|
||||
// borderLeft: "4px solid #228be6",
|
||||
// borderRadius: "8px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Logo and Drawer Header */}
|
||||
<Box style={{ display: "flex", alignItems: "center", marginBottom: "1rem" }}>
|
||||
<>
|
||||
<Image src={logo} alt="KCCB Logo" width={40} height={40} style={{ borderRadius: "50%" }} />
|
||||
<Text
|
||||
fw={700}
|
||||
ml="10px"
|
||||
style={{ fontSize: "18px", color: "#228be6" }}
|
||||
{/* Drawer Items */}
|
||||
<Stack gap="sm">
|
||||
{links.map((link) => {
|
||||
const isActive = pathname === link.href;
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={link.href}
|
||||
variant="subtle"
|
||||
component={Link}
|
||||
href={link.href}
|
||||
fullWidth
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
color: isActive ? "#fff" : "#1565c0",
|
||||
backgroundColor: isActive ? "#1565c0" : "#dceeff",
|
||||
borderRadius: "8px",
|
||||
padding: "10px 12px",
|
||||
transition: "0.2s",
|
||||
}}
|
||||
onClick={() => setDrawerOpened(false)}
|
||||
>
|
||||
Send Money
|
||||
</Text>
|
||||
</>
|
||||
</Box>
|
||||
{link.label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Drawer>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Menu Items */}
|
||||
<Stack gap="sm">
|
||||
{links.map((link) => {
|
||||
const isActive = pathname === link.href;
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={link.href}
|
||||
variant="subtle"
|
||||
component={Link}
|
||||
href={link.href}
|
||||
fullWidth
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
textDecoration: isActive ? "underline" : "none",
|
||||
color: isActive ? "#fff" : "#228be6",
|
||||
backgroundColor: isActive ? "#228be6" : "#dceeff",
|
||||
borderRadius: "8px",
|
||||
padding: "10px 12px",
|
||||
transition: "0.3s",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
const target = e.currentTarget as unknown as HTMLElement;
|
||||
target.style.backgroundColor = "#228be6";
|
||||
target.style.color = "#fff";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
const target = e.currentTarget as unknown as HTMLElement;
|
||||
target.style.backgroundColor = isActive ? "#228be6" : "#dceeff";
|
||||
target.style.color = isActive ? "#fff" : "#228be6";
|
||||
}}
|
||||
onClick={() => setDrawerOpened(false)}
|
||||
>
|
||||
{link.label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Drawer>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
{/* Content Area */}
|
||||
<Box style={{ flex: 1, padding: isMobile ? "0.5rem" : "1rem", overflowY: "auto" }}>
|
||||
{children}
|
||||
</Box>
|
||||
{/* ---------------- CONTENT AREA ---------------- */}
|
||||
<Box
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: isMobile ? "0.5rem" : "1rem",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ export default function QuickPay() {
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok && Array.isArray(data)) {
|
||||
const filterSAaccount = data.filter((acc) => ['SA', 'SB'].includes(acc.stAccountType));
|
||||
const filterSAaccount = data.filter((acc) => ['SA', 'SB','CC','OD','CA'].includes(acc.stAccountType));
|
||||
setAccountData(filterSAaccount);
|
||||
}
|
||||
} catch {
|
||||
@@ -395,12 +395,12 @@ export default function QuickPay() {
|
||||
</Group>
|
||||
</Modal>
|
||||
{/* main content */}
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={400}>
|
||||
<Title order={3} mb="md">
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
||||
<Title order={4} mb="md">
|
||||
Quick Pay - Own Bank
|
||||
</Title>
|
||||
|
||||
<div style={{ maxHeight: "290px", overflowY: "auto" }}>
|
||||
<div style={{ maxHeight: "350px", overflowY: "auto" }}>
|
||||
<Stack gap="xs">
|
||||
<Group grow>
|
||||
<Select
|
||||
|
||||
@@ -143,7 +143,7 @@ export default function SendToBeneficiaryOwn() {
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok && Array.isArray(data)) {
|
||||
const filterSAaccount = data.filter((acc) => ['SA', 'SB'].includes(acc.stAccountType));
|
||||
const filterSAaccount = data.filter((acc) => ['SA', 'SB','CC','OD','CA'].includes(acc.stAccountType));
|
||||
setAccountData(filterSAaccount);
|
||||
}
|
||||
} catch {
|
||||
@@ -395,11 +395,9 @@ export default function SendToBeneficiaryOwn() {
|
||||
</Group>
|
||||
</Modal>
|
||||
|
||||
|
||||
|
||||
{/* main content */}
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={400}>
|
||||
<Title order={3} mb="md">
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
||||
<Title order={4} mb="md">
|
||||
Send To Beneficiary
|
||||
</Title>
|
||||
<Radio.Group value={bankType} onChange={setBankType} name="bankType" withAsterisk mb="md">
|
||||
|
||||
@@ -200,7 +200,7 @@ export default function SendToBeneficiaryOthers() {
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok && Array.isArray(data)) {
|
||||
const filterSAaccount = data.filter((acc) => ['SA', 'SB'].includes(acc.stAccountType));
|
||||
const filterSAaccount = data.filter((acc) => ['SA', 'SB','CC','OD','CA'].includes(acc.stAccountType));
|
||||
setAccountData(filterSAaccount);
|
||||
}
|
||||
} catch {
|
||||
@@ -546,7 +546,7 @@ export default function SendToBeneficiaryOthers() {
|
||||
|
||||
{/* main content */}
|
||||
{!showIntroModal && (
|
||||
<div style={{ maxHeight: "290px", overflowY: "auto" }}>
|
||||
<div style={{ maxHeight: "350px", overflowY: "auto" }}>
|
||||
<Stack gap={5} justify="flex-start">
|
||||
<Group grow gap='xs' >
|
||||
<Select
|
||||
|
||||
@@ -21,6 +21,7 @@ export default function Home() {
|
||||
const [authorized, SetAuthorized] = useState<boolean | null>(null);
|
||||
const router = useRouter();
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const [userName, setUserName] = useState<string>("");
|
||||
const [accountData, SetAccountData] = useState<accountData[]>([]);
|
||||
const depositAccounts = accountData.filter(acc => acc.stAccountType !== "LN");
|
||||
const [selectedDA, setSelectedDA] = useState(depositAccounts[0]?.stAccountNo || "");
|
||||
@@ -66,7 +67,7 @@ export default function Home() {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"X-Login-Type": "IB",
|
||||
"X-Login-Type": "IB",
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
});
|
||||
@@ -117,6 +118,9 @@ export default function Home() {
|
||||
useEffect(() => {
|
||||
if (authorized) {
|
||||
handleFetchUserDetails();
|
||||
const fullName = localStorage.getItem("remitter_name") || "User";
|
||||
// const first = fullName.split(" ")[0];
|
||||
setUserName(fullName);
|
||||
}
|
||||
}, [authorized]);
|
||||
|
||||
@@ -124,38 +128,188 @@ export default function Home() {
|
||||
return (
|
||||
<Providers>
|
||||
<Box p={isMobile ? "8px" : "10px"}>
|
||||
<Title order={4} style={{ fontSize: isMobile ? "18px" : "22px" }}>
|
||||
|
||||
{/* ---------------------- WELCOME CARD ---------------------- */}
|
||||
<Title
|
||||
order={2}
|
||||
style={{
|
||||
marginTop: "4px",
|
||||
fontSize: isMobile ? "20px" : "28px",
|
||||
fontWeight: 800,
|
||||
background: "linear-gradient(56deg, rgba(16,114,152,1) 0%, rgba(62,230,132,1) 86%)",
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent",
|
||||
}}
|
||||
>
|
||||
Welcome, {userName}
|
||||
</Title>
|
||||
|
||||
{/* -------------------- ACCOUNT OVERVIEW HEADER -------------------- */}
|
||||
<Title
|
||||
order={4}
|
||||
style={{
|
||||
fontSize: isMobile ? "18px" : "22px",
|
||||
marginTop: isMobile ? "6px" : "10px", // ⭐ ADD THIS GAP
|
||||
}}
|
||||
>
|
||||
Accounts Overview
|
||||
</Title>
|
||||
|
||||
{/* Show Balance Switch */}
|
||||
|
||||
<Text size="sm" c="dimmed" mb={isMobile ? 10 : 14}>
|
||||
Your accounts at a glance
|
||||
</Text>
|
||||
|
||||
{/* --------------------- SHOW BALANCE TOGGLE ---------------------- */}
|
||||
<Group
|
||||
style={{
|
||||
flex: 1,
|
||||
// padding: isMobile ? "5px" : "10px 10px 4px 10px",
|
||||
marginLeft: isMobile ? 0 : "10px",
|
||||
display: "flex",
|
||||
marginBottom: isMobile ? "10px" : "14px",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
height: "auto",
|
||||
gap: isMobile ? 5 : 10,
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
<IconEye size={isMobile ? 16 : 20} />
|
||||
<Text fw={700} style={{ fontFamily: "inter", fontSize: isMobile ? "14px" : "17px" }}>
|
||||
<Text fw={700} style={{ fontSize: isMobile ? "14px" : "18px" }}>
|
||||
Show Balance
|
||||
</Text>
|
||||
|
||||
<Switch
|
||||
size={isMobile ? "sm" : "md"}
|
||||
onLabel="ON"
|
||||
offLabel="OFF"
|
||||
checked={showBalance}
|
||||
onChange={(event) => setShowBalance(event.currentTarget.checked)}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
{/* Cards Section */}
|
||||
{isMobile ? (
|
||||
{/* ----------------------- DESKTOP VIEW ------------------------ */}
|
||||
{!isMobile && (
|
||||
<Group align="flex-start" grow gap="md">
|
||||
|
||||
{/* ----------------------- DEPOSIT CARD ----------------------- */}
|
||||
<Paper p="md" radius="md"
|
||||
style={{
|
||||
background: "linear-gradient(56deg,rgba(179,214,227,1) 0%, rgba(142,230,179,1) 86%)",
|
||||
height: 195,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
>
|
||||
<Group gap="xs">
|
||||
<IconBuildingBank size={25} />
|
||||
<Text fw={700}>Deposit Account</Text>
|
||||
|
||||
<Select
|
||||
data={depositAccounts.map((acc) => ({
|
||||
value: acc.stAccountNo,
|
||||
label: `${acc.stAccountType}- ${acc.stAccountNo}`,
|
||||
}))}
|
||||
value={selectedDA}
|
||||
// @ts-ignore
|
||||
onChange={setSelectedDA}
|
||||
size="xs"
|
||||
styles={{
|
||||
input: { backgroundColor: "white", color: "black", marginLeft: 5, width: 140 }
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<Text c="dimmed">{selectedDAData?.stAccountNo}</Text>
|
||||
|
||||
<Title order={2}>
|
||||
{showBalance
|
||||
? `₹${Number(selectedDAData?.stAvailableBalance || 0).toLocaleString("en-IN")}`
|
||||
: "****"}
|
||||
</Title>
|
||||
|
||||
<Button
|
||||
fullWidth
|
||||
disabled={loadingAccountNo === selectedDA}
|
||||
onClick={() => handleGetAccountStatement(selectedDA)}
|
||||
>
|
||||
{loadingAccountNo === selectedDA ? "Loading...Please Wait" : "Get Statement"}
|
||||
</Button>
|
||||
</Paper>
|
||||
|
||||
{/* ----------------------- LOAN CARD ----------------------- */}
|
||||
<Paper p="md" radius="md"
|
||||
style={{
|
||||
background: "linear-gradient(56deg,rgba(179,214,227,1) 0%, rgba(142,230,179,1) 86%)",
|
||||
height: 195,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
>
|
||||
<Group gap="xs">
|
||||
<IconBuildingBank size={25} />
|
||||
<Text fw={700}>Loan Account</Text>
|
||||
|
||||
<Select
|
||||
data={loanAccounts.map((acc) => ({
|
||||
value: acc.stAccountNo,
|
||||
label: `${acc.stAccountType}- ${acc.stAccountNo}`,
|
||||
}))}
|
||||
value={selectedLN}
|
||||
// @ts-ignore
|
||||
onChange={setSelectedLN}
|
||||
size="xs"
|
||||
styles={{
|
||||
input: { backgroundColor: "white", color: "black", marginLeft: 30, width: 140 }
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<Text c="dimmed">{selectedLNData?.stAccountNo}</Text>
|
||||
|
||||
<Title order={2}>
|
||||
{showBalance
|
||||
? `₹${Number(selectedLNData?.stAvailableBalance || 0).toLocaleString("en-IN")}`
|
||||
: "****"}
|
||||
</Title>
|
||||
|
||||
<Button
|
||||
fullWidth
|
||||
disabled={loadingAccountNo === selectedLN}
|
||||
onClick={() => handleGetAccountStatement(selectedLN)}
|
||||
>
|
||||
{loadingAccountNo === selectedLN ? "Loading...Please Wait" : "Get Statement"}
|
||||
</Button>
|
||||
</Paper>
|
||||
|
||||
{/* ----------------------- QUICK LINKS ----------------------- */}
|
||||
<Paper p="md" radius="md" style={{ width: 300, backgroundColor: "#FFFFFF", border: "1px solid grey" }}>
|
||||
<Title order={5} mb="sm">Quick Links</Title>
|
||||
|
||||
<Stack gap="xs">
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
fullWidth
|
||||
onClick={() => window.open("https://kccbhp.bank.in/about-us/history-of-kccb/", "_blank")}
|
||||
>
|
||||
About Us
|
||||
</Button>
|
||||
<Button variant="light" color="green" fullWidth component="a" href="/BranchLocator" target="_blank">
|
||||
Branch Locator
|
||||
</Button>
|
||||
<Button variant="light" color="green" fullWidth component="a" href="/ATMLocator" target="_blank">
|
||||
ATM Locator
|
||||
</Button>
|
||||
<Button variant="light" color="green" fullWidth component="a" href="/CustomerCare" target="_blank">
|
||||
Customer Care
|
||||
</Button>
|
||||
<Button variant="light" color="green" fullWidth component="a" href="/FAQs" target="_blank">
|
||||
FAQs
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{/* ----------------------- MOBILE VERSION STAYS SAME ----------------------- */}
|
||||
{isMobile && (
|
||||
<Stack gap="md" style={{ width: "100%", marginTop: "10px" }}>
|
||||
{/* Deposit Account Card */}
|
||||
<Paper
|
||||
@@ -189,7 +343,7 @@ export default function Home() {
|
||||
backgroundColor: "white",
|
||||
color: "black",
|
||||
marginLeft: 5,
|
||||
width: "120px",
|
||||
width: "150px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@@ -262,7 +416,7 @@ export default function Home() {
|
||||
backgroundColor: "white",
|
||||
color: "black",
|
||||
marginLeft: 5,
|
||||
width: "120px",
|
||||
width: "150px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@@ -318,172 +472,42 @@ export default function Home() {
|
||||
Quick Links
|
||||
</Title>
|
||||
<Stack gap="xs">
|
||||
<Button variant="light" color="blue" fullWidth>
|
||||
Loan EMI Calculator
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
fullWidth
|
||||
onClick={() => window.open("https://kccbhp.bank.in/about-us/history-of-kccb/", "_blank")}
|
||||
>
|
||||
About Us
|
||||
</Button>
|
||||
<Button variant="light" color="blue" fullWidth>
|
||||
<Button variant="light" color="green" fullWidth component="a" href="/BranchLocator" target="_blank">
|
||||
Branch Locator
|
||||
</Button>
|
||||
<Button variant="light" color="blue" fullWidth>
|
||||
<Button variant="light" color="green" fullWidth component="a" href="/ATMLocator" target="_blank">
|
||||
ATM Locator
|
||||
</Button>
|
||||
<Button variant="light" color="green" fullWidth component="a" href="/CustomerCare" target="_blank">
|
||||
Customer Care
|
||||
</Button>
|
||||
<Button variant="light" color="blue" fullWidth>
|
||||
<Button variant="light" color="green" fullWidth component="a" href="/FAQs" target="_blank">
|
||||
FAQs
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
) : (
|
||||
<Group grow gap="md" style={{ width: "100%", marginTop: "10px" }}>
|
||||
{/* Desktop Cards */}
|
||||
{/* Deposit Account Card */}
|
||||
<Paper p="md" radius="md" style={{ backgroundColor: "#c1e0f0", width: 350, height: 195, display: "flex", flexDirection: "column", justifyContent: "space-between" }}>
|
||||
<Group gap="xs">
|
||||
<IconBuildingBank size={25} />
|
||||
<Text fw={700}>Deposit Account</Text>
|
||||
{depositAccounts.length > 0 ? (
|
||||
<Select
|
||||
data={depositAccounts.map((acc) => ({
|
||||
value: acc.stAccountNo,
|
||||
label: `${acc.stAccountType}- ${acc.stAccountNo}`,
|
||||
}))}
|
||||
value={selectedDA}
|
||||
// @ts-ignore
|
||||
onChange={setSelectedDA}
|
||||
size="xs"
|
||||
styles={{
|
||||
input: {
|
||||
backgroundColor: "white",
|
||||
color: "black",
|
||||
marginLeft: 5,
|
||||
width: 140,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Text c="dimmed" size="sm" ml="sm">
|
||||
No deposit account available
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{depositAccounts.length > 0 ? (
|
||||
<>
|
||||
<Text c="dimmed">{Number(selectedDAData?.stAccountNo || 0)}</Text>
|
||||
<Title order={2} mt="md">
|
||||
{showBalance ? `₹${Number(selectedDAData?.stAvailableBalance || 0).toLocaleString("en-IN")}` : "****"}
|
||||
</Title>
|
||||
<Button fullWidth mt="xs"
|
||||
// loading={loadingAccountNo === selectedDA}
|
||||
disabled={loadingAccountNo === selectedDA}
|
||||
onClick={() => handleGetAccountStatement(selectedDA)}
|
||||
>
|
||||
{loadingAccountNo === selectedDA ? "Loading... Please Wait" : "Get Statement"}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text c="dimmed" mt="md">
|
||||
Apply for a deposit account to get started
|
||||
</Text>
|
||||
<Button fullWidth mt="xs">
|
||||
Apply Now
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
{/* Loan Account Card */}
|
||||
<Paper p="md" radius="md" style={{ backgroundColor: "#c1e0f0", width: 355, height: 195, display: "flex", flexDirection: "column", justifyContent: "space-between" }}>
|
||||
<Group gap="xs">
|
||||
<IconBuildingBank size={25} />
|
||||
<Text fw={700}>Loan Account</Text>
|
||||
{loanAccounts.length > 0 ? (
|
||||
<Select
|
||||
data={loanAccounts.map((acc) => ({
|
||||
value: acc.stAccountNo,
|
||||
label: `${acc.stAccountType}- ${acc.stAccountNo}`,
|
||||
}))}
|
||||
value={selectedLN}
|
||||
// @ts-ignore
|
||||
onChange={setSelectedLN}
|
||||
size="xs"
|
||||
styles={{
|
||||
input: {
|
||||
backgroundColor: "white",
|
||||
color: "black",
|
||||
marginLeft: 30,
|
||||
width: 140,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Text c="dimmed" size="sm" ml="sm">
|
||||
No loan account available
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{loanAccounts.length > 0 ? (
|
||||
<>
|
||||
<Text c="dimmed">{Number(selectedLNData?.stAccountNo || 0)}</Text>
|
||||
<Title order={2} mt="md">
|
||||
{showBalance ? `₹${Number(selectedLNData?.stAvailableBalance || 0).toLocaleString("en-IN")}` : "****"}
|
||||
</Title>
|
||||
<Button fullWidth mt="xs"
|
||||
// loading={loadingAccountNo === selectedLN}
|
||||
disabled={loadingAccountNo === selectedLN}
|
||||
onClick={() => handleGetAccountStatement(selectedLN)}
|
||||
>
|
||||
{/* Get Statement */}
|
||||
{loadingAccountNo === selectedLN ? "Loading...Please Wait.." : "Get Statement"}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text c="dimmed" mt="md">
|
||||
Apply for a loan account to get started
|
||||
</Text>
|
||||
<Button fullWidth mt="xs">
|
||||
Apply Now
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
{/* Important Links Card */}
|
||||
<Paper p="md" radius="md" style={{ width: 300, backgroundColor: "#FFFFFF", border: "1px solid grey" }}>
|
||||
<Title order={5} mb="sm">
|
||||
Quick Links
|
||||
</Title>
|
||||
<Stack gap="xs">
|
||||
<Button variant="light" color="blue" fullWidth>
|
||||
Loan EMI Calculator
|
||||
</Button>
|
||||
<Button variant="light" color="blue" fullWidth>
|
||||
Branch Locator
|
||||
</Button>
|
||||
<Button variant="light" color="blue" fullWidth>
|
||||
Customer Care
|
||||
</Button>
|
||||
<Button variant="light" color="blue" fullWidth>
|
||||
FAQs
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{/* Notes Section */}
|
||||
<Box style={{ padding: "5px", display: "flex", justifyContent: "left" }}>
|
||||
{/* -------------------- NOTES SECTION (BOTTOM) --------------------- */}
|
||||
<Box mt="md">
|
||||
<Stack>
|
||||
<Text fw={700}> ** Book Balance includes uncleared effect.</Text>
|
||||
<Text fw={700}> ** Click on the "Show Balance" to display balance for the Deposit and Loan account.</Text>
|
||||
<Text fw={700}> ** Click "Show Balance" to display account balances.</Text>
|
||||
<Text fw={400} c="red">
|
||||
** Your Password will expire in {PassExpiryRemains} days.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
</Providers>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Box, Button, Divider, Group, Image, Modal, Popover, Stack, Text, Title } from '@mantine/core';
|
||||
import { IconBook, IconCurrencyRupee, IconHome, IconLogout, IconPhoneFilled, IconSettings } from '@tabler/icons-react';
|
||||
import { Anchor, Box, Button, Container, Divider, Group, Image, Modal, Popover, Stack, Switch, Text, Title, Grid } from '@mantine/core';
|
||||
import { IconHome, IconLogout, IconMoon, IconSend, IconSettings, IconSun, IconUserCircle, IconUsers, IconWallet } from '@tabler/icons-react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { Providers } from '../providers';
|
||||
@@ -10,6 +10,7 @@ import NextImage from 'next/image';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { useDisclosure, useMediaQuery } from '@mantine/hooks';
|
||||
import { fetchAndStoreUserName } from '../_util/userdetails';
|
||||
import styles from './page.module.css';
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter();
|
||||
@@ -20,6 +21,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const [sessionModal, setSessionModal] = useState(false);
|
||||
const [countdown, setCountdown] = useState(30); // 30 sec countdown before auto logout
|
||||
const [darkMode, setDarkMode] = useState(false);
|
||||
const firstName = custname ? custname.split(" ")[0] : "";
|
||||
const [userOpened, setUserOpened] = useState(false); // Manage dropdown visibility
|
||||
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
||||
@@ -38,6 +42,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
doLogout()
|
||||
router.push("/login");
|
||||
}
|
||||
// Toggle Dark/Light Mode
|
||||
const toggleDarkMode = () => {
|
||||
setDarkMode((prevMode) => !prevMode);
|
||||
};
|
||||
|
||||
// When reload and click on back then logout
|
||||
useEffect(() => {
|
||||
@@ -173,8 +181,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
|
||||
const navItems = [
|
||||
{ href: "/home", label: "Home", icon: IconHome },
|
||||
{ href: "/accounts", label: "Accounts", icon: IconBook },
|
||||
{ href: "/funds_transfer", label: "Send Money", icon: IconCurrencyRupee },
|
||||
{ href: "/accounts", label: "Accounts", icon: IconWallet },
|
||||
{ href: "/funds_transfer", label: "Fund Transfer", icon: IconSend },
|
||||
{ href: "/beneficiary", label: "Beneficiaries", icon: IconUsers },
|
||||
{ href: "/settings", label: "Settings", icon: IconSettings },
|
||||
];
|
||||
|
||||
@@ -183,113 +192,182 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<html lang="en">
|
||||
<body>
|
||||
<Providers>
|
||||
<Box style={{ backgroundColor: "#e6ffff", minHeight: "100vh", display: "flex", flexDirection: "column", padding: 0, margin: 0 }}>
|
||||
<Box style={{ minHeight: "100%", display: "flex", flexDirection: "column" }}>
|
||||
|
||||
{/* HEADER */}
|
||||
<Box
|
||||
component="header"
|
||||
className={styles.header}
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
height: "60px",
|
||||
// flexDirection: "row",
|
||||
padding: isMobile ? "0.6rem 1rem" : "0.8rem 2rem",
|
||||
background: darkMode
|
||||
? "linear-gradient(15deg, rgba(229, 101, 22, 1) 55%, rgba(28, 28, 30, 1) 100%)"
|
||||
: "linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
// padding: isMobile ? "0.5rem" : "0.5rem 1rem",
|
||||
background: "linear-gradient(15deg,rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
||||
position: "relative",
|
||||
// position: "fixed",
|
||||
}}
|
||||
>
|
||||
{/* Logo */}
|
||||
<Box style={{ width: isMobile ? "48px" : "65px", height: isMobile ? "50px" : "60px" }}>
|
||||
<Image src={logo} component={NextImage} alt="ebanking" style={{ width: "100%", height: "100%" }} />
|
||||
</Box>
|
||||
|
||||
{/* Title & Phone */}
|
||||
<Stack gap={isMobile ? 2 : 0} style={{ flex: 1, textAlign: isMobile ? "center" : "left", marginLeft: isMobile ? 0 : "1rem" }}>
|
||||
<Title order={isMobile ? 5 : 2} style={{ color: "white", fontFamily: "Roboto", lineHeight: 1.2 }}>
|
||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
<Text style={{ color: "white", fontSize: isMobile ? "0.8rem" : "0.9rem", textShadow: "1px 1px 2px black" }}>
|
||||
<IconPhoneFilled size={isMobile ? 16 : 20} /> Toll Free No : 1800-180-8008
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* WELCOME + NAV */}
|
||||
<Box
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
padding: isMobile ? "0.5rem" : "0.5rem 1rem",
|
||||
display: "flex",
|
||||
flexDirection: isMobile ? "column" : "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
gap: isMobile ? "0.5rem" : 0,
|
||||
color: "white",
|
||||
boxShadow: "0 2px 6px rgba(0,0,0,0.15)",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
zIndex: 200, // ↑ Increased for stability
|
||||
}}
|
||||
>
|
||||
<Stack gap={isMobile ? 2 : 0} align={isMobile ? "flex-start" : "flex-start"}>
|
||||
<Title order={isMobile ? 5 : 4} style={{ fontFamily: "inter", fontSize: isMobile ? "18px" : "22px" }}>
|
||||
Welcome, {custname ?? null}
|
||||
</Title>
|
||||
<Text size="xs" c="gray" style={{ fontFamily: "inter", fontSize: isMobile ? "11px" : "13px" }}>
|
||||
Last logged in at {userLastLoginDetails ? new Date(userLastLoginDetails).toLocaleString() : "N/A"}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Group gap="md" wrap="nowrap">
|
||||
<Image
|
||||
src={logo}
|
||||
component={NextImage}
|
||||
fit="contain"
|
||||
alt="ebanking"
|
||||
style={{
|
||||
width: isMobile ? "40px" : "60px",
|
||||
height: "auto",
|
||||
}}
|
||||
/>
|
||||
|
||||
<Group mt={isMobile ? "sm" : "md"} gap="sm" style={{ flexWrap: isMobile ? "wrap" : "nowrap" }}>
|
||||
{navItems.map((item) => {
|
||||
const isActive = pathname.startsWith(item.href);
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link key={item.href} href={item.href}>
|
||||
<Button
|
||||
leftSection={<Icon size={isMobile ? 16 : 20} />}
|
||||
variant={isActive ? "dark" : "subtle"}
|
||||
color={isActive ? "blue" : undefined}
|
||||
size={isMobile ? "xs" : "sm"}
|
||||
>
|
||||
{item.label}
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
<div>
|
||||
<Title
|
||||
order={isMobile ? 4 : 3}
|
||||
style={{
|
||||
fontFamily: "Roboto",
|
||||
color: "white",
|
||||
marginBottom: 2,
|
||||
fontSize: isMobile ? "14px" : "22px",
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
|
||||
<Popover opened={opened} onChange={close} position="bottom-end" withArrow shadow="md">
|
||||
<Popover.Target>
|
||||
<Button leftSection={<IconLogout size={isMobile ? 16 : 20} />} variant="subtle" size={isMobile ? "xs" : "sm"} onClick={open}>
|
||||
{!isMobile && (
|
||||
<Text size="xs" c="white" style={{ opacity: 0.85 }}>
|
||||
Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</Group>
|
||||
|
||||
{/* USER BUTTON */}
|
||||
<Popover
|
||||
opened={userOpened}
|
||||
onChange={setUserOpened}
|
||||
position="bottom-end"
|
||||
withArrow
|
||||
shadow="md"
|
||||
>
|
||||
<Popover.Target>
|
||||
<Button
|
||||
leftSection={<IconUserCircle size={isMobile ? 18 : 22} />}
|
||||
variant="subtle"
|
||||
onClick={() => setUserOpened((prev) => !prev)}
|
||||
color='white'
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
padding: isMobile ? "4px 8px" : "6px 12px",
|
||||
fontSize: isMobile ? "12px" : "14px",
|
||||
}}
|
||||
>
|
||||
Welcome, {firstName}
|
||||
</Button>
|
||||
</Popover.Target>
|
||||
|
||||
<Popover.Dropdown style={{ minWidth: 230, padding: 15 }}>
|
||||
<Stack gap="xs">
|
||||
<Box>
|
||||
<Text size="sm" fw={700}>{custname}</Text>
|
||||
<Text size="xs" c="dimmed">Full Name</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text size="sm">
|
||||
{userLastLoginDetails
|
||||
? new Date(userLastLoginDetails).toLocaleString()
|
||||
: "N/A"}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">Last Login</Text>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Button
|
||||
leftSection={<IconLogout size={18} />}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
Logout
|
||||
</Button>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Text size="sm" mb="sm">
|
||||
Are you sure you want to logout?
|
||||
</Text>
|
||||
<Group justify="flex-end" gap="sm">
|
||||
<Button variant="default" onClick={close}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleLogout}>Logout</Button>
|
||||
</Group>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</Box>
|
||||
|
||||
<Divider size="xs" color="#99c2ff" />
|
||||
{/* NAVBAR — desktop unchanged, mobile scrollable */}
|
||||
<Group
|
||||
style={{
|
||||
background: "#c8eeacff",
|
||||
boxShadow: "0 6px 6px rgba(0,0,0,0.06)",
|
||||
position: "sticky",
|
||||
top: isMobile ? 60 : 85,
|
||||
zIndex: 150,
|
||||
|
||||
{/* CHILDREN */}
|
||||
/* MOBILE FIX make it scrollable */
|
||||
overflowX: isMobile ? "auto" : "visible",
|
||||
whiteSpace: isMobile ? "nowrap" : "normal",
|
||||
padding: isMobile ? "6px 4px" : "0.05rem",
|
||||
}}
|
||||
>
|
||||
{navItems.map((item) => {
|
||||
const isActive = pathname.startsWith(item.href);
|
||||
const Icon = item.icon;
|
||||
|
||||
return (
|
||||
<Link key={item.href} href={item.href} style={{ textDecoration: "none" }}>
|
||||
<Group
|
||||
gap={8}
|
||||
style={{
|
||||
padding: isMobile ? "10px 14px" : "14px 16px",
|
||||
// borderRadius: isMobile ? 6 : 8,
|
||||
width: "100%",
|
||||
transition: "0.2s ease",
|
||||
background: isActive ? "rgba(50, 159, 81, 1)" : "transparent",
|
||||
color: isActive ? "white" : "#3b3b3b",
|
||||
fontWeight: isActive ? 600 : 500,
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
size={isMobile ? 16 : 18}
|
||||
color={isActive ? "white" : "#3b3b3b"}
|
||||
/>
|
||||
<Text size={isMobile ? "xs" : "sm"} fw={500}>
|
||||
{item.label}
|
||||
</Text>
|
||||
</Group>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</Group>
|
||||
|
||||
{/* CONTENT */}
|
||||
<Box
|
||||
style={{
|
||||
flex: 1,
|
||||
overflowY: "auto",
|
||||
borderTop: "1px solid #ddd",
|
||||
borderBottom: "1px solid #ddd",
|
||||
// padding: isMobile ? "0.5rem" : "1rem",
|
||||
backgroundColor: "#f5f7fa",
|
||||
padding: isMobile ? "0.8rem" : "1.5rem",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<Box
|
||||
style={{
|
||||
margin: "0 auto",
|
||||
background: "white",
|
||||
padding: isMobile ? "1rem" : "1.8rem",
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
|
||||
minHeight: "75vh",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* this model for session logout */}
|
||||
<Modal
|
||||
opened={sessionModal}
|
||||
@@ -317,27 +395,113 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
<Divider size="xs" color="blue" />
|
||||
|
||||
{/* FOOTER */}
|
||||
{/* FOOTER (desktop same, mobile stacked) */}
|
||||
<Box
|
||||
component="footer"
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#f8f9fa",
|
||||
// padding: isMobile ? "0.25rem" : "0.5rem",
|
||||
backgroundColor: "rgba(60, 54, 74, 1)",
|
||||
paddingTop: "2rem",
|
||||
paddingBottom: "2rem",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
<Text c="dimmed" size={isMobile ? "xs" : "sm"}>
|
||||
© 2025 The Kangra Central Co-Operative Bank
|
||||
</Text>
|
||||
<Container size="xl">
|
||||
<Grid gutter="xl">
|
||||
|
||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||
<Group mb="md">
|
||||
<Box
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
background: 'linear-gradient(135deg, #16a34a 0%, #10b981 100%)',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={logo}
|
||||
component={NextImage}
|
||||
fit="cover"
|
||||
alt="ebanking"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={500}>The Kangra Central</Text>
|
||||
<Text size="sm" fw={500}>Co-operative Bank Ltd</Text>
|
||||
</div>
|
||||
</Group>
|
||||
<Text size="sm" c="dimmed">
|
||||
Serving the community since inception.
|
||||
</Text>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||
<Text size="sm" fw={500} mb="md">Quick Links</Text>
|
||||
<Stack gap="xs">
|
||||
<Anchor
|
||||
href="https://kccbhp.bank.in/about-us/history-of-kccb/"
|
||||
size="sm"
|
||||
c="dimmed"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
About Us
|
||||
</Anchor>
|
||||
<Anchor
|
||||
href="https://kccbhp.bank.in/products/service-products/service-charges/"
|
||||
size="sm"
|
||||
c="dimmed"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Products & Services
|
||||
</Anchor>
|
||||
<Anchor
|
||||
href="/CustomerCare"
|
||||
size="sm"
|
||||
c="dimmed"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Help & Support
|
||||
</Anchor>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||
<Text size="sm" fw={500} mb="md">Contact Us</Text>
|
||||
<Stack gap="xs">
|
||||
<Text size="sm" c="dimmed">Phone: +91-1800-1808008 </Text>
|
||||
<Text size="sm" c="dimmed">Mon–Sat 10 AM – 5 PM</Text>
|
||||
<Text size="sm" c="dimmed">(The Second and fourth Saturdays are holidays)</Text>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Text
|
||||
size="sm"
|
||||
c="dimmed"
|
||||
ta="center"
|
||||
style={{ borderTop: "1px solid rgba(255,255,255,0.2)", marginTop: 20, paddingTop: 20 }}
|
||||
>
|
||||
© 2025 The Kangra Central Co-operative Bank Ltd. All rights reserved.
|
||||
</Text>
|
||||
</Container>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
</Providers>
|
||||
</body>
|
||||
</html >
|
||||
</html>
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
74
src/app/(main)/page.module.css
Normal file
@@ -0,0 +1,74 @@
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.8rem 2rem;
|
||||
background: linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%);
|
||||
flex-wrap: wrap; /* Allow wrapping on mobile */
|
||||
}
|
||||
|
||||
.header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.desktop-text {
|
||||
color: white;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.desktop-address {
|
||||
font-family: Roboto, sans-serif;
|
||||
color: white;
|
||||
font-size: 0.9rem;
|
||||
text-shadow: 1px 1px 2px blue;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* Mobile styles */
|
||||
.mobile-text {
|
||||
color: white;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 1rem;
|
||||
line-height: 1.2;
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Media query for mobile */
|
||||
@media screen and (max-width: 768px) {
|
||||
.header {
|
||||
justify-content: center;
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 50px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.desktop-text,
|
||||
.desktop-address {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-text {
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
@@ -58,10 +58,6 @@ export default function ChangePassword() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
regenerateCaptcha();
|
||||
}, []);
|
||||
@@ -232,8 +228,8 @@ export default function ChangePassword() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={400}>
|
||||
<Title order={3} mb="sm">
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
||||
<Title order={4} mb="sm">
|
||||
Change Login Password
|
||||
</Title>
|
||||
{/* Scrollable form area */}
|
||||
|
||||
@@ -240,8 +240,8 @@ export default function ChangePassword() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={400}>
|
||||
<Title order={3} mb="sm">
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
||||
<Title order={4} mb="sm">
|
||||
Change Transaction Password
|
||||
</Title>
|
||||
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
"use client";
|
||||
import { Box, Burger, Button, Divider, Drawer, Stack, Text } from '@mantine/core';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMediaQuery } from '@mantine/hooks';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Stack,
|
||||
Text,
|
||||
SegmentedControl,
|
||||
ScrollArea,
|
||||
Burger,
|
||||
Drawer,
|
||||
Button,
|
||||
} from "@mantine/core";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import Image from "next/image";
|
||||
import logo from "@/app/image/logo1.jpg";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const [authorized, SetAuthorized] = useState<boolean | null>(null);
|
||||
const [authorized, setAuthorized] = useState<boolean | null>(null);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
@@ -21,159 +30,164 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
{ label: "Change transaction Password", href: "/settings/change_txn_password" },
|
||||
{ label: "Set transaction Password", href: "/settings/set_txn_password" },
|
||||
{ label: "Preferred Name", href: "/settings/user_name" },
|
||||
{ label: "Set Transaction Limit ", href: "/settings/set_txn_limit" },
|
||||
|
||||
{ label: "Set Transaction Limit", href: "/settings/set_txn_limit" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("access_token");
|
||||
if (!token) {
|
||||
SetAuthorized(false);
|
||||
setAuthorized(false);
|
||||
router.push("/login");
|
||||
}
|
||||
else {
|
||||
SetAuthorized(true);
|
||||
} else {
|
||||
setAuthorized(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (authorized) {
|
||||
return (
|
||||
<Box style={{ display: "flex", height: "100%", flexDirection: isMobile ? "column" : "row" }}>
|
||||
{/* Desktop Sidebar */}
|
||||
{!isMobile && (
|
||||
if (!authorized) return null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{/* ---------------- DESKTOP HEADER ---------------- */}
|
||||
{!isMobile && (
|
||||
<>
|
||||
<Box mb="1rem">
|
||||
<ScrollArea type="never" offsetScrollbars>
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
value={pathname}
|
||||
onChange={(value) => router.push(value)}
|
||||
data={links.map((link) => ({
|
||||
label: link.label,
|
||||
value: link.href,
|
||||
}))}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "#e9ecef",
|
||||
borderRadius: 999,
|
||||
padding: 4,
|
||||
},
|
||||
control: {
|
||||
borderRadius: 999,
|
||||
transition: "0.3s",
|
||||
},
|
||||
|
||||
indicator: {
|
||||
borderRadius: 999,
|
||||
background: "linear-gradient(90deg, #02a355 0%, #5483c9ff 100%)",
|
||||
boxShadow: "0 3px 8px rgba(0,0,0,0.15)",
|
||||
},
|
||||
|
||||
label: {
|
||||
fontSize: 13,
|
||||
padding: "6px 10px",
|
||||
textAlign: "center",
|
||||
fontWeight: 600,
|
||||
color: "#000",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ScrollArea>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ---------------- MOBILE HEADER ---------------- */}
|
||||
{isMobile && (
|
||||
<>
|
||||
{/* Mobile Header */}
|
||||
<Box
|
||||
style={{
|
||||
width: "16%",
|
||||
backgroundColor: "#c5e4f9",
|
||||
borderRight: "1px solid #ccc",
|
||||
backgroundColor: "#228be6",
|
||||
padding: "0.8rem 1rem",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Stack style={{ background: "#228be6", height: "10%", alignItems: "center" }}>
|
||||
<Text fw={700} c="white" style={{ textAlign: "center", marginTop: "10px" }}>
|
||||
Settings
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack gap="sm" justify="flex-start" style={{ padding: "1rem" }}>
|
||||
{links.map((link) => {
|
||||
const isActive = pathname === link.href;
|
||||
return (
|
||||
<Text
|
||||
key={link.href}
|
||||
component={Link}
|
||||
href={link.href}
|
||||
c={isActive ? "darkblue" : "blue"}
|
||||
style={{
|
||||
textDecoration: isActive ? "underline" : "none",
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
}}
|
||||
>
|
||||
{link.label}
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
<Burger
|
||||
opened={drawerOpened}
|
||||
onClick={() => setDrawerOpened(true)}
|
||||
size="sm"
|
||||
color="white"
|
||||
/>
|
||||
<Text fw={600} c="white">
|
||||
Settings
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Mobile: Burger & Drawer */}
|
||||
{isMobile && (
|
||||
<>
|
||||
{/* Top header with burger and title */}
|
||||
{/* Mobile Drawer */}
|
||||
<Drawer
|
||||
opened={drawerOpened}
|
||||
onClose={() => setDrawerOpened(false)}
|
||||
padding="md"
|
||||
size="75%"
|
||||
overlayProps={{ color: "black", opacity: 0.55, blur: 2 }}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: "#228be6",
|
||||
// padding: "0.5rem 1rem",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: "1.2rem",
|
||||
}}
|
||||
>
|
||||
<Burger
|
||||
opened={drawerOpened}
|
||||
onClick={() => setDrawerOpened(!drawerOpened)}
|
||||
size="sm"
|
||||
color="white"
|
||||
<Image
|
||||
src={logo}
|
||||
alt="Logo"
|
||||
width={40}
|
||||
height={40}
|
||||
style={{ borderRadius: "50%" }}
|
||||
/>
|
||||
<Text fw={500} c="white">
|
||||
<Text fw={700} ml="10px" style={{ color: "#228be6", fontSize: "18px" }}>
|
||||
Settings
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Drawer
|
||||
opened={drawerOpened}
|
||||
onClose={() => setDrawerOpened(false)}
|
||||
padding="md"
|
||||
size="75%"
|
||||
overlayProps={{ color: "black", opacity: 0.55, blur: 3 }}
|
||||
styles={{
|
||||
root: {
|
||||
backgroundColor: "#e6f5ff", // soft background for drawer
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Logo and Drawer Header */}
|
||||
<Box style={{ display: "flex", alignItems: "center", marginBottom: "1rem" }}>
|
||||
<>
|
||||
<Image src={logo} alt="KCCB Logo" width={40} height={40} style={{ borderRadius: "50%" }} />
|
||||
<Text
|
||||
fw={700}
|
||||
ml="10px"
|
||||
style={{ fontSize: "18px", color: "#228be6" }}
|
||||
<Stack gap="xs">
|
||||
{links.map((link) => {
|
||||
const isActive = pathname === link.href;
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={link.href}
|
||||
component={Link}
|
||||
href={link.href}
|
||||
fullWidth
|
||||
variant="light"
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
backgroundColor: isActive ? "#228be6" : "#e2efff",
|
||||
color: isActive ? "#fff" : "#1b69c7",
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
borderRadius: 10,
|
||||
padding: "10px",
|
||||
}}
|
||||
onClick={() => setDrawerOpened(false)}
|
||||
>
|
||||
Settings
|
||||
</Text>
|
||||
</>
|
||||
</Box>
|
||||
{link.label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Drawer>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Menu Items */}
|
||||
<Stack gap="sm">
|
||||
{links.map((link) => {
|
||||
const isActive = pathname === link.href;
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={link.href}
|
||||
variant="subtle"
|
||||
component={Link}
|
||||
href={link.href}
|
||||
fullWidth
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
textDecoration: isActive ? "underline" : "none",
|
||||
color: isActive ? "#fff" : "#228be6",
|
||||
backgroundColor: isActive ? "#228be6" : "#dceeff",
|
||||
borderRadius: "8px",
|
||||
padding: "10px 12px",
|
||||
transition: "0.3s",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
const target = e.currentTarget as unknown as HTMLElement;
|
||||
target.style.backgroundColor = "#228be6";
|
||||
target.style.color = "#fff";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
const target = e.currentTarget as unknown as HTMLElement;
|
||||
target.style.backgroundColor = isActive ? "#228be6" : "#dceeff";
|
||||
target.style.color = isActive ? "#fff" : "#228be6";
|
||||
}}
|
||||
onClick={() => setDrawerOpened(false)}
|
||||
>
|
||||
{link.label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Drawer>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
{/* Content Area */}
|
||||
<Box style={{ flex: 1, padding: isMobile ? "0.5rem" : "1rem", overflowY: "auto" }}>
|
||||
{children}
|
||||
</Box>
|
||||
{/* ---------------- CONTENT AREA ---------------- */}
|
||||
<Box
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: isMobile ? "0.8rem" : "1rem",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,9 +10,10 @@ import {
|
||||
Divider,
|
||||
Loader,
|
||||
Center,
|
||||
ActionIcon,
|
||||
} from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { IconEye, IconEyeOff } from "@tabler/icons-react";
|
||||
|
||||
// Response structure from backend
|
||||
interface ProfileData {
|
||||
@@ -24,13 +25,13 @@ interface ProfileData {
|
||||
id: string;
|
||||
custaddress: string;
|
||||
pincode: string;
|
||||
|
||||
}
|
||||
|
||||
export default function ViewProfile() {
|
||||
const router = useRouter();
|
||||
const [profileData, setProfileData] = useState<ProfileData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showCIF, setShowCIF] = useState(false);
|
||||
const [showPrimaryID, setShowPrimaryID] = useState(false);
|
||||
|
||||
// Fetch API with same style as RootLayout
|
||||
async function handleFetchProfile() {
|
||||
@@ -40,7 +41,7 @@ export default function ViewProfile() {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Login-Type": "IB",
|
||||
"X-Login-Type": "IB",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
@@ -79,39 +80,90 @@ export default function ViewProfile() {
|
||||
handleFetchProfile();
|
||||
}, []);
|
||||
|
||||
const maskValue = (value: string) => {
|
||||
if (!value || value.length <= 4) return value;
|
||||
return "*".repeat(value.length - 4) + value.slice(-4);
|
||||
};
|
||||
|
||||
const formatDOB = (dob?: string) => {
|
||||
if (!dob || dob.length !== 8) return dob;
|
||||
const dd = dob.slice(0, 2);
|
||||
const mm = dob.slice(2, 4);
|
||||
const yyyy = dob.slice(4, 8);
|
||||
return `${dd}-${mm}-${yyyy}`;
|
||||
};
|
||||
|
||||
const formatMobile = (mobile?: string): string => {
|
||||
if (!mobile) return "";
|
||||
// If number starts with country code 91
|
||||
if (mobile.startsWith("91") && mobile.length === 12) {
|
||||
return `+91 ${mobile.slice(2, 7)} ${mobile.slice(7)}`;
|
||||
}
|
||||
|
||||
// If already 10-digit number
|
||||
if (mobile.length === 10) {
|
||||
return `${mobile.slice(0, 5)} ${mobile.slice(5)}`;
|
||||
}
|
||||
|
||||
return mobile; // fallback
|
||||
};
|
||||
|
||||
|
||||
const Row = ({
|
||||
label,
|
||||
value,
|
||||
link,
|
||||
masked,
|
||||
showValue,
|
||||
onToggle,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
link?: string;
|
||||
}) => (
|
||||
<Grid align="flex-start" gutter="xs" mb={6}>
|
||||
<Grid.Col span={3}>
|
||||
<Text c="dimmed" size="sm" fw={500}>
|
||||
{label}
|
||||
</Text>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={9}>
|
||||
{link ? (
|
||||
<Anchor size="sm" href={link} target="_blank" rel="noopener noreferrer">
|
||||
{value}
|
||||
</Anchor>
|
||||
) : (
|
||||
<Text size="sm">{value}</Text>
|
||||
)}
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
);
|
||||
masked?: boolean;
|
||||
showValue?: boolean;
|
||||
onToggle?: () => void;
|
||||
}) => {
|
||||
const displayValue = masked && !showValue ? maskValue(value) : value;
|
||||
|
||||
return (
|
||||
<Grid align="flex-start" gutter="xs" mb={6}>
|
||||
<Grid.Col span={3}>
|
||||
<Text c="dimmed" size="sm" fw={500}>
|
||||
{label}
|
||||
</Text>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={9}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
{link ? (
|
||||
<Anchor size="sm" href={link} target="_blank" rel="noopener noreferrer">
|
||||
{displayValue}
|
||||
</Anchor>
|
||||
) : (
|
||||
<Text size="sm">{displayValue}</Text>
|
||||
)}
|
||||
{masked && onToggle && (
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={onToggle}
|
||||
aria-label={showValue ? "Hide" : "Show"}
|
||||
>
|
||||
{showValue ? <IconEyeOff size={16} /> : <IconEye size={16} />}
|
||||
</ActionIcon>
|
||||
)}
|
||||
</div>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper shadow="xs" radius="md" p="md" withBorder h={400}>
|
||||
<Title order={4} mb="sm">
|
||||
<Paper shadow="xs" radius="md" p="md" withBorder h={500}>
|
||||
{/* <Title order={4} mb="sm">
|
||||
View Profile
|
||||
</Title>
|
||||
<Divider mb="sm" />
|
||||
<Divider mb="sm" /> */}
|
||||
|
||||
{loading ? (
|
||||
<Center>
|
||||
@@ -119,22 +171,39 @@ export default function ViewProfile() {
|
||||
</Center>
|
||||
) : profileData ? (
|
||||
<>
|
||||
<Row label="Customer ID (CIF)" value={profileData.cifNumber} />
|
||||
<Row label="Customer Name" value={profileData.custname} />
|
||||
<Row label="ID" value={profileData.id} />
|
||||
<Row label="Branch No" value={profileData.stBranchNo} />
|
||||
<Row label="Date of Birth" value={profileData.custdob} />
|
||||
<Row label="Mobile Number" value={profileData.mobileno} />
|
||||
{/* Personal Details Section */}
|
||||
<Title order={4} mb="sm" mt="md">
|
||||
Personal Details
|
||||
</Title>
|
||||
<Divider mb="sm" />
|
||||
|
||||
<Row
|
||||
label="Address"
|
||||
value={profileData.custaddress}
|
||||
// link={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
|
||||
// profileData.custaddress
|
||||
// )}`}
|
||||
label="Customer ID (CIF)"
|
||||
value={profileData.cifNumber}
|
||||
masked={true}
|
||||
showValue={showCIF}
|
||||
onToggle={() => setShowCIF(!showCIF)}
|
||||
/>
|
||||
<Row label="Customer Name" value={profileData.custname} />
|
||||
<Row label="Branch No" value={profileData.stBranchNo} />
|
||||
<Row label="Date of Birth" value={formatDOB(profileData.custdob) ?? ""} />
|
||||
<Row label="Mobile Number" value={formatMobile(profileData.mobileno) ?? ""} />
|
||||
<Row label="Address" value={profileData.custaddress} />
|
||||
<Row label="Pincode" value={profileData.pincode} />
|
||||
|
||||
{/* KYC Details Section */}
|
||||
<Title order={4} mb="sm" mt="md">
|
||||
KYC Details
|
||||
</Title>
|
||||
<Divider mb="sm" />
|
||||
|
||||
<Row
|
||||
label="Primary ID"
|
||||
value={profileData.id}
|
||||
masked={true}
|
||||
showValue={showPrimaryID}
|
||||
// onToggle={() => setShowPrimaryID(!showPrimaryID)}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Text c="red" size="sm">
|
||||
|
||||
@@ -224,8 +224,8 @@ export default function SetTransactionLimit() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={400}>
|
||||
<Title order={3} mb="sm">
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
||||
<Title order={4} mb="sm">
|
||||
Set Transaction Limit
|
||||
</Title>
|
||||
|
||||
@@ -237,9 +237,13 @@ export default function SetTransactionLimit() {
|
||||
value={limit}
|
||||
onChange={(e) => {
|
||||
const val = e.currentTarget.value;
|
||||
// Only allow digits
|
||||
|
||||
// Allow only digits AND max value 500000
|
||||
if (/^\d*$/.test(val)) {
|
||||
setLimit(val);
|
||||
const num = Number(val);
|
||||
if (num <= 500000) {
|
||||
setLimit(val);
|
||||
}
|
||||
}
|
||||
}}
|
||||
leftSection={icon}
|
||||
@@ -247,6 +251,7 @@ export default function SetTransactionLimit() {
|
||||
mb="sm"
|
||||
readOnly={step !== "form"}
|
||||
/>
|
||||
|
||||
</Group>
|
||||
{dailyLimit !== null ? (
|
||||
<Text size="xs" c="green">
|
||||
|
||||
@@ -240,8 +240,8 @@ export default function ChangePassword() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={400} >
|
||||
<Title order={3} mb="sm">
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500} >
|
||||
<Title order={4} mb="sm">
|
||||
Set Transaction Password
|
||||
</Title>
|
||||
|
||||
|
||||
@@ -261,8 +261,8 @@ export default function SetPreferredNameSimple() {
|
||||
if (loading) return <Text>Loading...</Text>;
|
||||
|
||||
return (
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder>
|
||||
<Title order={3} mb="sm">
|
||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
||||
<Title order={4} mb="sm">
|
||||
Set Preferred Name
|
||||
</Title>
|
||||
|
||||
|
||||
232
src/app/ATMLocator/page.tsx
Normal file
@@ -0,0 +1,232 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Box, Button, Group, Stack, Text, Paper, Collapse, Title, Image } from "@mantine/core";
|
||||
import { IconChevronDown, IconChevronUp } from "@tabler/icons-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import NextImage from "next/image";
|
||||
import logo from "@/app/image/logo1.jpg";
|
||||
|
||||
export default function ATMListPage() {
|
||||
const [atms, setAtms] = useState([]);
|
||||
const [openedIndex, setOpenedIndex] = useState<number | null>(null);
|
||||
const [authorized, setAuthorized] = useState<boolean | null>(null);
|
||||
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const router = useRouter();
|
||||
|
||||
// ✔ Authorization Check
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("access_token");
|
||||
if (!token) {
|
||||
setAuthorized(false);
|
||||
router.push("/login");
|
||||
} else {
|
||||
setAuthorized(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// ✔ Fetch ATM Data
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
try {
|
||||
const token = localStorage.getItem("access_token");
|
||||
const response = await fetch("/api/atm", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Login-Type": "IB",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
const list = Array.isArray(result)
|
||||
? result
|
||||
: Array.isArray(result.data)
|
||||
? result.data
|
||||
: [];
|
||||
|
||||
setAtms(list);
|
||||
} catch (error) {
|
||||
console.error("ATM list API error:", error);
|
||||
setAtms([]);
|
||||
}
|
||||
}
|
||||
|
||||
if (authorized) fetchData();
|
||||
}, [authorized]);
|
||||
|
||||
// Wait until auth is checked
|
||||
if (authorized === null) return null;
|
||||
|
||||
const toggleRow = (index: number) => {
|
||||
setOpenedIndex((prev) => (prev === index ? null : index));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{/* HEADER */}
|
||||
<Box
|
||||
component="header"
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: isMobile ? "0.6rem 1rem" : "0.8rem 2rem",
|
||||
background:
|
||||
"linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
||||
color: "white",
|
||||
boxShadow: "0 2px 6px rgba(0,0,0,0.15)",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Group gap="md" wrap="nowrap" align="center">
|
||||
<Image
|
||||
src={logo}
|
||||
component={NextImage}
|
||||
fit="contain"
|
||||
alt="ebanking"
|
||||
style={{ width: isMobile ? "45px" : "60px", height: "auto" }}
|
||||
/>
|
||||
<div>
|
||||
<Title
|
||||
order={isMobile ? 4 : 3}
|
||||
style={{ fontFamily: "Roboto", color: "white", marginBottom: 2 }}
|
||||
>
|
||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
{!isMobile && (
|
||||
<Text size="xs" c="white" style={{ opacity: 0.85 }}>
|
||||
Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
{/* SECOND FIXED BAR */}
|
||||
<Box
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: isMobile ? "0.5rem 1rem" : "0.6rem 1.5rem",
|
||||
background: "#ffffff",
|
||||
borderBottom: "1px solid #ddd",
|
||||
position: "sticky",
|
||||
top: isMobile ? "65px" : "70px",
|
||||
zIndex: 200,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
fw={700}
|
||||
size={isMobile ? "md" : "lg"}
|
||||
ta="center"
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
List of Available ATMs
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size={isMobile ? "xs" : "sm"}
|
||||
onClick={() => window.close()}
|
||||
style={{ marginLeft: "auto" }}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* SCROLLABLE CONTENT */}
|
||||
<Box
|
||||
style={{
|
||||
flex: 1,
|
||||
overflowY: "auto",
|
||||
padding: isMobile ? "0.5rem" : "1rem",
|
||||
paddingTop: isMobile ? "60px" : "40px",
|
||||
}}
|
||||
>
|
||||
<Stack>
|
||||
{atms.map((atm: any, index: number) => {
|
||||
const isOpen = openedIndex === index;
|
||||
|
||||
return (
|
||||
<Paper
|
||||
key={index}
|
||||
p={isMobile ? "sm" : "md"}
|
||||
radius="md"
|
||||
withBorder
|
||||
shadow="xs"
|
||||
>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Text fw={600} size={isMobile ? "sm" : "md"}>
|
||||
{atm.name}
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
variant="subtle"
|
||||
size={isMobile ? "xs" : "md"}
|
||||
onClick={() => toggleRow(index)}
|
||||
leftSection={
|
||||
isOpen ? <IconChevronUp size={18} /> : <IconChevronDown size={18} />
|
||||
}
|
||||
>
|
||||
{isOpen ? "Hide" : "View"}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
{/* ATM Extra Info */}
|
||||
<Collapse in={isOpen}>
|
||||
<Box mt="md" ml="md">
|
||||
<Text size={isMobile ? "xs" : "sm"}>
|
||||
<b>ATM Name:</b> {atm.name}
|
||||
</Text>
|
||||
|
||||
<Text size={isMobile ? "xs" : "sm"}>
|
||||
<b>Availability:</b> 24x7
|
||||
</Text>
|
||||
|
||||
<Text size={isMobile ? "xs" : "sm"}>
|
||||
<b>Type:</b> Cash Withdrawal Only
|
||||
</Text>
|
||||
|
||||
<Text size={isMobile ? "xs" : "sm"}>
|
||||
<b>Status:</b>{" "}
|
||||
<span style={{ color: "green", fontWeight: 600 }}>Active</span>
|
||||
</Text>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Paper>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* FOOTER */}
|
||||
<Box
|
||||
component="footer"
|
||||
style={{
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
padding: isMobile ? "8px" : "12px 0",
|
||||
background: "#ffffff",
|
||||
borderTop: "1px solid #ddd",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Text size={isMobile ? "xs" : "sm"} c="dimmed">
|
||||
© 2025 KCC Bank. All rights reserved.
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
254
src/app/BranchLocator/page.tsx
Normal file
@@ -0,0 +1,254 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Box, Button, Group, Stack, Text, Paper, Collapse, Title, Image } from "@mantine/core";
|
||||
import { IconChevronDown, IconChevronUp } from "@tabler/icons-react";
|
||||
import NextImage from 'next/image';
|
||||
import logo from '@/app/image/logo1.jpg';
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
|
||||
export default function BranchListPage() {
|
||||
const [branches, setBranches] = useState([]);
|
||||
const [openedIndex, setOpenedIndex] = useState<number | null>(null);
|
||||
const [authorized, SetAuthorized] = useState<boolean | null>(null);
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const router = useRouter();
|
||||
|
||||
// Fetch branch API
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
try {
|
||||
const token = localStorage.getItem("access_token");
|
||||
const response = await fetch('api/branch', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"X-Login-Type": "IB",
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
// Handle any response shape
|
||||
const list =
|
||||
Array.isArray(result) ? result :
|
||||
Array.isArray(result.data) ? result.data :
|
||||
Array.isArray(result.branches) ? result.branches :
|
||||
[];
|
||||
|
||||
setBranches(list);
|
||||
} catch (error) {
|
||||
console.error("Error fetching branches:", error);
|
||||
setBranches([]);
|
||||
}
|
||||
}
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("access_token");
|
||||
if (!token) {
|
||||
SetAuthorized(false);
|
||||
router.push("/login");
|
||||
}
|
||||
else {
|
||||
SetAuthorized(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
const toggleRow = (index: number) => {
|
||||
setOpenedIndex((prev) => (prev === index ? null : index));
|
||||
};
|
||||
|
||||
if (authorized === null) {
|
||||
return null; // Prevent rendering until check is done
|
||||
}
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{/* HEADER */}
|
||||
<Box
|
||||
component="header"
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: isMobile ? "0.6rem 1rem" : "0.8rem 2rem",
|
||||
background:
|
||||
"linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
justifyContent: "space-between",
|
||||
color: "white",
|
||||
boxShadow: "0 2px 6px rgba(0,0,0,0.15)",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Group gap="md" wrap="nowrap" align="center">
|
||||
<Image
|
||||
src={logo}
|
||||
component={NextImage}
|
||||
fit="contain"
|
||||
alt="ebanking"
|
||||
style={{ width: isMobile ? "45px" : "60px", height: "auto" }}
|
||||
/>
|
||||
<div>
|
||||
<Title
|
||||
order={isMobile ? 4 : 3}
|
||||
style={{ fontFamily: "Roboto", color: "white", marginBottom: 2 }}
|
||||
>
|
||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
{!isMobile && (
|
||||
<Text size="xs" c="white" style={{ opacity: 0.85 }}>
|
||||
Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
{/* SECOND FIXED BAR */}
|
||||
<Box
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: isMobile ? "0.5rem 1rem" : "0.6rem 1.5rem",
|
||||
background: "#ffffff",
|
||||
borderBottom: "1px solid #ddd",
|
||||
position: "sticky",
|
||||
top: isMobile ? "65px" : "70px",
|
||||
zIndex: 200,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
fw={700}
|
||||
size={isMobile ? "md" : "lg"}
|
||||
ta="center"
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
List of Branches
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size={isMobile ? "xs" : "sm"}
|
||||
onClick={() => window.close()}
|
||||
style={{ marginLeft: "auto" }}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* SCROLLABLE CONTENT */}
|
||||
<Box
|
||||
style={{
|
||||
flex: 1,
|
||||
overflowY: "auto",
|
||||
padding: isMobile ? "0.5rem" : "1rem",
|
||||
paddingTop: isMobile ? "60px" : "40px",
|
||||
boxShadow: "0 4px 12px rgba(0,0,0,0.08)"
|
||||
}}>
|
||||
<Stack>
|
||||
{branches.map((branch: any, index: number) => {
|
||||
const isOpen = openedIndex === index;
|
||||
|
||||
return (
|
||||
<Paper
|
||||
key={branch.branch_code}
|
||||
p={isMobile ? "sm" : "md"}
|
||||
radius="md"
|
||||
withBorder
|
||||
shadow="xs"
|
||||
>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Text fw={600} size={isMobile ? "sm" : "md"}>
|
||||
{branch.branch_name}
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
variant="subtle"
|
||||
size={isMobile ? "xs" : "md"}
|
||||
onClick={() => toggleRow(index)}
|
||||
leftSection={
|
||||
isOpen ? (
|
||||
<IconChevronUp size={18} />
|
||||
) : (
|
||||
<IconChevronDown size={18} />
|
||||
)
|
||||
}
|
||||
>
|
||||
{isOpen ? "Hide" : "View"}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<Collapse in={isOpen} transitionDuration={200}>
|
||||
<Box mt="md" ml="md">
|
||||
<Text size={isMobile ? "xs" : "sm"}>
|
||||
<b>Branch Code:</b> {branch.branch_code}
|
||||
</Text>
|
||||
|
||||
<Text size={isMobile ? "xs" : "sm"}>
|
||||
<b>Zone:</b> {branch.zone}
|
||||
</Text>
|
||||
|
||||
<Text size={isMobile ? "xs" : "sm"}>
|
||||
<b>Tehsil:</b> {branch.tehsil}
|
||||
</Text>
|
||||
|
||||
<Text size={isMobile ? "xs" : "sm"}>
|
||||
<b>Block:</b> {branch.block}
|
||||
</Text>
|
||||
|
||||
<Text size={isMobile ? "xs" : "sm"}>
|
||||
<b>District:</b> {branch.distt_name}
|
||||
</Text>
|
||||
|
||||
<Text size={isMobile ? "xs" : "sm"}>
|
||||
<b>Pincode:</b> {branch.pincode}
|
||||
</Text>
|
||||
|
||||
<Text size={isMobile ? "xs" : "sm"}>
|
||||
<span style={{ fontWeight: 600, color: "black" }}>
|
||||
Telephone:
|
||||
</span>{" "}
|
||||
<span style={{ color: "blue" }}>
|
||||
{branch.telephone_no}
|
||||
</span>
|
||||
</Text>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Paper>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* FOOTER */}
|
||||
<Box
|
||||
component="footer"
|
||||
style={{
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
padding: isMobile ? "8px" : "12px 0",
|
||||
background: "#ffffff",
|
||||
borderTop: "1px solid #ddd",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Text size={isMobile ? "xs" : "sm"} c="dimmed">
|
||||
© 2025 KCC Bank. All rights reserved.
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
}
|
||||
243
src/app/CustomerCare/page.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
Paper,
|
||||
Title,
|
||||
Image,
|
||||
} from "@mantine/core";
|
||||
import { IconMail, IconPhone, IconChevronLeft } from "@tabler/icons-react";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import { useRouter } from "next/navigation";
|
||||
import NextImage from "next/image";
|
||||
import logo from "@/app/image/logo1.jpg";
|
||||
|
||||
export default function EnquiryPage() {
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const router = useRouter();
|
||||
|
||||
// FAKE CONTACT LIST — replace with your API if needed
|
||||
const contacts = [
|
||||
{
|
||||
title: "Chairman",
|
||||
email: "chairman@kccb.in",
|
||||
phone: "01892-222677",
|
||||
},
|
||||
{
|
||||
title: "Managing Director",
|
||||
email: "md@kccb.in",
|
||||
phone: "01892-224960",
|
||||
},
|
||||
{
|
||||
title: "General Manager (West)",
|
||||
email: "gmw@kccb.in",
|
||||
phone: "01892-223280",
|
||||
},
|
||||
{
|
||||
title: "General Manager (North)",
|
||||
email: "gmn@kccb.in",
|
||||
phone: "01892-224607",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{/* HEADER */}
|
||||
<Box
|
||||
component="header"
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: isMobile ? "0.6rem 1rem" : "0.8rem 2rem",
|
||||
background:
|
||||
"linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
||||
color: "white",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "1rem",
|
||||
boxShadow: "0 2px 6px rgba(0,0,0,0.15)",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<IconChevronLeft
|
||||
size={28}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => router.back()}
|
||||
/>
|
||||
|
||||
<Group gap="md" wrap="nowrap" align="center">
|
||||
<Image
|
||||
src={logo}
|
||||
component={NextImage}
|
||||
fit="contain"
|
||||
alt="ebanking"
|
||||
style={{
|
||||
width: isMobile ? "45px" : "60px",
|
||||
height: "auto",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Title
|
||||
order={isMobile ? 4 : 3}
|
||||
style={{ fontFamily: "Roboto", color: "white", marginBottom: 2 }}
|
||||
>
|
||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
{!isMobile && (
|
||||
<Text size="xs" style={{ color: "white", opacity: 0.85 }}>
|
||||
Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
{/* SECOND BAR */}
|
||||
<Box
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: isMobile ? "0.5rem 1rem" : "0.6rem 1.5rem",
|
||||
background: "#ffffff",
|
||||
borderBottom: "1px solid #ddd",
|
||||
position: "sticky",
|
||||
top: isMobile ? "65px" : "70px",
|
||||
zIndex: 200,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
fw={700}
|
||||
size={isMobile ? "md" : "lg"}
|
||||
ta="center"
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
Customer Care
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size={isMobile ? "xs" : "sm"}
|
||||
onClick={() => window.close()}
|
||||
style={{ marginLeft: "auto" }}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* BODY CONTENT */}
|
||||
<Box
|
||||
style={{
|
||||
flex: 1,
|
||||
overflowY: "auto",
|
||||
padding: isMobile ? "1rem" : "2rem",
|
||||
background: "#f8f9fa",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: "1600px",
|
||||
margin: "0 auto",
|
||||
}}
|
||||
>
|
||||
{/* Complaint Form */}
|
||||
<Paper
|
||||
withBorder
|
||||
p="md"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{
|
||||
marginBottom: "1.5rem",
|
||||
background: "white",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text fw={600}>Complaint Form</Text>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
onClick={() => window.open("https://kccbhp.bank.in/complaint-form/", "_blank")}
|
||||
>
|
||||
Register Complain
|
||||
</Button>
|
||||
</Paper>
|
||||
|
||||
{/* Key Contacts Title */}
|
||||
<Text fw={700} size="lg" mb="sm">
|
||||
Key Contacts
|
||||
</Text>
|
||||
|
||||
{/* 2-COLUMN GRID */}
|
||||
<Box
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: isMobile ? "1fr" : "1fr 1fr",
|
||||
gap: "1.2rem",
|
||||
}}
|
||||
>
|
||||
{contacts.map((contact, index) => (
|
||||
<Paper
|
||||
key={index}
|
||||
withBorder
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="md"
|
||||
style={{
|
||||
background: "white",
|
||||
minHeight: "120px",
|
||||
}}
|
||||
>
|
||||
<Text fw={600} mb="xs" size="md">
|
||||
{contact.title}
|
||||
</Text>
|
||||
|
||||
<Group gap="xs" mb={6}>
|
||||
<IconMail size={18} />
|
||||
<Text size="sm">{contact.email}</Text>
|
||||
</Group>
|
||||
|
||||
<Group gap="xs">
|
||||
<IconPhone size={18} />
|
||||
<Text size="sm">{contact.phone}</Text>
|
||||
</Group>
|
||||
</Paper>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* FOOTER */}
|
||||
<Box
|
||||
component="footer"
|
||||
style={{
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
padding: isMobile ? "8px" : "12px 0",
|
||||
background: "#ffffff",
|
||||
borderTop: "1px solid #ddd",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Text size={isMobile ? "xs" : "sm"} c="dimmed">
|
||||
© 2025 KCC Bank. All rights reserved.
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
243
src/app/FAQs/page.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
Paper,
|
||||
Collapse,
|
||||
Title,
|
||||
Image,
|
||||
} from "@mantine/core";
|
||||
import { IconChevronLeft, IconChevronDown, IconChevronUp } from "@tabler/icons-react";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import { useRouter } from "next/navigation";
|
||||
import NextImage from "next/image";
|
||||
import logo from "@/app/image/logo1.jpg";
|
||||
|
||||
export default function FAQPage() {
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const router = useRouter();
|
||||
const [openedIndex, setOpenedIndex] = useState<number | null>(null);
|
||||
|
||||
const toggleRow = (index: number) => {
|
||||
setOpenedIndex((prev) => (prev === index ? null : index));
|
||||
};
|
||||
|
||||
// FAQ DATA
|
||||
const faqs = [
|
||||
{
|
||||
question: "How do I log in to the Internet Banking portal?",
|
||||
answer:
|
||||
"Open the login page and enter your User ID and Password. You may also be required to verify using an OTP sent to your registered mobile number.",
|
||||
},
|
||||
{
|
||||
question: "Is my banking information secure on this app?",
|
||||
answer:
|
||||
"Yes. All transactions are fully encrypted. We use multi-factor authentication, secure session management, and industry-standard protection mechanisms.",
|
||||
},
|
||||
{
|
||||
question: "How can I check my account balance?",
|
||||
answer:
|
||||
"Go to the Accounts section after logging in. All your linked accounts and available balances will be displayed instantly.",
|
||||
},
|
||||
{
|
||||
question: "Can I transfer money to other bank accounts?",
|
||||
answer:
|
||||
"Yes. You can transfer funds using NEFT, RTGS, IMPS, or within-bank transfers. Add a beneficiary before initiating a transfer.",
|
||||
},
|
||||
{
|
||||
question: "How do I view my transaction history?",
|
||||
answer:
|
||||
"Open the 'Account Statement' or 'Transactions' tab. You can filter by date, download a PDF.",
|
||||
},
|
||||
// {
|
||||
// question: "What should I do if I forget my Internet Banking password?",
|
||||
// answer:
|
||||
// "Use the 'Forgot Password' option on the login page and follow the steps to reset your password using OTP verification.",
|
||||
// },
|
||||
{
|
||||
question: "How do I update my mobile number or email ID?",
|
||||
answer:
|
||||
"You can update your contact details by visiting your nearest branch.",
|
||||
},
|
||||
{
|
||||
question: "What should I do if my account gets locked?",
|
||||
answer:
|
||||
"Your account may lock after multiple incorrect login attempts. To unlock your Internet Banking access, please visit your nearest KCC Bank branch or contact customer care.",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{/* HEADER */}
|
||||
<Box
|
||||
component="header"
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: isMobile ? "0.6rem 1rem" : "0.8rem 2rem",
|
||||
background:
|
||||
"linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
||||
color: "white",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "1rem",
|
||||
boxShadow: "0 2px 6px rgba(0,0,0,0.15)",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<IconChevronLeft
|
||||
size={28}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => router.back()}
|
||||
/>
|
||||
|
||||
<Group gap="md" wrap="nowrap" align="center">
|
||||
<Image
|
||||
src={logo}
|
||||
component={NextImage}
|
||||
fit="contain"
|
||||
alt="ebanking"
|
||||
style={{
|
||||
width: isMobile ? "45px" : "60px",
|
||||
height: "auto",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Title
|
||||
order={isMobile ? 4 : 3}
|
||||
style={{ fontFamily: "Roboto", color: "white", marginBottom: 2 }}
|
||||
>
|
||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
{!isMobile && (
|
||||
<Text size="xs" style={{ color: "white", opacity: 0.85 }}>
|
||||
Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
{/* SECOND BAR */}
|
||||
<Box
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: isMobile ? "0.5rem 1rem" : "0.6rem 1.5rem",
|
||||
background: "#ffffff",
|
||||
borderBottom: "1px solid #ddd",
|
||||
position: "sticky",
|
||||
top: isMobile ? "65px" : "70px",
|
||||
zIndex: 200,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
fw={700}
|
||||
size={isMobile ? "md" : "lg"}
|
||||
ta="center"
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
Frequently Asked Questions
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size={isMobile ? "xs" : "sm"}
|
||||
onClick={() => window.close()}
|
||||
style={{ marginLeft: "auto" }}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* BODY CONTENT */}
|
||||
<Box
|
||||
style={{
|
||||
flex: 1,
|
||||
overflowY: "auto",
|
||||
padding: isMobile ? "1rem" : "2rem",
|
||||
background: "#f8f9fa",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: "900px",
|
||||
margin: "0 auto",
|
||||
}}
|
||||
>
|
||||
<Stack>
|
||||
{faqs.map((item, index) => {
|
||||
const isOpen = openedIndex === index;
|
||||
|
||||
return (
|
||||
<Paper
|
||||
key={index}
|
||||
withBorder
|
||||
radius="md"
|
||||
p="md"
|
||||
shadow="xs"
|
||||
style={{ cursor: "pointer", background: "white" }}
|
||||
>
|
||||
{/* QUESTION */}
|
||||
<Group
|
||||
justify="space-between"
|
||||
onClick={() => toggleRow(index)}
|
||||
>
|
||||
<Text fw={600} size={isMobile ? "sm" : "md"}>
|
||||
{item.question}
|
||||
</Text>
|
||||
|
||||
{isOpen ? (
|
||||
<IconChevronUp size={20} />
|
||||
) : (
|
||||
<IconChevronDown size={20} />
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{/* ANSWER */}
|
||||
<Collapse in={isOpen}>
|
||||
<Text size={isMobile ? "xs" : "sm"} mt="sm" c="dimmed">
|
||||
{item.answer}
|
||||
</Text>
|
||||
</Collapse>
|
||||
</Paper>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* FOOTER */}
|
||||
<Box
|
||||
component="footer"
|
||||
style={{
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
padding: isMobile ? "8px" : "12px 0",
|
||||
background: "#ffffff",
|
||||
borderTop: "1px solid #ddd",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Text size={isMobile ? "xs" : "sm"} c="dimmed">
|
||||
© 2025 KCC Bank. All rights reserved.
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -396,7 +396,7 @@ export default function SetLoginPwd() {
|
||||
}}
|
||||
>
|
||||
<Text c="dimmed" size="xs">
|
||||
© 2025 The Kangra Central Co-Operative Bank
|
||||
© 2025 The Kangra Central Co-Operative Bank Ltd.
|
||||
</Text>
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
@@ -1,60 +1,619 @@
|
||||
"use client";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
interface CsvGeneratorProps {
|
||||
accountNo: string;
|
||||
balance: string;
|
||||
txns: any[];
|
||||
}
|
||||
|
||||
export const generateCSV = (
|
||||
export const generateExcel = (
|
||||
accountNo: string,
|
||||
balance: string,
|
||||
txns: any[]
|
||||
txns: any[],
|
||||
customerName: string,
|
||||
periodFrom: string,
|
||||
periodTo: string,
|
||||
branchCode: string,
|
||||
cifNumber: string,
|
||||
address: string
|
||||
) => {
|
||||
// CSV Header
|
||||
let csv = `Bank Statement\n`;
|
||||
csv += `Account No:,${accountNo}\n`;
|
||||
csv += `Available Balance:,${balance}\n`;
|
||||
csv += `Generated:,${new Date().toLocaleString()}\n\n`;
|
||||
// Import ExcelJS dynamically
|
||||
const ExcelJS = require("exceljs");
|
||||
|
||||
// Column headers
|
||||
csv += "Date,Name,Type,Amount\n";
|
||||
|
||||
// Rows
|
||||
txns.forEach((txn) => {
|
||||
csv += `${txn.date},${txn.name},${txn.type.toLowerCase()},${txn.amount}\n`;
|
||||
// Create a new workbook and worksheet
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet("Account Statement", {
|
||||
pageSetup: {
|
||||
paperSize: 9, // A4
|
||||
orientation: "portrait",
|
||||
fitToPage: true,
|
||||
fitToWidth: 1,
|
||||
margins: {
|
||||
left: 0.5,
|
||||
right: 0.5,
|
||||
top: 0.75,
|
||||
bottom: 0.75,
|
||||
header: 0.3,
|
||||
footer: 0.3,
|
||||
},
|
||||
},
|
||||
views: [{ showGridLines: false }],
|
||||
});
|
||||
|
||||
// Trigger browser download
|
||||
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
let currentRow = 1;
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.setAttribute("href", url);
|
||||
link.setAttribute("download", `statement_${accountNo}.csv`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
/* ---------------------------------------------------------
|
||||
* 1) HEADER SECTION
|
||||
* --------------------------------------------------------- */
|
||||
|
||||
// Bank Name
|
||||
const headerRow = worksheet.getRow(currentRow);
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
const headerCell = worksheet.getCell(`A${currentRow}`);
|
||||
headerCell.value = "THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.";
|
||||
headerCell.font = {
|
||||
name: "Times New Roman",
|
||||
size: 16,
|
||||
bold: true,
|
||||
color: { argb: "FF1a5f3a" },
|
||||
};
|
||||
headerCell.alignment = { vertical: "middle", horizontal: "center" };
|
||||
headerRow.height = 25;
|
||||
currentRow++;
|
||||
|
||||
// Bank Address
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
const addressCell = worksheet.getCell(`A${currentRow}`);
|
||||
addressCell.value = "Head Office: Dharmsala, District Kangra (H.P.), Pin. 176215";
|
||||
addressCell.font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
color: { argb: "FF666666" },
|
||||
};
|
||||
addressCell.alignment = { vertical: "middle", horizontal: "center" };
|
||||
currentRow++;
|
||||
|
||||
// e-Statement Service & Date
|
||||
worksheet.mergeCells(`A${currentRow}:B${currentRow}`);
|
||||
const eStatementCell = worksheet.getCell(`A${currentRow}`);
|
||||
eStatementCell.value = "e-Statement Service";
|
||||
eStatementCell.font = {
|
||||
name: "Times New Roman",
|
||||
size: 10,
|
||||
bold: true,
|
||||
color: { argb: "FF1a5f3a" },
|
||||
};
|
||||
eStatementCell.alignment = { vertical: "middle", horizontal: "left" };
|
||||
|
||||
worksheet.mergeCells(`C${currentRow}:D${currentRow}`);
|
||||
const dateCell = worksheet.getCell(`C${currentRow}`);
|
||||
dateCell.value = `Generated: ${dayjs().format("DD/MM/YYYY HH:mm")}`;
|
||||
dateCell.font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
color: { argb: "FF666666" },
|
||||
};
|
||||
dateCell.alignment = { vertical: "middle", horizontal: "right" };
|
||||
currentRow++;
|
||||
|
||||
// Border line
|
||||
currentRow++;
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* 2) ACCOUNT DETAILS SECTION
|
||||
* --------------------------------------------------------- */
|
||||
|
||||
// Account Details Header
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
const detailsHeaderCell = worksheet.getCell(`A${currentRow}`);
|
||||
detailsHeaderCell.value = "ACCOUNT DETAILS";
|
||||
detailsHeaderCell.font = {
|
||||
name: "Times New Roman",
|
||||
size: 11,
|
||||
bold: true,
|
||||
color: { argb: "FF1a5f3a" },
|
||||
};
|
||||
detailsHeaderCell.fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
detailsHeaderCell.alignment = { vertical: "middle", horizontal: "center" };
|
||||
detailsHeaderCell.border = {
|
||||
top: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
left: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
right: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
};
|
||||
currentRow++;
|
||||
|
||||
// Account Name
|
||||
worksheet.getCell(`A${currentRow}`).value = "Account Name";
|
||||
worksheet.getCell(`A${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
color: { argb: "FF666666" },
|
||||
};
|
||||
worksheet.getCell(`A${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
worksheet.getCell(`B${currentRow}`).value = customerName;
|
||||
worksheet.getCell(`B${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 10,
|
||||
bold: true,
|
||||
};
|
||||
worksheet.getCell(`B${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
|
||||
worksheet.getCell(`C${currentRow}`).value = "Branch Name";
|
||||
worksheet.getCell(`C${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
color: { argb: "FF666666" },
|
||||
};
|
||||
worksheet.getCell(`C${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
worksheet.getCell(`D${currentRow}`).value = branchCode;
|
||||
worksheet.getCell(`D${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 10,
|
||||
bold: true,
|
||||
};
|
||||
worksheet.getCell(`D${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
|
||||
// Apply borders
|
||||
["A", "B", "C", "D"].forEach((col) => {
|
||||
worksheet.getCell(`${col}${currentRow}`).border = {
|
||||
top: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
left: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
right: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
};
|
||||
});
|
||||
currentRow++;
|
||||
|
||||
// Account Number
|
||||
worksheet.getCell(`A${currentRow}`).value = "Account Number";
|
||||
worksheet.getCell(`A${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
color: { argb: "FF666666" },
|
||||
};
|
||||
worksheet.getCell(`A${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
worksheet.getCell(`B${currentRow}`).value = accountNo;
|
||||
worksheet.getCell(`B${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 10,
|
||||
bold: true,
|
||||
};
|
||||
worksheet.getCell(`B${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
|
||||
worksheet.getCell(`C${currentRow}`).value = "Branch Code";
|
||||
worksheet.getCell(`C${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
color: { argb: "FF666666" },
|
||||
};
|
||||
worksheet.getCell(`C${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
worksheet.getCell(`D${currentRow}`).value = branchCode;
|
||||
worksheet.getCell(`D${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 10,
|
||||
bold: true,
|
||||
};
|
||||
worksheet.getCell(`D${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
|
||||
["A", "B", "C", "D"].forEach((col) => {
|
||||
worksheet.getCell(`${col}${currentRow}`).border = {
|
||||
top: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
left: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
right: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
};
|
||||
});
|
||||
currentRow++;
|
||||
|
||||
// CIF Number
|
||||
worksheet.getCell(`A${currentRow}`).value = "CIF Number";
|
||||
worksheet.getCell(`A${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
color: { argb: "FF666666" },
|
||||
};
|
||||
worksheet.getCell(`A${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
worksheet.getCell(`B${currentRow}`).value = cifNumber;
|
||||
worksheet.getCell(`B${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 10,
|
||||
bold: true,
|
||||
};
|
||||
worksheet.getCell(`B${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
|
||||
worksheet.getCell(`C${currentRow}`).value = "Branch Address";
|
||||
worksheet.getCell(`C${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
color: { argb: "FF666666" },
|
||||
};
|
||||
worksheet.getCell(`C${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
worksheet.getCell(`D${currentRow}`).value = "DHARAMSHALA, KANGRA";
|
||||
worksheet.getCell(`D${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 10,
|
||||
bold: true,
|
||||
};
|
||||
worksheet.getCell(`D${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
|
||||
["A", "B", "C", "D"].forEach((col) => {
|
||||
worksheet.getCell(`${col}${currentRow}`).border = {
|
||||
top: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
left: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
right: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
};
|
||||
});
|
||||
currentRow++;
|
||||
|
||||
// Address
|
||||
worksheet.getCell(`A${currentRow}`).value = "Address";
|
||||
worksheet.getCell(`A${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
color: { argb: "FF666666" },
|
||||
};
|
||||
worksheet.getCell(`A${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
worksheet.mergeCells(`B${currentRow}:D${currentRow}`);
|
||||
worksheet.getCell(`B${currentRow}`).value = address;
|
||||
worksheet.getCell(`B${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 10,
|
||||
bold: true,
|
||||
};
|
||||
worksheet.getCell(`B${currentRow}`).fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF9F9F9" },
|
||||
};
|
||||
|
||||
["A", "B", "C", "D"].forEach((col) => {
|
||||
worksheet.getCell(`${col}${currentRow}`).border = {
|
||||
top: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
left: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
right: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
};
|
||||
});
|
||||
currentRow++;
|
||||
|
||||
currentRow++; // Empty row
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* 3) WARNING SECTION
|
||||
* --------------------------------------------------------- */
|
||||
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
const warningCell = worksheet.getCell(`A${currentRow}`);
|
||||
warningCell.value =
|
||||
"⚠ NEVER SHARE your Card number, CVV, PIN, OTP, Internet Banking User ID, Password or URB with anyone even if the caller claims to be a bank employee. Sharing these details can lead to unauthorized access to your account.";
|
||||
warningCell.font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
color: { argb: "FF721c24" },
|
||||
};
|
||||
warningCell.fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF8D7DA" },
|
||||
};
|
||||
warningCell.border = {
|
||||
top: { style: "thin", color: { argb: "FFF5C6CB" } },
|
||||
bottom: { style: "thin", color: { argb: "FFF5C6CB" } },
|
||||
left: { style: "medium", color: { argb: "FFD32F2F" } },
|
||||
right: { style: "thin", color: { argb: "FFF5C6CB" } },
|
||||
};
|
||||
warningCell.alignment = { vertical: "middle", horizontal: "left", wrapText: true };
|
||||
worksheet.getRow(currentRow).height = 40;
|
||||
currentRow++;
|
||||
|
||||
currentRow++; // Empty row
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* 4) STATEMENT PERIOD
|
||||
* --------------------------------------------------------- */
|
||||
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
const periodCell = worksheet.getCell(`A${currentRow}`);
|
||||
periodCell.value = `Account statement from ${periodFrom} to ${periodTo}`;
|
||||
periodCell.font = {
|
||||
name: "Times New Roman",
|
||||
size: 11,
|
||||
bold: true,
|
||||
color: { argb: "FF1a5f3a" },
|
||||
};
|
||||
periodCell.fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FFF5F5F5" },
|
||||
};
|
||||
periodCell.alignment = { vertical: "middle", horizontal: "center" };
|
||||
periodCell.border = {
|
||||
top: { style: "medium", color: { argb: "FF1a5f3a" } },
|
||||
bottom: { style: "medium", color: { argb: "FF1a5f3a" } },
|
||||
};
|
||||
worksheet.getRow(currentRow).height = 22;
|
||||
currentRow++;
|
||||
|
||||
currentRow++; // Empty row
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* 5) TRANSACTION TABLE
|
||||
* --------------------------------------------------------- */
|
||||
|
||||
// Table Headers
|
||||
const headerRowNum = currentRow;
|
||||
worksheet.getCell(`A${currentRow}`).value = "Date";
|
||||
worksheet.getCell(`B${currentRow}`).value = "Mode / Particulars";
|
||||
worksheet.getCell(`C${currentRow}`).value = "Withdrawals / Deposits";
|
||||
worksheet.getCell(`D${currentRow}`).value = "Balance";
|
||||
|
||||
["A", "B", "C", "D"].forEach((col) => {
|
||||
const cell = worksheet.getCell(`${col}${currentRow}`);
|
||||
cell.font = {
|
||||
name: "Times New Roman",
|
||||
size: 10,
|
||||
bold: true,
|
||||
color: { argb: "FFFFFFFF" },
|
||||
};
|
||||
cell.fill = {
|
||||
type: "pattern",
|
||||
pattern: "solid",
|
||||
fgColor: { argb: "FF2e7d32" },
|
||||
};
|
||||
cell.alignment = { vertical: "middle", horizontal: "center" };
|
||||
cell.border = {
|
||||
top: { style: "thin", color: { argb: "FF1a5f3a" } },
|
||||
bottom: { style: "thin", color: { argb: "FF1a5f3a" } },
|
||||
left: { style: "thin", color: { argb: "FF1a5f3a" } },
|
||||
right: { style: "thin", color: { argb: "FF1a5f3a" } },
|
||||
};
|
||||
});
|
||||
worksheet.getRow(currentRow).height = 20;
|
||||
currentRow++;
|
||||
|
||||
// Transaction Rows
|
||||
txns.forEach((t: any) => {
|
||||
worksheet.getCell(`A${currentRow}`).value = t.date;
|
||||
worksheet.getCell(`A${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
};
|
||||
worksheet.getCell(`A${currentRow}`).alignment = {
|
||||
vertical: "middle",
|
||||
horizontal: "center",
|
||||
};
|
||||
worksheet.getCell(`A${currentRow}`).border = {
|
||||
top: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
left: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
right: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
};
|
||||
|
||||
worksheet.getCell(`B${currentRow}`).value = t.name;
|
||||
worksheet.getCell(`B${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
};
|
||||
worksheet.getCell(`B${currentRow}`).alignment = {
|
||||
vertical: "middle",
|
||||
horizontal: "left",
|
||||
};
|
||||
worksheet.getCell(`B${currentRow}`).border = {
|
||||
top: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
left: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
right: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
};
|
||||
|
||||
const amount = parseFloat(t.amount).toLocaleString("en-IN", {
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
worksheet.getCell(`C${currentRow}`).value = `${amount} ${t.type}`;
|
||||
worksheet.getCell(`C${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
bold: true,
|
||||
color: { argb: t.type === "DR" ? "FFD32F2F" : "FF2E7D32" },
|
||||
};
|
||||
worksheet.getCell(`C${currentRow}`).alignment = {
|
||||
vertical: "middle",
|
||||
horizontal: "right",
|
||||
};
|
||||
worksheet.getCell(`C${currentRow}`).border = {
|
||||
top: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
left: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
right: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
};
|
||||
|
||||
worksheet.getCell(`D${currentRow}`).value = t.balance;
|
||||
worksheet.getCell(`D${currentRow}`).font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
bold: true,
|
||||
};
|
||||
worksheet.getCell(`D${currentRow}`).alignment = {
|
||||
vertical: "middle",
|
||||
horizontal: "right",
|
||||
};
|
||||
worksheet.getCell(`D${currentRow}`).border = {
|
||||
top: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
left: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
right: { style: "thin", color: { argb: "FFD0D0D0" } },
|
||||
};
|
||||
|
||||
currentRow++;
|
||||
});
|
||||
|
||||
currentRow++; // Empty row
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* 6) END OF STATEMENT
|
||||
* --------------------------------------------------------- */
|
||||
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
const endCell = worksheet.getCell(`A${currentRow}`);
|
||||
endCell.value = "*** END OF STATEMENT ***";
|
||||
endCell.font = {
|
||||
name: "Times New Roman",
|
||||
size: 11,
|
||||
bold: true,
|
||||
color: { argb: "FF1a5f3a" },
|
||||
};
|
||||
endCell.alignment = { vertical: "middle", horizontal: "center" };
|
||||
endCell.border = {
|
||||
top: { style: "medium", color: { argb: "FF1a5f3a" } },
|
||||
};
|
||||
currentRow++;
|
||||
|
||||
currentRow += 2; // Empty rows
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* 7) IMPORTANT INFORMATION
|
||||
* --------------------------------------------------------- */
|
||||
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
const infoHeaderCell = worksheet.getCell(`A${currentRow}`);
|
||||
infoHeaderCell.value = "IMPORTANT INFORMATION:";
|
||||
infoHeaderCell.font = {
|
||||
name: "Times New Roman",
|
||||
size: 11,
|
||||
bold: true,
|
||||
color: { argb: "FF1a5f3a" },
|
||||
};
|
||||
infoHeaderCell.alignment = { vertical: "middle", horizontal: "left" };
|
||||
infoHeaderCell.border = {
|
||||
bottom: { style: "medium", color: { argb: "FF1a5f3a" } },
|
||||
};
|
||||
currentRow++;
|
||||
|
||||
const importantPoints = [
|
||||
"The Kangra Central Cooperative Bank Officials or representatives will NEVER ask you for your personal information i.e. your card details, passwords, PIN, CVV, OTP etc. Do not share such details with anyone over phone, SMS or email.",
|
||||
"Always stay vigilant to suspicious emails. Do not open any suspicious emails.",
|
||||
"Always stay vigilant when giving out sensitive personal or account information.",
|
||||
"Beware of messages that instill a sense of urgency (e.g., account will expire unless you \"verify\" your information). Contact the Bank directly if unsure.",
|
||||
"Always log out of secondary devices and reset your passwords frequently.",
|
||||
"Use strong passwords: Create strong passwords that are difficult for hackers to guess.",
|
||||
"Use public Wi-Fi with caution: Be careful when using public Wi-Fi networks.",
|
||||
"Back up your data regularly to a secure, encrypted, off-site location.",
|
||||
"Follow corporate security policies: Adhere to your company's security guidelines.",
|
||||
"Assess third-party app permissions carefully before granting access.",
|
||||
"Lock your computer and mobile phone when not in use.",
|
||||
"Don't leave devices unattended. Keep all mobile devices, such as laptops and cell phones, physically secured.",
|
||||
"Don't leave Bluetooth / Wireless turned on when not in use. Enable them only when needed and in a safe environment.",
|
||||
];
|
||||
|
||||
importantPoints.forEach((point) => {
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
const pointCell = worksheet.getCell(`A${currentRow}`);
|
||||
pointCell.value = `• ${point}`;
|
||||
pointCell.font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
color: { argb: "FF333333" },
|
||||
};
|
||||
pointCell.alignment = {
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
wrapText: true,
|
||||
};
|
||||
worksheet.getRow(currentRow).height = 25;
|
||||
currentRow++;
|
||||
});
|
||||
|
||||
currentRow += 2; // Empty rows
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* 8) FOOTER NOTE
|
||||
* --------------------------------------------------------- */
|
||||
|
||||
worksheet.mergeCells(`A${currentRow}:D${currentRow}`);
|
||||
const footerCell = worksheet.getCell(`A${currentRow}`);
|
||||
footerCell.value = "** This is only for information purpose and not for legal use **";
|
||||
footerCell.font = {
|
||||
name: "Times New Roman",
|
||||
size: 9,
|
||||
italic: true,
|
||||
color: { argb: "FF666666" },
|
||||
};
|
||||
footerCell.alignment = { vertical: "middle", horizontal: "center" };
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* 9) COLUMN WIDTHS
|
||||
* --------------------------------------------------------- */
|
||||
|
||||
worksheet.getColumn(1).width = 15; // Date
|
||||
worksheet.getColumn(2).width = 45; // Particulars
|
||||
worksheet.getColumn(3).width = 25; // Withdrawals/Deposits
|
||||
worksheet.getColumn(4).width = 20; // Balance
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* 10) SAVE FILE
|
||||
* --------------------------------------------------------- */
|
||||
|
||||
workbook.xlsx.writeBuffer().then((buffer: any) => {
|
||||
const blob = new Blob([buffer], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `Statement_${accountNo}_${dayjs().format("DDMMYYYY_HHmm")}.xlsx`;
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
});
|
||||
};
|
||||
|
||||
export default function CsvGenerator({
|
||||
accountNo,
|
||||
balance,
|
||||
txns,
|
||||
}: CsvGeneratorProps) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => generateCSV(accountNo, balance, txns)}
|
||||
style={{
|
||||
padding: "6px 12px",
|
||||
borderRadius: "6px",
|
||||
background: "#02a355",
|
||||
color: "white",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Download CSV
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,107 +7,369 @@ export const generatePDF = (
|
||||
txns: any[],
|
||||
customerName: string,
|
||||
periodFrom: string,
|
||||
periodTo: string
|
||||
periodTo: string,
|
||||
// branchName: string,
|
||||
branchCode: string,
|
||||
cifNumber: string,
|
||||
address: string
|
||||
) => {
|
||||
const html2pdf = require("html2pdf.js");
|
||||
|
||||
// Build rows
|
||||
const rows = txns.map(
|
||||
(t: any) => `
|
||||
<tr style="page-break-inside: avoid;">
|
||||
<td style="border:1px solid #ccc;padding:6px;text-align:center;">${t.date}</td>
|
||||
<td style="border:1px solid #ccc;padding:6px;text-align:left;">${t.name}</td>
|
||||
<td style="border:1px solid #ccc;padding:6px;text-align:right;color:${t.type === "DR" ? "red" : "green"
|
||||
}">
|
||||
${parseFloat(t.amount).toLocaleString("en-IN", {
|
||||
minimumFractionDigits: 2,
|
||||
})} ${t.type === "DR" ? "dr." : "cr."}
|
||||
</td>
|
||||
<td style="border:1px solid #ccc; padding:6px; text-align:right; color: blue;">${t.balance}</td>
|
||||
</tr>
|
||||
`
|
||||
);
|
||||
/* ---------------------------------------------------------
|
||||
* 1) BUILD TRANSACTION ROWS
|
||||
* --------------------------------------------------------- */
|
||||
|
||||
// Content for first page
|
||||
const content = `
|
||||
<div style="font-family:Arial, sans-serif;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<img src="/logo.jpg" alt="Bank Logo" style="height:50px;" />
|
||||
<h2 style="margin:0;">The Kangra Central Co Operative Bank</h2>
|
||||
</div>
|
||||
<div style="font-size:12px;color:#555;">
|
||||
Report generated: ${dayjs().format("DD/MM/YYYY HH:mm")}
|
||||
const rows = txns
|
||||
.map((t: any) => {
|
||||
return `
|
||||
<tr class="txn-row">
|
||||
<td style="border:1px solid #d0d0d0;padding:8px 10px;text-align:center;font-size:11px;">${t.date}</td>
|
||||
<td style="border:1px solid #d0d0d0;padding:8px 10px;text-align:left;font-size:11px;">${t.name}</td>
|
||||
<td style="border:1px solid #d0d0d0;padding:8px 10px;text-align:right;font-size:11px;color:${t.type === "DR" ? "#d32f2f" : "#2e7d32"};font-weight:500;">
|
||||
${parseFloat(t.amount).toLocaleString("en-IN", {
|
||||
minimumFractionDigits: 2,
|
||||
})} ${t.type}
|
||||
</td>
|
||||
<td style="border:1px solid #d0d0d0;padding:8px 10px;text-align:right;font-size:11px;font-weight:500;">${t.balance}</td>
|
||||
</tr>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* 2) MAIN PAGE CONTENT
|
||||
* --------------------------------------------------------- */
|
||||
const firstPage = `
|
||||
<div style="font-family:'Times New Roman', serif; font-size:12px;line-height:1.5;">
|
||||
|
||||
<style>
|
||||
@page {
|
||||
margin: 15mm 10mm 20mm 10mm;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.txn-row {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
table {
|
||||
page-break-inside: auto;
|
||||
}
|
||||
tr {
|
||||
page-break-inside: avoid;
|
||||
page-break-after: auto;
|
||||
}
|
||||
thead {
|
||||
display: table-header-group;
|
||||
}
|
||||
tfoot {
|
||||
display: table-footer-group;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div style="
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:12px;
|
||||
border-bottom:2px solid #1a5f3a;
|
||||
padding-bottom:12px;
|
||||
margin-bottom:15px;
|
||||
">
|
||||
<img src="/logo.jpg" style="height:55px;width:auto;" />
|
||||
<div style="flex:1;">
|
||||
<h2 style="margin:0 0 3px 0;font-size:18px;color:#1a5f3a;letter-spacing:0.3px;">
|
||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||
</h2>
|
||||
<p style="margin:0;font-size:10px;color:#666;">Head Office: Dharmsala, District Kangra (H.P.), Pin. 176215</p>
|
||||
</div>
|
||||
<div style="text-align:right;">
|
||||
<div style="font-size:11px;font-weight:bold;color:#1a5f3a;margin-bottom:2px;">e-Statement Service</div>
|
||||
<div style="font-size:9px;color:#666;">
|
||||
Generated: ${dayjs().format("DD/MM/YYYY HH:mm")}
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
|
||||
<h3 style="text-align:center;">Account Statement</h3>
|
||||
<p><strong>Account No:</strong> ${accountNo}</p>
|
||||
<p><strong>Account Holder:</strong> ${customerName}</p>
|
||||
<p><strong>Statement Period:</strong> ${periodFrom} to ${periodTo}</p>
|
||||
<p><strong>Available Balance:</strong> ₹ ${parseFloat(balance).toLocaleString(
|
||||
"en-IN",
|
||||
{ minimumFractionDigits: 2 }
|
||||
)}</p>
|
||||
|
||||
<table style="width:100%;border-collapse:collapse;font-size:12px;margin-top:10px;page-break-inside:auto;">
|
||||
<thead style="background:#f0f0f0;">
|
||||
<tr>
|
||||
<th style="border:1px solid #ccc;padding:6px;text-align:center;">Date</th>
|
||||
<th style="border:1px solid #ccc;padding:6px;text-align:left;">Description</th>
|
||||
<th style="border:1px solid #ccc;padding:6px;text-align:right;">Amount (₹)</th>
|
||||
<th style="border:1px solid #ccc;padding:6px;text-align:right;">Available Amount (₹)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${rows.join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ACCOUNT DETAILS -->
|
||||
<table style="
|
||||
width:100%;
|
||||
margin-bottom:12px;
|
||||
border:1px solid #d0d0d0;
|
||||
background:#f9f9f9;
|
||||
border-collapse:collapse;
|
||||
">
|
||||
<tr>
|
||||
<td style="padding:8px 12px;border-bottom:1px solid #e0e0e0;width:50%;">
|
||||
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">Account Name</span>
|
||||
<strong style="font-size:11px;color:#000;">${customerName}</strong>
|
||||
</td>
|
||||
<td style="padding:8px 12px;border-bottom:1px solid #e0e0e0;border-left:1px solid #e0e0e0;width:50%;">
|
||||
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">Branch Name</span>
|
||||
<strong style="font-size:11px;color:#000;">${branchCode}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 12px;border-bottom:1px solid #e0e0e0;">
|
||||
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">Account Number</span>
|
||||
<strong style="font-size:11px;color:#000;">${accountNo}</strong>
|
||||
</td>
|
||||
<td style="padding:8px 12px;border-bottom:1px solid #e0e0e0;border-left:1px solid #e0e0e0;">
|
||||
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">Branch Code</span>
|
||||
<strong style="font-size:11px;color:#000;">${branchCode}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 12px;border-bottom:1px solid #e0e0e0;">
|
||||
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">CIF Number</span>
|
||||
<strong style="font-size:11px;color:#000;">${cifNumber}</strong>
|
||||
</td>
|
||||
<td style="padding:8px 12px;border-bottom:1px solid #e0e0e0;border-left:1px solid #e0e0e0;">
|
||||
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">Branch Address</span>
|
||||
<strong style="font-size:11px;color:#000;">DHARAMSHALA, KANGRA</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" style="padding:8px 12px;">
|
||||
<span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">Address</span>
|
||||
<strong style="font-size:11px;color:#000;">${address}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- RED WARNING -->
|
||||
<div style="
|
||||
margin-bottom:15px;
|
||||
border:1px solid #f5c6cb;
|
||||
background:#f8d7da;
|
||||
padding:10px 12px;
|
||||
border-radius:4px;
|
||||
border-left:4px solid #d32f2f;
|
||||
">
|
||||
<div style="display:flex;gap:10px;align-items:flex-start;">
|
||||
<div style="font-size:16px;color:#d32f2f;line-height:1;margin-top:1px;">⚠</div>
|
||||
<div style="font-size:10px;line-height:1.5;color:#721c24;">
|
||||
<strong>NEVER SHARE</strong> your Card number, CVV, PIN, OTP, Internet Banking User ID, Password or URB with
|
||||
anyone even if the caller claims to be a bank employee. Sharing these details can lead to unauthorized
|
||||
access to your account.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PERIOD -->
|
||||
<div style="
|
||||
text-align:center;
|
||||
margin:15px 0 12px 0;
|
||||
padding:10px;
|
||||
background:#f5f5f5;
|
||||
border-top:2px solid #1a5f3a;
|
||||
border-bottom:2px solid #1a5f3a;
|
||||
">
|
||||
<h3 style="margin:0;font-size:13px;color:#1a5f3a;font-weight:600;">
|
||||
Account statement from <strong>${periodFrom}</strong> to <strong>${periodTo}</strong>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<!-- TABLE -->
|
||||
<table style="
|
||||
width:100%;
|
||||
border-collapse:collapse;
|
||||
font-size:11px;
|
||||
margin-bottom:15px;
|
||||
border:1px solid #d0d0d0;
|
||||
">
|
||||
|
||||
<thead style="display:table-header-group;">
|
||||
<tr style="background:#2e7d32;color:white;">
|
||||
<th style="border:1px solid #1a5f3a;padding:10px 8px;text-align:center;font-weight:600;font-size:11px;width:15%;">Date</th>
|
||||
<th style="border:1px solid #1a5f3a;padding:10px 8px;text-align:left;font-weight:600;font-size:11px;width:40%;">Mode / Particulars</th>
|
||||
<th style="border:1px solid #1a5f3a;padding:10px 8px;text-align:center;font-weight:600;font-size:11px;width:22.5%;">Withdrawals / Deposits</th>
|
||||
<th style="border:1px solid #1a5f3a;padding:10px 8px;text-align:center;font-weight:600;font-size:11px;width:22.5%;">Balance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody style="background:white;">${rows}</tbody>
|
||||
</table>
|
||||
|
||||
<!-- END OF STATEMENT -->
|
||||
<div style="
|
||||
text-align:center;
|
||||
margin-top:20px;
|
||||
padding:15px;
|
||||
border-top:2px solid #1a5f3a;
|
||||
page-break-before:auto;
|
||||
page-break-after:always;
|
||||
">
|
||||
<p style="font-weight:bold;font-size:12px;color:#1a5f3a;margin:0;letter-spacing:1px;">
|
||||
*** END OF STATEMENT ***
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* 3) LAST PAGE
|
||||
* --------------------------------------------------------- */
|
||||
const lastPage = `
|
||||
<div style="font-family:'Times New Roman', serif; font-size:11px;line-height:1.6;margin-top:0;padding-top:20px;">
|
||||
<div style="
|
||||
border:2px solid #1a5f3a;
|
||||
padding:20px 25px;
|
||||
margin:0 20px;
|
||||
background:#fafafa;
|
||||
border-radius:4px;
|
||||
">
|
||||
<h3 style="
|
||||
font-weight:bold;
|
||||
margin:0 0 15px 0;
|
||||
font-size:14px;
|
||||
color:#1a5f3a;
|
||||
border-bottom:2px solid #1a5f3a;
|
||||
padding-bottom:8px;
|
||||
">IMPORTANT INFORMATION:</h3>
|
||||
|
||||
<div style="color:#333;">
|
||||
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
|
||||
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
|
||||
The Kangra Central Cooperative Bank Officials or representatives will NEVER ask you for your personal information i.e. your card details, passwords, PIN, CVV, OTP etc. Do not share such details with anyone over phone, SMS or email.
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
|
||||
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
|
||||
Always stay vigilant to suspicious emails. Do not open any suspicious emails.
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
|
||||
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
|
||||
Always stay vigilant when giving out sensitive personal or account information.
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
|
||||
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
|
||||
Beware of messages that instill a sense of urgency (e.g., account will expire unless you "verify" your information). Contact the Bank directly if unsure.
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
|
||||
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
|
||||
Always log out of secondary devices and reset your passwords frequently.
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
|
||||
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
|
||||
Use strong passwords: Create strong passwords that are difficult for hackers to guess.
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
|
||||
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
|
||||
Use public Wi-Fi with caution: Be careful when using public Wi-Fi networks.
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
|
||||
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
|
||||
Back up your data regularly to a secure, encrypted, off-site location.
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
|
||||
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
|
||||
Follow corporate security policies: Adhere to your company's security guidelines.
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
|
||||
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
|
||||
Assess third-party app permissions carefully before granting access.
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
|
||||
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
|
||||
Lock your computer and mobile phone when not in use.
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 10px 0;padding-left:15px;position:relative;">
|
||||
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
|
||||
Don't leave devices unattended. Keep all mobile devices, such as laptops and cell phones, physically secured.
|
||||
</p>
|
||||
|
||||
<p style="margin:0 0 0 0;padding-left:15px;position:relative;">
|
||||
<span style="position:absolute;left:0;color:#1a5f3a;">•</span>
|
||||
Don't leave Bluetooth / Wireless turned on when not in use. Enable them only when needed and in a safe environment.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* 4) PDF GENERATION
|
||||
* --------------------------------------------------------- */
|
||||
const opt = {
|
||||
margin: [20, 10, 20, 10],
|
||||
filename: `AccountStatement_${accountNo}_${dayjs().format("DD/MM/YYYY HH:mm")}.pdf`,
|
||||
margin: [15, 10, 20, 10],
|
||||
filename: `Statement_${accountNo}_${dayjs().format("DDMMYYYY_HHmm")}.pdf`,
|
||||
image: { type: "jpeg", quality: 0.98 },
|
||||
html2canvas: { scale: 2 },
|
||||
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" },
|
||||
pagebreak: { mode: ["avoid-all", "css", "legacy"] },
|
||||
html2canvas: {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
letterRendering: true,
|
||||
},
|
||||
jsPDF: {
|
||||
unit: "mm",
|
||||
format: "a4",
|
||||
orientation: "portrait",
|
||||
compress: true,
|
||||
},
|
||||
pagebreak: {
|
||||
mode: ["css", "legacy"],
|
||||
before: '.page-break-before',
|
||||
after: '.page-break-after',
|
||||
avoid: ['tr', '.txn-row']
|
||||
},
|
||||
};
|
||||
|
||||
html2pdf()
|
||||
.set(opt)
|
||||
.from(content)
|
||||
.from(firstPage + lastPage)
|
||||
.toPdf()
|
||||
.get("pdf")
|
||||
.then((pdf: any) => {
|
||||
const totalPages = pdf.internal.getNumberOfPages();
|
||||
const pageWidth = pdf.internal.pageSize.getWidth();
|
||||
const total = pdf.internal.getNumberOfPages();
|
||||
const w = pdf.internal.pageSize.getWidth();
|
||||
const h = pdf.internal.pageSize.getHeight();
|
||||
|
||||
for (let i = 2; i <= totalPages; i++) {
|
||||
const hasGState =
|
||||
typeof pdf.GState === "function" && typeof pdf.setGState === "function";
|
||||
|
||||
for (let i = 1; i <= total; i++) {
|
||||
pdf.setPage(i);
|
||||
pdf.setFontSize(10);
|
||||
|
||||
// ✅ Left side Account No
|
||||
pdf.setFont("helvetica", "bold");
|
||||
// pdf.text(`Account No: ${accountNo}`, 15, 18);
|
||||
/* LIGHT WATERMARK */
|
||||
try {
|
||||
if (hasGState) {
|
||||
const light = pdf.GState({ opacity: 0.03 });
|
||||
pdf.setGState(light);
|
||||
pdf.addImage("/kccb_watermark.png", "PNG", 35, 70, 140, 140);
|
||||
pdf.setGState(pdf.GState({ opacity: 1 }));
|
||||
} else {
|
||||
pdf.addImage("/kccb_watermark.png", "PNG", 35, 70, 140, 140);
|
||||
}
|
||||
} catch { }
|
||||
|
||||
// ✅ Centered Statement Period
|
||||
/* FOOTER */
|
||||
pdf.setFont("times", "italic");
|
||||
pdf.setFontSize(9);
|
||||
pdf.setTextColor(100, 100, 100);
|
||||
pdf.text(
|
||||
`Statement Period: ${periodFrom} to ${periodTo}`,
|
||||
pageWidth / 2,
|
||||
18,
|
||||
"** This is only for information purpose and not for legal use **",
|
||||
w / 2,
|
||||
h - 12,
|
||||
{ align: "center" }
|
||||
);
|
||||
|
||||
// Footer page numbers
|
||||
/* PAGE NUMBER */
|
||||
pdf.setFont("times", "normal");
|
||||
pdf.setFontSize(9);
|
||||
pdf.text(
|
||||
`Page ${i} of ${totalPages}`,
|
||||
pageWidth - 40,
|
||||
pdf.internal.pageSize.getHeight() - 10
|
||||
);
|
||||
pdf.setTextColor(0, 0, 0);
|
||||
pdf.text(`Page ${i} of ${total}`, w - 15, h - 12, {
|
||||
align: "right",
|
||||
});
|
||||
}
|
||||
})
|
||||
.save();
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
|
||||
import { MantineColorsTuple, createTheme } from "@mantine/core";
|
||||
|
||||
// const KccbColors: MantineColorsTuple = [
|
||||
// "#e3f2fd", "#bbdefb", "#90caf9", "#64b5f6", "#42a5f5",
|
||||
// "#2196f3", "#1e88e5", "#1976d2", "#1565c0", "#0d47a1"
|
||||
// ];
|
||||
const KccbColors: MantineColorsTuple = [
|
||||
"#e3f2fd", "#bbdefb", "#90caf9", "#64b5f6", "#42a5f5",
|
||||
"#2196f3", "#1e88e5", "#1976d2", "#1565c0", "#0d47a1"
|
||||
"#e8f5e9", "#c8e6c9", "#a5d6a7", "#81c784", "#66bb6a", // Lighter greens
|
||||
"#4caf50", "#43a047", "#388e3c", "#2c6f2c", "#1b5e20" // Darker greens
|
||||
];
|
||||
|
||||
|
||||
export const KccbTheme = createTheme({
|
||||
/* Put your mantine theme override here */
|
||||
primaryColor: 'kccb-colors',
|
||||
|
||||
@@ -8,7 +8,7 @@ export async function fetchAndStoreUserName(token: string) {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Login-Type": "IB",
|
||||
"X-Login-Type": "IB",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
@@ -28,14 +28,20 @@ export async function fetchAndStoreUserName(token: string) {
|
||||
|
||||
const data = await response.json();
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const { custname, mobileno } = data[0];
|
||||
const { custname, mobileno, cifNumber, stBranchNo,custaddress } = data[0];
|
||||
localStorage.setItem("remitter_name", custname);
|
||||
localStorage.setItem("remitter_mobile_no", mobileno);
|
||||
localStorage.setItem("remitter_cif_no", cifNumber);
|
||||
localStorage.setItem("remitter_branch_no", stBranchNo);
|
||||
localStorage.setItem("remitter_address", custaddress);
|
||||
}
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
localStorage.removeItem("remitter_name");
|
||||
localStorage.removeItem("remitter_mobile_no");
|
||||
localStorage.removeItem("remitter_cif_no");
|
||||
localStorage.removeItem("remitter_branch_no");
|
||||
localStorage.removeItem("remitter_address");
|
||||
console.error('Error sending OTP:', error.response?.data || error.message);
|
||||
notifications.show({
|
||||
withBorder: true,
|
||||
|
||||
@@ -411,7 +411,7 @@ export default function Login() {
|
||||
|
||||
<Divider size="xs" color="#99c2ff" />
|
||||
<Text size="xs" style={{ textAlign: "center" }}>
|
||||
© 2025 The Kangra Central Co-Operative Bank
|
||||
© 2025 The Kangra Central Co-Operative Bank Ltd.
|
||||
</Text>
|
||||
</div>
|
||||
</Providers>
|
||||
|
||||
40
src/app/eMandate/authUtils.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
import { notifications } from "@mantine/notifications";
|
||||
|
||||
export const fetchUserDetails = 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("Failed");
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const name = data[0].custname;
|
||||
const mobile = data[0].mobileno;
|
||||
|
||||
localStorage.setItem("user_name", name);
|
||||
localStorage.setItem("userMobNo", mobile);
|
||||
|
||||
return { name, mobile };
|
||||
}
|
||||
} catch {
|
||||
notifications.show({
|
||||
title: "Please try again later",
|
||||
message: "Unable to fetch user details",
|
||||
color: "red",
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -21,26 +21,30 @@ function LoginEmandate() {
|
||||
const [isLogging, setIsLogging] = useState(false);
|
||||
const searchParams = useSearchParams();
|
||||
const data = searchParams.get("data");
|
||||
const payload = searchParams.get("payload");
|
||||
const mandateReqDoc = searchParams.get("mandateReqDoc");
|
||||
const mndtType = searchParams.get("mndtType");
|
||||
|
||||
// useEffect(() => {
|
||||
// if (payload) {
|
||||
// try {
|
||||
// const parsed = JSON.parse(decodeURIComponent(payload));
|
||||
// console.log("Received payload from SoftTech:", parsed);
|
||||
// localStorage.setItem("Emandate_data", JSON.stringify(parsed));
|
||||
// } catch (e) {
|
||||
// console.error("Failed to parse payload:", e);
|
||||
// }
|
||||
// if (data) {
|
||||
// console.log("URL parameter 'data':", data);
|
||||
// localStorage.setItem("Emendate_data", data);
|
||||
// localStorage.setItem("Emendate_req_doc", mandateReqDoc || "");
|
||||
// localStorage.setItem("Emendate_type", mndtType || "");
|
||||
// }
|
||||
// }, [payload]);
|
||||
|
||||
// }, [data]);
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
console.log("URL parameter 'data':", data);
|
||||
if (data && mandateReqDoc && mndtType) {
|
||||
console.log("eMandate parameters found");
|
||||
localStorage.setItem("Emendate_data", data);
|
||||
localStorage.setItem("Emendate_req_doc", mandateReqDoc);
|
||||
localStorage.setItem("Emendate_type", mndtType);
|
||||
} else {
|
||||
console.log("eMandate parameters missing — clearing localStorage");
|
||||
localStorage.removeItem("Emendate_data");
|
||||
localStorage.removeItem("Emendate_req_doc");
|
||||
localStorage.removeItem("Emendate_type");
|
||||
}
|
||||
}, [data]);
|
||||
}, [data, mandateReqDoc, mndtType]);
|
||||
|
||||
useEffect(() => {
|
||||
const loadCaptcha = async () => {
|
||||
@@ -50,7 +54,6 @@ function LoginEmandate() {
|
||||
loadCaptcha();
|
||||
}, []);
|
||||
|
||||
|
||||
const regenerateCaptcha = () => {
|
||||
// setCaptcha(generateCaptcha());
|
||||
const loadCaptcha = async () => {
|
||||
@@ -107,6 +110,7 @@ function LoginEmandate() {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setIsLogging(true);
|
||||
const response = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -131,9 +135,9 @@ function LoginEmandate() {
|
||||
localStorage.removeItem("mandate_token");
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
setIsLogging(false);
|
||||
return;
|
||||
}
|
||||
setIsLogging(true);
|
||||
if (response.ok) {
|
||||
// console.log(data);
|
||||
const token = data.token;
|
||||
@@ -147,9 +151,40 @@ function LoginEmandate() {
|
||||
message: "Please go to Internet Banking, set your credentials, and then try logging in here again.",
|
||||
autoClose: 5000,
|
||||
});
|
||||
setIsLogging(false);
|
||||
}
|
||||
else {
|
||||
router.push("/eMandate/mandate_page");
|
||||
console.log("Start to validate soft tech data");
|
||||
const response = await fetch('/api/e-mandate/validation', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Login-Type': 'eMandate',
|
||||
'Authorization': `Bearer ${localStorage.getItem("mandate_token")}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: localStorage.getItem("Emendate_data"),
|
||||
mandateRequest: localStorage.getItem("Emendate_req_doc"),
|
||||
mandateType: localStorage.getItem("Emendate_type"),
|
||||
}),
|
||||
});
|
||||
const result = await response.json();
|
||||
localStorage.setItem("Validate_data", result.data);
|
||||
console.log("Validate Result : ", result);
|
||||
if (response.ok) {
|
||||
router.push("/eMandate/otp_page");
|
||||
}
|
||||
else {
|
||||
console.log("validation failed: response",result);
|
||||
notifications.show({
|
||||
withBorder: true,
|
||||
color: "red",
|
||||
title: "Validation failed",
|
||||
message: "Failed to validate.Please try again later.",
|
||||
autoClose: 5000,
|
||||
});
|
||||
setIsLogging(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -172,51 +207,53 @@ function LoginEmandate() {
|
||||
message: "Internal Server Error,Please try again Later",
|
||||
autoClose: 5000,
|
||||
});
|
||||
setIsLogging(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
{/* Main Screen */}
|
||||
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "auto", paddingTop: "5%" }}>
|
||||
{/* Header */}
|
||||
if (data && mandateReqDoc && mndtType) {
|
||||
return (
|
||||
<Providers>
|
||||
{/* Main Screen */}
|
||||
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "auto", paddingTop: "5%" }}>
|
||||
{/* Header */}
|
||||
|
||||
<Box className={styles.header}>
|
||||
<Image src={logo} component={NextImage} fit="contain" alt="ebanking" style={{ width: "60px", height: "auto" }} />
|
||||
<Box className={styles['header-text']}>
|
||||
<Title className={styles['desktop-text']} order={4}>THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.</Title>
|
||||
<Text className={styles['desktop-text']} size="xs">Head Office: Dharmshala, District Kangra (H.P), Pin: 176215</Text>
|
||||
<Title className={styles['mobile-text']} order={5}>THE KANGRA CENTRAL</Title>
|
||||
<Title className={styles['mobile-text']} order={5}>CO-OPERATIVE BANK LTD.</Title>
|
||||
<Box className={styles.header}>
|
||||
<Image src={logo} component={NextImage} fit="contain" alt="ebanking" style={{ width: "60px", height: "auto" }} />
|
||||
<Box className={styles['header-text']}>
|
||||
<Title className={styles['desktop-text']} order={4}>THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.</Title>
|
||||
<Text className={styles['desktop-text']} size="xs">Head Office: Dharmshala, District Kangra (H.P), Pin: 176215</Text>
|
||||
<Title className={styles['mobile-text']} order={5}>THE KANGRA CENTRAL</Title>
|
||||
<Title className={styles['mobile-text']} order={5}>CO-OPERATIVE BANK LTD.</Title>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
{/* Movable text */}
|
||||
<Box
|
||||
className={styles['desktop-scroll-text']}
|
||||
>
|
||||
<Text
|
||||
component="span"
|
||||
style={{
|
||||
display: "inline-block",
|
||||
paddingLeft: "100%",
|
||||
animation: "scroll-left 60s linear infinite",
|
||||
fontWeight: "bold",
|
||||
color: "#004d99",
|
||||
}}
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
{/* Movable text */}
|
||||
<Box
|
||||
className={styles['desktop-scroll-text']}
|
||||
>
|
||||
⚠️ Always login to our Net Banking site directly or through Banks website.
|
||||
⚠️ Do not disclose your User Id and Password to any third party and keep Your User Id and Password strictly confidential.
|
||||
⚠️ KCC Bank never asks for User Id,Passwords and Pins through email or phone.
|
||||
⚠️ Be ware of Phishing mails with links to fake bank's websites asking for personal information are in circulation.
|
||||
⚠️ Please DO NOT Click on the links given in the emails asking for personal details like bank account number, user ID and password.
|
||||
⚠️ If you had shared your User Id and Password through such mails or links, please change your Password immediately.
|
||||
⚠️ Inform the Bank/branch in which your account is maintained for resetting your password.
|
||||
</Text>
|
||||
<style>
|
||||
{`
|
||||
<Text
|
||||
component="span"
|
||||
style={{
|
||||
display: "inline-block",
|
||||
paddingLeft: "100%",
|
||||
animation: "scroll-left 60s linear infinite",
|
||||
fontWeight: "bold",
|
||||
color: "#004d99",
|
||||
}}
|
||||
>
|
||||
⚠️ Always login to our Net Banking site directly or through Banks website.
|
||||
⚠️ Do not disclose your User Id and Password to any third party and keep Your User Id and Password strictly confidential.
|
||||
⚠️ KCC Bank never asks for User Id,Passwords and Pins through email or phone.
|
||||
⚠️ Be ware of Phishing mails with links to fake bank's websites asking for personal information are in circulation.
|
||||
⚠️ Please DO NOT Click on the links given in the emails asking for personal details like bank account number, user ID and password.
|
||||
⚠️ If you had shared your User Id and Password through such mails or links, please change your Password immediately.
|
||||
⚠️ Inform the Bank/branch in which your account is maintained for resetting your password.
|
||||
</Text>
|
||||
<style>
|
||||
{`
|
||||
@keyframes scroll-left {
|
||||
0% { transform: translateX(0%); }
|
||||
100% { transform: translateX(-100%); }
|
||||
@@ -225,32 +262,90 @@ function LoginEmandate() {
|
||||
.desktop-scroll-text { display: none; }
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</Box>
|
||||
</style>
|
||||
</Box>
|
||||
|
||||
{/* Main */}
|
||||
<Box visibleFrom="md"
|
||||
style={{
|
||||
display: "flex", height: "75vh", overflow: "hidden", position: "relative",
|
||||
background: 'linear-gradient(179deg, #3faa56ff 49%, #3aa760ff 80%)'
|
||||
}}>
|
||||
<div style={{ flex: 1, backgroundColor: "#c1e0f0", position: "relative" }}>
|
||||
<Image
|
||||
fit="cover"
|
||||
src={frontPage}
|
||||
component={NextImage}
|
||||
alt="ebanking"
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
/>
|
||||
</div>
|
||||
<Box w={{ base: "100%", md: "45%" }} p="lg">
|
||||
<Card shadow="md" padding="xl" radius="md" style={{ maxWidth: 550, margin: "0 auto", height: '68vh' }}>
|
||||
{/* Main */}
|
||||
<Box visibleFrom="md"
|
||||
style={{
|
||||
display: "flex", height: "75vh", overflow: "hidden", position: "relative",
|
||||
background: 'linear-gradient(179deg, #3faa56ff 49%, #3aa760ff 80%)'
|
||||
}}>
|
||||
<div style={{ flex: 1, backgroundColor: "#c1e0f0", position: "relative" }}>
|
||||
<Image
|
||||
fit="cover"
|
||||
src={frontPage}
|
||||
component={NextImage}
|
||||
alt="ebanking"
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
/>
|
||||
</div>
|
||||
<Box w={{ base: "100%", md: "45%" }} p="lg">
|
||||
<Card shadow="md" padding="xl" radius="md" style={{ maxWidth: 550, margin: "0 auto", height: '68vh' }}>
|
||||
<form onSubmit={handleMandateLogin}>
|
||||
<Text
|
||||
ta="center"
|
||||
fw={700}
|
||||
style={{ fontSize: "18px" }}
|
||||
>
|
||||
E-Mandate Login
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
label="User ID"
|
||||
placeholder="Enter your CIF No"
|
||||
value={CIF}
|
||||
onInput={(e) => {
|
||||
const input = e.currentTarget.value.replace(/\D/g, "");
|
||||
if (input.length <= 11) SetCIF(input);
|
||||
}}
|
||||
withAsterisk
|
||||
/>
|
||||
<PasswordInput
|
||||
label="Password"
|
||||
placeholder="Enter your password"
|
||||
value={psw}
|
||||
onChange={(e) => SetPsw(e.currentTarget.value)}
|
||||
withAsterisk
|
||||
mt="sm"
|
||||
/>
|
||||
|
||||
<Group mt="sm" align="center">
|
||||
<Box style={{ backgroundColor: "#fff", fontSize: "18px", textDecoration: "line-through", padding: "4px 8px", fontFamily: "Verdana" }}>{captcha}</Box>
|
||||
<Button size="xs" variant="light" onClick={regenerateCaptcha}>Refresh</Button>
|
||||
</Group>
|
||||
<TextInput
|
||||
label="Enter CAPTCHA"
|
||||
placeholder="Enter above text"
|
||||
value={inputCaptcha}
|
||||
onChange={(e) => setInputCaptcha(e.currentTarget.value)}
|
||||
withAsterisk
|
||||
mt="sm"
|
||||
/>
|
||||
<Button type="submit" fullWidth mt="md" disabled={isLogging}>
|
||||
{isLogging ? "Logging..." : "Login"}
|
||||
</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Mobile layout */}
|
||||
<Box
|
||||
hiddenFrom="md"
|
||||
style={{
|
||||
marginTop: "25%",
|
||||
padding: "1rem",
|
||||
background: "linear-gradient(179deg, #3faa56ff 49%, #3aa760ff 80%)",
|
||||
minHeight: "80vh",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<Card shadow="md" padding="lg" radius="md" style={{ width: "100%" }}>
|
||||
<form onSubmit={handleMandateLogin}>
|
||||
<Text
|
||||
ta="center"
|
||||
fw={700}
|
||||
style={{ fontSize: "18px" }}
|
||||
>
|
||||
<Text ta="center" fw={700} style={{ fontSize: "16px" }}>
|
||||
E-Mandate Login
|
||||
</Text>
|
||||
|
||||
@@ -263,7 +358,9 @@ function LoginEmandate() {
|
||||
if (input.length <= 11) SetCIF(input);
|
||||
}}
|
||||
withAsterisk
|
||||
mt="sm"
|
||||
/>
|
||||
|
||||
<PasswordInput
|
||||
label="Password"
|
||||
placeholder="Enter your password"
|
||||
@@ -273,10 +370,23 @@ function LoginEmandate() {
|
||||
mt="sm"
|
||||
/>
|
||||
|
||||
<Group mt="sm" align="center">
|
||||
<Box style={{ backgroundColor: "#fff", fontSize: "18px", textDecoration: "line-through", padding: "4px 8px", fontFamily: "Verdana" }}>{captcha}</Box>
|
||||
<Button size="xs" variant="light" onClick={regenerateCaptcha}>Refresh</Button>
|
||||
<Group mt="sm" align="center" grow>
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: "#fff",
|
||||
fontSize: "16px",
|
||||
textDecoration: "line-through",
|
||||
padding: "4px 8px",
|
||||
fontFamily: "Verdana",
|
||||
}}
|
||||
>
|
||||
{captcha}
|
||||
</Box>
|
||||
<Button size="xs" variant="light" onClick={regenerateCaptcha}>
|
||||
Refresh
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<TextInput
|
||||
label="Enter CAPTCHA"
|
||||
placeholder="Enter above text"
|
||||
@@ -285,108 +395,57 @@ function LoginEmandate() {
|
||||
withAsterisk
|
||||
mt="sm"
|
||||
/>
|
||||
|
||||
<Button type="submit" fullWidth mt="md" disabled={isLogging}>
|
||||
{isLogging ? "Logging..." : "Login"}
|
||||
</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Mobile layout */}
|
||||
<Box
|
||||
hiddenFrom="md"
|
||||
style={{
|
||||
marginTop: "25%",
|
||||
padding: "1rem",
|
||||
background: "linear-gradient(179deg, #3faa56ff 49%, #3aa760ff 80%)",
|
||||
minHeight: "80vh",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<Card shadow="md" padding="lg" radius="md" style={{ width: "100%" }}>
|
||||
<form onSubmit={handleMandateLogin}>
|
||||
<Text ta="center" fw={700} style={{ fontSize: "16px" }}>
|
||||
E-Mandate Login
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
label="User ID"
|
||||
placeholder="Enter your CIF No"
|
||||
value={CIF}
|
||||
onInput={(e) => {
|
||||
const input = e.currentTarget.value.replace(/\D/g, "");
|
||||
if (input.length <= 11) SetCIF(input);
|
||||
}}
|
||||
withAsterisk
|
||||
mt="sm"
|
||||
/>
|
||||
|
||||
<PasswordInput
|
||||
label="Password"
|
||||
placeholder="Enter your password"
|
||||
value={psw}
|
||||
onChange={(e) => SetPsw(e.currentTarget.value)}
|
||||
withAsterisk
|
||||
mt="sm"
|
||||
/>
|
||||
|
||||
<Group mt="sm" align="center" grow>
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: "#fff",
|
||||
fontSize: "16px",
|
||||
textDecoration: "line-through",
|
||||
padding: "4px 8px",
|
||||
fontFamily: "Verdana",
|
||||
}}
|
||||
>
|
||||
{captcha}
|
||||
</Box>
|
||||
<Button size="xs" variant="light" onClick={regenerateCaptcha}>
|
||||
Refresh
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<TextInput
|
||||
label="Enter CAPTCHA"
|
||||
placeholder="Enter above text"
|
||||
value={inputCaptcha}
|
||||
onChange={(e) => setInputCaptcha(e.currentTarget.value)}
|
||||
withAsterisk
|
||||
mt="sm"
|
||||
/>
|
||||
|
||||
<Button type="submit" fullWidth mt="md" disabled={isLogging}>
|
||||
{isLogging ? "Logging..." : "Login"}
|
||||
</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</Box>
|
||||
|
||||
{/* Footer */}
|
||||
<Box
|
||||
component="footer"
|
||||
style={{
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
padding: "10px 0",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
zIndex: 1000,
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
© 2025 KCC Bank. All rights reserved.
|
||||
</Text>
|
||||
</Box>
|
||||
{/* Footer */}
|
||||
<Box
|
||||
component="footer"
|
||||
style={{
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
padding: "10px 0",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
zIndex: 1000,
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
© 2025 KCC Bank. All rights reserved.
|
||||
</Text>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100vh",
|
||||
backgroundColor: "#f8f9fa",
|
||||
}}
|
||||
>
|
||||
<Text c="red" fw={600} size="lg">
|
||||
⚠️ Required data not found
|
||||
</Text>
|
||||
<Text c="dimmed" size="sm">
|
||||
Please access this page through the correct eMandate link.
|
||||
</Text>
|
||||
</div>
|
||||
</Providers>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default function Login() {
|
||||
@@ -396,4 +455,3 @@ export default function Login() {
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,41 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const formData = await req.formData();
|
||||
const data = formData.get("data");
|
||||
const mandateReqDoc = formData.get("mandateReqDoc");
|
||||
const mndtType = formData.get("mndtType");
|
||||
// const { searchParams } = new URL(req.url);
|
||||
// const data = searchParams.get("data");
|
||||
// const mandateReqDoc = searchParams.get("mandateReqDoc");
|
||||
// const mndtType = searchParams.get("mndtType");
|
||||
|
||||
console.log("Received from SoftTech:", { data, mandateReqDoc, mndtType });
|
||||
if (!data) {
|
||||
return NextResponse.json({ error: "Missing data" }, { status: 400 });
|
||||
}
|
||||
const encodedData = String(data);
|
||||
// Redirect browser to page UI with data
|
||||
console.log ("Return to the Urls.");
|
||||
return NextResponse.redirect(
|
||||
new URL(`/eMandate/login/page?data=${encodedData}`, req.url)
|
||||
);
|
||||
const forwardedHost = req.headers.get("x-forwarded-host") || req.headers.get("host");
|
||||
const forwardedProto = req.headers.get("x-forwarded-proto") || "https";
|
||||
// const encodedData = String(data);
|
||||
// const redirectUrl = `${forwardedProto}://${forwardedHost}/eMandate/login/page?data=${encodedData}`;
|
||||
|
||||
const redirectUrl =`${forwardedProto}://${forwardedHost}/eMandate/login/page?data=${encodeURIComponent(
|
||||
String(data)
|
||||
)}&mandateReqDoc=${encodeURIComponent(String(mandateReqDoc))}&mndtType=${encodeURIComponent(
|
||||
String(mndtType)
|
||||
)}`;
|
||||
console.log("redirect urls:", redirectUrl);
|
||||
const response = NextResponse.redirect(redirectUrl, 303); // use 303 instead of 302
|
||||
|
||||
response.headers.set("Access-Control-Allow-Origin", "*");
|
||||
response.headers.set("Access-Control-Allow-Methods", "POST, OPTIONS");
|
||||
response.headers.set("Access-Control-Allow-Headers", "Content-Type, Origin");
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error handling POST:", error);
|
||||
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function OPTIONS() {
|
||||
const res = new NextResponse(null, { status: 204 });
|
||||
res.headers.set("Access-Control-Allow-Origin", "*");
|
||||
res.headers.set("Access-Control-Allow-Methods", "POST, OPTIONS");
|
||||
res.headers.set("Access-Control-Allow-Headers", "Content-Type, Origin");
|
||||
return res;
|
||||
}
|
||||
|
||||
30
src/app/eMandate/logout/page.module.css
Normal file
@@ -0,0 +1,30 @@
|
||||
/* Header */
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 1rem;
|
||||
background: linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%);
|
||||
}
|
||||
|
||||
.header-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Desktop header text */
|
||||
.desktop-text {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mobile-text {
|
||||
color: white;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
92
src/app/eMandate/logout/page.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
"use client";
|
||||
import { Text, Title, Box, Image } from "@mantine/core";
|
||||
import NextImage from "next/image";
|
||||
import logo from "@/app/image/logo1.jpg";
|
||||
import { Providers } from "@/app/providers";
|
||||
import styles from "./page.module.css";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function Logout() {
|
||||
useEffect(() => {
|
||||
localStorage.removeItem("mandate_token");
|
||||
localStorage.removeItem("user_name");
|
||||
localStorage.removeItem("userMobNo");
|
||||
localStorage.removeItem("Emendate_data");
|
||||
localStorage.removeItem("Emendate_req_doc");
|
||||
localStorage.removeItem("Emendate_type");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "#f8f9fa",
|
||||
width: "100%",
|
||||
minHeight: "100vh",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<Box className={styles.header} mt="md">
|
||||
<Image
|
||||
src={logo}
|
||||
component={NextImage}
|
||||
fit="contain"
|
||||
alt="ebanking"
|
||||
style={{ width: "60px", height: "auto" }}
|
||||
/>
|
||||
<Box className={styles["header-text"]}>
|
||||
<Title className={styles["desktop-text"]} order={4}>
|
||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
<Text className={styles["desktop-text"]} size="xs">
|
||||
Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
|
||||
</Text>
|
||||
<Title className={styles["mobile-text"]} order={5}>
|
||||
THE KANGRA CENTRAL
|
||||
</Title>
|
||||
<Title className={styles["mobile-text"]} order={5}>
|
||||
CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Centered Message */}
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text c="green" fw={600} size="lg">
|
||||
✅ You have successfully logged out
|
||||
</Text>
|
||||
<Text c="dimmed" size="sm" mt="xs">
|
||||
Please access this eMandate login page through the correct eMandate link.
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<Box
|
||||
component="footer"
|
||||
style={{
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
padding: "10px 0",
|
||||
fontSize: "14px",
|
||||
color: "#6c757d",
|
||||
}}
|
||||
>
|
||||
© 2025 KCC Bank. All rights reserved.
|
||||
</Box>
|
||||
</div>
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
@@ -117,7 +117,10 @@ export default function MandatePage() {
|
||||
localStorage.removeItem("mandate_token");
|
||||
localStorage.removeItem("user_name");
|
||||
localStorage.removeItem("userMobNo");
|
||||
router.push("/eMandate/login");
|
||||
localStorage.removeItem("Emendate_data");
|
||||
localStorage.removeItem("Emendate_req_doc");
|
||||
localStorage.removeItem("Emendate_type");
|
||||
router.push("/eMandate/logout");
|
||||
};
|
||||
|
||||
const handleFetchUserName = async () => {
|
||||
@@ -430,7 +433,7 @@ export default function MandatePage() {
|
||||
}}
|
||||
>
|
||||
<Text c="dimmed" size="xs">
|
||||
© 2025 The Kangra Central Co-Operative Bank
|
||||
© 2025 The Kangra Central Co-Operative Bank Ltd.
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
|
||||
65
src/app/eMandate/otpUtils.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
import { notifications } from "@mantine/notifications";
|
||||
|
||||
export const sendOtp = async (mobile: string) => {
|
||||
try {
|
||||
const response = await fetch("/api/otp/send", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Login-Type": "eMandate",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
mobileNumber: mobile,
|
||||
type: "EMandate",
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("Failed");
|
||||
|
||||
notifications.show({
|
||||
color: "green",
|
||||
title: "OTP Sent",
|
||||
message: "An OTP has been sent to your registered mobile number",
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
notifications.show({
|
||||
color: "red",
|
||||
title: "Error",
|
||||
message: "Failed to send OTP",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const verifyOtp = async (otp: string, mobile: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/otp/verify?mobileNumber=${mobile}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Login-Type": "eMandate",
|
||||
},
|
||||
body: JSON.stringify({ otp }),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("Invalid OTP");
|
||||
|
||||
notifications.show({
|
||||
color: "green",
|
||||
title: "Success",
|
||||
message: "OTP verified successfully!",
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
notifications.show({
|
||||
color: "red",
|
||||
title: "Verification Failed",
|
||||
message: "Invalid OTP",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
505
src/app/eMandate/otp_page/page.tsx
Normal file
@@ -0,0 +1,505 @@
|
||||
"use client";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import {
|
||||
Text,
|
||||
Title,
|
||||
Box,
|
||||
Image,
|
||||
Button,
|
||||
Group,
|
||||
Container,
|
||||
ActionIcon,
|
||||
Divider,
|
||||
PinInput,
|
||||
Paper,
|
||||
Stack,
|
||||
Center,
|
||||
Loader,
|
||||
} 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, IconShieldCheck, IconRefresh } from "@tabler/icons-react";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import { sendOtp, verifyOtp } from "../otpUtils";
|
||||
import { fetchUserDetails } from "../authUtils";
|
||||
|
||||
export default function VerifyOtpPage() {
|
||||
const router = useRouter();
|
||||
const [authorized, setAuthorized] = useState<boolean | null>(null);
|
||||
const [custname, setCustname] = useState<string | null>(null);
|
||||
const [otp, setOtp] = useState("");
|
||||
const [isVerifying, setIsVerifying] = useState(false);
|
||||
const [isResending, setIsResending] = useState(false);
|
||||
const [timer, setTimer] = useState(60);
|
||||
const [canResend, setCanResend] = useState(false);
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const otpSentRef = useRef(false);
|
||||
|
||||
//On First Load: Check Token → Fetch User → Send OTP
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("mandate_token");
|
||||
if (!token) {
|
||||
handleLogout();
|
||||
setAuthorized(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setAuthorized(true);
|
||||
|
||||
// Get User Name + Mobile
|
||||
fetchUserDetails().then((res) => {
|
||||
if (res) {
|
||||
setCustname(res.name);
|
||||
if (!otpSentRef.current) {
|
||||
sendOtp(res.mobile);
|
||||
// sendOtp("7890544527");
|
||||
otpSentRef.current = true; // Prevent second OTP
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent back button
|
||||
const handlePopState = () => {
|
||||
handleLogout();
|
||||
};
|
||||
window.addEventListener("popstate", handlePopState);
|
||||
window.history.pushState(null, "", window.location.href);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("popstate", handlePopState);
|
||||
if (timerRef.current) clearInterval(timerRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
// Timer Countdown
|
||||
useEffect(() => {
|
||||
timerRef.current = setInterval(() => {
|
||||
setTimer((prev) => {
|
||||
if (prev === 1) {
|
||||
setCanResend(true);
|
||||
clearInterval(timerRef.current!);
|
||||
}
|
||||
return prev - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(timerRef.current!);
|
||||
}, []);
|
||||
|
||||
|
||||
// Logout
|
||||
const handleLogout = () => {
|
||||
localStorage.clear();
|
||||
router.push("/eMandate/logout");
|
||||
};
|
||||
|
||||
//Resend OTP
|
||||
const handleResendOtp = async () => {
|
||||
setIsResending(true);
|
||||
|
||||
const mobile = localStorage.getItem("userMobNo");
|
||||
// const mobile = "7890544527";
|
||||
if (!mobile) return;
|
||||
|
||||
await sendOtp(mobile);
|
||||
setTimer(60);
|
||||
setCanResend(false);
|
||||
setOtp("");
|
||||
|
||||
setIsResending(false);
|
||||
};
|
||||
|
||||
|
||||
// Verify OTP using verifyOtp() Utility
|
||||
|
||||
// const handleVerifyOtp = async () => {
|
||||
// const mobile = localStorage.getItem("userMobNo");
|
||||
// // const mobile = "7890544527";
|
||||
// if (!mobile || otp.length !== 6) return;
|
||||
|
||||
// setIsVerifying(true);
|
||||
|
||||
// const success = await verifyOtp(otp, mobile);
|
||||
|
||||
// // if (success) {
|
||||
// // // const encoded_data = localStorage.getItem("Validate_data");
|
||||
// // // const res = await fetch(
|
||||
// // // `https://apiemandate.kccb.in:9035/EMandate/auth-cbs-resp?data=${encoded_data}`,
|
||||
// // // {
|
||||
// // // method: "POST",
|
||||
// // // headers: {
|
||||
// // // "Content-Type": "application/json",
|
||||
// // // },
|
||||
// // // }
|
||||
// // // );
|
||||
// // const formData = new FormData();
|
||||
// // const encoded_data = localStorage.getItem("Validate_data");
|
||||
// // formData.append("data", encoded_data);
|
||||
|
||||
// // const res = await fetch(
|
||||
// // "https://apiemandate.kccb.in:9035/EMandate/auth-cbs-resp",
|
||||
// // {
|
||||
// // method: "POST",
|
||||
// // body: formData,
|
||||
// // }
|
||||
// // );
|
||||
|
||||
// // const result = await res.json();
|
||||
// // console.log(result);
|
||||
// // if (!res.ok) {
|
||||
// // throw new Error("CBS response API failed");
|
||||
// // }
|
||||
// // if (res.ok) {
|
||||
// // // navigate only after successful API hit
|
||||
// // setTimeout(() => {
|
||||
// // router.push("/eMandate/mandate_page");
|
||||
// // }, 1500);
|
||||
// // }
|
||||
// // }
|
||||
// if (success) {
|
||||
// try {
|
||||
// const encoded_data = localStorage.getItem("Validate_data");
|
||||
// if (!encoded_data) {
|
||||
// console.error("Validate_data not found in localStorage");
|
||||
// return;
|
||||
// }
|
||||
// const formData = new FormData();
|
||||
// formData.append("data", encoded_data);
|
||||
|
||||
// const res = await fetch(
|
||||
// "https://apiemandate.kccb.in:9035/EMandate/auth-cbs-resp",
|
||||
// {
|
||||
// method: "POST",
|
||||
// body: formData,
|
||||
// }
|
||||
// );
|
||||
|
||||
// if (!res.ok) {
|
||||
// throw new Error(`CBS response API failed: ${res.status}`);
|
||||
// }
|
||||
// const contentType = res.headers.get("content-type");
|
||||
// const result =
|
||||
// contentType && contentType.includes("application/json")
|
||||
// ? await res.json()
|
||||
// : await res.text();
|
||||
|
||||
// console.log("CBS Response:", result);
|
||||
// setTimeout(() => {
|
||||
// router.push("/eMandate/mandate_page");
|
||||
// }, 1000);
|
||||
|
||||
// } catch (error) {
|
||||
// console.error("CBS API Error:", error);
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// setOtp("");
|
||||
// }
|
||||
// setIsVerifying(false);
|
||||
// };
|
||||
|
||||
const handleVerifyOtp = async () => {
|
||||
const mobile = localStorage.getItem("userMobNo");
|
||||
|
||||
if (!mobile || otp.length !== 6) return;
|
||||
|
||||
setIsVerifying(true);
|
||||
const success = await verifyOtp(otp, mobile);
|
||||
if (success) {
|
||||
const encoded_data = localStorage.getItem("Validate_data");
|
||||
|
||||
if (!encoded_data) {
|
||||
console.error("Validate_data not found in localStorage");
|
||||
setIsVerifying(false);
|
||||
return;
|
||||
}
|
||||
const url = "https://apiemandate.kccb.in:9035/EMandate/auth-cbs-resp";
|
||||
console.log("Redirecting (POST) to URL:", url);
|
||||
console.log("POST Body Data:", { data: encoded_data });
|
||||
|
||||
// Create a form for POST redirect
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.action = url;
|
||||
// Add hidden field "data"
|
||||
const input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
input.name = "data";
|
||||
input.value = encoded_data;
|
||||
|
||||
form.appendChild(input);
|
||||
document.body.appendChild(form);
|
||||
|
||||
// Submit → browser navigates to URL using POST
|
||||
form.submit();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
setOtp("");
|
||||
}
|
||||
setIsVerifying(false);
|
||||
};
|
||||
|
||||
|
||||
if (authorized === null) {
|
||||
return (
|
||||
<Center style={{ height: "100vh" }}>
|
||||
<Loader size="lg" color="green" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<Box
|
||||
style={{
|
||||
minHeight: "100vh",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
// background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
||||
background: 'linear-gradient(179deg, #3faa56ff 49%, #3aa760ff 80%)'
|
||||
}}
|
||||
>
|
||||
{/* HEADER */}
|
||||
<Paper
|
||||
radius={0}
|
||||
shadow="md"
|
||||
style={{
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
zIndex: 100,
|
||||
background: "linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
||||
// padding: isMobile ? "0.75rem 1rem" : "1rem 1.5rem",
|
||||
}}
|
||||
>
|
||||
<Container size="xl">
|
||||
<Group justify="space-between">
|
||||
<Group gap="md">
|
||||
<Image
|
||||
src={logo}
|
||||
component={NextImage}
|
||||
fit="contain"
|
||||
alt="KCC Bank Logo"
|
||||
style={{
|
||||
width: isMobile ? "50px" : "70px",
|
||||
height: isMobile ? "50px" : "70px",
|
||||
}}
|
||||
/>
|
||||
{!isMobile && (
|
||||
<Title
|
||||
order={2}
|
||||
c="white"
|
||||
style={{
|
||||
fontSize: "clamp(1rem, 2vw, 1.5rem)",
|
||||
fontFamily: "Roboto",
|
||||
}}
|
||||
>
|
||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{isMobile ? (
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="white"
|
||||
size="lg"
|
||||
onClick={handleLogout}
|
||||
title="Logout"
|
||||
>
|
||||
<IconLogout size={24} />
|
||||
</ActionIcon>
|
||||
) : (
|
||||
<Button
|
||||
variant="white"
|
||||
color="gray"
|
||||
onClick={handleLogout}
|
||||
leftSection={<IconLogout size={20} />}
|
||||
>
|
||||
Logout
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
</Container>
|
||||
</Paper>
|
||||
|
||||
{/* WELCOME BAR */}
|
||||
<Paper
|
||||
radius={0}
|
||||
p={isMobile ? "sm" : "md"}
|
||||
bg="white"
|
||||
shadow="sm"
|
||||
style={{ borderBottom: "3px solid #0a7228" }}
|
||||
>
|
||||
<Container size="xl">
|
||||
<Stack gap="xs">
|
||||
<Text
|
||||
fw={600}
|
||||
size={isMobile ? "lg" : "xl"}
|
||||
style={{ fontFamily: "Inter" }}
|
||||
>
|
||||
Welcome, {custname ?? "User"}
|
||||
</Text>
|
||||
<Divider />
|
||||
</Stack>
|
||||
</Container>
|
||||
</Paper>
|
||||
|
||||
{/* MAIN CONTENT */}
|
||||
<Box style={{ flex: 1, padding: "2rem 1rem" }}>
|
||||
<Container size="sm">
|
||||
<Center>
|
||||
<Paper
|
||||
shadow="xl"
|
||||
radius="xl"
|
||||
p={isMobile ? "xl" : "2rem"}
|
||||
style={{
|
||||
maxWidth: "500px",
|
||||
width: "100%",
|
||||
background: "rgba(255, 255, 255, 0.95)",
|
||||
backdropFilter: "blur(10px)",
|
||||
}}
|
||||
>
|
||||
<Stack gap="xl" align="center">
|
||||
{/* Icon */}
|
||||
<Box
|
||||
style={{
|
||||
width: "80px",
|
||||
height: "80px",
|
||||
borderRadius: "50%",
|
||||
background:
|
||||
"linear-gradient(135deg, #0a7228 0%, #2563eb 100%)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<IconShieldCheck size={48} color="white" />
|
||||
</Box>
|
||||
|
||||
{/* Title */}
|
||||
<Stack gap="xs" align="center">
|
||||
<Title
|
||||
order={2}
|
||||
ta="center"
|
||||
style={{
|
||||
fontSize: isMobile ? "1.5rem" : "2rem",
|
||||
}}
|
||||
>
|
||||
OTP Verification
|
||||
</Title>
|
||||
<Text size="sm" c="dimmed" ta="center">
|
||||
Enter the 6-digit OTP sent to your registered mobile
|
||||
number
|
||||
</Text>
|
||||
<Text size="sm" fw={600} c="blue">
|
||||
{localStorage.getItem("userMobNo")?.replace(
|
||||
/(\d{2})(\d{4})(\d{4})/,
|
||||
"+91 $1****$3"
|
||||
)}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
{/* OTP Input */}
|
||||
<Stack gap="md" align="center" w="100%">
|
||||
<PinInput
|
||||
size={isMobile ? "lg" : "xl"}
|
||||
length={6}
|
||||
value={otp}
|
||||
onChange={setOtp}
|
||||
placeholder="○"
|
||||
type="number"
|
||||
oneTimeCode
|
||||
styles={{
|
||||
input: {
|
||||
fontSize: isMobile ? "1.5rem" : "2rem",
|
||||
fontWeight: 600,
|
||||
borderColor: "#0a7228",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Timer */}
|
||||
<Group gap="xs">
|
||||
<Text size="sm" c="dimmed">
|
||||
{canResend ? (
|
||||
"Didn't receive OTP?"
|
||||
) : (
|
||||
<>
|
||||
Resend OTP in{" "}
|
||||
<Text component="span" fw={600} c="blue">
|
||||
{timer}s
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
{/* Verify Button */}
|
||||
<Button
|
||||
fullWidth
|
||||
size="lg"
|
||||
onClick={handleVerifyOtp}
|
||||
loading={isVerifying}
|
||||
disabled={otp.length !== 6 || isVerifying}
|
||||
gradient={{ from: "teal", to: "blue", deg: 60 }}
|
||||
variant="gradient"
|
||||
radius="md"
|
||||
>
|
||||
{isVerifying ? "Verifying..." : "Verify OTP"}
|
||||
</Button>
|
||||
|
||||
{/* Resend Button */}
|
||||
<Button
|
||||
fullWidth
|
||||
variant="light"
|
||||
size="md"
|
||||
onClick={handleResendOtp}
|
||||
disabled={!canResend || isResending}
|
||||
loading={isResending}
|
||||
leftSection={<IconRefresh size={18} />}
|
||||
>
|
||||
{isResending ? "Sending..." : "Resend OTP"}
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
{/* Info */}
|
||||
<Paper p="md" radius="md" bg="blue.0" w="100%">
|
||||
<Text size="xs" c="blue.9" ta="center">
|
||||
<Text component="span" fw={600}>
|
||||
Note:{" "}
|
||||
</Text>
|
||||
Please do not share your OTP with anyone. KCC Bank
|
||||
will never ask for your OTP.
|
||||
</Text>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Center>
|
||||
</Container>
|
||||
</Box>
|
||||
|
||||
{/* FOOTER */}
|
||||
<Paper
|
||||
radius={0}
|
||||
p="md"
|
||||
bg="gray.9"
|
||||
style={{
|
||||
borderTop: "1px solid #ddd",
|
||||
}}
|
||||
>
|
||||
<Container size="xl">
|
||||
<Text size="xs" c="white" ta="center">
|
||||
© 2025 The Kangra Central Co-Operative Bank Ltd. All rights
|
||||
reserved.
|
||||
</Text>
|
||||
</Container>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
BIN
src/app/image/DICGC_image - Copy.jpg
Normal file
|
After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 91 KiB |
BIN
src/app/image/ib_front_2.jpg
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
src/app/image/ib_front_3.jpg
Normal file
|
After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 101 KiB |
@@ -1,46 +1,46 @@
|
||||
'use client';
|
||||
|
||||
|
||||
import { Box, Image, ActionIcon } from '@mantine/core';
|
||||
import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react';
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import DICGC from '@/app/image/DICGC_image.jpg';
|
||||
import objective from '@/app/image/objective.jpg';
|
||||
import vision from '@/app/image/vision.jpg';
|
||||
|
||||
|
||||
export default function CustomCarousel() {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const images = [DICGC,vision,objective];
|
||||
const images = [DICGC, vision, objective];
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
|
||||
|
||||
const scrollToIndex = (index: number) => {
|
||||
const container = scrollRef.current;
|
||||
if (!container) return;
|
||||
|
||||
|
||||
const slideWidth = container.offsetWidth; // full width per slide
|
||||
container.scrollTo({
|
||||
left: slideWidth * index,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const scrollRight = () => {
|
||||
const nextIndex = (currentIndex + 1) % images.length;
|
||||
setCurrentIndex(nextIndex);
|
||||
scrollToIndex(nextIndex);
|
||||
};
|
||||
|
||||
|
||||
const scrollLeft = () => {
|
||||
const prevIndex = (currentIndex - 1 + images.length) % images.length;
|
||||
setCurrentIndex(prevIndex);
|
||||
scrollToIndex(prevIndex);
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
scrollToIndex(currentIndex);
|
||||
}, [currentIndex]);
|
||||
|
||||
|
||||
return (
|
||||
<Box style={{ position: 'relative', width: '90%', overflow: 'hidden',backgroundColor:"white" }}>
|
||||
<Box style={{ position: 'relative', width: '90%', overflow: 'hidden', backgroundColor: "white" }}>
|
||||
{/* Scrollable container */}
|
||||
<Box
|
||||
ref={scrollRef}
|
||||
@@ -54,29 +54,31 @@ export default function CustomCarousel() {
|
||||
{images.map((img, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
onClick={() => {
|
||||
if (i === 0) window.open("https://dicgc.org.in", "_blank");
|
||||
}}
|
||||
style={{
|
||||
flex: '0 0 100%',
|
||||
scrollSnapAlign: 'start',
|
||||
height: '250px',
|
||||
minWidth: '100%',
|
||||
maxWidth: '100%',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: 'white',
|
||||
flex: "0 0 100%",
|
||||
scrollSnapAlign: "start",
|
||||
height: "250px",
|
||||
minWidth: "100%",
|
||||
cursor: i === 0 ? "pointer" : "default",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={img.src}
|
||||
alt={`Slide ${i + 1}`}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'contain',
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
</Box>
|
||||
|
||||
|
||||
{/* Left Scroll Button */}
|
||||
<ActionIcon
|
||||
variant="filled"
|
||||
@@ -92,7 +94,7 @@ export default function CustomCarousel() {
|
||||
>
|
||||
<IconChevronLeft size={24} />
|
||||
</ActionIcon>
|
||||
|
||||
|
||||
{/* Right Scroll Button */}
|
||||
<ActionIcon
|
||||
variant="filled"
|
||||
@@ -111,4 +113,3 @@ export default function CustomCarousel() {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 1rem;
|
||||
padding: 0.8rem 2rem;
|
||||
background: linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%);
|
||||
flex-wrap: wrap; /* allow wrapping on mobile */
|
||||
flex-wrap: wrap; /* Allow wrapping on mobile */
|
||||
}
|
||||
|
||||
.header-text {
|
||||
@@ -20,7 +20,6 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Desktop text */
|
||||
.desktop-text {
|
||||
color: white;
|
||||
font-family: Roboto, sans-serif;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { sendOtp, verifyLoginOtp } from '@/app/_util/otp';
|
||||
import NextImage from "next/image";
|
||||
import styles from './page.module.css';
|
||||
import logo from '@/app/image/logo1.jpg';
|
||||
import frontPage from '@/app/image/ib_front_1.jpg';
|
||||
import frontPage from '@/app/image/ib_front_3.jpg';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { generateCaptcha } from '@/app/captcha';
|
||||
import { IconRefresh, IconShieldLockFilled } from "@tabler/icons-react";
|
||||
@@ -45,8 +45,9 @@ export default function Login() {
|
||||
}
|
||||
|
||||
try {
|
||||
// await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: mobile });
|
||||
await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: "6297421727" });
|
||||
const maskedCIF = CIF?.replace(/.(?=.{3})/g, '*');
|
||||
await sendOtp({ type: 'LOGIN_OTP', username: maskedCIF, mobileNumber: mobile });
|
||||
// await sendOtp({ type: 'LOGIN_OTP', username: maskedCIF, mobileNumber: "7890544527" });
|
||||
notifications.show({
|
||||
color: 'orange',
|
||||
title: 'OTP Required',
|
||||
@@ -78,6 +79,7 @@ export default function Login() {
|
||||
title: `${err.message}`,
|
||||
message: 'OTP verification failed. Please try again later.',
|
||||
color: 'red',
|
||||
autoClose: 700,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -441,38 +443,45 @@ export default function Login() {
|
||||
)}
|
||||
</Modal>
|
||||
{/* Main Screen */}
|
||||
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "auto", paddingTop: "5%" }}>
|
||||
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "auto" }}>
|
||||
{/* Header */}
|
||||
<Box className={styles.header}>
|
||||
<Image
|
||||
src={logo}
|
||||
component={NextImage}
|
||||
fit="contain"
|
||||
alt="ebanking"
|
||||
style={{ width: "60px", height: "auto" }}
|
||||
/>
|
||||
<Box className={styles['header-text']}>
|
||||
{/* Desktop */}
|
||||
<Title className={styles['desktop-text']} ref={headerRef} order={2}>
|
||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
<Text className={styles['desktop-address']} size="xs">
|
||||
Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
|
||||
</Text>
|
||||
|
||||
{/* Mobile */}
|
||||
<Title className={styles['mobile-text']} order={5}>
|
||||
THE KANGRA CENTRAL
|
||||
</Title>
|
||||
<Title className={styles['mobile-text']} order={5}>
|
||||
CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
<Text className={styles['mobile-text']} size="xs">
|
||||
Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
|
||||
</Text>
|
||||
</Box>
|
||||
<Box
|
||||
component="header"
|
||||
className={styles.header}
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "0.8rem 2rem",
|
||||
background: "linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
color: "white",
|
||||
boxShadow: "0 2px 6px rgba(0,0,0,0.15)",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
<Group gap="md">
|
||||
<Image
|
||||
src={logo}
|
||||
component={NextImage}
|
||||
fit="contain"
|
||||
alt="ebanking"
|
||||
style={{ width: "60px", height: "auto" }}
|
||||
/>
|
||||
<div>
|
||||
<Title order={3} style={{ fontFamily: "Roboto", color: "white", marginBottom: 2 }}>
|
||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||
</Title>
|
||||
<Text size="xs" c="white" style={{ opacity: 0.85 }}>
|
||||
Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
{/* Movable text */}
|
||||
<Box
|
||||
@@ -526,8 +535,30 @@ export default function Login() {
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
/>
|
||||
</div>
|
||||
<Box w={{ base: "100%", md: "45%" }} p="lg">
|
||||
<Card shadow="md" padding="xl" radius="md" style={{ maxWidth: 550, height: '70vh', justifyContent: 'space-between' }} >
|
||||
<Box
|
||||
w={{ base: "100%", md: "45%" }}
|
||||
p="lg"
|
||||
style={{
|
||||
height: "100%", // becomes 80vh automatically
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
shadow="md"
|
||||
padding="xl"
|
||||
radius="md"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%", // fills Box → fills 80vh
|
||||
maxWidth: "100%",
|
||||
overflowY: "auto",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
>
|
||||
<form onSubmit={handleLogin}>
|
||||
<TextInput
|
||||
label="User ID / User Name"
|
||||
@@ -619,7 +650,7 @@ export default function Login() {
|
||||
{isLogging ? "Processing..." : buttonLabel}
|
||||
</Button>
|
||||
<Box mt="xs">
|
||||
<Text size="sm">
|
||||
<Text size="md">
|
||||
<Text component="span" c="red" fw={600}>Note: </Text>
|
||||
<Text component="span" c="black">
|
||||
Existing users logging in to the new Internet Banking for the first time should use their CIF number to avoid login issues.
|
||||
@@ -630,6 +661,7 @@ export default function Login() {
|
||||
</Card>
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
{/* Carousel and Notes */}
|
||||
<Flex direction={{ base: "column", md: "row" }} mt="md" px="md" py="sm" gap="sm">
|
||||
<Box w={{ base: "100%", md: "85%" }}>
|
||||
@@ -639,7 +671,14 @@ export default function Login() {
|
||||
<Title order={2}> <IconShieldLockFilled />Security Notes :</Title>
|
||||
<Text mt="sm" size="md">When you Login, Your User Id and Password travels in an encrypted and highly secured mode.</Text>
|
||||
<Text mt="sm" fs="italic">For more information on Products and Services, Please Visit</Text>
|
||||
<Anchor href="http://www.kccb.in/"> http://www.kccb.in/</Anchor>
|
||||
<Anchor
|
||||
href="https://kccbhp.bank.in/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
https://kccbhp.bank.in/
|
||||
</Anchor>
|
||||
|
||||
</Box>
|
||||
</Flex>
|
||||
{/* Footer */}
|
||||
|
||||