33 Commits
admin ... dev

Author SHA1 Message Date
e968cd296f fix : responsive for set login and transaction password 2025-12-30 16:39:05 +05:30
97272e2ded feat : integrated forget password logic.
feat: make the screen responsive.
fix: change the frontend of login screen
2025-12-30 15:59:51 +05:30
9925905a23 fix : client visual requirement 2025-12-26 14:56:05 +05:30
6c153b6421 fix: approve balance in loan 2025-12-26 13:48:49 +05:30
1a57f18d40 feat: admin module
fix: mobile number
2025-12-23 13:33:30 +05:30
2f4858927c Merge branch 'dev' of https://7o9o-lb-526275444.ap-south-1.elb.amazonaws.com/tomosa.sarkar/IB into admin_feature 2025-12-23 11:17:20 +05:30
0c019d52de fix : E mandate 2025-12-15 13:46:45 +05:30
51e0c6dad6 fix: e mandate 2025-12-15 12:27:52 +05:30
5a6d324ec9 fix: e mandate 2025-12-15 12:18:14 +05:30
793c0920f9 fix: send eMandate encrypted data into form data 2025-12-10 10:56:36 +05:30
c8377b7a88 fix: masked CIF 2025-12-09 12:20:05 +05:30
df0058541e fix: eMandate login page
fix: reserver url
2025-12-08 16:41:30 +05:30
74297959d7 feat : after successful OTP I send the return request. 2025-12-08 12:33:08 +05:30
ad758eb14d fix: design in view profile and account overview
feat : page add for e mandate otp
2025-12-06 13:54:20 +05:30
cf9faf2e82 fix: Create report for pdf and excel
feat: Change design the Application
2025-11-28 17:14:59 +05:30
c1d0519c09 Fix: design and few function 2025-11-27 18:13:06 +05:30
7460157b46 changes : Changes the design of IB 2025-11-24 18:07:06 +05:30
f4b1752fe2 fix: Design : add recommended header. 2025-11-20 13:45:55 +05:30
6258080848 fix: Change bank name
fix: otp filled null after complete the process
2025-11-18 16:13:23 +05:30
a399856a1b feat : admin feature 2025-11-18 15:46:47 +05:30
e3c9458ace feat : add beneficiary api for inside bank 2025-11-18 15:42:19 +05:30
c2c87f8c71 fix: E mandate Login and logout page 2025-11-12 15:15:48 +05:30
f1ff27ccf0 fix: E mandate validation 2025-11-12 13:49:54 +05:30
6de3508021 feat : connect api for e mandate validation 2025-11-12 12:59:33 +05:30
eb8f5a7931 fix: EMandate 2025-11-06 17:21:28 +05:30
7b23ec532f fix: eMandate 2025-11-06 17:18:17 +05:30
dca5c94030 fix :eMandate 2025-11-06 17:08:30 +05:30
29eaa06ff9 fix: emandate issue 2025-11-06 15:17:18 +05:30
f7bd1200ec fix: For eMandate fetching the data from from data of the request 2025-11-06 10:51:43 +05:30
ab3308b26f fix: Emandate page 2025-11-05 14:45:47 +05:30
d02bb382be fix: EMandate issue 2025-11-05 13:43:01 +05:30
a67eddd293 fix : Added post method for fetching the eMandate page 2025-11-05 13:26:35 +05:30
960b052d55 fix: change captcha font family 2025-11-05 11:15:37 +05:30
68 changed files with 8383 additions and 2081 deletions

934
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,7 @@
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"exceljs": "^4.4.0",
"html2pdf.js": "^0.12.0", "html2pdf.js": "^0.12.0",
"ib": "file:", "ib": "file:",
"IB": "file:", "IB": "file:",

BIN
public/kccb_watermark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -2,15 +2,36 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { import {
Group, Container,
Paper, Paper,
Select, Select,
Stack, Stack,
Text, Text,
Title, Title,
Group,
Badge,
Divider,
Loader,
Center,
Card,
SimpleGrid,
ThemeIcon,
Box,
rem,
Grid,
} from "@mantine/core"; } from "@mantine/core";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { useRouter } from "next/navigation"; import {
IconCreditCard,
IconWallet,
IconTrendingUp,
IconBuilding,
IconCircleCheck,
IconAlertCircle,
IconUser,
IconFileText,
IconCircleDot,
} from "@tabler/icons-react";
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
interface accountData { interface accountData {
@@ -19,23 +40,24 @@ interface accountData {
stAvailableBalance: string; stAvailableBalance: string;
custname: string; custname: string;
stBookingNumber: string; stBookingNumber: string;
stApprovedAmount?: string; // optional for loan accounts stApprovedAmount?: string;
} }
export default function AccountDetails() { export default function App() {
const router = useRouter();
const [accountOptions, setAccountOptions] = useState<{ value: string; label: string }[]>([]); const [accountOptions, setAccountOptions] = useState<{ value: string; label: string }[]>([]);
const [selectedAccNo, setSelectedAccNo] = useState<string | null>(null); const [selectedAccNo, setSelectedAccNo] = useState<string | null>(null);
const [authorized, setAuthorized] = useState<boolean | null>(null); const [authorized, setAuthorized] = useState<boolean | null>(null);
const [accountDetails, setAccountDetails] = useState<accountData | null>(null); const [accountDetails, setAccountDetails] = useState<accountData | null>(null);
const [loading, setLoading] = useState(false);
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const passedAccNo = searchParams.get("accNo"); const passedAccNo = searchParams.get("accNo");
useEffect(() => { useEffect(() => {
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
if (!token) { if (!token) {
setAuthorized(false); setAuthorized(false);
router.push("/login"); // router.push("/login");
} else { } else {
setAuthorized(true); setAuthorized(true);
} }
@@ -51,17 +73,27 @@ export default function AccountDetails() {
value: acc.stAccountNo, value: acc.stAccountNo,
})); }));
setAccountOptions(options); setAccountOptions(options);
if (passedAccNo) {
handleAccountSelection(passedAccNo);
}
} }
} }
}, [authorized]); }, [authorized]);
useEffect(() => {
if (authorized && passedAccNo && accountOptions.length > 0) {
// auto select account
setSelectedAccNo(passedAccNo);
// reuse existing logic
handleAccountSelection(passedAccNo);
}
}, [authorized, passedAccNo, accountOptions]);
const handleAccountSelection = async (accNo: string | null) => { const handleAccountSelection = async (accNo: string | null) => {
setSelectedAccNo(accNo); setSelectedAccNo(accNo);
setAccountDetails(null); setAccountDetails(null);
if (!accNo) return; if (!accNo) return;
setLoading(true);
try { try {
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
const response = await fetch("/api/customer", { const response = await fetch("/api/customer", {
@@ -74,14 +106,14 @@ export default function AccountDetails() {
}); });
const data: accountData[] = await response.json(); const data: accountData[] = await response.json();
// console.log(data);
if (response.ok && Array.isArray(data)) { if (response.ok && Array.isArray(data)) {
const matched = data.find((acc) => acc.stAccountNo === accNo); const matched = data.find((acc) => acc.stAccountNo === accNo);
if (matched) { if (matched) {
// Simulate approvedBalance for loan accounts
if (matched.stAccountType.toUpperCase().includes("LN")) { if (matched.stAccountType.toUpperCase().includes("LN")) {
matched.stApprovedAmount = ( matched.stApprovedAmount = (
parseFloat(matched.stAvailableBalance) + 20000 parseFloat(matched.stApprovedAmount ?? "0")
).toFixed(2); // dummy logic ).toFixed(2);
} }
setAccountDetails(matched); setAccountDetails(matched);
} else { } else {
@@ -102,79 +134,270 @@ export default function AccountDetails() {
title: "Fetch failed", title: "Fetch failed",
message: "Could not fetch account details. Try again.", 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; if (!authorized) return null;
return ( return (
<Paper shadow="sm" radius="md" p="md" withBorder h={400}>
<Title order={3} mb="md">
Account Details
</Title>
<Stack gap="md"> <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 <Select
label="Select Account Number" label="Select Account Number"
placeholder="Choose account number" placeholder="Choose an account number"
data={accountOptions} data={accountOptions}
value={selectedAccNo} value={selectedAccNo}
onChange={handleAccountSelection} onChange={handleAccountSelection}
searchable searchable
size="sm"
leftSection={<IconCreditCard size={20} />}
styles={{
input: {
borderRadius: rem(12),
},
}}
/> />
</Paper>
</Grid.Col>
{accountDetails && ( {/* Right side Account Details */}
<Paper withBorder p="md" radius="md" bg="gray.0"> <Grid.Col span={{ base: 12, md: 8 }}>
<Stack gap="sm"> <Paper
<Group p="apart"> shadow="sm"
<Text size="sm" fw={500} c="dimmed">Account Number</Text> radius="md"
<Text size="md">{accountDetails.stAccountNo}</Text> p="md"
withBorder
h={500}
style={{ display: "flex", flexDirection: "column", overflow: "auto" }}
>
<Title order={4} mb="md">
Account Details
</Title>
{/* 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>
)}
{/* 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>
<Badge size="md" variant="white" color={accountColor} radius="md">
{accountDetails.stAccountType}
</Badge>
</Group> </Group>
<Group p="apart"> <Divider color="white" opacity={0.2} my="sm" />
<Text size="sm" fw={500} c="dimmed">Account Type</Text>
<Text size="md">{accountDetails.stAccountType}</Text>
</Group>
<Group p="apart"> <Box>
<Text size="sm" fw={500} c="dimmed">Description</Text> <Text size="xs" c="white" opacity={0.8} mb={4}>
<Text size="md">{accountDetails.stBookingNumber}</Text> Description
</Group> </Text>
<Text c="white">{accountDetails.stBookingNumber}</Text>
</Box>
</Paper>
{/* Show Loan-specific fields */} {/* Balance Cards */}
{accountDetails.stAccountType.toUpperCase().includes("LN") ? ( {isLoanAccount ? (
<> <SimpleGrid cols={{ base: 1, sm: 2 }} spacing="sm">
<Group p="apart"> {/* Approved Balance */}
<Text size="sm" fw={500} c="dimmed">Approved Balance</Text> <Card shadow="sm" padding="md" radius="md" withBorder>
<Text size="md" c="gray.8"> <Group gap="xs" mb="xs">
{parseFloat(accountDetails.stApprovedAmount || "0").toLocaleString("en-IN", { <ThemeIcon size={32} radius="md" variant="light" color="teal">
minimumFractionDigits: 2, <IconCircleCheck size={20} />
})} </ThemeIcon>
<Text size="xs" c="dimmed">
Approved Balance
</Text> </Text>
</Group> </Group>
<Group p="apart"> <Title order={3} c="dark">
<Text size="sm" fw={500} c="dimmed">Available Balance</Text>
<Text size="md" c="red"> {parseFloat(accountDetails.stApprovedAmount || "0").toLocaleString(
{parseFloat(accountDetails.stAvailableBalance).toLocaleString("en-IN", { "en-IN",
{
minimumFractionDigits: 2, 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> </Text>
</Group> </Group>
</> <Title order={3} c="red">
{parseFloat(accountDetails.stAvailableBalance).toLocaleString(
"en-IN",
{
minimumFractionDigits: 2,
}
)}
</Title>
</Card>
</SimpleGrid>
) : ( ) : (
<Group p="apart"> <Card shadow="sm" padding="md" radius="md" withBorder>
<Text size="sm" fw={500} c="dimmed">Available Balance</Text> <Group gap="xs" mb="xs">
<Text size="md" c="green"> <ThemeIcon size={36} radius="md" variant="light" color="teal">
{parseFloat(accountDetails.stAvailableBalance).toLocaleString("en-IN", { <IconWallet size={24} />
minimumFractionDigits: 2, </ThemeIcon>
})} <Text size="xs" c="dimmed">
Available Balance
</Text> </Text>
</Group> </Group>
<Title order={2} c="teal">
{parseFloat(accountDetails.stAvailableBalance).toLocaleString("en-IN", {
minimumFractionDigits: 2,
})}
</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>
<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">
Account Description
</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> </Paper>
</Stack>
)} )}
{/* 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> </Stack>
</Center>
)}
</Paper> </Paper>
</Grid.Col>
</Grid>
); );
} }

View File

@@ -1,13 +1,13 @@
"use client"; "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 { DateInput } from '@mantine/dates';
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import dayjs from 'dayjs'; 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 { 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"; import { useMediaQuery } from "@mantine/hooks";
export default function AccountStatementPage() { export default function AccountStatementPage() {
@@ -168,8 +168,8 @@ export default function AccountStatementPage() {
<Grid gutter="md"> <Grid gutter="md">
{/* Left side form */} {/* Left side form */}
<Grid.Col span={{ base: 12, md: 4 }}> <Grid.Col span={{ base: 12, md: 4 }}>
<Paper shadow="sm" radius="md" p="md" withBorder h={400}> <Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Title order={4} mb="sm">Account Transactions</Title> <Title order={4} mb="sm">Transaction Filters</Title>
<Select <Select
label="Select Account Number" label="Select Account Number"
placeholder="Choose account number" placeholder="Choose account number"
@@ -202,8 +202,8 @@ export default function AccountStatementPage() {
{/* Right side transaction list */} {/* Right side transaction list */}
<Grid.Col span={{ base: 12, md: 8 }}> <Grid.Col span={{ base: 12, md: 8 }}>
<Paper shadow="sm" radius="md" p="md" withBorder h={400} style={{ display: 'flex', flexDirection: 'column' }}> <Paper shadow="sm" radius="md" p="md" withBorder h={500} style={{ display: 'flex', flexDirection: 'column' }}>
<Title order={5} mb="xs">Account Transactions</Title> <Title order={4} mb="xs">Account Transactions</Title>
<Group justify="space-between" align="center" mt="sm"> <Group justify="space-between" align="center" mt="sm">
<div> <div>
<Text fw={500} ><strong>Account No :</strong> {selectedAccNo}</Text> <Text fw={500} ><strong>Account No :</strong> {selectedAccNo}</Text>
@@ -223,17 +223,34 @@ export default function AccountStatementPage() {
size={22} size={22}
style={{ cursor: "pointer" }} style={{ cursor: "pointer" }}
onClick={() => onClick={() =>
generatePDF(selectedAccNo || "", availableBalance || "0", transactions, generatePDF(
selectedAccNo || "",
availableBalance || "0",
transactions,
localStorage.getItem("remitter_name") || "", localStorage.getItem("remitter_name") || "",
startDate ? dayjs(startDate).format("DD/MM/YYYY") : "", 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 <IconFileSpreadsheet
size={22} size={22}
style={{ cursor: "pointer" }} style={{ cursor: "pointer" }}
onClick={() => 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> </Stack>
</Center> </Center>
) : transactions.length === 0 ? ( ) : 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 ? ( ) : isMobile ? (
// ✅ Mobile View Card Layout // ✅ Mobile View Card Layout
<Stack gap="sm"> <Stack gap="sm">
@@ -291,13 +321,68 @@ export default function AccountStatementPage() {
) : ( ) : (
// ✅ Desktop View Table Layout // ✅ Desktop View Table Layout
<Table style={{ borderCollapse: "collapse", width: "100%" }}> <Table style={{ borderCollapse: "collapse", width: "100%" }}>
<thead style={{ backgroundColor: "#3385ff" }}> <thead
{/* <tr> style={{
<th style={{ ...cellStyle, textAlign: "left", color: "white" }}>Narration</th> background: "linear-gradient(56deg, rgba(24,140,186,1) 0%, rgba(62,230,132,1) 86%)",
<th style={{ ...cellStyle, textAlign: "left", color: "white" }}>Date</th> position: "sticky",
<th style={{ ...cellStyle, textAlign: "right", color: "white" }}>Amount (₹)</th> top: 0,
<th style={{ ...cellStyle, textAlign: "right", color: "white" }}>Balance (₹)</th> zIndex: 5,
</tr> */} }}
>
<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> </thead>
<tbody> <tbody>
{transactions.map((txn, i) => ( {transactions.map((txn, i) => (

View File

@@ -1,5 +1,5 @@
"use client"; "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 { usePathname } from 'next/navigation';
import Link from 'next/link'; import Link from 'next/link';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -16,7 +16,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
const [drawerOpened, setDrawerOpened] = useState(false); const [drawerOpened, setDrawerOpened] = useState(false);
const links = [ const links = [
{ label: "Account Summary", href: "/accounts" }, { label: " Account Summary", href: "/accounts" },
{ label: "Account Statement ", href: "/accounts/account_statement" }, { label: "Account Statement ", href: "/accounts/account_statement" },
{ label: "Account Details", href: "/accounts/account_details" }, { label: "Account Details", href: "/accounts/account_details" },
]; ];
@@ -33,55 +33,62 @@ export default function Layout({ children }: { children: React.ReactNode }) {
if (authorized) { if (authorized) {
return ( return (
<Box style={{ display: "flex", height: "100%", flexDirection: isMobile ? "column" : "row" }}> <Box style={{ display: "flex", height: "100%", flexDirection: "column" }}>
{/* Desktop Sidebar */} {/* ---------------- DESKTOP SIDEBAR ---------------- */}
{!isMobile && ( {!isMobile && (
<Box <>
style={{ {/* Segmented Tabs */}
width: "16%", <Box mb="1rem">
backgroundColor: "#c5e4f9", <ScrollArea type="never" offsetScrollbars>
borderRight: "1px solid #ccc", <SegmentedControl
}} fullWidth
> value={pathname}
<Stack style={{ background: "#228be6", height: "10%", alignItems: "center" }}> onChange={(value) => router.push(value)}
<Text fw={700} c="white" style={{ textAlign: "center", marginTop: "10px" }}> data={links.map((link) => ({
My Accounts label: link.label,
</Text> value: link.href,
</Stack> }))}
styles={{
root: {
backgroundColor: "#e9ecef",
borderRadius: 999,
padding: 4,
},
control: {
borderRadius: 999,
transition: "0.3s",
},
<Stack gap="sm" justify="flex-start" style={{ padding: "1rem" }}> indicator: {
{links.map((link) => { borderRadius: 999,
const isActive = pathname === link.href; background: "linear-gradient(90deg, #02a355 0%, #5483c9ff 100%)",
return ( boxShadow: "0 3px 8px rgba(0,0,0,0.15)",
<Text },
key={link.href}
component={Link} label: {
href={link.href} fontSize: 13,
c={isActive ? "darkblue" : "blue"} padding: "6px 10px",
style={{ textAlign: "center",
textDecoration: isActive ? "underline" : "none", fontWeight: 600,
fontWeight: isActive ? 600 : 400, color: "#000",
},
}} }}
> />
{link.label} </ScrollArea>
</Text>
);
})}
</Stack>
</Box> </Box>
</>
)} )}
{/* Mobile: Burger & Drawer */} {/* ---------------- MOBILE TOP BAR ---------------- */}
{isMobile && ( {isMobile && (
<> <>
{/* Top header with burger and title */}
<Box <Box
style={{ style={{
backgroundColor: "#228be6", background: "linear-gradient(135deg, #1e88e5 0%, #1565c0 100%)",
// padding: "0.5rem 1rem",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
padding: "0.7rem 1rem",
}} }}
> >
<Burger <Burger
@@ -90,11 +97,12 @@ export default function Layout({ children }: { children: React.ReactNode }) {
size="sm" size="sm"
color="white" color="white"
/> />
<Text fw={500} c="white"> <Text fw={600} c="white">
My Accounts Accounts
</Text> </Text>
</Box> </Box>
{/* MOBILE DRAWER */}
<Drawer <Drawer
opened={drawerOpened} opened={drawerOpened}
onClose={() => setDrawerOpened(false)} onClose={() => setDrawerOpened(false)}
@@ -103,27 +111,31 @@ export default function Layout({ children }: { children: React.ReactNode }) {
overlayProps={{ color: "black", opacity: 0.55, blur: 3 }} overlayProps={{ color: "black", opacity: 0.55, blur: 3 }}
styles={{ styles={{
root: { root: {
backgroundColor: "#e6f5ff", // soft background for drawer backgroundColor: "#eaf4ff",
// borderLeft: "4px solid #228be6",
// borderRadius: "8px",
}, },
}} }}
> >
{/* Logo and Drawer Header */} {/* Drawer Header */}
<Box style={{ display: "flex", alignItems: "center", marginBottom: "1rem" }}> <Box
<> style={{
<Image src={logo} alt="KCCB Logo" width={40} height={40} style={{ borderRadius: "50%" }} /> display: "flex",
<Text alignItems: "center",
fw={700} marginBottom: "1rem",
ml="10px" }}
style={{ fontSize: "18px", color: "#228be6" }}
> >
My Accounts <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> </Text>
</>
</Box> </Box>
{/* Menu Items */} {/* Drawer Items */}
<Stack gap="sm"> <Stack gap="sm">
{links.map((link) => { {links.map((link) => {
const isActive = pathname === link.href; const isActive = pathname === link.href;
@@ -138,22 +150,11 @@ export default function Layout({ children }: { children: React.ReactNode }) {
style={{ style={{
justifyContent: "flex-start", justifyContent: "flex-start",
fontWeight: isActive ? 600 : 400, fontWeight: isActive ? 600 : 400,
textDecoration: isActive ? "underline" : "none", color: isActive ? "#fff" : "#1565c0",
color: isActive ? "#fff" : "#228be6", backgroundColor: isActive ? "#1565c0" : "#dceeff",
backgroundColor: isActive ? "#228be6" : "#dceeff",
borderRadius: "8px", borderRadius: "8px",
padding: "10px 12px", padding: "10px 12px",
transition: "0.3s", transition: "0.2s",
}}
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)} onClick={() => setDrawerOpened(false)}
> >
@@ -166,9 +167,14 @@ export default function Layout({ children }: { children: React.ReactNode }) {
</> </>
)} )}
{/* ---------------- CONTENT AREA ---------------- */}
{/* Content Area */} <Box
<Box style={{ flex: 1, padding: isMobile ? "0.5rem" : "1rem", overflowY: "auto" }}> style={{
flex: 1,
padding: isMobile ? "0.5rem" : "1rem",
overflowY: "auto",
}}
>
{children} {children}
</Box> </Box>
</Box> </Box>

View File

@@ -106,7 +106,7 @@ export default function AccountSummary() {
<ScrollArea> <ScrollArea>
<Table style={{ borderCollapse: "collapse", width: "100%" }}> <Table style={{ borderCollapse: "collapse", width: "100%" }}>
<thead> <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: "left" }}>Account Type</th>
<th style={{ ...cellStyle, textAlign: "right" }}>Account No.</th> <th style={{ ...cellStyle, textAlign: "right" }}>Account No.</th>
{title.includes("Deposit Accounts (INR)") ? {title.includes("Deposit Accounts (INR)") ?
@@ -124,16 +124,16 @@ export default function AccountSummary() {
if (!authorized) return null; if (!authorized) return null;
return ( return (
<Paper shadow="sm" radius="md" p="md" withBorder> <Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Title <Title
order={isMobile ? 4 : 3} order={isMobile ? 4 : 4}
mb="md" mb="md"
style={{ textAlign: isMobile ? "center" : "left" }} style={{ textAlign: isMobile ? "center" : "left" }}
> >
Account Summary Account Summary
</Title> </Title>
{/* Responsive layout: Group for desktop, Stack for mobile */} {/* Responsive layout: Group for desktop, Stack for mobile */}
{isMobile ? ( {isMobile ? (
<Stack gap="md"> <Stack gap="md">
{depositAccounts.length > 0 && {depositAccounts.length > 0 &&

View File

@@ -135,7 +135,7 @@ export default function AddBeneficiaryOthers() {
}, [ifsccode]); }, [ifsccode]);
const validateAndSendOtp = async () => { const validateAndSendOtp = async () => {
if (!bankName || !ifsccode || !branchName || !accountNo || !confirmAccountNo || !beneficiaryType) { if (!bankName || !ifsccode || !branchName || !accountNo || !confirmAccountNo || !beneficiaryType || !nickName) {
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "red", color: "red",
@@ -145,20 +145,18 @@ export default function AddBeneficiaryOthers() {
}); });
return; return;
} }
const trimmedIfsc = ifsccode.trim().toUpperCase(); const trimmedIfsc = ifsccode.trim().toUpperCase();
const isValidIfscCode = (code: string) => { // const isValidIfscCode = (code: string) => {
return /^[A-Z]{4}0[0-9]{6}$/.test(code); // return /^[A-Z]{4}0[0-9]{6}$/.test(code);
}; // };
// if (!isValidIfscCode(trimmedIfsc)) {
if (!isValidIfscCode(trimmedIfsc)) { // notifications.show({
notifications.show({ // title: "Invalid IFSC Code",
title: "Invalid IFSC Code", // message: "Must be 11 characters: 4 uppercase letters, 0, then 6 digits (e.g., HDFC0123456)",
message: "Must be 11 characters: 4 uppercase letters, 0, then 6 digits (e.g., HDFC0123456)", // color: "red",
color: "red", // });
}); // return;
return; // }
}
if (accountNo.length < 10 || accountNo.length > 17) { if (accountNo.length < 10 || accountNo.length > 17) {
notifications.show({ notifications.show({
@@ -185,7 +183,11 @@ export default function AddBeneficiaryOthers() {
try { try {
setLoading(true); setLoading(true);
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
const response = await fetch(`/api/beneficiary/validate/outside-bank?accountNo=${accountNo}&ifscCode=${ifsccode}&remitterName=""`, 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", method: "GET",
headers: { headers: {
@@ -195,13 +197,32 @@ export default function AddBeneficiaryOthers() {
}, },
} }
); );
}
// 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(); const data = await response.json();
if (response.ok && data?.name) { if (response.ok && data?.name) {
setBeneficiaryName(data.name); setBeneficiaryName(data.name);
setValidationStatus("success"); setValidationStatus("success");
setIsVisibilityLocked(true); setIsVisibilityLocked(true);
setOtpSent(true); setOtpSent(true);
const otp = await handleSendOtp();
await handleSendOtp();
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "green", color: "green",
@@ -309,6 +330,7 @@ export default function AddBeneficiaryOthers() {
setConfirmAccountNo(''); setConfirmAccountNo('');
setBeneficiaryName(''); setBeneficiaryName('');
setNickName(''); setNickName('');
setOtp('');
setBeneficiaryType(null); setBeneficiaryType(null);
setIsVisibilityLocked(false); setIsVisibilityLocked(false);
setOtpSent(false); setOtpSent(false);
@@ -328,6 +350,7 @@ export default function AddBeneficiaryOthers() {
val = val.replace(/[^A-Z0-9]/g, ""); // block special chars val = val.replace(/[^A-Z0-9]/g, ""); // block special chars
setIfsccode(val); setIfsccode(val);
}} }}
readOnly={isVisibilityLocked}
maxLength={11} maxLength={11}
withAsterisk withAsterisk
/> />
@@ -408,6 +431,16 @@ export default function AddBeneficiaryOthers() {
withAsterisk withAsterisk
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={4}>
<TextInput
label="Nick Name (Optional)"
placeholder="Enter nickname (optional)"
value={nickName}
required
onChange={(e) => setNickName(e.currentTarget.value)}
readOnly={isVisibilityLocked}
/>
</Grid.Col>
<Grid.Col span={4}> <Grid.Col span={4}>
<TextInput <TextInput
label="Beneficiary Name" label="Beneficiary Name"
@@ -417,15 +450,6 @@ export default function AddBeneficiaryOthers() {
required required
/> />
</Grid.Col> </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 && ( {!otpSent && (
<Grid.Col > <Grid.Col >
<Group gap="sm"> <Group gap="sm">
@@ -473,7 +497,7 @@ export default function AddBeneficiaryOthers() {
{!otpVerified ? ( {!otpVerified ? (
<Button onClick={verify_otp}>Validate OTP</Button> <Button onClick={verify_otp}>Validate OTP</Button>
) : ( ) : (
<Button color="blue" size="sm" onClick={AddBen}> <Button size="sm" onClick={AddBen}>
Add Add
</Button> </Button>
)} )}

View File

@@ -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 {TextInput,Button,Select,Title,Paper,Grid,Group,Radio,Text,PasswordInput} from '@mantine/core';
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { notifications } from '@mantine/notifications'; import { notifications } from '@mantine/notifications';
import AddBeneficiaryOthers from './addBeneficiaryOthers'; import AddBeneficiaryOthers from '@/app/(main)/beneficiary/add_beneficiary/addBeneficiaryOthers';
const bankOptions = [ 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 = () => { const AddBeneficiary: React.FC = () => {
@@ -189,8 +189,8 @@ const AddBeneficiary: React.FC = () => {
if (!authorized) return null; if (!authorized) return null;
return ( return (
<Paper shadow="sm" radius="md" p="md" withBorder h={400}> <Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Title order={3} mb="md">Add Beneficiary</Title> <Title order={4} mb="md">Add Beneficiary</Title>
{/* <Radio.Group value={bankType} onChange={setBankType} name="bankType" withAsterisk mb="md"> {/* <Radio.Group value={bankType} onChange={setBankType} name="bankType" withAsterisk mb="md">
<Group justify="center"> <Group justify="center">

View 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 >
);
}

View File

@@ -0,0 +1,12 @@
"use client";
import React from "react";
import ViewBeneficiary from "./view_beneficiary/page";
export default function Beneficiary() {
return (
<ViewBeneficiary />
);
}

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import React, { useEffect, useState } from "react"; 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 { notifications } from "@mantine/notifications";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Image from "next/image"; import Image from "next/image";
@@ -215,24 +215,42 @@ export default function ViewBeneficiary() {
return ( return (
<Paper shadow="sm" radius="md" p="md" withBorder h={400}> <Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Title order={3} mb="md">My Beneficiaries</Title> <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 ? ( {beneficiaries.length === 0 ? (
<Text>No beneficiaries found.</Text> <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 highlightOnHover withTableBorder stickyHeader style={{ borderCollapse: "collapse", width: "100%" }}>
<Table.Thead> <thead style={{
<Table.Tr style={{ backgroundColor: "#3385ff" }}> background: "linear-gradient(56deg, rgba(24,140,186,1) 0%, rgba(62,230,132,1) 86%)",
<Table.Th>Bank</Table.Th> position: "sticky",
top: 0,
zIndex: 5,
}}>
<tr>
<Table.Th >Bank</Table.Th>
<Table.Th style={{ textAlign: "right" }}>Account No</Table.Th> <Table.Th style={{ textAlign: "right" }}>Account No</Table.Th>
<Table.Th>Name</Table.Th> <Table.Th >Name</Table.Th>
<Table.Th>Type</Table.Th> <Table.Th >Type</Table.Th>
<Table.Th style={{ textAlign: "center" }}>IFSC</Table.Th> <Table.Th style={{ textAlign: "center" }}>IFSC</Table.Th>
<Table.Th style={{ textAlign: "center" }}>Action Icon</Table.Th> <Table.Th style={{ textAlign: "center" }}>Action Icon</Table.Th>
</Table.Tr> </tr>
</Table.Thead> </thead>
<Table.Tbody> <Table.Tbody>
{beneficiaries.map((b, i) => ( {beneficiaries.map((b, i) => (
<Table.Tr key={i}> <Table.Tr key={i}>
@@ -295,7 +313,7 @@ export default function ViewBeneficiary() {
) : ( ) : (
<> <>
<Text mb="sm">Enter OTP sent to your registered number:</Text> <Text mb="sm">Enter OTP sent to your registered number:</Text>
<TextInput <PasswordInput
value={otp} value={otp}
onChange={(e) => setOtp(e.currentTarget.value)} onChange={(e) => setOtp(e.currentTarget.value)}
placeholder="Enter OTP" placeholder="Enter OTP"

View File

@@ -1,12 +1,12 @@
"use client"; "use client";
import { Box, Burger, Button, Divider, Drawer, Stack, Text } from '@mantine/core'; import { Box, Burger, Button, Drawer, ScrollArea, SegmentedControl, Stack, Text } from "@mantine/core";
import { usePathname } from 'next/navigation'; import { usePathname } from "next/navigation";
import Link from 'next/link'; import Link from "next/link";
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Image from "next/image"; import Image from "next/image";
import logo from "@/app/image/logo1.jpg"; 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 }) { export default function Layout({ children }: { children: React.ReactNode }) {
const [authorized, SetAuthorized] = useState<boolean | null>(null); const [authorized, SetAuthorized] = useState<boolean | null>(null);
@@ -16,73 +16,79 @@ export default function Layout({ children }: { children: React.ReactNode }) {
const [drawerOpened, setDrawerOpened] = useState(false); const [drawerOpened, setDrawerOpened] = useState(false);
const links = [ const links = [
{ label: " Quick Pay", href: "/funds_transfer" }, { label: "Quick Pay", href: "/funds_transfer" },
{ label: "Add Beneficiary", href: "/funds_transfer/add_beneficiary" }, { label: "Bank Transfer", href: "/funds_transfer/send_beneficiary" },
{ label: "View Beneficiary ", href: "/funds_transfer/view_beneficiary" },
{ label: "Send to Beneficiary", href: "/funds_transfer/send_beneficiary" },
]; ];
useEffect(() => { useEffect(() => {
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
if (!token) { if (!token) {
SetAuthorized(false); SetAuthorized(false);
router.push("/login"); router.push("/login");
} } else {
else {
SetAuthorized(true); SetAuthorized(true);
} }
}, []); }, []);
if (authorized) { if (!authorized) return null;
return (
<Box style={{ display: "flex", height: "100%", flexDirection: isMobile ? "column" : "row" }}>
{/* 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" }}>
Send Money
</Text>
</Stack>
<Stack gap="sm" justify="flex-start" style={{ padding: "1rem" }}>
{links.map((link) => {
const isActive = pathname === link.href;
return ( return (
<Text <Box style={{ display: "flex", height: "100%", flexDirection: "column" }}>
key={link.href} {/* ---------------- DESKTOP SIDEBAR ---------------- */}
component={Link} {!isMobile && (
href={link.href} <>
c={isActive ? "darkblue" : "blue"} {/* Segmented Tabs */}
style={{ <Box mb="1rem">
textDecoration: isActive ? "underline" : "none", <ScrollArea type="never" offsetScrollbars>
fontWeight: isActive ? 600 : 400, <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",
},
}} }}
> />
{link.label} </ScrollArea>
</Text>
);
})}
</Stack>
</Box> </Box>
</>
)} )}
{/* Mobile: Burger & Drawer */} {/* ---------------- MOBILE TOP BAR ---------------- */}
{isMobile && ( {isMobile && (
<> <>
{/* Top header with burger and title */}
<Box <Box
style={{ style={{
backgroundColor: "#228be6", background: "linear-gradient(135deg, #1e88e5 0%, #1565c0 100%)",
// padding: "0.5rem 1rem",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
padding: "0.7rem 1rem",
}} }}
> >
<Burger <Burger
@@ -91,11 +97,12 @@ export default function Layout({ children }: { children: React.ReactNode }) {
size="sm" size="sm"
color="white" color="white"
/> />
<Text fw={500} c="white"> <Text fw={600} c="white">
Send Money Send Money
</Text> </Text>
</Box> </Box>
{/* MOBILE DRAWER */}
<Drawer <Drawer
opened={drawerOpened} opened={drawerOpened}
onClose={() => setDrawerOpened(false)} onClose={() => setDrawerOpened(false)}
@@ -104,27 +111,31 @@ export default function Layout({ children }: { children: React.ReactNode }) {
overlayProps={{ color: "black", opacity: 0.55, blur: 3 }} overlayProps={{ color: "black", opacity: 0.55, blur: 3 }}
styles={{ styles={{
root: { root: {
backgroundColor: "#e6f5ff", // soft background for drawer backgroundColor: "#eaf4ff",
// borderLeft: "4px solid #228be6",
// borderRadius: "8px",
}, },
}} }}
> >
{/* Logo and Drawer Header */} {/* Drawer Header */}
<Box style={{ display: "flex", alignItems: "center", marginBottom: "1rem" }}> <Box
<> style={{
<Image src={logo} alt="KCCB Logo" width={40} height={40} style={{ borderRadius: "50%" }} /> display: "flex",
<Text alignItems: "center",
fw={700} marginBottom: "1rem",
ml="10px" }}
style={{ fontSize: "18px", color: "#228be6" }}
> >
<Image
src={logo}
alt="KCCB Logo"
width={45}
height={45}
style={{ borderRadius: "50%" }}
/>
<Text fw={700} ml="10px" style={{ fontSize: "19px", color: "#1565c0" }}>
Send Money Send Money
</Text> </Text>
</>
</Box> </Box>
{/* Menu Items */} {/* Drawer Items */}
<Stack gap="sm"> <Stack gap="sm">
{links.map((link) => { {links.map((link) => {
const isActive = pathname === link.href; const isActive = pathname === link.href;
@@ -139,22 +150,11 @@ export default function Layout({ children }: { children: React.ReactNode }) {
style={{ style={{
justifyContent: "flex-start", justifyContent: "flex-start",
fontWeight: isActive ? 600 : 400, fontWeight: isActive ? 600 : 400,
textDecoration: isActive ? "underline" : "none", color: isActive ? "#fff" : "#1565c0",
color: isActive ? "#fff" : "#228be6", backgroundColor: isActive ? "#1565c0" : "#dceeff",
backgroundColor: isActive ? "#228be6" : "#dceeff",
borderRadius: "8px", borderRadius: "8px",
padding: "10px 12px", padding: "10px 12px",
transition: "0.3s", transition: "0.2s",
}}
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)} onClick={() => setDrawerOpened(false)}
> >
@@ -167,12 +167,16 @@ export default function Layout({ children }: { children: React.ReactNode }) {
</> </>
)} )}
{/* ---------------- CONTENT AREA ---------------- */}
{/* Content Area */} <Box
<Box style={{ flex: 1, padding: isMobile ? "0.5rem" : "1rem", overflowY: "auto" }}> style={{
flex: 1,
padding: isMobile ? "0.5rem" : "1rem",
overflowY: "auto",
}}
>
{children} {children}
</Box> </Box>
</Box> </Box>
); );
}
} }

View File

@@ -115,7 +115,7 @@ export default function QuickPay() {
}); });
const data = await response.json(); const data = await response.json();
if (response.ok && Array.isArray(data)) { 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); setAccountData(filterSAaccount);
} }
} catch { } catch {
@@ -395,12 +395,12 @@ export default function QuickPay() {
</Group> </Group>
</Modal> </Modal>
{/* main content */} {/* main content */}
<Paper shadow="sm" radius="md" p="md" withBorder h={400}> <Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Title order={3} mb="md"> <Title order={4} mb="md">
Quick Pay - Own Bank Quick Pay - Own Bank
</Title> </Title>
<div style={{ maxHeight: "290px", overflowY: "auto" }}> <div style={{ maxHeight: "350px", overflowY: "auto" }}>
<Stack gap="xs"> <Stack gap="xs">
<Group grow> <Group grow>
<Select <Select

View File

@@ -143,7 +143,7 @@ export default function SendToBeneficiaryOwn() {
}); });
const data = await response.json(); const data = await response.json();
if (response.ok && Array.isArray(data)) { 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); setAccountData(filterSAaccount);
} }
} catch { } catch {
@@ -395,11 +395,9 @@ export default function SendToBeneficiaryOwn() {
</Group> </Group>
</Modal> </Modal>
{/* main content */} {/* main content */}
<Paper shadow="sm" radius="md" p="md" withBorder h={400}> <Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Title order={3} mb="md"> <Title order={4} mb="md">
Send To Beneficiary Send To Beneficiary
</Title> </Title>
<Radio.Group value={bankType} onChange={setBankType} name="bankType" withAsterisk mb="md"> <Radio.Group value={bankType} onChange={setBankType} name="bankType" withAsterisk mb="md">

View File

@@ -200,7 +200,7 @@ export default function SendToBeneficiaryOthers() {
}); });
const data = await response.json(); const data = await response.json();
if (response.ok && Array.isArray(data)) { 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); setAccountData(filterSAaccount);
} }
} catch { } catch {
@@ -546,7 +546,7 @@ export default function SendToBeneficiaryOthers() {
{/* main content */} {/* main content */}
{!showIntroModal && ( {!showIntroModal && (
<div style={{ maxHeight: "290px", overflowY: "auto" }}> <div style={{ maxHeight: "350px", overflowY: "auto" }}>
<Stack gap={5} justify="flex-start"> <Stack gap={5} justify="flex-start">
<Group grow gap='xs' > <Group grow gap='xs' >
<Select <Select

View File

@@ -21,6 +21,7 @@ export default function Home() {
const [authorized, SetAuthorized] = useState<boolean | null>(null); const [authorized, SetAuthorized] = useState<boolean | null>(null);
const router = useRouter(); const router = useRouter();
const isMobile = useMediaQuery("(max-width: 768px)"); const isMobile = useMediaQuery("(max-width: 768px)");
const [userName, setUserName] = useState<string>("");
const [accountData, SetAccountData] = useState<accountData[]>([]); const [accountData, SetAccountData] = useState<accountData[]>([]);
const depositAccounts = accountData.filter(acc => acc.stAccountType !== "LN"); const depositAccounts = accountData.filter(acc => acc.stAccountType !== "LN");
const [selectedDA, setSelectedDA] = useState(depositAccounts[0]?.stAccountNo || ""); const [selectedDA, setSelectedDA] = useState(depositAccounts[0]?.stAccountNo || "");
@@ -117,6 +118,9 @@ export default function Home() {
useEffect(() => { useEffect(() => {
if (authorized) { if (authorized) {
handleFetchUserDetails(); handleFetchUserDetails();
const fullName = localStorage.getItem("remitter_name") || "User";
// const first = fullName.split(" ")[0];
setUserName(fullName);
} }
}, [authorized]); }, [authorized]);
@@ -124,38 +128,188 @@ export default function Home() {
return ( return (
<Providers> <Providers>
<Box p={isMobile ? "8px" : "10px"}> <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 Accounts Overview
</Title> </Title>
{/* Show Balance Switch */}
<Text size="sm" c="dimmed" mb={isMobile ? 10 : 14}>
Your accounts at a glance
</Text>
{/* --------------------- SHOW BALANCE TOGGLE ---------------------- */}
<Group <Group
style={{ style={{
flex: 1, marginBottom: isMobile ? "10px" : "14px",
// padding: isMobile ? "5px" : "10px 10px 4px 10px",
marginLeft: isMobile ? 0 : "10px",
display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "flex-start", justifyContent: "flex-start",
height: "auto", gap: 10,
gap: isMobile ? 5 : 10,
}} }}
> >
<IconEye size={isMobile ? 16 : 20} /> <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 Show Balance
</Text> </Text>
<Switch <Switch
size={isMobile ? "sm" : "md"} size={isMobile ? "sm" : "md"}
onLabel="ON"
offLabel="OFF"
checked={showBalance} checked={showBalance}
onChange={(event) => setShowBalance(event.currentTarget.checked)} onChange={(event) => setShowBalance(event.currentTarget.checked)}
/> />
</Group> </Group>
{/* Cards Section */} {/* ----------------------- DESKTOP VIEW ------------------------ */}
{isMobile ? ( {!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" }}> <Stack gap="md" style={{ width: "100%", marginTop: "10px" }}>
{/* Deposit Account Card */} {/* Deposit Account Card */}
<Paper <Paper
@@ -189,7 +343,7 @@ export default function Home() {
backgroundColor: "white", backgroundColor: "white",
color: "black", color: "black",
marginLeft: 5, marginLeft: 5,
width: "120px", width: "150px",
}, },
}} }}
/> />
@@ -262,7 +416,7 @@ export default function Home() {
backgroundColor: "white", backgroundColor: "white",
color: "black", color: "black",
marginLeft: 5, marginLeft: 5,
width: "120px", width: "150px",
}, },
}} }}
/> />
@@ -318,172 +472,42 @@ export default function Home() {
Quick Links Quick Links
</Title> </Title>
<Stack gap="xs"> <Stack gap="xs">
<Button variant="light" color="blue" fullWidth> <Button
Loan EMI Calculator variant="light"
color="green"
fullWidth
onClick={() => window.open("https://kccbhp.bank.in/about-us/history-of-kccb/", "_blank")}
>
About Us
</Button> </Button>
<Button variant="light" color="blue" fullWidth> <Button variant="light" color="green" fullWidth component="a" href="/BranchLocator" target="_blank">
Branch Locator Branch Locator
</Button> </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 Customer Care
</Button> </Button>
<Button variant="light" color="blue" fullWidth> <Button variant="light" color="green" fullWidth component="a" href="/FAQs" target="_blank">
FAQs FAQs
</Button> </Button>
</Stack> </Stack>
</Paper> </Paper>
</Stack> </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 */} {/* -------------------- NOTES SECTION (BOTTOM) --------------------- */}
<Box style={{ padding: "5px", display: "flex", justifyContent: "left" }}> <Box mt="md">
<Stack> <Stack>
<Text fw={700}> ** Book Balance includes uncleared effect.</Text> <Text fw={700}> ** Book Balance includes uncleared effect.</Text>
<Text fw={700}> ** Click on the &quot;Show Balance&quot; to display balance for the Deposit and Loan account.</Text> <Text fw={700}> ** Click &quot;Show Balance&quot; to display account balances.</Text>
<Text fw={400} c="red"> <Text fw={400} c="red">
** Your Password will expire in {PassExpiryRemains} days. ** Your Password will expire in {PassExpiryRemains} days.
</Text> </Text>
</Stack> </Stack>
</Box> </Box>
</Box> </Box>
</Providers> </Providers>
); );

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Box, Button, Divider, Group, Image, Modal, Popover, Stack, Text, Title } from '@mantine/core'; import { Anchor, Box, Button, Container, Divider, Group, Image, Modal, Popover, Stack, Switch, Text, Title, Grid } from '@mantine/core';
import { IconBook, IconCurrencyRupee, IconHome, IconLogout, IconPhoneFilled, IconSettings } from '@tabler/icons-react'; import { IconHome, IconLogout, IconMoon, IconSend, IconSettings, IconSun, IconUserCircle, IconUsers, IconWallet } from '@tabler/icons-react';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter, usePathname } from "next/navigation"; import { useRouter, usePathname } from "next/navigation";
import { Providers } from '../providers'; import { Providers } from '../providers';
@@ -10,6 +10,7 @@ import NextImage from 'next/image';
import { notifications } from '@mantine/notifications'; import { notifications } from '@mantine/notifications';
import { useDisclosure, useMediaQuery } from '@mantine/hooks'; import { useDisclosure, useMediaQuery } from '@mantine/hooks';
import { fetchAndStoreUserName } from '../_util/userdetails'; import { fetchAndStoreUserName } from '../_util/userdetails';
import styles from './page.module.css';
export default function RootLayout({ children }: { children: React.ReactNode }) { export default function RootLayout({ children }: { children: React.ReactNode }) {
const router = useRouter(); const router = useRouter();
@@ -20,6 +21,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
const isMobile = useMediaQuery("(max-width: 768px)"); const isMobile = useMediaQuery("(max-width: 768px)");
const [sessionModal, setSessionModal] = useState(false); const [sessionModal, setSessionModal] = useState(false);
const [countdown, setCountdown] = useState(30); // 30 sec countdown before auto logout 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); const [opened, { open, close }] = useDisclosure(false);
@@ -38,6 +42,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
doLogout() doLogout()
router.push("/login"); router.push("/login");
} }
// Toggle Dark/Light Mode
const toggleDarkMode = () => {
setDarkMode((prevMode) => !prevMode);
};
// When reload and click on back then logout // When reload and click on back then logout
useEffect(() => { useEffect(() => {
@@ -171,10 +179,70 @@ export default function RootLayout({ children }: { children: React.ReactNode })
}; };
}, []); }, []);
// useEffect(() => {
// const INACTIVITY_LIMIT = 5 * 60 * 1000; // 5 minutes
// let inactivityTimer: NodeJS.Timeout;
// let countdownTimer: NodeJS.Timeout;
// let isSessionExpired = false; // lock flag
// const startLogoutCountdown = () => {
// isSessionExpired = true; // lock session
// setSessionModal(true);
// setCountdown(30);
// countdownTimer = setInterval(() => {
// setCountdown((prev) => {
// if (prev <= 1) {
// clearInterval(countdownTimer);
// doLogout();
// return 0;
// }
// return prev - 1;
// });
// }, 1000);
// };
// const resetInactivityTimer = () => {
// // If modal already shown, ignore ALL activity
// if (isSessionExpired) return;
// clearTimeout(inactivityTimer);
// inactivityTimer = setTimeout(() => {
// startLogoutCountdown();
// }, INACTIVITY_LIMIT);
// };
// const activityEvents = [
// "mousemove",
// "mousedown",
// "keydown",
// "scroll",
// "touchstart",
// "click",
// ];
// activityEvents.forEach((event) =>
// window.addEventListener(event, resetInactivityTimer)
// );
// // Start timer on load
// resetInactivityTimer();
// return () => {
// activityEvents.forEach((event) =>
// window.removeEventListener(event, resetInactivityTimer)
// );
// clearTimeout(inactivityTimer);
// clearInterval(countdownTimer);
// };
// }, []);
const navItems = [ const navItems = [
{ href: "/home", label: "Home", icon: IconHome }, { href: "/home", label: "Home", icon: IconHome },
{ href: "/accounts", label: "Accounts", icon: IconBook }, { href: "/accounts", label: "Accounts", icon: IconWallet },
{ href: "/funds_transfer", label: "Send Money", icon: IconCurrencyRupee }, { href: "/funds_transfer", label: "Fund Transfer", icon: IconSend },
{ href: "/beneficiary", label: "Beneficiaries", icon: IconUsers },
{ href: "/settings", label: "Settings", icon: IconSettings }, { href: "/settings", label: "Settings", icon: IconSettings },
]; ];
@@ -183,113 +251,182 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<html lang="en"> <html lang="en">
<body> <body>
<Providers> <Providers>
<Box style={{ backgroundColor: "#e6ffff", minHeight: "100vh", display: "flex", flexDirection: "column", padding: 0, margin: 0 }}> <Box style={{ minHeight: "100%", display: "flex", flexDirection: "column" }}>
{/* HEADER */} {/* HEADER */}
<Box <Box
component="header"
className={styles.header}
style={{ style={{
width: "100%", width: "100%",
display: "flex", padding: isMobile ? "0.6rem 1rem" : "0.8rem 2rem",
height: "60px", background: darkMode
// flexDirection: "row", ? "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", alignItems: "center",
justifyContent: "flex-start", justifyContent: "space-between",
// padding: isMobile ? "0.5rem" : "0.5rem 1rem", color: "white",
background: "linear-gradient(15deg,rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)", boxShadow: "0 2px 6px rgba(0,0,0,0.15)",
position: "relative", position: "sticky",
// position: "fixed", top: 0,
zIndex: 200, // ↑ Increased for stability
}} }}
> >
{/* Logo */} <Group gap="md" wrap="nowrap">
<Box style={{ width: isMobile ? "48px" : "65px", height: isMobile ? "50px" : "60px" }}> <Image
<Image src={logo} component={NextImage} alt="ebanking" style={{ width: "100%", height: "100%" }} /> src={logo}
</Box> component={NextImage}
fit="contain"
alt="ebanking"
style={{
width: isMobile ? "40px" : "60px",
height: "auto",
}}
/>
{/* Title & Phone */} <div>
<Stack gap={isMobile ? 2 : 0} style={{ flex: 1, textAlign: isMobile ? "center" : "left", marginLeft: isMobile ? 0 : "1rem" }}> <Title
<Title order={isMobile ? 5 : 2} style={{ color: "white", fontFamily: "Roboto", lineHeight: 1.2 }}> 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. THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
</Title> </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 */} {!isMobile && (
<Box <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={{ style={{
flexShrink: 0, fontWeight: 500,
padding: isMobile ? "0.5rem" : "0.5rem 1rem", padding: isMobile ? "4px 8px" : "6px 12px",
display: "flex", fontSize: isMobile ? "12px" : "14px",
flexDirection: isMobile ? "column" : "row",
justifyContent: "space-between",
alignItems: isMobile ? "flex-start" : "center",
gap: isMobile ? "0.5rem" : 0,
}} }}
> >
<Stack gap={isMobile ? 2 : 0} align={isMobile ? "flex-start" : "flex-start"}> Welcome, {firstName}
<Title order={isMobile ? 5 : 4} style={{ fontFamily: "inter", fontSize: isMobile ? "18px" : "22px" }}> </Button>
Welcome, {custname ?? null} </Popover.Target>
</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 mt={isMobile ? "sm" : "md"} gap="sm" style={{ flexWrap: isMobile ? "wrap" : "nowrap" }}> <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>
</Stack>
</Popover.Dropdown>
</Popover>
</Box>
{/* 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,
/* MOBILE FIX make it scrollable */
overflowX: isMobile ? "auto" : "visible",
whiteSpace: isMobile ? "nowrap" : "normal",
padding: isMobile ? "6px 4px" : "0.05rem",
}}
>
{navItems.map((item) => { {navItems.map((item) => {
const isActive = pathname.startsWith(item.href); const isActive = pathname.startsWith(item.href);
const Icon = item.icon; const Icon = item.icon;
return ( return (
<Link key={item.href} href={item.href}> <Link key={item.href} href={item.href} style={{ textDecoration: "none" }}>
<Button <Group
leftSection={<Icon size={isMobile ? 16 : 20} />} gap={8}
variant={isActive ? "dark" : "subtle"} style={{
color={isActive ? "blue" : undefined} padding: isMobile ? "10px 14px" : "14px 16px",
size={isMobile ? "xs" : "sm"} // 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} {item.label}
</Button> </Text>
</Group>
</Link> </Link>
); );
})} })}
<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}>
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> </Group>
</Popover.Dropdown>
</Popover>
</Group>
</Box>
<Divider size="xs" color="#99c2ff" /> {/* CONTENT */}
{/* CHILDREN */}
<Box <Box
style={{ style={{
flex: 1, flex: 1,
overflowY: "auto", backgroundColor: "#f5f7fa",
borderTop: "1px solid #ddd", padding: isMobile ? "0.8rem" : "1.5rem",
borderBottom: "1px solid #ddd", }}
// padding: isMobile ? "0.5rem" : "1rem", >
<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} {children}
</Box> </Box>
</Box>
{/* this model for session logout */} {/* this model for session logout */}
<Modal <Modal
opened={sessionModal} opened={sessionModal}
@@ -317,27 +454,113 @@ export default function RootLayout({ children }: { children: React.ReactNode })
</Stack> </Stack>
</Modal> </Modal>
<Divider size="xs" color="blue" /> {/* FOOTER (desktop same, mobile stacked) */}
{/* FOOTER */}
<Box <Box
component="footer"
style={{ style={{
flexShrink: 0, backgroundColor: "rgba(60, 54, 74, 1)",
display: "flex", paddingTop: "2rem",
justifyContent: "center", paddingBottom: "2rem",
alignItems: "center", color: "white",
backgroundColor: "#f8f9fa",
// padding: isMobile ? "0.25rem" : "0.5rem",
}} }}
> >
<Text c="dimmed" size={isMobile ? "xs" : "sm"}> <Container size="xl">
© 2025 The Kangra Central Co-Operative Bank <Grid gutter="xl">
</Text>
<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> </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">MonSat 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> </Box>
</Providers> </Providers>
</body> </body>
</html > </html>
); );
} }
} }

View 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;
}
}

View File

@@ -58,10 +58,6 @@ export default function ChangePassword() {
} }
} }
useEffect(() => { useEffect(() => {
regenerateCaptcha(); regenerateCaptcha();
}, []); }, []);
@@ -232,8 +228,8 @@ export default function ChangePassword() {
}; };
return ( return (
<Paper shadow="sm" radius="md" p="md" withBorder h={400}> <Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Title order={3} mb="sm"> <Title order={4} mb="sm">
Change Login Password Change Login Password
</Title> </Title>
{/* Scrollable form area */} {/* Scrollable form area */}
@@ -291,7 +287,7 @@ export default function ChangePassword() {
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
userSelect: "none", userSelect: "none",
textDecoration: "line-through", textDecoration: "line-through",
fontFamily: "cursive", fontFamily: "Verdana",
}} }}
> >
{captcha} {captcha}

View File

@@ -240,8 +240,8 @@ export default function ChangePassword() {
}; };
return ( return (
<Paper shadow="sm" radius="md" p="md" withBorder h={400}> <Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Title order={3} mb="sm"> <Title order={4} mb="sm">
Change Transaction Password Change Transaction Password
</Title> </Title>
@@ -298,7 +298,7 @@ export default function ChangePassword() {
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
userSelect: "none", userSelect: "none",
textDecoration: "line-through", textDecoration: "line-through",
fontFamily: "cursive", fontFamily: "Verdana",
}} }}
> >
{captcha} {captcha}

View File

@@ -1,15 +1,24 @@
"use client"; "use client";
import { Box, Burger, Button, Divider, Drawer, Stack, Text } from '@mantine/core';
import { usePathname } from 'next/navigation'; import {
import Link from 'next/link'; Box,
import React, { useEffect, useState } from 'react'; Stack,
import { useRouter } from "next/navigation"; Text,
import { useMediaQuery } from '@mantine/hooks'; 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 Image from "next/image";
import logo from "@/app/image/logo1.jpg"; import logo from "@/app/image/logo1.jpg";
import Link from "next/link";
export default function Layout({ children }: { children: React.ReactNode }) { 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 router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
const isMobile = useMediaQuery("(max-width: 768px)"); const isMobile = useMediaQuery("(max-width: 768px)");
@@ -21,68 +30,81 @@ export default function Layout({ children }: { children: React.ReactNode }) {
{ label: "Change transaction Password", href: "/settings/change_txn_password" }, { label: "Change transaction Password", href: "/settings/change_txn_password" },
{ label: "Set transaction Password", href: "/settings/set_txn_password" }, { label: "Set transaction Password", href: "/settings/set_txn_password" },
{ label: "Preferred Name", href: "/settings/user_name" }, { 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(() => { useEffect(() => {
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
if (!token) { if (!token) {
SetAuthorized(false); setAuthorized(false);
router.push("/login"); router.push("/login");
} } else {
else { setAuthorized(true);
SetAuthorized(true);
} }
}, []); }, []);
if (authorized) { if (!authorized) return null;
return ( return (
<Box style={{ display: "flex", height: "100%", flexDirection: isMobile ? "column" : "row" }}>
{/* Desktop Sidebar */}
{!isMobile && (
<Box <Box
style={{ style={{
width: "16%", display: "flex",
backgroundColor: "#c5e4f9", flexDirection: "column",
borderRight: "1px solid #ccc", height: "100%",
}} }}
> >
<Stack style={{ background: "#228be6", height: "10%", alignItems: "center" }}> {/* ---------------- DESKTOP HEADER ---------------- */}
<Text fw={700} c="white" style={{ textAlign: "center", marginTop: "10px" }}> {!isMobile && (
Settings <>
</Text> <Box mb="1rem">
</Stack> <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" }}> indicator: {
{links.map((link) => { borderRadius: 999,
const isActive = pathname === link.href; background: "linear-gradient(90deg, #02a355 0%, #5483c9ff 100%)",
return ( boxShadow: "0 3px 8px rgba(0,0,0,0.15)",
<Text },
key={link.href}
component={Link} label: {
href={link.href} fontSize: 13,
c={isActive ? "darkblue" : "blue"} padding: "6px 10px",
style={{ textAlign: "center",
textDecoration: isActive ? "underline" : "none", fontWeight: 600,
fontWeight: isActive ? 600 : 400, color: "#000",
},
}} }}
> />
{link.label} </ScrollArea>
</Text>
);
})}
</Stack>
</Box> </Box>
</>
)} )}
{/* Mobile: Burger & Drawer */} {/* ---------------- MOBILE HEADER ---------------- */}
{isMobile && ( {isMobile && (
<> <>
{/* Top header with burger and title */} {/* Mobile Header */}
<Box <Box
style={{ style={{
backgroundColor: "#228be6", backgroundColor: "#228be6",
// padding: "0.5rem 1rem", padding: "0.8rem 1rem",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
@@ -90,72 +112,60 @@ export default function Layout({ children }: { children: React.ReactNode }) {
> >
<Burger <Burger
opened={drawerOpened} opened={drawerOpened}
onClick={() => setDrawerOpened(!drawerOpened)} onClick={() => setDrawerOpened(true)}
size="sm" size="sm"
color="white" color="white"
/> />
<Text fw={500} c="white"> <Text fw={600} c="white">
Settings Settings
</Text> </Text>
</Box> </Box>
{/* Mobile Drawer */}
<Drawer <Drawer
opened={drawerOpened} opened={drawerOpened}
onClose={() => setDrawerOpened(false)} onClose={() => setDrawerOpened(false)}
padding="md" padding="md"
size="75%" size="75%"
overlayProps={{ color: "black", opacity: 0.55, blur: 3 }} overlayProps={{ color: "black", opacity: 0.55, blur: 2 }}
styles={{ >
root: { <Box
backgroundColor: "#e6f5ff", // soft background for drawer style={{
}, display: "flex",
alignItems: "center",
marginBottom: "1.2rem",
}} }}
> >
{/* Logo and Drawer Header */} <Image
<Box style={{ display: "flex", alignItems: "center", marginBottom: "1rem" }}> src={logo}
<> alt="Logo"
<Image src={logo} alt="KCCB Logo" width={40} height={40} style={{ borderRadius: "50%" }} /> width={40}
<Text height={40}
fw={700} style={{ borderRadius: "50%" }}
ml="10px" />
style={{ fontSize: "18px", color: "#228be6" }} <Text fw={700} ml="10px" style={{ color: "#228be6", fontSize: "18px" }}>
>
Settings Settings
</Text> </Text>
</>
</Box> </Box>
{/* Menu Items */} <Stack gap="xs">
<Stack gap="sm">
{links.map((link) => { {links.map((link) => {
const isActive = pathname === link.href; const isActive = pathname === link.href;
return ( return (
<Button <Button
key={link.href} key={link.href}
variant="subtle"
component={Link} component={Link}
href={link.href} href={link.href}
fullWidth fullWidth
variant="light"
style={{ style={{
justifyContent: "flex-start", justifyContent: "flex-start",
backgroundColor: isActive ? "#228be6" : "#e2efff",
color: isActive ? "#fff" : "#1b69c7",
fontWeight: isActive ? 600 : 400, fontWeight: isActive ? 600 : 400,
textDecoration: isActive ? "underline" : "none", borderRadius: 10,
color: isActive ? "#fff" : "#228be6", padding: "10px",
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)} onClick={() => setDrawerOpened(false)}
> >
@@ -168,12 +178,16 @@ export default function Layout({ children }: { children: React.ReactNode }) {
</> </>
)} )}
{/* ---------------- CONTENT AREA ---------------- */}
{/* Content Area */} <Box
<Box style={{ flex: 1, padding: isMobile ? "0.5rem" : "1rem", overflowY: "auto" }}> style={{
flex: 1,
padding: isMobile ? "0.8rem" : "1rem",
overflowY: "auto",
}}
>
{children} {children}
</Box> </Box>
</Box> </Box>
); );
}
} }

View File

@@ -10,9 +10,10 @@ import {
Divider, Divider,
Loader, Loader,
Center, Center,
ActionIcon,
} from "@mantine/core"; } from "@mantine/core";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { useRouter } from "next/navigation"; import { IconEye, IconEyeOff } from "@tabler/icons-react";
// Response structure from backend // Response structure from backend
interface ProfileData { interface ProfileData {
@@ -24,13 +25,13 @@ interface ProfileData {
id: string; id: string;
custaddress: string; custaddress: string;
pincode: string; pincode: string;
} }
export default function ViewProfile() { export default function ViewProfile() {
const router = useRouter();
const [profileData, setProfileData] = useState<ProfileData | null>(null); const [profileData, setProfileData] = useState<ProfileData | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [showCIF, setShowCIF] = useState(false);
const [showPrimaryID, setShowPrimaryID] = useState(false);
// Fetch API with same style as RootLayout // Fetch API with same style as RootLayout
async function handleFetchProfile() { async function handleFetchProfile() {
@@ -79,15 +80,53 @@ export default function ViewProfile() {
handleFetchProfile(); 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 = ({ const Row = ({
label, label,
value, value,
link, link,
masked,
showValue,
onToggle,
}: { }: {
label: string; label: string;
value: string; value: string;
link?: string; link?: string;
}) => ( masked?: boolean;
showValue?: boolean;
onToggle?: () => void;
}) => {
const displayValue = masked && !showValue ? maskValue(value) : value;
return (
<Grid align="flex-start" gutter="xs" mb={6}> <Grid align="flex-start" gutter="xs" mb={6}>
<Grid.Col span={3}> <Grid.Col span={3}>
<Text c="dimmed" size="sm" fw={500}> <Text c="dimmed" size="sm" fw={500}>
@@ -95,23 +134,36 @@ export default function ViewProfile() {
</Text> </Text>
</Grid.Col> </Grid.Col>
<Grid.Col span={9}> <Grid.Col span={9}>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
{link ? ( {link ? (
<Anchor size="sm" href={link} target="_blank" rel="noopener noreferrer"> <Anchor size="sm" href={link} target="_blank" rel="noopener noreferrer">
{value} {displayValue}
</Anchor> </Anchor>
) : ( ) : (
<Text size="sm">{value}</Text> <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.Col>
</Grid> </Grid>
); );
};
return ( return (
<Paper shadow="xs" radius="md" p="md" withBorder h={400}> <Paper shadow="xs" radius="md" p="md" withBorder h={500}>
<Title order={4} mb="sm"> {/* <Title order={4} mb="sm">
View Profile View Profile
</Title> </Title>
<Divider mb="sm" /> <Divider mb="sm" /> */}
{loading ? ( {loading ? (
<Center> <Center>
@@ -119,22 +171,39 @@ export default function ViewProfile() {
</Center> </Center>
) : profileData ? ( ) : profileData ? (
<> <>
<Row label="Customer ID (CIF)" value={profileData.cifNumber} /> {/* Personal Details Section */}
<Row label="Customer Name" value={profileData.custname} /> <Title order={4} mb="sm" mt="md">
<Row label="ID" value={profileData.id} /> Personal Details
<Row label="Branch No" value={profileData.stBranchNo} /> </Title>
<Row label="Date of Birth" value={profileData.custdob} /> <Divider mb="sm" />
<Row label="Mobile Number" value={profileData.mobileno} />
<Row <Row
label="Address" label="Customer ID (CIF)"
value={profileData.custaddress} value={profileData.cifNumber}
// link={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent( masked={true}
// profileData.custaddress 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} /> <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"> <Text c="red" size="sm">

View File

@@ -224,8 +224,8 @@ export default function SetTransactionLimit() {
}; };
return ( return (
<Paper shadow="sm" radius="md" p="md" withBorder h={400}> <Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Title order={3} mb="sm"> <Title order={4} mb="sm">
Set Transaction Limit Set Transaction Limit
</Title> </Title>
@@ -237,16 +237,21 @@ export default function SetTransactionLimit() {
value={limit} value={limit}
onChange={(e) => { onChange={(e) => {
const val = e.currentTarget.value; const val = e.currentTarget.value;
// Only allow digits
// Allow only digits AND max value 500000
if (/^\d*$/.test(val)) { if (/^\d*$/.test(val)) {
const num = Number(val);
if (num <= 500000) {
setLimit(val); setLimit(val);
} }
}
}} }}
leftSection={icon} leftSection={icon}
withAsterisk withAsterisk
mb="sm" mb="sm"
readOnly={step !== "form"} readOnly={step !== "form"}
/> />
</Group> </Group>
{dailyLimit !== null ? ( {dailyLimit !== null ? (
<Text size="xs" c="green"> <Text size="xs" c="green">
@@ -282,7 +287,7 @@ export default function SetTransactionLimit() {
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
userSelect: "none", userSelect: "none",
textDecoration: "line-through", textDecoration: "line-through",
fontFamily: "cursive", fontFamily: "Verdana",
}} }}
> >
{captcha} {captcha}

View File

@@ -240,8 +240,8 @@ export default function ChangePassword() {
}; };
return ( return (
<Paper shadow="sm" radius="md" p="md" withBorder h={400} > <Paper shadow="sm" radius="md" p="md" withBorder h={500} >
<Title order={3} mb="sm"> <Title order={4} mb="sm">
Set Transaction Password Set Transaction Password
</Title> </Title>
@@ -296,7 +296,7 @@ export default function ChangePassword() {
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
userSelect: "none", userSelect: "none",
textDecoration: "line-through", textDecoration: "line-through",
fontFamily: "cursive", fontFamily: "Verdana",
}} }}
> >
{captcha} {captcha}

View File

@@ -215,7 +215,7 @@ export default function SetPreferredNameSimple() {
}); });
return; return;
} }
if(preferredName !== confirmName){ if (preferredName !== confirmName) {
notifications.show({ notifications.show({
title: "Mismatch Input", title: "Mismatch Input",
message: "Preferred name and Confirm preferred name not same.", message: "Preferred name and Confirm preferred name not same.",
@@ -261,8 +261,8 @@ export default function SetPreferredNameSimple() {
if (loading) return <Text>Loading...</Text>; if (loading) return <Text>Loading...</Text>;
return ( return (
<Paper shadow="sm" radius="md" p="md" withBorder> <Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Title order={3} mb="sm"> <Title order={4} mb="sm">
Set Preferred Name Set Preferred Name
</Title> </Title>
@@ -330,7 +330,7 @@ export default function SetPreferredNameSimple() {
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
userSelect: "none", userSelect: "none",
textDecoration: "line-through", textDecoration: "line-through",
fontFamily: "cursive", fontFamily: "Verdana",
}} }}
> >
{captcha} {captcha}

232
src/app/ATMLocator/page.tsx Normal file
View 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>
);
}

View 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>
);
}

View File

@@ -361,7 +361,7 @@ export default function ChangePassword() {
fontSize: "18px", fontSize: "18px",
textDecoration: "line-through", textDecoration: "line-through",
padding: "4px 8px", padding: "4px 8px",
fontFamily: "cursive", fontFamily: "Verdana",
}} }}
> >
{captcha} {captcha}

View 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
View 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>
);
}

View File

@@ -1,91 +1,126 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Text, Button, TextInput, PasswordInput, Title, Card, Box, Image } from "@mantine/core"; import { Text, Button, TextInput, PasswordInput, Title, Card, Box, Image, Container, Grid, Progress } from "@mantine/core";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { Providers } from "@/app/providers"; import { Providers } from "@/app/providers";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import NextImage from "next/image"; import NextImage from "next/image";
import logo from '@/app/image/logo.jpg'; import logo from '@/app/image/logo1.jpg';
import changePwdImage from '@/app/image/changepw.png'; import forget_psw from '@/app/image/forget_password.jpg';
// import CaptchaImage from './CaptchaImage'; import { generateCaptcha } from '@/app/captcha';
import { IconEye, IconEyeOff, IconLock, IconLogout } from '@tabler/icons-react'; import { IconLock, IconRefresh, IconShieldCheck } from '@tabler/icons-react';
export default function ForgetLoginPwd() { export default function ForgetLoginPwd() {
const router = useRouter(); const router = useRouter();
const [authorized, SetAuthorized] = useState<boolean | null>(null); const [authorized, setAuthorized] = useState<boolean | null>(null);
const [captcha, setCaptcha] = useState(""); const [captcha, setCaptcha] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState("");
const [captchaInput, setCaptchaInput] = useState(''); const [captchaInput, setCaptchaInput] = useState('');
const [captchaError, setCaptchaError] = useState(''); const [captchaError, setCaptchaError] = useState('');
const [confirmVisible, setConfirmVisible] = useState(false);
const toggleConfirmVisibility = () => setConfirmVisible((v) => !v);
const icon = <IconLock size={18} stroke={1.5} />; const icon = <IconLock size={18} stroke={1.5} />;
async function handleLogout(e: React.FormEvent) {
e.preventDefault();
localStorage.removeItem("access_token");
sessionStorage.removeItem("access_token")
localStorage.clear();
sessionStorage.clear();
router.push("/login")
}
useEffect(() => { useEffect(() => {
generateCaptcha(); const loadCaptcha = async () => {
const newCaptcha = await generateCaptcha();
setCaptcha(newCaptcha);
};
loadCaptcha();
}, []); }, []);
const generateCaptcha = () => { // Add this useEffect in your component
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = ''; useEffect(() => {
for (let i = 0; i < 6; i++) { // Check authorization on mount
result += chars.charAt(Math.floor(Math.random() * chars.length)); const token = localStorage.getItem("reset_pwd_token");
if (!token) {
setAuthorized(false);
router.push("/login");
return;
} }
setCaptcha(result); setAuthorized(true);
// Handle back button navigation
const handlePopState = () => {
localStorage.removeItem("reset_pwd_token");
router.push("/login");
};
// Handle page refresh/reload
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
localStorage.removeItem("reset_pwd_token");
};
// Add event listeners
window.addEventListener('popstate', handlePopState);
window.addEventListener('beforeunload', handleBeforeUnload);
// Cleanup listeners on unmount
return () => {
window.removeEventListener('popstate', handlePopState);
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [router]);
useEffect(() => {
const token = localStorage.getItem("reset_pwd_token");
if (!token) {
setAuthorized(false);
router.push("/login");
} else {
setAuthorized(true);
}
}, [router]);
const handleRefreshCaptcha = async () => {
const newCaptcha = await generateCaptcha();
setCaptcha(newCaptcha);
setCaptchaInput(''); setCaptchaInput('');
setCaptchaError(''); setCaptchaError('');
}; };
async function handleSetLoginPassword(e: React.FormEvent) { const handleSetLoginPassword = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
const pwdRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/; const pwdRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
if (captchaInput !== captcha) {
setCaptchaError("Incorrect CAPTCHA. Please try again.");
setCaptchaInput('');
return;
}
if (!password || !confirmPassword) { if (!password || !confirmPassword) {
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "red", color: "red",
title: "Both password fields are required.", title: "Required Fields",
message: "Both password fields are required.", message: "Both password fields are required.",
autoClose: 5000, autoClose: 5000,
}); });
return; return;
// alert("Both password fields are required."); }
} else if (password !== confirmPassword) {
// alert("Passwords do not match."); if (password !== confirmPassword) {
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "red", color: "red",
title: "Passwords do not match.", title: "Password Mismatch",
message: "Passwords do not match.", message: "Passwords do not match.",
autoClose: 5000, autoClose: 5000,
}); });
return; return;
} }
else if (!pwdRegex.test(password)) {
// alert("Password must contain at least 1 capital letter, 1 number, 1 special character, and be at least 8 characters long."); if (!pwdRegex.test(password)) {
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "red", color: "red",
title: "Password must contain at least 1 capital letter, 1 number, 1 special character, and be at least 8 characters long.", title: "Weak Password",
message: "Password must contain at least 1 capital letter, 1 number, 1 special character, and be at least 8 characters long.", message: "Password must contain at least 1 capital letter, 1 number, 1 special character, and be at least 8 characters long.",
autoClose: 5000, autoClose: 5000,
}); });
return; return;
} }
else if (captchaInput !== captcha) {
setCaptchaError("Incorrect CAPTCHA."); const token = localStorage.getItem("reset_pwd_token");
return;
}
const token = localStorage.getItem("access_token");
const response = await fetch('api/auth/login_password', { const response = await fetch('api/auth/login_password', {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -97,157 +132,289 @@ export default function ForgetLoginPwd() {
login_password: password, login_password: password,
}), }),
}); });
const data = await response.json(); const data = await response.json();
if (response.ok) { if (response.ok) {
// console.log(data);
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "green", color: "green",
title: "Login Password has been set", title: "Success",
message: "Login Password has been set", message: "Login Password has been set successfully",
autoClose: 5000, autoClose: 5000,
}); });
router.push("/SetTxn"); localStorage.removeItem("reset_pwd_token");
} router.push("/login");
else { } else {
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "red", color: "red",
title: "Please try again later ", title: "Error",
message: "Please try again later ", message: "Please try again later",
autoClose: 5000, autoClose: 5000,
}); });
router.push("/login"); router.push("/login");
} }
} };
// useEffect(() => { if (authorized) {
// const token = localStorage.getItem("access_token");
// if (!token) {
// SetAuthorized(false);
// router.push("/login");
// }
// else {
// SetAuthorized(true);
// }
// }, []);
// if (authorized) {
return ( return (
<Providers> <Providers>
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "auto", paddingTop: "5%" }}>
<Box style={{ <Box style={{
position: 'fixed', width: '100%', height: '12%', top: 0, left: 0, zIndex: 100, minHeight: "100vh",
display: "flex", display: "flex",
flexDirection: "column",
background: "linear-gradient(135deg, #f5f7fa 0%, #e8f5e9 100%)"
}}>
{/* Header */}
<Box
style={{
position: 'sticky',
top: 0,
zIndex: 100,
display: "flex",
alignItems: "center",
justifyContent: "flex-start", justifyContent: "flex-start",
background: "linear-gradient(15deg,rgba(2, 163, 85, 1) 55%, rgba(101, 101, 184, 1) 100%)" background: "linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
padding: "10px 15px",
minHeight: "80px",
}}> }}>
<Image <Image
// radius="md"
fit="cover"
src={logo} src={logo}
component={NextImage} component={NextImage}
fit="contain"
alt="ebanking" alt="ebanking"
style={{ width: "100%", height: "100%" }} style={{
/> width: "60px",
<Button style={{ height: "60px",
position: 'absolute', minWidth: "50px",
top: '50%', marginRight: "15px"
left: '90%',
color: 'white',
textShadow: '1px 1px 2px black',
fontSize: "20px"
}} }}
leftSection={<IconLogout color='white' />} variant="subtle" onClick={handleLogout}>Logout</Button> />
<Box style={{ flex: 1 }}>
<Title
order={3}
style={{
fontFamily: "Roboto",
color: "white",
marginBottom: 2,
fontSize: "clamp(0.9rem, 2.5vw, 1.25rem)",
lineHeight: 1.3
}}>
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
</Title>
<Text
size="xs"
c="white"
style={{
opacity: 0.85,
fontSize: "clamp(0.65rem, 1.5vw, 0.75rem)"
}}>
Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
</Text>
</Box>
</Box> </Box>
<div>
<Box style={{ display: "flex", justifyContent: "center", alignItems: "center", columnGap: "5rem" }} bg="#c1e0f0">
<Image h="85vh" fit="contain" component={NextImage} src={changePwdImage} alt="Change Password Image" />
<Box h="100%" style={{ display: "flex", justifyContent: "center", alignItems: "center" }}>
<Card p="xl" w="40vw" h='82vh'>
<Title order={3}
// @ts-ignore
align="center" mb="md">Set Login Password</Title>
<form onSubmit={handleSetLoginPassword}> <Box
style={{
flex: 1,
padding: "2rem 0",
display: "flex",
alignItems: "center"
}}
>
<Container size="xl" style={{ width: "100%" }}>
<Grid gutter={{ base: "md", md: "xl" }} align="center">
{/* Image Column - Now visible on all screens */}
<Grid.Col span={{ base: 12, md: 6, lg: 7 }}>
<Box
style={{
position: "relative",
height: "700px",
borderRadius: "24px",
overflow: "hidden",
boxShadow: "0 20px 60px rgba(0,0,0,0.15)",
background: `url(${forget_psw.src})`,
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
backgroundSize: "cover",
}}
>
<Box style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
padding: "2rem",
background: "linear-gradient(to top, rgba(0,0,0,0.7), transparent)",
}}>
<Title order={2} c="white" mb="sm">
Welcome to KCCB Internet Banking
</Title>
<Text c="white" size="sm" style={{ opacity: 0.9 }}>
Experience secure, fast, and convenient banking at your fingertips
</Text>
</Box>
</Box>
</Grid.Col>
{/* Form Column */}
<Grid.Col span={{ base: 12, md: 6, lg: 5 }}>
<Card
radius="xl"
shadow="xl"
p={{ base: "xl", sm: "2rem" }}
style={{
background: "rgba(255, 255, 255, 0.95)",
backdropFilter: "blur(10px)",
border: "1px solid rgba(255,255,255,0.5)",
maxWidth: "480px",
margin: "0 auto"
}}
>
<Box style={{ textAlign: "center", marginBottom: "2rem" }}>
<Title order={3} style={{ color: "#35487eff", fontWeight: 700 }}>
Set Login Password
</Title>
<Text size="sm" c="dimmed">
Create a strong password to secure your account
</Text>
</Box>
<Box mb="md">
<PasswordInput <PasswordInput
label="Login Password" label="Login Password"
placeholder="Enter your password" placeholder="Enter your password"
required required
id="loginPassword" size="md"
value={password} value={password}
onChange={(e) => setPassword(e.currentTarget.value)} onChange={(e) => setPassword(e.currentTarget.value)}
onCopy={(e) => e.preventDefault()} onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()} onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()} onCut={(e) => e.preventDefault()}
styles={{
input: {
borderRadius: "8px",
border: "2px solid #e9ecef"
}
}}
/> />
</Box>
<Box mb="md">
<PasswordInput <PasswordInput
label="Confirm Login Password" label="Confirm Login Password"
placeholder="Enter your password" placeholder="Re-enter your password"
required required
size="md"
rightSection={icon} rightSection={icon}
id="confirmPassword"
value={confirmPassword} value={confirmPassword}
onChange={(e) => setConfirmPassword(e.currentTarget.value)} onChange={(e) => setConfirmPassword(e.currentTarget.value)}
type={confirmVisible ? 'text' : 'password'}
// rightSection={
// <button
// type="button"
// onClick={toggleConfirmVisibility}
// style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'grey' }}
// >
// {confirmVisible ? <IconEyeOff size={18} /> : <IconEye size={18} />}
// </button>
// }
onCopy={(e) => e.preventDefault()} onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()} onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()} onCut={(e) => e.preventDefault()}
styles={{
input: {
borderRadius: "8px",
border: "2px solid #e9ecef"
}
}}
/> />
</Box>
{/* CAPTCHA */} {/* CAPTCHA Section */}
<div style={{ marginTop: 20 }}> <Box mb="md">
<label style={{ fontWeight: 600 }}>Enter CAPTCHA *</label> <Text size="sm" fw={600} mb="xs">Verification Code</Text>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 5 }}> <Box style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
{/* <CaptchaImage text={captcha} /> */} <Box
<Button size="xs" variant="outline" onClick={generateCaptcha}>Refresh</Button> style={{
</div> flex: 1,
padding: "5px",
background: "white",
borderRadius: "8px",
border: "2px dashed #dee6deff",
textAlign: "center",
fontWeight: 500,
fontSize: "22px",
letterSpacing: "6px",
color: "#0a7228",
fontFamily: "Verdana",
userSelect: "none",
textDecoration: "line-through"
// fontFamily: "monospace"
}}
>
{captcha}
</Box>
<Button
size="md"
variant="light"
color="green"
onClick={handleRefreshCaptcha}
leftSection={<IconRefresh size={16} />}
style={{ borderRadius: "8px" }}
>
Refresh
</Button>
</Box>
<TextInput <TextInput
placeholder="Enter above text" placeholder="Enter the text above"
value={captchaInput} value={captchaInput}
onChange={(e) => setCaptchaInput(e.currentTarget.value)} onChange={(e) => {
setCaptchaInput(e.currentTarget.value);
setCaptchaError('');
}}
required required
size="md"
error={captchaError}
styles={{
input: {
borderRadius: "8px"
}
}}
/> />
{captchaError && <p style={{ color: 'red', fontSize: '12px' }}>{captchaError}</p>} </Box>
</div>
<Button <Button
type="submit" type="submit"
fullWidth fullWidth
mt="sm" size="lg"
color="blue" onClick={handleSetLoginPassword}
>
Set
</Button>
</form>
<br></br>
<Box
style={{ style={{
flex: 1, borderRadius: "10px",
borderLeft: '1px solid #ccc', fontWeight: 600,
paddingLeft: 16, marginBottom: "1.5rem"
minHeight: 90,
}} }}
> >
<Text size="sm"> Set Password
<strong>Note:</strong> Password will contains minimum one alphabet, one digit, one special symbol and total 8 charecters. </Button>
<Box
p="md"
style={{
background: "#cdffdfff",
borderRadius: "10px",
border: "1px solid #42c442ff",
borderLeft: "4px solid #42c442ff"
}}
>
<Text size="sm" fw={100} mb={4} c="#856404">
Password Requirements:
</Text>
<Text size="xs" c="#101010ff" style={{ lineHeight: 1.6 }}>
Minimum 8 characters<br />
At least 1 uppercase letter<br />
At least 1 number<br />
At least 1 special character (@$!%*#?&)
</Text> </Text>
</Box> </Box>
</Card> </Card>
</Grid.Col>
</Grid>
</Container>
</Box> </Box>
</Box>
{/* Footer */}
<Box <Box
style={{ style={{
flexShrink: 0, flexShrink: 0,
@@ -255,16 +422,15 @@ export default function ForgetLoginPwd() {
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
backgroundColor: "#f8f9fa", backgroundColor: "#f8f9fa",
marginTop: "0.5rem", padding: "1rem",
}} }}
> >
<Text c="dimmed" size="xs"> <Text c="dimmed" size="xs">
© 2025 Kangra Central Co-Operative Bank © 2025 Kangra Central Co-Operative Bank. All rights reserved.
</Text> </Text>
</Box> </Box>
</div> </Box>
</div> </Providers>
</Providers >
); );
// } }
} }

View File

@@ -1,13 +1,13 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Text, Button, TextInput, PasswordInput, Title, Card, Box, Image, Group } from "@mantine/core"; import { Text, Button, TextInput, PasswordInput, Title, Card, Box, Image, Group, Grid, Container, Stack } from "@mantine/core";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { Providers } from "@/app/providers"; import { Providers } from "@/app/providers";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import NextImage from "next/image"; import NextImage from "next/image";
import logo from '@/app/image/logo1.jpg'; import logo from '@/app/image/logo1.jpg';
import changePwdImage from '@/app/image/set_log_pass.jpg'; import changePwdImage from '@/app/image/forget_password.jpg';
import { IconLock, IconLogout, IconRefresh } from '@tabler/icons-react'; import { IconLock, IconLogout, IconPhone, IconRefresh } from '@tabler/icons-react';
import { generateCaptcha } from '@/app/captcha'; import { generateCaptcha } from '@/app/captcha';
import { sendOtp, verifyOtp } from "../_util/otp"; import { sendOtp, verifyOtp } from "../_util/otp";
//const [showOtpField, setShowOtpField] = useState(false); //const [showOtpField, setShowOtpField] = useState(false);
@@ -25,7 +25,7 @@ export default function SetLoginPwd() {
const [countdown, setCountdown] = useState(60); const [countdown, setCountdown] = useState(60);
const [timerActive, setTimerActive] = useState(false); const [timerActive, setTimerActive] = useState(false);
const icon = <IconLock size={18} stroke={1.5} />; const icon = <IconLock size={18} stroke={1.5} />;
const [generateOtp, setGenerateOtp] = useState(""); // const [generateOtp, setGenerateOtp] = useState("");
const [step, setStep] = useState<"form" | "otp" | "final">("form"); const [step, setStep] = useState<"form" | "otp" | "final">("form");
const [otpValidated, setOtpValidated] = useState(false); const [otpValidated, setOtpValidated] = useState(false);
@@ -165,7 +165,6 @@ export default function SetLoginPwd() {
}); });
return; return;
} }
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
@@ -237,57 +236,160 @@ export default function SetLoginPwd() {
if (authorized) { if (authorized) {
return ( return (
<Providers> <Providers>
<div style={{ backgroundColor: "#f8f9fa", width: "auto", height: "auto", paddingTop: "5%" }}>
<Box style={{ <Box style={{
position: 'fixed', width: '100%', height: '12%', top: 0, left: 0, zIndex: 100, minHeight: "100vh",
display: "flex", display: "flex",
flexDirection: "column",
background: "linear-gradient(135deg, #f5f7fa 0%, #e8f5e9 100%)"
}}>
{/* Header */}
<Box
style={{
position: 'sticky',
top: 0,
zIndex: 100,
display: "flex",
alignItems: "center",
justifyContent: "flex-start", justifyContent: "flex-start",
background: "linear-gradient(15deg,rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)" background: "linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
padding: "10px 15px",
minHeight: "80px",
}}> }}>
<Image <Image
fit="cover"
src={logo} src={logo}
component={NextImage} component={NextImage}
fit="contain"
alt="ebanking" alt="ebanking"
style={{ width: "100%", height: "100%" }}
/>
<Title
order={2}
style={{ style={{
fontFamily: 'Roboto', width: "60px",
position: 'absolute', height: "60px",
top: '30%', minWidth: "50px",
left: '7%', marginRight: "15px"
color: 'White',
transition: "opacity 0.5s ease-in-out",
}} }}
> />
<Box style={{ flex: 1 }}>
<Title
order={3}
style={{
fontFamily: "Roboto",
color: "white",
marginBottom: 2,
fontSize: "clamp(0.9rem, 2.5vw, 1.25rem)",
lineHeight: 1.3
}}>
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD. THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
</Title> </Title>
<Button style={{ <Text
position: 'absolute', size="xs"
top: '50%', c="white"
left: '90%', style={{
color: 'white', opacity: 0.85,
textShadow: '1px 1px 2px black', fontSize: "clamp(0.65rem, 1.5vw, 0.75rem)"
fontSize: "20px" }}>
}} Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
leftSection={<IconLogout color='white' />} variant="subtle" onClick={handleLogout}>Logout</Button> </Text>
</Box> </Box>
<div> <Button
<Box style={{ display: "flex", justifyContent: "center", alignItems: "center" }} bg="#80868989"> style={{
<Image h="85vh" w="100%" fit="contain" component={NextImage} src={changePwdImage} alt="Change Password Image" /> color: 'white',
<Box h="100%" style={{ display: "flex", justifyContent: "center", alignItems: "center" }}> fontWeight: 600
<Card p="xl" w="40vw" h='85vh'> }}
<Title order={4} leftSection={<IconLogout color='white' />}
// @ts-ignore variant="subtle"
align="center" mb="md">Set Login Password</Title> onClick={handleLogout}
<form onSubmit={handleSetLoginPassword}> visibleFrom="md"
>
Logout
</Button>
</Box>
{/* Main Content */}
<Box
style={{
flex: 1,
padding: "2rem 0",
display: "flex",
alignItems: "center"
}}
>
<Container size="xl" style={{ width: "100%" }}>
<Grid gutter={{ base: "md", md: "xl" }} align="center">
{/* Image Column */}
<Grid.Col span={{ base: 12, md: 6, lg: 7 }}>
<Box
style={{
position: "relative",
height: "700px",
borderRadius: "24px",
overflow: "hidden",
boxShadow: "0 20px 60px rgba(0,0,0,0.15)",
background: `url(${changePwdImage.src})`,
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
backgroundSize: "cover",
}}
>
<Box style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
padding: "2rem",
background: "linear-gradient(to top, rgba(0,0,0,0.7), transparent)",
}}>
<Title order={2} c="white" mb="sm">
Welcome to KCCB Internet Banking
</Title>
<Text c="white" size="sm" style={{ opacity: 0.9 }}>
Secure your account with a strong password
</Text>
</Box>
</Box>
</Grid.Col>
{/* Form Column */}
<Grid.Col span={{ base: 12, md: 6, lg: 5 }}>
<Card
radius="xl"
shadow="xl"
p={{ base: "xl", sm: "2rem" }}
style={{
background: "rgba(255, 255, 255, 0.95)",
backdropFilter: "blur(10px)",
border: "1px solid rgba(255,255,255,0.5)",
maxWidth: "480px",
margin: "0 auto"
}}
>
{/* Decorative top bar */}
<Box
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: "6px",
background: "linear-gradient(90deg, #0a7228 0%, #6565b8 100%)",
borderRadius: "16px 16px 0 0"
}}
/>
<Box style={{ textAlign: "center", marginBottom: "2rem", marginTop: "1rem" }}>
<Title order={3} style={{ color: "#35487eff", fontWeight: 700 }}>
Set Login Password
</Title>
<Text size="sm" c="dimmed">
Create a strong password to secure your account
</Text>
</Box>
<Stack gap="md">
<PasswordInput <PasswordInput
label="Login Password" label="Login Password"
placeholder="Enter your password" placeholder="Enter your password"
withAsterisk withAsterisk
id="loginPassword" size="md"
value={password} value={password}
minLength={8} minLength={8}
maxLength={15} maxLength={15}
@@ -296,95 +398,165 @@ export default function SetLoginPwd() {
onPaste={(e) => e.preventDefault()} onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()} onCut={(e) => e.preventDefault()}
readOnly={captchaValidate} readOnly={captchaValidate}
styles={{
input: {
borderRadius: "8px",
border: "2px solid #e9ecef"
}
}}
/> />
<PasswordInput <PasswordInput
label="Confirm Login Password" label="Confirm Login Password"
placeholder="Enter your password" placeholder="Re-enter your password"
withAsterisk withAsterisk
size="md"
rightSection={icon} rightSection={icon}
id="confirmPassword"
value={confirmPassword} value={confirmPassword}
onChange={(e) => setConfirmPassword(e.currentTarget.value)} onChange={(e) => setConfirmPassword(e.currentTarget.value)}
onCopy={(e) => e.preventDefault()} onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()} onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()} onCut={(e) => e.preventDefault()}
readOnly={captchaValidate} readOnly={captchaValidate}
/> styles={{
<Group mt="sm" align="center"> input: {
<Box style={{ borderRadius: "8px",
backgroundColor: "#fff", fontSize: "18px", textDecoration: "line-through", padding: "4px 8px", fontFamily: "cursive", border: "2px solid #e9ecef"
userSelect: "none", }
pointerEvents: "none",
}} }}
/>
{/* CAPTCHA Section */}
<Box>
<Text size="sm" fw={600} mb="xs">Verification Code</Text>
<Group gap="sm" align="center" mb="xs">
<Box style={{
flex: 1,
padding: "5px",
background: "white",
borderRadius: "8px",
border: "2px dashed #dee6deff",
textAlign: "center",
fontWeight: 500,
fontSize: "22px",
letterSpacing: "6px",
color: "#0a7228",
fontFamily: "Verdana",
userSelect: "none",
textDecoration: "line-through"
}}
onCopy={(e) => e.preventDefault()} onCopy={(e) => e.preventDefault()}
onContextMenu={(e) => e.preventDefault()}> onContextMenu={(e) => e.preventDefault()}
{captcha}</Box> >
{captcha}
</Box>
<Button size="xs" variant="light" onClick={regenerateCaptcha}>Refresh</Button> <Button
size="md"
variant="light"
color="green"
onClick={regenerateCaptcha}
leftSection={<IconRefresh size={16} />}
>
Refresh
</Button>
</Group> </Group>
<TextInput <TextInput
label="Enter CAPTCHA" placeholder="Enter the code above"
placeholder="Enter above text"
value={captchaInput} value={captchaInput}
onChange={(e) => setCaptchaInput(e.currentTarget.value)} onChange={(e) => setCaptchaInput(e.currentTarget.value)}
withAsterisk withAsterisk
size="md"
readOnly={captchaValidate} readOnly={captchaValidate}
styles={{
input: {
borderRadius: "8px"
}
}}
/> />
<Box style={{ height: 60 }}> </Box>
{/* OTP Section */}
{captchaValidate && ( {captchaValidate && (
<Box>
<Group gap="xs" align="flex-end"> <Group gap="xs" align="flex-end">
<PasswordInput <PasswordInput
label="Enter OTP" label="One-Time Password (OTP)"
placeholder="Enter the OTP" placeholder="Enter 6-digit OTP"
leftSection={<IconPhone size={18} />}
value={otp} value={otp}
maxLength={6} maxLength={6}
onChange={(e) => setOtp(e.currentTarget.value)} onChange={(e) => setOtp(e.currentTarget.value)}
withAsterisk withAsterisk
size="md"
style={{ flex: 1 }} style={{ flex: 1 }}
styles={{
input: {
borderRadius: "8px",
letterSpacing: "4px"
}
}}
/> />
{timerActive ? ( {timerActive ? (
<Text size="xs" c="dimmed"> <Text size="xs" c="dimmed" style={{ marginBottom: "8px" }}>
Resend OTP will be enabled in 00:{countdown < 10 ? `0${countdown}` : countdown} min Resend in 00:{countdown < 10 ? `0${countdown}` : countdown}
</Text> </Text>
) : ( ) : (
<Button <Button
variant="subtle" variant="light"
px={8} color="green"
onClick={handleSendOtp} onClick={handleSendOtp}
leftSection={<IconRefresh size={16} />} leftSection={<IconRefresh size={16} />}
style={{ marginBottom: "4px" }}
> >
Resend Resend
</Button> </Button>
)} )}
</Group> </Group>
)}
</Box> </Box>
)}
<Button <Button
type="submit" type="submit"
fullWidth fullWidth
mt="sm" size="lg"
onClick={handleSetLoginPassword}
style={{
borderRadius: "10px",
fontWeight: 600,
marginTop: "0.5rem"
}}
> >
{!captchaValidate ? "Validate" : "Set"} {!captchaValidate ? "Validate" : "Set"}
</Button> </Button>
</form>
{/* Note Box */}
<Box <Box
p="md"
style={{ style={{
flex: 1, background: "#cdffdfff",
borderLeft: '1px solid #ccc', borderRadius: "10px",
paddingLeft: 16, border: "1px solid #42c442ff",
minHeight: 80, borderLeft: "4px solid #42c442ff"
gap: "sm"
}} }}
> >
<Text size="sm"> <Text size="sm" fw={600} mb={4} c="#856404">
<strong>Note:</strong> Password must contain at least one capital letter(A-Z), one digit(0-9), one special symbol(e.g.,@,#,$), and be 8-15 characters long. Password Requirements:
</Text>
<Text size="xs" c="#101010ff" style={{ lineHeight: 1.6 }}>
Minimum 8 characters, maximum 15 characters<br />
At least 1 uppercase letter (A-Z)<br />
At least 1 number (0-9)<br />
At least 1 special character (e.g., @, #, $)
</Text> </Text>
</Box> </Box>
</Stack>
</Card> </Card>
</Grid.Col>
</Grid>
</Container>
</Box> </Box>
</Box>
{/* Footer */}
<Box <Box
style={{ style={{
flexShrink: 0, flexShrink: 0,
@@ -392,16 +564,15 @@ export default function SetLoginPwd() {
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
backgroundColor: "#f8f9fa", backgroundColor: "#f8f9fa",
marginTop: "0.5rem", padding: "1rem",
}} }}
> >
<Text c="dimmed" size="xs"> <Text c="dimmed" size="xs">
© 2025 The Kangra Central Co-Operative Bank © 2025 The Kangra Central Co-Operative Bank Ltd. All rights reserved.
</Text> </Text>
</Box> </Box>
</div> </Box>
</div> </Providers>
</Providers >
); );
} }
} }

View File

@@ -1,14 +1,14 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Text, Button, TextInput, PasswordInput, Title, Card, Box, Image, Group } from "@mantine/core"; import { Text, Button, TextInput, PasswordInput, Title, Card, Box, Image, Group, Container, Grid, Stack, Anchor } from "@mantine/core";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { Providers } from "@/app/providers"; import { Providers } from "@/app/providers";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import NextImage from "next/image"; import NextImage from "next/image";
import logo from '@/app/image/logo1.jpg'; import logo from '@/app/image/logo1.jpg';
import changePwdImage from '@/app/image/set_tran_pass.jpg'; import changePwdImage from '@/app/image/forget_password.jpg';
import { generateCaptcha } from '@/app/captcha'; import { generateCaptcha } from '@/app/captcha';
import { IconLock, IconLogout, IconRefresh } from '@tabler/icons-react'; import { IconLock, IconLogout, IconPhone, IconRefresh, IconShieldCheck } from '@tabler/icons-react';
import { sendOtp, verifyOtp } from "../_util/otp"; import { sendOtp, verifyOtp } from "../_util/otp";
@@ -25,14 +25,9 @@ export default function SetTransactionPwd() {
const [countdown, setCountdown] = useState(60); const [countdown, setCountdown] = useState(60);
const [timerActive, setTimerActive] = useState(false); const [timerActive, setTimerActive] = useState(false);
const icon = <IconLock size={18} stroke={1.5} />; const icon = <IconLock size={18} stroke={1.5} />;
const [generateOtp, setGenerateOtp] = useState("");
const [showOtpField, setShowOtpField] = useState(false);
const [step, setStep] = useState<"form" | "otp" | "final">("form"); const [step, setStep] = useState<"form" | "otp" | "final">("form");
const [otpValidated, setOtpValidated] = useState(false); const [otpValidated, setOtpValidated] = useState(false);
async function handleSendOtp() { async function handleSendOtp() {
const mobileNumber = localStorage.getItem('remitter_mobile_no'); const mobileNumber = localStorage.getItem('remitter_mobile_no');
if (!mobileNumber) { if (!mobileNumber) {
@@ -66,13 +61,6 @@ export default function SetTransactionPwd() {
} }
} }
async function handleGenerateOtp() {
const value = "123456";
setGenerateOtp(value);
setCountdown(60);
setTimerActive(true);
}
async function handleLogout(e: React.FormEvent) { async function handleLogout(e: React.FormEvent) {
e.preventDefault(); e.preventDefault();
localStorage.removeItem("access_token"); localStorage.removeItem("access_token");
@@ -193,7 +181,6 @@ export default function SetTransactionPwd() {
return; return;
} }
setOtpValidated(true); setOtpValidated(true);
setStep("final"); setStep("final");
notifications.show({ notifications.show({
@@ -202,11 +189,8 @@ export default function SetTransactionPwd() {
color: "green", color: "green",
}); });
return; return;
} }
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
const response = await fetch('api/auth/transaction_password', { const response = await fetch('api/auth/transaction_password', {
method: 'POST', method: 'POST',
@@ -256,75 +240,196 @@ export default function SetTransactionPwd() {
if (authorized) { if (authorized) {
return ( return (
<Providers> <Providers>
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "auto", paddingTop: "5%" }}>
<Box style={{ <Box style={{
position: 'fixed', width: '100%', height: '12%', top: 0, left: 0, zIndex: 100, minHeight: "100vh",
display: "flex", display: "flex",
flexDirection: "column",
background: "linear-gradient(135deg, #f5f7fa 0%, #e8f5e9 100%)"
}}>
{/* Header */}
<Box
style={{
position: 'sticky',
top: 0,
zIndex: 100,
display: "flex",
alignItems: "center",
justifyContent: "flex-start", justifyContent: "flex-start",
background: "linear-gradient(15deg,rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)" background: "linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
padding: "10px 15px",
minHeight: "80px",
}}> }}>
<Image <Image
fit="cover"
src={logo} src={logo}
component={NextImage} component={NextImage}
fit="contain"
alt="ebanking" alt="ebanking"
style={{ width: "100%", height: "100%" }}
/>
<Title
order={2}
style={{ style={{
fontFamily: 'Roboto', width: "60px",
position: 'absolute', height: "60px",
top: '30%', minWidth: "50px",
left: '7%', marginRight: "15px"
color: 'White',
transition: "opacity 0.5s ease-in-out",
}} }}
> />
<Box style={{ flex: 1 }}>
<Title
order={3}
style={{
fontFamily: "Roboto",
color: "white",
marginBottom: 2,
fontSize: "clamp(0.9rem, 2.5vw, 1.25rem)",
lineHeight: 1.3
}}>
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD. THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
</Title> </Title>
<Button style={{ <Text
position: 'absolute', size="xs"
top: '50%', c="white"
left: '90%', style={{
opacity: 0.85,
fontSize: "clamp(0.65rem, 1.5vw, 0.75rem)"
}}>
Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
</Text>
</Box>
<Button
style={{
color: 'white', color: 'white',
textShadow: '1px 1px 2px black', fontWeight: 600
fontSize: "20px"
}} }}
leftSection={<IconLogout color='white' />} variant="subtle" onClick={handleLogout}>Logout leftSection={<IconLogout color='white' />}
variant="subtle"
onClick={handleLogout}
visibleFrom="md"
>
Logout
</Button> </Button>
</Box> </Box>
<div>
<Box style={{ display: "flex", justifyContent: "center", alignItems: "center" }} bg="#80868989">
<Image h="85vh" fit="contain" component={NextImage} src={changePwdImage} alt="Change Password Image" />
<Box h="100%" style={{ display: "flex", justifyContent: "center", alignItems: "center" }}>
<Card p="xl" w="40vw" h='85vh'>
<Text onClick={() => router.push("/login")}
style={{
position: 'absolute', top: '1rem', right: '2rem', cursor: 'pointer', fontWeight: 500, color: '#7091ecff', textDecoration: 'underline'
}}> Skip now</Text>
<Title order={3} {/* Main Content */}
// @ts-ignore <Box
align="center" mb="md">Set Transaction Password</Title> style={{
<form onSubmit={handleSetTransactionPassword}> flex: 1,
padding: "2rem 0",
display: "flex",
alignItems: "center"
}}
>
<Container size="xl" style={{ width: "100%" }}>
<Grid gutter={{ base: "md", md: "xl" }} align="center">
{/* Image Column */}
<Grid.Col span={{ base: 12, md: 6, lg: 7 }}>
<Box
style={{
position: "relative",
height: "750px",
borderRadius: "24px",
overflow: "hidden",
boxShadow: "0 20px 60px rgba(0,0,0,0.15)",
background: `url(${changePwdImage.src})`,
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
backgroundSize: "cover",
}}
>
<Box style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
padding: "2rem",
background: "linear-gradient(to top, rgba(0,0,0,0.7), transparent)",
}}>
<Title order={2} c="white" mb="sm">
Welcome to KCCB Internet Banking
</Title>
<Text c="white" size="sm" style={{ opacity: 0.9 }}>
Set up your transaction password for secure payments
</Text>
</Box>
</Box>
</Grid.Col>
{/* Form Column */}
<Grid.Col span={{ base: 12, md: 6, lg: 5 }}>
<Card
radius="xl"
shadow="xl"
p={{ base: "xl", sm: "2rem" }}
style={{
background: "rgba(255, 255, 255, 0.95)",
backdropFilter: "blur(10px)",
border: "1px solid rgba(255,255,255,0.5)",
maxWidth: "480px",
margin: "0 auto",
position: "relative"
}}
>
{/* Decorative top bar
<Box
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: "6px",
background: "linear-gradient(90deg, #0a7228 0%, #6565b8 100%)",
borderRadius: "16px 16px 0 0"
}}
/> */}
{/* Skip Now Link */}
<Anchor
onClick={() => router.push("/login")}
style={{
position: 'absolute',
top: '1.5rem',
right: '1.5rem',
cursor: 'pointer',
fontWeight: 600,
color: '#0a7228',
fontSize: "14px"
}}
>
Skip now
</Anchor>
<Box style={{ textAlign: "center", marginBottom: "2rem", marginTop: "1rem" }}>
<Title order={3} style={{ color: "#35487eff", fontWeight: 700 }}>
Set Transaction Password
</Title>
<Text size="sm" c="dimmed">
Secure your transactions with a strong password
</Text>
</Box>
<Stack gap="md">
<PasswordInput <PasswordInput
label="Transaction Password" label="Transaction Password"
placeholder="Enter your Transaction password" placeholder="Enter your transaction password"
withAsterisk withAsterisk
id="loginPassword" size="md"
value={password} value={password}
onChange={(e) => setPassword(e.currentTarget.value)} onChange={(e) => setPassword(e.currentTarget.value)}
onCopy={(e) => e.preventDefault()} onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()} onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()} onCut={(e) => e.preventDefault()}
readOnly={captchaValidate} readOnly={captchaValidate}
styles={{
input: {
borderRadius: "8px",
border: "2px solid #e9ecef"
}
}}
/> />
<PasswordInput <PasswordInput
label="Confirm Transaction Password" label="Confirm Transaction Password"
placeholder="Re-enter your Transaction password" placeholder="Re-enter your transaction password"
withAsterisk withAsterisk
id="confirmPassword" size="md"
rightSection={icon} rightSection={icon}
value={confirmPassword} value={confirmPassword}
onChange={(e) => setConfirmPassword(e.currentTarget.value)} onChange={(e) => setConfirmPassword(e.currentTarget.value)}
@@ -332,84 +437,145 @@ export default function SetTransactionPwd() {
onPaste={(e) => e.preventDefault()} onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()} onCut={(e) => e.preventDefault()}
readOnly={captchaValidate} readOnly={captchaValidate}
styles={{
input: {
borderRadius: "8px",
border: "2px solid #e9ecef"
}
}}
/> />
{/* CAPTCHA */}
<Group mt="sm" align="center"> {/* CAPTCHA Section */}
<Box>
<Text size="sm" fw={600} mb="xs">Verification Code</Text>
<Group gap="sm" align="center" mb="xs">
<Box style={{ <Box style={{
backgroundColor: "#fff", fontSize: "18px", textDecoration: "line-through", padding: "4px 8px", fontFamily: "cursive", flex: 1,
padding: "5px",
background: "white",
borderRadius: "8px",
border: "2px dashed #dee6deff",
textAlign: "center",
fontWeight: 500,
fontSize: "22px",
letterSpacing: "6px",
color: "#0a7228",
fontFamily: "Verdana",
userSelect: "none", userSelect: "none",
pointerEvents: "none", textDecoration: "line-through"
}} }}
onCopy={(e) => e.preventDefault()} onCopy={(e) => e.preventDefault()}
onContextMenu={(e) => e.preventDefault()}> onContextMenu={(e) => e.preventDefault()}
>
{captcha}</Box> {captcha}
<Button size="xs" variant="light" onClick={regenerateCaptcha}>Refresh</Button> </Box>
<Button
size="md"
variant="light"
color="green"
onClick={regenerateCaptcha}
leftSection={<IconRefresh size={16} />}
>
Refresh
</Button>
</Group> </Group>
<TextInput <TextInput
label="Enter CAPTCHA" placeholder="Enter the code above"
placeholder="Enter above text"
value={captchaInput} value={captchaInput}
onChange={(e) => setCaptchaInput(e.currentTarget.value)} onChange={(e) => setCaptchaInput(e.currentTarget.value)}
withAsterisk withAsterisk
mt="sm" size="md"
readOnly={captchaValidate} readOnly={captchaValidate}
styles={{
input: {
borderRadius: "8px"
}
}}
/> />
<Box style={{ height: 60 }}> </Box>
{/* OTP Section */}
{captchaValidate && ( {captchaValidate && (
<Box>
<Group gap="xs" align="flex-end"> <Group gap="xs" align="flex-end">
<PasswordInput <PasswordInput
label="Enter OTP" label="One-Time Password (OTP)"
placeholder="Enter the OTP" placeholder="Enter 6-digit OTP"
leftSection={<IconPhone size={18} />}
value={otp} value={otp}
maxLength={6} maxLength={6}
onChange={(e) => setOtp(e.currentTarget.value)} onChange={(e) => setOtp(e.currentTarget.value)}
withAsterisk withAsterisk
size="md"
style={{ flex: 1 }} style={{ flex: 1 }}
styles={{
input: {
borderRadius: "8px",
letterSpacing: "4px"
}
}}
/> />
{timerActive ? ( {timerActive ? (
<Text size="xs" c="dimmed"> <Text size="xs" c="dimmed" style={{ marginBottom: "8px" }}>
Resend OTP will be enabled in 00:{countdown < 10 ? `0${countdown}` : countdown} min Resend in 00:{countdown < 10 ? `0${countdown}` : countdown}
</Text> </Text>
) : ( ) : (
<Button <Button
variant="subtle" variant="light"
px={8} color="green"
onClick={handleSendOtp} onClick={handleSendOtp}
leftSection={<IconRefresh size={16} />} leftSection={<IconRefresh size={16} />}
style={{ marginBottom: "4px" }}
> >
Resend Resend
</Button> </Button>
)} )}
</Group> </Group>
)}
</Box> </Box>
)}
<Button <Button
type="submit" type="submit"
fullWidth fullWidth
mt="sm" size="lg"
color="blue" onClick={handleSetTransactionPassword}
style={{
borderRadius: "10px",
fontWeight: 600,
marginTop: "0.5rem"
}}
> >
Set Set
</Button> </Button>
</form>
<br></br>
<Box
style={{
flex: 1,
borderLeft: '1px solid #ccc',
paddingLeft: 16,
minHeight: 90,
{/* Note Box */}
<Box
p="md"
style={{
background: "#cdffdfff",
borderRadius: "10px",
border: "1px solid #42c442ff",
borderLeft: "4px solid #42c442ff"
}} }}
> >
<Text size="sm"> <Text size="sm" fw={600} mb={4} c="#856404">
<strong>Note:</strong> Password must contain at least one capital letter(A-Z), one digit(0-9), one special symbol(e.g.,@,#,$), and be 8-15 characters long. Password Requirements:
</Text>
<Text size="xs" c="#101010ff" style={{ lineHeight: 1.6 }}>
Minimum 8 characters, maximum 15 characters<br />
At least 1 uppercase letter (A-Z)<br />
At least 1 number (0-9)<br />
At least 1 special character (e.g., @, #, $)
</Text> </Text>
</Box> </Box>
</Stack>
</Card> </Card>
</Grid.Col>
</Grid>
</Container>
</Box> </Box>
</Box>
{/* Footer */}
<Box <Box
style={{ style={{
flexShrink: 0, flexShrink: 0,
@@ -417,16 +583,15 @@ export default function SetTransactionPwd() {
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
backgroundColor: "#f8f9fa", backgroundColor: "#f8f9fa",
marginTop: "0.5rem", padding: "1rem",
}} }}
> >
<Text c="dimmed" size="xs"> <Text c="dimmed" size="xs">
© 2025 Kangra Central Co-Operative Bank © 2025 Kangra Central Co-Operative Bank. All rights reserved.
</Text> </Text>
</Box> </Box>
</div> </Box>
</div> </Providers>
</Providers >
); );
} }
} }

View File

@@ -1,13 +1,13 @@
"use client"; "use client";
import React, { useEffect, useState, useRef } from "react"; import React, { useState } from "react";
import { Text, Button, TextInput, PasswordInput, Title, Card, Box, Image } from "@mantine/core"; import { Text, Button, TextInput, PasswordInput, Title, Card, Box, Image, Container, Grid, Stack, Divider, Badge } from "@mantine/core";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { Providers } from "@/app/providers"; import { Providers } from "@/app/providers";
import NextImage from "next/image"; import NextImage from "next/image";
import logo from '@/app/image/logo1.jpg'; import logo from '@/app/image/logo1.jpg';
import changePwdImage from '@/app/image/changepw.png'; import ValidateUserId from '@/app/image/validate_page.jpg';
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { IconShieldCheck, IconPhone, IconKey } from '@tabler/icons-react';
export default function ValidateUser() { export default function ValidateUser() {
const router = useRouter(); const router = useRouter();
@@ -15,250 +15,456 @@ export default function ValidateUser() {
const [otp, setOTP] = useState(""); const [otp, setOTP] = useState("");
const [mobileNumber, setMobileNumber] = useState(""); const [mobileNumber, setMobileNumber] = useState("");
const [isCifValidated, setIsCifValidated] = useState(false); const [isCifValidated, setIsCifValidated] = useState(false);
const [generateOtp, setGenerateOtp] = useState(""); const [loading, setLoading] = useState(false);
const headerRef = useRef<HTMLHeadingElement>(null);
const validUsers = [ const handleValidateUser = async () => {
{ cif: "11111111111", mobile: "7890544528" },
{ cif: "30022497139", mobile: "6230573848" },
{ cif: "11122233344", mobile: "9998887776" },
];
async function handleGenerateOtp() {
const value = "123456";
setGenerateOtp(value);
return value;
}
const handleValidateUser = async (e: React.FormEvent) => {
e.preventDefault();
if (!Cif) {
notifications.show({
title: "Invalid CIF",
message: "Please Enter your 11 digit CIF number.",
color: "red",
});
setIsCifValidated(false);
return;
}
if (!/^\d{11}$/.test(Cif)) { if (!/^\d{11}$/.test(Cif)) {
notifications.show({ notifications.show({
title: "Invalid CIF", title: "Invalid CIF",
message: "CIF number must be exactly 11 digits.", message: "CIF number must be exactly 11 digits.",
color: "red", color: "red",
}); });
setIsCifValidated(false);
return; return;
} }
const user = validUsers.find((u) => u.cif === Cif); setLoading(true);
if (user) {
setMobileNumber(user.mobile); try {
const res = await fetch(
`/api/otp/send/set-password?customerNo=${Cif}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "IB",
},
}
);
const data = await res.json();
if (!res.ok) {
throw new Error(data?.message || "OTP send failed");
}
setIsCifValidated(true); setIsCifValidated(true);
const Otp = await handleGenerateOtp(); setMobileNumber(data?.mobileNo || "");
const masked = `xxxxxx${user.mobile.slice(-4)}`;
const masked = data?.mobileNo
// ? `xxxxxx${data.mobileNo.slice(-4)}`
? "registered number"
: "registered number";
notifications.show({ notifications.show({
title: "OTP Sent", title: "OTP Sent",
message: `OTP sent to your registered mobile number ${masked}`, message: `OTP sent to ${masked}`,
color: "green", color: "green",
}); });
} else {
setIsCifValidated(false); } catch (err) {
console.error("Validation error:", err);
notifications.show({ notifications.show({
title: "User Not Found", title: "Validation Failed",
message: "No such user is present.", message: err instanceof Error ? err.message : "Unable to send OTP. Please try again.",
color: "red", color: "red",
}); });
} finally {
setLoading(false);
} }
}; };
const handleSubmitOtp = () => { const handleSubmitOtp = async () => {
if (!otp) { if (!otp || otp.length !== 6) {
notifications.show({ notifications.show({
title: "Invalid OTP", title: "Invalid OTP",
message: "Please Enter OTP Before You Submit.", message: "Please enter 6 digit OTP.",
color: "red", color: "red",
}); });
return; return;
} }
if (otp === generateOtp) {
setLoading(true);
try {
const res = await fetch(
`/api/otp/verify/set-password?customerNo=${Cif}&otp=${otp}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "IB",
},
}
);
const data = await res.json();
// console.log(data);
if (!res.ok) {
throw new Error(data?.message || "OTP verification failed");
}
if (data?.token) {
localStorage.setItem("reset_pwd_token", data.token);
} else {
throw new Error("Token not received from server");
}
notifications.show({ notifications.show({
title: "OTP Verified", title: "OTP Verified",
message: `OTP matched successfully`, message: "OTP verified successfully.",
color: "green", color: "green",
}); });
router.push("/ForgetPassword")
} router.push("/ForgetPassword");
else {
setOTP(""); } catch (err) {
console.error("OTP verification error:", err);
notifications.show({ notifications.show({
title: "Invalid OTP", title: "Invalid OTP",
message: `The OTP you entered is incorrect`, message: err instanceof Error ? err.message : "OTP verification failed.",
color: "red", color: "red",
}); });
return; setOTP("");
} finally {
setLoading(false);
} }
};
const handleSubmit = () => {
if (isCifValidated) {
handleSubmitOtp();
} else {
handleValidateUser();
} }
};
useEffect(() => {
const headerData = [
"THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.",
"कांगड़ा केन्द्रीय सहकारी बैंक सीमित",
];
let index = 0;
const interval = setInterval(() => {
index = (index + 1) % headerData.length;
if (headerRef.current) {
headerRef.current.textContent = headerData[index];
}
}, 2000);
return () => clearInterval(interval);
}, []);
return ( return (
<Providers> <Providers>
<div style={{ backgroundColor: "#f8f9fa", width: "100%", paddingTop: "5%" }}> <Box style={{
minHeight: "100vh",
display: "flex",
flexDirection: "column",
background: "linear-gradient(135deg, #f5f7fa 0%, #e8f5e9 100%)"
}}>
{/* Header */}
<Box <Box
style={{ style={{
position: 'fixed', width: '100%', height: '12%', top: 0, left: 0, zIndex: 100, position: 'sticky',
top: 0,
zIndex: 100,
display: "flex", display: "flex",
alignItems: "center",
justifyContent: "flex-start", justifyContent: "flex-start",
background: "linear-gradient(15deg,rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)", background: "linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
padding: "10px 15px",
minHeight: "80px",
}}> }}>
<Image <Image
src={logo} src={logo}
component={NextImage} component={NextImage}
fit="contain" fit="contain"
alt="ebanking" alt="ebanking"
style={{ width: "100%", height: "auto", flexShrink: 0 }}
/>
<Title ref={headerRef}
order={2}
style={{ style={{
fontFamily: 'Roboto', width: "40px",
position: 'absolute', height: "60px",
top: '30%', minWidth: "50px",
left: '7%', marginRight: "15px"
color: 'White',
transition: "opacity 0.5s ease-in-out",
}} }}
> />
<Box style={{ flex: 1 }}>
<Title
order={3}
style={{
fontFamily: "Roboto",
color: "white",
marginBottom: 2,
fontSize: "clamp(0.9rem, 2.5vw, 1.25rem)",
lineHeight: 1.3
}}>
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD. THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
</Title> </Title>
<Text size="sm" c="white" <Text
size="xs"
c="white"
style={{ style={{
backgroundColor: '#1f1f14', opacity: 0.85,
fontFamily: 'Roboto', fontSize: "clamp(0.65rem, 1.5vw, 0.75rem)"
position: 'absolute', }}>
top: '59%', Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
left: '72%', </Text>
color: 'white', </Box>
textShadow: '1px 1px 2px blue', </Box>
{/* Main Content */}
<Box
style={{
flex: 1,
padding: "2rem 0",
display: "flex",
alignItems: "center"
}} }}
> >
Head Office : Dharmshala, District: Kangra(H.P), Pin: 176215 <Container size="xl" style={{ width: "100%" }}>
</Text> <Grid gutter={{ base: "md", md: "xl" }} align="center">
{/* <Box style={{ position: "absolute", right: "1rem", top: "50%", transform: 'translateY(-50%)' }}> {/* Image Column - Hidden on mobile */}
<Tooltip <Grid.Col
label='Head Office : Dharmshala, District: Kangra(H.P), Pin: 176215' span={{ base: 12, md: 6, lg: 7 }}
position="right" visibleFrom="md"
withArrow> >
<IconBuildingBank size={40} style={{ cursor: "pointer", color: "white" }} /> <Box
</Tooltip> style={{
</Box> */} position: "relative",
</Box> height: "500px",
<Box style={{ display: "flex", justifyContent: "center", alignItems: "center", columnGap: "5rem" }} bg="#c1e0f0"> borderRadius: "24px",
<Image h="85vh" fit="contain" component={NextImage} src={changePwdImage} alt="Change Password Image" /> overflow: "hidden",
<Box h="100%" style={{ display: "flex", justifyContent: "center", alignItems: "center" }}> boxShadow: "0 20px 60px rgba(0,0,0,0.15)",
<Card p="xl" w="35vw" h="65vh" style={{ display: "flex", flexDirection: "column", justifyContent: "center" }}> background: `url(${ValidateUserId.src})`,
{/* @ts-ignore */} backgroundRepeat: "no-repeat",
<Title order={3} align="center" mb="md">Validate User</Title> backgroundPosition: "center",
backgroundSize: "cover",
{isCifValidated && ( }}
// @ts-ignore >
<Text align="center"> Welcome {" "}{Cif}</Text> <Box style={{
)} position: "absolute",
<form onSubmit={(e) => { bottom: 0,
e.preventDefault(); left: 0,
isCifValidated ? handleSubmitOtp() : handleValidateUser(e); right: 0,
padding: "2rem",
background: "linear-gradient(to top, rgba(0,0,0,0.7), transparent)",
}}> }}>
<Title order={2} c="white" mb="sm">
Welcome to KCCB Internet Banking
</Title>
<Text c="white" size="sm" style={{ opacity: 0.9 }}>
Experience secure, fast, and convenient banking at your fingertips
</Text>
</Box>
</Box>
</Grid.Col>
{/* Form Column */}
<Grid.Col span={{ base: 12, md: 6, lg: 5 }}>
<Card
radius="xl"
shadow="xl"
p={{ base: "xl", sm: "2rem" }}
style={{
background: "rgba(255, 255, 255, 0.95)",
backdropFilter: "blur(10px)",
border: "1px solid rgba(255,255,255,0.5)",
maxWidth: "480px",
height:"100%",
margin: "0 auto"
}}
>
<Stack gap="lg">
{/* Header Section */}
<Box style={{ textAlign: "center" }}>
{/* <Box style={{
width: "70px",
height: "70px",
margin: "0 auto 1rem",
background: "linear-gradient(135deg, #3a3293ff 0%, #895fd1ff 100%)",
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
boxShadow: "0 4px 20px rgba(10, 114, 40, 0.3)"
}}>
<IconShieldCheck size={36} color="white" />
</Box> */}
<Title order={3} mb="xs" style={{ color: "#35487eff", fontWeight: 700 }}>
Validate User
</Title>
<Text size="sm" c="dimmed" style={{ lineHeight: 1.6 }}>
Secure access to your KCCB Internet Banking
</Text>
</Box>
<Divider />
{/* Welcome Message */}
{isCifValidated && (
<Box style={{
background: "linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%)",
padding: "1rem",
borderRadius: "12px",
textAlign: "center",
border: "1px solid #a5d6a7"
}}>
<Text size="sm" c="dimmed">Welcome</Text>
<Text size="lg" fw={600} c="#0a7228">{Cif}</Text>
</Box>
)}
{/* Form Fields */}
<Stack gap="md">
<TextInput <TextInput
label="Enter Your CIF Number" label="CIF Number"
placeholder="Enter your CIF" placeholder="Enter your 11-digit CIF"
size="md"
withAsterisk withAsterisk
value={Cif} value={Cif}
disabled={isCifValidated} disabled={isCifValidated}
leftSection={<IconKey size={18} />}
styles={{
input: {
borderRadius: "12px",
border: "2px solid #e0e0e0",
fontSize: "1rem",
padding: "1.5rem 1rem 1.5rem 2.5rem",
transition: "all 0.3s ease",
'&:focus': {
borderColor: "#0a7228",
boxShadow: "0 0 0 3px rgba(10, 114, 40, 0.1)"
}
}
}}
onChange={(e) => { onChange={(e) => {
const input = e.currentTarget.value; const value = e.currentTarget.value;
if (input.length <= 11) { if (value.length <= 11 && /^\d*$/.test(value)) {
setCif(input); setCif(value);
} }
}} }}
onKeyDown={(e) => { onKeyDown={(e) => {
const isNumberKey = /[0-9]/.test(e.key); if (e.key === 'Enter') {
const isControlKey = ["Backspace", "Tab", "ArrowLeft", "ArrowRight"].includes(e.key);
if (!isNumberKey && !isControlKey) {
e.preventDefault(); e.preventDefault();
handleSubmit();
} }
}} }}
/> />
<div style={{ marginTop: "1rem", minHeight: "80px" }}>
{!isCifValidated && ( {!isCifValidated && (
<> <Box style={{
<br></br> background: "#f5f5f5",
<Text fs="italic" c="dimmed">NOTE: Your CIF number travels in an encrypted and highly secured mode.</Text> padding: "0.75rem",
</> borderRadius: "8px",
borderLeft: "3px solid #4caf50"
}}>
<Text size="xs" c="dimmed" style={{ lineHeight: 1.5 }}>
🔒 Your CIF is transmitted securely using encrypted channels
</Text>
</Box>
)} )}
{isCifValidated && ( {isCifValidated && (
<> <>
<PasswordInput <PasswordInput
label="Enter OTP" label="One-Time Password (OTP)"
placeholder="Enter your OTP" placeholder="Enter 6-digit OTP"
size="md"
withAsterisk withAsterisk
value={otp} value={otp}
maxLength={6} maxLength={6}
onKeyDown={(e) => { leftSection={<IconPhone size={18} />}
const isNumberKey = /[0-9]/.test(e.key); styles={{
const isControlKey = ["Backspace", "Tab", "ArrowLeft", "ArrowRight"].includes(e.key); input: {
if (!isNumberKey && !isControlKey) { borderRadius: "12px",
e.preventDefault(); border: "2px solid #e0e0e0",
fontSize: "1rem",
padding: "1.5rem 1rem 1.5rem 2.5rem",
transition: "all 0.3s ease",
letterSpacing: "3px",
'&:focus': {
borderColor: "#0a7228",
boxShadow: "0 0 0 3px rgba(10, 114, 40, 0.1)"
}
}
}}
onChange={(e) => {
const value = e.currentTarget.value;
if (/^\d*$/.test(value)) {
setOTP(value);
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
handleSubmit();
} }
}} }}
onChange={(e) => setOTP(e.currentTarget.value)}
/> />
<Text size="xs" mt="xs" c='green'> <Box style={{
OTP sent to your registered mobile number{" "} background: "#e8f5e9",
<b>{`xxxxxx${mobileNumber.slice(-4)}`}</b> padding: "0.75rem",
borderRadius: "8px",
borderLeft: "3px solid #4caf50"
}}>
<Text size="xs" c="#4caf50" style={{ lineHeight: 1.5 }}>
{/* 📱 OTP sent to <strong>{`xxxxxx${mobileNumber.slice(-4)}`}</strong> */}
📱 OTP sent to <strong>Registered Mobile Number</strong>
</Text> </Text>
</Box>
</> </>
)} )}
</div>
<Button type="submit" mt="lg" color="blue"> <Button
{isCifValidated ? "Submit OTP" : "Validate"} fullWidth
size="lg"
onClick={handleSubmit}
loading={loading}
style={{
marginTop: "1rem",
// background: "linear-gradient(135deg, #0a7228 0%, #1b5e20 100%)",
borderRadius: "12px",
height: "52px",
fontSize: "1rem",
fontWeight: 600,
letterSpacing: "0.5px",
boxShadow: "0 4px 15px #4caf50",
transition: "all 0.3s ease",
border: "none"
}}
styles={{
root: {
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: "0 6px 20px rgba(10, 114, 40, 0.4)"
},
'&:active': {
transform: 'translateY(0)'
}
}
}}
>
{isCifValidated ? "Verify OTP" : "Send OTP"}
</Button> </Button>
</form> </Stack>
</Card>
{/* Security Note */}
<Box style={{
textAlign: "center",
padding: "0.75rem",
borderTop: "1px solid #e0e0e0"
}}>
<Text size="xs" c="dimmed" style={{ lineHeight: 1.6 }}>
Having trouble? Contact our support team
</Text>
</Box> </Box>
</Stack>
</Card>
</Grid.Col>
</Grid>
</Container>
</Box> </Box>
{/* Footer */}
<Box <Box
style={{ style={{
flexShrink: 0,
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
backgroundColor: "#f8f9fa", backgroundColor: "#f8f9fa",
marginTop: "0.5rem", padding: "15px 10px",
textAlign: "center",
}} }}
> >
<Text c="dimmed" size="xs"> <Text c="dimmed" size="xs">
© 2025 Kangra Central Co-Operative Bank © 2025 Kangra Central Co-Operative Bank
</Text> </Text>
</Box> </Box>
</Box>
</div>
</Providers> </Providers>
); );
} }

View File

@@ -0,0 +1,103 @@
"use client";
import dayjs from "dayjs";
export const generateActiveUsersPDF = (reportData: any, startDate: string, endDate: string) => {
const html2pdf = require("html2pdf.js");
// Build rows from user list
const rows = reportData.active_user_list
.map(
(user: any) => `
<tr style="page-break-inside: avoid;">
<td style="border:1px solid #ccc;padding:6px;text-align:center;">${user.customer_no}</td>
<td style="border:1px solid #ccc;padding:6px;text-align:left;">${user.user_name}</td>
<td style="border:1px solid #ccc;padding:6px;text-align:center;">
${new Date(user.created_at).toLocaleString()}
</td>
<td style="border:1px solid #ccc;padding:6px;text-align:center;">
${new Date(user.last_login).toLocaleString()}
</td>
<td style="border:1px solid #ccc;padding:6px;text-align:center;
color:${user.status === "active" ? "green" : "red"};">
${user.status.toUpperCase()}
</td>
</tr>`
)
.join("");
// HTML content for PDF
const content = `
<div style="font-family:Arial, sans-serif; font-size:12px;">
<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="/logo1.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")}
</div>
</div>
<hr/>
<h3 style="text-align:center;margin:10px 0;">Active Users Report</h3>
<p style="text-align:center; font-size:13px; margin-top:-5px;">
<strong>Report from ${startDate} to ${endDate}</strong>
</p>
<p><strong>Total Users:</strong> ${reportData.total_users}</p>
<p><strong>Active Users:</strong> ${reportData.active_users}</p>
<table style="width:100%;border-collapse:collapse;font-size:12px;margin-top:10px;">
<thead style="background:#f0f0f0;">
<tr>
<th style="border:1px solid #ccc;padding:6px;text-align:center;">Customer No</th>
<th style="border:1px solid #ccc;padding:6px;text-align:left;">User Name</th>
<th style="border:1px solid #ccc;padding:6px;text-align:center;">Created At</th>
<th style="border:1px solid #ccc;padding:6px;text-align:center;">Last Login</th>
<th style="border:1px solid #ccc;padding:6px;text-align:center;">Status</th>
</tr>
</thead>
<tbody>
${rows}
</tbody>
</table>
</div>
`;
const opt = {
margin: [20, 10, 20, 10],
filename: `ActiveUsersReport_${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"] },
};
// ✅ Properly call .save() INSIDE the .then()
html2pdf()
.set(opt)
.from(content)
.toPdf()
.get("pdf")
.then((pdf: any) => {
const totalPages = pdf.internal.getNumberOfPages();
const pageWidth = pdf.internal.pageSize.getWidth();
for (let i = 2; i <= totalPages; i++) {
pdf.setPage(i);
pdf.setFontSize(10);
pdf.setFont("helvetica", "bold");
pdf.text("Active Users Report", pageWidth / 2, 18, { align: "center" });
pdf.setFontSize(9);
pdf.text(
`Page ${i} of ${totalPages}`,
pageWidth - 40,
pdf.internal.pageSize.getHeight() - 10
);
}
// ✅ Save PDF here
pdf.save(`ActiveUsersReport_${dayjs().format("DDMMYYYY_HHmm")}.pdf`);
});
};

View File

@@ -0,0 +1,105 @@
"use client";
import dayjs from "dayjs";
export const generateInactiveUsersPDF = (
reportData: any,
startDate: string,
endDate: string
) => {
const html2pdf = require("html2pdf.js");
// ✅ Use correct list (change to 'active_user_list' if needed)
const userList = reportData.inactive_user_list || reportData.active_user_list || [];
// Build table rows
const rows = userList
.map(
(user: any) => `
<tr style="page-break-inside: avoid;">
<td style="border:1px solid #ccc;padding:6px;text-align:center;">${user.customer_no}</td>
<td style="border:1px solid #ccc;padding:6px;text-align:left;">${user.user_name}</td>
<td style="border:1px solid #ccc;padding:6px;text-align:center;">
${new Date(user.created_at).toLocaleString()}
</td>
<td style="border:1px solid #ccc;padding:6px;text-align:center;">
${new Date(user.last_login).toLocaleString()}
</td>
<td style="border:1px solid #ccc;padding:6px;text-align:center;color:${
user.status === "active" ? "green" : "red"
};">
${user.status.toUpperCase()}
</td>
</tr>`
)
.join("");
// ✅ HTML layout
const content = `
<div style="font-family:Arial, sans-serif; font-size:12px;">
<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="/logo1.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")}
</div>
</div>
<hr/>
<h3 style="text-align:center;margin:10px 0;">Inactive Users Report</h3>
<p style="text-align:center; font-size:13px; margin-top:-5px;">
<strong>Report from ${startDate} to ${endDate}</strong>
</p>
<p><strong>Total Users:</strong> ${reportData.total_users}</p>
<p><strong>Inactive Users:</strong> ${reportData.inactive_users}</p>
<table style="width:100%;border-collapse:collapse;font-size:12px;margin-top:10px;">
<thead style="background:#f0f0f0;">
<tr>
<th style="border:1px solid #ccc;padding:6px;text-align:center;">Customer No</th>
<th style="border:1px solid #ccc;padding:6px;text-align:left;">User Name</th>
<th style="border:1px solid #ccc;padding:6px;text-align:center;">Created At</th>
<th style="border:1px solid #ccc;padding:6px;text-align:center;">Last Login</th>
<th style="border:1px solid #ccc;padding:6px;text-align:center;">Status</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>
</div>
`;
// ✅ Options — fix margin type
const opt = {
margin: [20, 10, 20, 10] as [number, number, number, number],
filename: `InactiveUsersReport_${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"] },
};
// ✅ Proper html2pdf chain
html2pdf()
.set(opt)
.from(content)
.toPdf()
.get("pdf")
.then((pdf: any) => {
const totalPages = pdf.internal.getNumberOfPages();
const pageWidth = pdf.internal.pageSize.getWidth();
for (let i = 1; i <= totalPages; i++) {
pdf.setPage(i);
pdf.setFontSize(9);
pdf.text(
`Page ${i} of ${totalPages}`,
pageWidth - 40,
pdf.internal.pageSize.getHeight() - 10
);
}
})
.save(); // ✅ this triggers the download
};

View File

@@ -1,60 +1,619 @@
"use client"; "use client";
import dayjs from "dayjs";
interface CsvGeneratorProps { export const generateExcel = (
accountNo: string;
balance: string;
txns: any[];
}
export const generateCSV = (
accountNo: string, accountNo: string,
balance: string, balance: string,
txns: any[] txns: any[],
customerName: string,
periodFrom: string,
periodTo: string,
branchCode: string,
cifNumber: string,
address: string
) => { ) => {
// CSV Header // Import ExcelJS dynamically
let csv = `Bank Statement\n`; const ExcelJS = require("exceljs");
csv += `Account No:,${accountNo}\n`;
csv += `Available Balance:,${balance}\n`;
csv += `Generated:,${new Date().toLocaleString()}\n\n`;
// Column headers // Create a new workbook and worksheet
csv += "Date,Name,Type,Amount\n"; const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Account Statement", {
// Rows pageSetup: {
txns.forEach((txn) => { paperSize: 9, // A4
csv += `${txn.date},${txn.name},${txn.type.toLowerCase()},${txn.amount}\n`; 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 let currentRow = 1;
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a"); /* ---------------------------------------------------------
link.setAttribute("href", url); * 1) HEADER SECTION
link.setAttribute("download", `statement_${accountNo}.csv`); * --------------------------------------------------------- */
document.body.appendChild(link);
link.click(); // Bank Name
document.body.removeChild(link); 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>
);
}

View File

@@ -7,107 +7,369 @@ export const generatePDF = (
txns: any[], txns: any[],
customerName: string, customerName: string,
periodFrom: string, periodFrom: string,
periodTo: string periodTo: string,
// branchName: string,
branchCode: string,
cifNumber: string,
address: string
) => { ) => {
const html2pdf = require("html2pdf.js"); const html2pdf = require("html2pdf.js");
// Build rows /* ---------------------------------------------------------
const rows = txns.map( * 1) BUILD TRANSACTION ROWS
(t: any) => ` * --------------------------------------------------------- */
<tr style="page-break-inside: avoid;">
<td style="border:1px solid #ccc;padding:6px;text-align:center;">${t.date}</td> const rows = txns
<td style="border:1px solid #ccc;padding:6px;text-align:left;">${t.name}</td> .map((t: any) => {
<td style="border:1px solid #ccc;padding:6px;text-align:right;color:${t.type === "DR" ? "red" : "green" 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", { ${parseFloat(t.amount).toLocaleString("en-IN", {
minimumFractionDigits: 2, minimumFractionDigits: 2,
})} ${t.type === "DR" ? "dr." : "cr."} })} ${t.type}
</td> </td>
<td style="border:1px solid #ccc; padding:6px; text-align:right; color: blue;">${t.balance}</td> <td style="border:1px solid #d0d0d0;padding:8px 10px;text-align:right;font-size:11px;font-weight:500;">${t.balance}</td>
</tr> </tr>
` `;
); })
.join("");
// Content for first page /* ---------------------------------------------------------
const content = ` * 2) MAIN PAGE CONTENT
<div style="font-family:Arial, sans-serif;"> * --------------------------------------------------------- */
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;"> const firstPage = `
<div style="display:flex;align-items:center;gap:10px;"> <div style="font-family:'Times New Roman', serif; font-size:12px;line-height:1.5;">
<img src="/logo.jpg" alt="Bank Logo" style="height:50px;" />
<h2 style="margin:0;">The Kangra Central Co Operative Bank</h2> <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="/logo1.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>
<div style="font-size:12px;color:#555;">
Report generated: ${dayjs().format("DD/MM/YYYY HH:mm")}
</div> </div>
</div> </div>
<hr/>
<h3 style="text-align:center;">Account Statement</h3> <!-- ACCOUNT DETAILS -->
<p><strong>Account No:</strong> ${accountNo}</p> <table style="
<p><strong>Account Holder:</strong> ${customerName}</p> width:100%;
<p><strong>Statement Period:</strong> ${periodFrom} to ${periodTo}</p> margin-bottom:12px;
<p><strong>Available Balance:</strong> ₹ ${parseFloat(balance).toLocaleString( border:1px solid #d0d0d0;
"en-IN", background:#f9f9f9;
{ minimumFractionDigits: 2 } border-collapse:collapse;
)}</p> ">
<table style="width:100%;border-collapse:collapse;font-size:12px;margin-top:10px;page-break-inside:auto;">
<thead style="background:#f0f0f0;">
<tr> <tr>
<th style="border:1px solid #ccc;padding:6px;text-align:center;">Date</th> <td style="padding:8px 12px;border-bottom:1px solid #e0e0e0;width:50%;">
<th style="border:1px solid #ccc;padding:6px;text-align:left;">Description</th> <span style="font-size:10px;color:#666;display:block;margin-bottom:2px;">Account Name</span>
<th style="border:1px solid #ccc;padding:6px;text-align:right;">Amount (₹)</th> <strong style="font-size:11px;color:#000;">${customerName}</strong>
<th style="border:1px solid #ccc;padding:6px;text-align:right;">Available Amount (₹)</th> </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;">&#9888;</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> </tr>
</thead> </thead>
<tbody> <tbody style="background:white;">${rows}</tbody>
${rows.join("")}
</tbody>
</table> </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> </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 = { const opt = {
margin: [20, 10, 20, 10], margin: [15, 10, 20, 10],
filename: `AccountStatement_${accountNo}_${dayjs().format("DD/MM/YYYY HH:mm")}.pdf`, filename: `Statement_${accountNo}_${dayjs().format("DDMMYYYY_HHmm")}.pdf`,
image: { type: "jpeg", quality: 0.98 }, image: { type: "jpeg", quality: 0.98 },
html2canvas: { scale: 2 }, html2canvas: {
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" }, scale: 2,
pagebreak: { mode: ["avoid-all", "css", "legacy"] }, 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() html2pdf()
.set(opt) .set(opt)
.from(content) .from(firstPage + lastPage)
.toPdf() .toPdf()
.get("pdf") .get("pdf")
.then((pdf: any) => { .then((pdf: any) => {
const totalPages = pdf.internal.getNumberOfPages(); const total = pdf.internal.getNumberOfPages();
const pageWidth = pdf.internal.pageSize.getWidth(); 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.setPage(i);
pdf.setFontSize(10);
// ✅ Left side Account No /* LIGHT WATERMARK */
pdf.setFont("helvetica", "bold"); try {
// pdf.text(`Account No: ${accountNo}`, 15, 18); 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( pdf.text(
`Statement Period: ${periodFrom} to ${periodTo}`, "** This is only for information purpose and not for legal use **",
pageWidth / 2, w / 2,
18, h - 12,
{ align: "center" } { align: "center" }
); );
// Footer page numbers /* PAGE NUMBER */
pdf.setFont("times", "normal");
pdf.setFontSize(9); pdf.setFontSize(9);
pdf.text( pdf.setTextColor(0, 0, 0);
`Page ${i} of ${totalPages}`, pdf.text(`Page ${i} of ${total}`, w - 15, h - 12, {
pageWidth - 40, align: "right",
pdf.internal.pageSize.getHeight() - 10 });
);
} }
}) })
.save(); .save();

View File

@@ -2,11 +2,16 @@
import { MantineColorsTuple, createTheme } from "@mantine/core"; import { MantineColorsTuple, createTheme } from "@mantine/core";
// const KccbColors: MantineColorsTuple = [
// "#e3f2fd", "#bbdefb", "#90caf9", "#64b5f6", "#42a5f5",
// "#2196f3", "#1e88e5", "#1976d2", "#1565c0", "#0d47a1"
// ];
const KccbColors: MantineColorsTuple = [ const KccbColors: MantineColorsTuple = [
"#e3f2fd", "#bbdefb", "#90caf9", "#64b5f6", "#42a5f5", "#e8f5e9", "#c8e6c9", "#a5d6a7", "#81c784", "#66bb6a", // Lighter greens
"#2196f3", "#1e88e5", "#1976d2", "#1565c0", "#0d47a1" "#4caf50", "#43a047", "#388e3c", "#2c6f2c", "#1b5e20" // Darker greens
]; ];
export const KccbTheme = createTheme({ export const KccbTheme = createTheme({
/* Put your mantine theme override here */ /* Put your mantine theme override here */
primaryColor: 'kccb-colors', primaryColor: 'kccb-colors',

View File

@@ -14,8 +14,8 @@ interface SendOtpPayload {
} }
function getStoredMobileNumber(): string { function getStoredMobileNumber(): string {
// const mobileNumber = localStorage.getItem('remitter_mobile_no'); const mobileNumber = localStorage.getItem('remitter_mobile_no');
const mobileNumber = "7890544527"; // const mobileNumber = "7890544527";
if (!mobileNumber) throw new Error('Mobile number not found.'); if (!mobileNumber) throw new Error('Mobile number not found.');
return mobileNumber; return mobileNumber;
} }

View File

@@ -28,14 +28,20 @@ export async function fetchAndStoreUserName(token: string) {
const data = await response.json(); const data = await response.json();
if (Array.isArray(data) && data.length > 0) { 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_name", custname);
localStorage.setItem("remitter_mobile_no", mobileno); 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; return true;
} catch (error: any) { } catch (error: any) {
localStorage.removeItem("remitter_name"); localStorage.removeItem("remitter_name");
localStorage.removeItem("remitter_mobile_no"); 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); console.error('Error sending OTP:', error.response?.data || error.message);
notifications.show({ notifications.show({
withBorder: true, withBorder: true,

View File

@@ -0,0 +1,299 @@
"use client";
import React, { useState } from "react";
import {
Box,
Button,
Group,
Stack,
Modal,
Table,
Text,
Title,
Divider,
Badge,
} from "@mantine/core";
import Image from "next/image";
import img from '@/app/image/logo1.jpg'
import { DatePickerInput } from "@mantine/dates";
import { IconDownload, IconRefresh } from "@tabler/icons-react";
import { notifications } from "@mantine/notifications";
import { generateActiveUsersPDF } from "@/app/_components/report/Activeuser_report";
import dayjs from "dayjs";
export default function ActiveUsersReport() {
const [startDate, setStartDate] = useState<Date | null>(null);
const [endDate, setEndDate] = useState<Date | null>(null);
const [loading, setLoading] = useState(false);
const [opened, setOpened] = useState(false);
const [reportData, setReportData] = useState<any>(null);
const handleFetch = async () => {
if (!startDate || !endDate) {
notifications.show({
color: "red",
title: "Missing Dates",
message: "Please select both start and end dates.",
});
return;
}
// const monthDiff =
// (endDate.getFullYear() - startDate.getFullYear()) * 12 +
// (endDate.getMonth() - startDate.getMonth());
const monthDiff = dayjs(endDate).diff(dayjs(startDate), "month", true);
if (monthDiff > 3) {
notifications.show({
color: "red",
title: "Invalid Range",
message: "You can only fetch reports up to 3 months at a time.",
});
return;
}
setLoading(true);
try {
const token = localStorage.getItem("admin_access_token");
const response = await fetch(`/api/report/active_users`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "Admin",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
from_date: dayjs(startDate).format("YYYY-MM-DD"),
to_date: dayjs(endDate).format("YYYY-MM-DD"),
}),
});
const data = await response.json(); // Expected format from your backend
console.log(data);
if (response.ok) {
setReportData(data);
setOpened(true);
notifications.show({
withBorder: true,
color: "green",
title: "Success",
message: "Report fetched successfully",
autoClose: 5000,
});
} else {
notifications.show({
withBorder: true,
color: "red",
title: "Error",
message: data?.error || "Failed to fetch report",
autoClose: 5000,
});
}
} catch (error) {
notifications.show({
title: "Error",
message: "Something went wrong while fetching report",
color: "red",
});
} finally {
setLoading(false);
}
};
// Download button inside modal
const handleDownload = () => {
if (!reportData) {
notifications.show({
color: "red",
title: "No Data",
message: "No report data to download.",
});
return;
}
// Call your external PDF generator
generateActiveUsersPDF(
reportData,
dayjs(startDate).format("YYYY-MM-DD"),
dayjs(endDate).format("YYYY-MM-DD")
);
notifications.show({
color: "blue",
title: "Download",
message: "PDF downloaded successfully.",
});
};
// Reset button
const handleReset = () => {
setStartDate(null);
setEndDate(null);
setReportData(null);
notifications.show({
color: "yellow",
title: "Form Reset",
message: "Dates cleared successfully.",
});
};
return (
<>
<Box
p="md"
style={{
backgroundColor: "white",
borderRadius: "8px",
boxShadow: "0 0 5px rgba(0,0,0,0.1)",
maxWidth: 1000,
}}
>
<Stack gap="md">
<Group grow>
<DatePickerInput
label="Start Date"
placeholder="Select start date"
value={startDate}
onChange={setStartDate}
required
maxDate={new Date()}
/>
<DatePickerInput
label="End Date"
placeholder="Select end date"
value={endDate}
onChange={setEndDate}
required
maxDate={new Date()}
/>
</Group>
<Group justify="flex-end" mt="sm">
<Button onClick={handleFetch} loading={loading}>
Fetch
</Button>
<Button
onClick={handleReset}
variant="outline"
color="red"
title="Reset Form"
>
<IconRefresh size={18} />
</Button>
</Group>
</Stack>
</Box>
<Modal
opened={opened}
onClose={() => setOpened(false)}
size="xl"
centered
withCloseButton
>
{reportData ? (
<Box>
<Box mb="sm">
<img
src={img.src}
alt="KCC Bank Logo"
width={60}
height={60}
style={{
display: "block",
marginBottom: "10px",
}}
/>
</Box>
<Stack gap={4} mb="md">
<Title order={4}>Active Users Report</Title>
<Text>
<b>Total Users:</b> {reportData.total_users}
</Text>
<Text >
<b>Active Users:</b> {reportData.active_users}
</Text>
</Stack>
<Divider my="sm" />
<Group justify="space-between" align="center" mb="xs">
<Title order={5}>
Active User List From{" "}
{startDate ? dayjs(startDate).format("YYYY-MM-DD") : "—"} To{" "}
{endDate ? dayjs(endDate).format("YYYY-MM-DD") : "—"}
</Title>
<Button
onClick={handleDownload}
size="xs"
leftSection={<IconDownload size={16} />}
color="blue"
variant="light"
>
Download
</Button>
</Group>
<Box style={{ maxHeight: "400px", overflowY: "auto" }}>
<Table striped withTableBorder style={{ width: "100%" }}>
<Table.Thead>
<Table.Tr>
<Table.Th>Customer No</Table.Th>
<Table.Th>User Name</Table.Th>
<Table.Th>Created At</Table.Th>
<Table.Th>Last Login</Table.Th>
<Table.Th>Status</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{reportData.active_user_list.map((user: any, index: number) => (
<Table.Tr key={index}>
<Table.Td>{user.customer_no}</Table.Td>
<Table.Td>{user.user_name}</Table.Td>
<Table.Td>
{new Date(user.created_at).toLocaleString()}
</Table.Td>
<Table.Td>
{new Date(user.last_login).toLocaleString()}
</Table.Td>
<Table.Td>
<Badge
color={user.status === "active" ? "green" : "red"}
variant="filled"
>
{user.status.toUpperCase()}
</Badge>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Box>
</Box>
) : (
<Text ta="center" c="dimmed">
No data available
</Text>
)}
</Modal>
</>
);
}

View File

@@ -0,0 +1,300 @@
"use client";
import React, { useState } from "react";
import {
Box,
Button,
Group,
Stack,
Modal,
Table,
Text,
Title,
Divider,
Badge,
} from "@mantine/core";
import Image from "next/image";
import img from '@/app/image/logo1.jpg'
import { DatePickerInput } from "@mantine/dates";
import { IconDownload, IconRefresh } from "@tabler/icons-react";
import { notifications } from "@mantine/notifications";
import { generateInactiveUsersPDF } from "@/app/_components/report/Inactiveuser_report";
import dayjs from "dayjs";
export default function InactiveUsersReport() {
const [startDate, setStartDate] = useState<Date | null>(null);
const [endDate, setEndDate] = useState<Date | null>(null);
const [loading, setLoading] = useState(false);
const [opened, setOpened] = useState(false);
const [reportData, setReportData] = useState<any>(null);
const handleFetch = async () => {
if (!startDate || !endDate) {
notifications.show({
color: "red",
title: "Missing Dates",
message: "Please select both start and end dates.",
});
return;
}
// const monthDiff =
// (endDate.getFullYear() - startDate.getFullYear()) * 12 +
// (endDate.getMonth() - startDate.getMonth());
const monthDiff = dayjs(endDate).diff(dayjs(startDate), "month", true);
if (monthDiff > 3) {
notifications.show({
color: "red",
title: "Invalid Range",
message: "You can only fetch reports up to 3 months at a time.",
});
return;
}
setLoading(true);
try {
const token = localStorage.getItem("admin_access_token");
const response = await fetch(`/api/report/in-active_users`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "Admin",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
from_date: dayjs(startDate).format("YYYY-MM-DD"),
to_date: dayjs(endDate).format("YYYY-MM-DD"),
}),
});
const data = await response.json(); // Expected format from your backend
console.log(data);
if (response.ok) {
setReportData(data);
setOpened(true);
notifications.show({
withBorder: true,
color: "green",
title: "Success",
message: "Report fetched successfully",
autoClose: 5000,
});
} else {
notifications.show({
withBorder: true,
color: "red",
title: "Error",
message: data?.error || "Failed to fetch report",
autoClose: 5000,
});
}
} catch (error) {
notifications.show({
title: "Error",
message: "Something went wrong while fetching report",
color: "red",
});
} finally {
setLoading(false);
}
};
// Download button inside modal
const handleDownload = () => {
if (!reportData) {
notifications.show({
color: "red",
title: "No Data",
message: "No report data to download.",
});
return;
}
// Call your external PDF generator
generateInactiveUsersPDF(
reportData,
dayjs(startDate).format("YYYY-MM-DD"),
dayjs(endDate).format("YYYY-MM-DD")
);
notifications.show({
color: "blue",
title: "Download",
message: "PDF downloaded successfully.",
});
};
// Reset button
const handleReset = () => {
setStartDate(null);
setEndDate(null);
setReportData(null);
notifications.show({
color: "yellow",
title: "Form Reset",
message: "Dates cleared successfully.",
});
};
return (
<>
<Box
p="md"
style={{
backgroundColor: "white",
borderRadius: "8px",
boxShadow: "0 0 5px rgba(0,0,0,0.1)",
maxWidth: 1000,
}}
>
<Stack gap="md">
<Group grow>
<DatePickerInput
label="Start Date"
placeholder="Select start date"
value={startDate}
onChange={setStartDate}
required
maxDate={new Date()}
/>
<DatePickerInput
label="End Date"
placeholder="Select end date"
value={endDate}
onChange={setEndDate}
required
maxDate={new Date()}
/>
</Group>
<Group justify="flex-end" mt="sm">
<Button onClick={handleFetch} loading={loading}>
Fetch
</Button>
<Button
onClick={handleReset}
variant="outline"
color="red"
title="Reset Form"
>
<IconRefresh size={18} />
</Button>
</Group>
</Stack>
</Box>
<Modal
opened={opened}
onClose={() => setOpened(false)}
size="xl"
centered
withCloseButton
>
{reportData ? (
<Box>
<Box mb="sm">
<img
src={img.src}
alt="KCC Bank Logo"
width={60}
height={60}
style={{
display: "block",
marginBottom: "10px",
}}
/>
</Box>
<Stack gap={4} mb="md">
<Title order={4}>Inactive Users Report</Title>
<Text>
<b>Total Users:</b> {reportData.total_users}
</Text>
<Text >
<b>Inactive Users:</b> {reportData.active_users}
</Text>
</Stack>
<Divider my="sm" />
<Group justify="space-between" align="center" mb="xs">
<Title order={5}>
Active User List From{" "}
{startDate ? dayjs(startDate).format("YYYY-MM-DD") : "—"} To{" "}
{endDate ? dayjs(endDate).format("YYYY-MM-DD") : "—"}
</Title>
<Button
onClick={handleDownload}
size="xs"
leftSection={<IconDownload size={16} />}
color="blue"
variant="light"
>
Download
</Button>
</Group>
<Box style={{ maxHeight: "400px", overflowY: "auto" }}>
<Table striped withTableBorder style={{ width: "100%" }}>
<Table.Thead>
<Table.Tr>
<Table.Th>Customer No</Table.Th>
<Table.Th>User Name</Table.Th>
<Table.Th>Created At</Table.Th>
<Table.Th>Last Login</Table.Th>
<Table.Th>Status</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{reportData.inactive_user_list.map((user: any, index: number) => (
<Table.Tr key={index}>
<Table.Td>{user.customer_no}</Table.Td>
<Table.Td>{user.user_name}</Table.Td>
<Table.Td>
{new Date(user.created_at).toLocaleString()}
</Table.Td>
<Table.Td>
{new Date(user.last_login).toLocaleString()}
</Table.Td>
<Table.Td>
<Badge
color={user.status === "inactive" ? "green" : "red"}
variant="filled"
>
{user.status.toUpperCase()}
</Badge>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Box>
</Box>
) : (
<Text ta="center" c="dimmed">
No data available
</Text>
)}
</Modal>
</>
);
}

View File

@@ -0,0 +1,189 @@
"use client";
import React, { useState } from "react";
import { Box, Button, Group, Stack, Select } from "@mantine/core";
import { DatePickerInput } from "@mantine/dates";
import { IconDownload, IconRefresh } from "@tabler/icons-react";
import { notifications } from "@mantine/notifications";
export default function TransactionReport() {
const [selectType, setSelectType] = useState<string | null>(null);
const [transactionType, setTransactionType] = useState<string | null>(null);
const [fromDate, setFromDate] = useState<Date | null>(null);
const [toDate, setToDate] = useState<Date | null>(null);
const [loading, setLoading] = useState(false);
// 🔹 Fetch logic
const handleFetch = async () => {
if (!selectType || !transactionType || !fromDate || !toDate) {
notifications.show({
color: "red",
title: "Missing Fields",
message: "Please fill all fields before fetching report.",
});
return;
}
setLoading(true);
try {
const response = await fetch(
`/api/reports/transactions?type=${selectType}&txnType=${transactionType}&start=${fromDate.toISOString()}&end=${toDate.toISOString()}`
);
if (!response.ok) throw new Error("Failed to fetch report");
const data = await response.json();
console.log("Fetched Transaction Report:", data);
notifications.show({
color: "green",
title: "Success",
message: "Transaction report fetched successfully!",
});
} catch (err) {
notifications.show({
color: "red",
title: "Error",
message: "Unable to fetch transaction report. Try again later.",
});
} finally {
setLoading(false);
}
};
// Download logic with 3-month validation
const handleDownload = () => {
if (!selectType || !transactionType || !fromDate || !toDate) {
notifications.show({
color: "red",
title: "Missing Fields",
message: "Please fill all fields before downloading report.",
});
return;
}
const monthDiff =
(toDate.getFullYear() - fromDate.getFullYear()) * 12 +
(toDate.getMonth() - fromDate.getMonth());
if (monthDiff > 3) {
notifications.show({
color: "red",
title: "Invalid Range",
message: "You can only download reports up to 3 months at a time.",
});
return;
}
notifications.show({
color: "blue",
title: "Download Started",
message: "Downloading transaction report...",
});
// Add your actual download API logic here
console.log("Downloading report:", {
selectType,
transactionType,
fromDate,
toDate,
});
};
// Reset logic
const handleReset = () => {
setSelectType(null);
setTransactionType(null);
setFromDate(null);
setToDate(null);
notifications.show({
color: "yellow",
title: "Form Reset",
message: "All fields cleared successfully.",
});
};
return (
<Box
p="md"
style={{
backgroundColor: "white",
borderRadius: "8px",
boxShadow: "0 0 5px rgba(0,0,0,0.1)",
maxWidth: 1000,
}}
>
<Stack gap="md">
<Group gap="md" grow>
{/* Select Type */}
<Select
label="Select Type"
placeholder="Choose Type"
data={[
{ value: "IB", label: "IB" },
{ value: "MB", label: "MB" },
]}
value={selectType}
onChange={setSelectType}
style={{ width: "200px" }}
/>
{/* Transaction Type */}
<Select
label="Transaction Type"
placeholder="Select Transaction Type"
data={[
{ value: "IMPS", label: "IMPS" },
{ value: "RTGS", label: "RTGS" },
{ value: "NEFT", label: "NEFT" },
]}
value={transactionType}
onChange={setTransactionType}
style={{ width: "200px" }}
/>
</Group>
{/* From and To Date on same line */}
<Group grow>
<DatePickerInput
label="From Date"
placeholder="Select from date"
value={fromDate}
onChange={setFromDate}
/>
<DatePickerInput
label="To Date"
placeholder="Select to date"
value={toDate}
onChange={setToDate}
/>
</Group>
{/* Buttons aligned to right */}
<Group justify="flex-end" mt="sm">
<Button
onClick={handleFetch}
loading={loading}
>
Fetch
</Button>
<Button
onClick={handleDownload}
variant="outline"
color="blue"
title="Download Report"
>
<IconDownload size={18} />
</Button>
<Button
onClick={handleReset}
variant="outline"
color="red"
title="Reset Form"
>
<IconRefresh size={18} />
</Button>
</Group>
</Stack>
</Box>
);
}

View File

@@ -28,6 +28,10 @@ import {
import UserConfiguration from "./UserConfiguration"; import UserConfiguration from "./UserConfiguration";
import ViewUserConfiguration from "./ViewUserConfiguration"; import ViewUserConfiguration from "./ViewUserConfiguration";
import UnlockedUsers from "./UnlockedUsers"; import UnlockedUsers from "./UnlockedUsers";
import ActiveUsersReport from "./ActiveUsersReport";
import InactiveUsersReport from "./InactiveUsersReport";
import TransactionReport from "./TransactionReport";
export default function Login() { export default function Login() {
const router = useRouter(); const router = useRouter();
@@ -267,6 +271,20 @@ export default function Login() {
> >
Active Users Report Active Users Report
</Box> </Box>
<Box
onClick={() => setView("inactiveUsersReport")}
style={{
cursor: "pointer",
color: view === "inactiveUsersReport" ? "#02a355" : "white",
backgroundColor: view === "inactiveUsersReport" ? "white" : "transparent",
borderRadius: "3px",
padding: "3px 6px",
transition: "0.2s",
}}
>
Inactive Users Report
</Box>
<Box <Box
onClick={() => setView("noOfTrans")} onClick={() => setView("noOfTrans")}
style={{ style={{
@@ -373,29 +391,18 @@ export default function Login() {
{view === "userConf" && <UserConfiguration />} {view === "userConf" && <UserConfiguration />}
{view === "viewUser" && <ViewUserConfiguration />} {view === "viewUser" && <ViewUserConfiguration />}
{view === "unlockUser" && <UnlockedUsers />} {view === "unlockUser" && <UnlockedUsers />}
{view === "noOfTrans" && ( {view === "activeUsersReport" && <ActiveUsersReport />}
<Text size="sm" c="gray"> {view === "inactiveUsersReport" && <InactiveUsersReport />}
Reports will be Coming Soon {view === "noOfTrans" && <TransactionReport />}
</Text>
)}
{view === "activeUsersReport" && (
<Text size="sm" c="gray">
Active users Reports will be Coming Soon
</Text>
)}
{view === "ifscConfig" && (
<Text size="sm" c="gray">
IFSC Configuration Page Coming Soon
</Text>
)}
{!view && ( {!view && (
<> <>
<Text size="xl" c="gray"> {/* <Text size="xl" c="gray">
Welcome To The Internet Banking Admin Portal Welcome To The Internet Banking Admin Portal
</Text> </Text>
<Text size="sm" c="black"> <Text size="sm" c="black">
Choose the service from the menu. Choose the service from the menu.
</Text> </Text> */}
</> </>
)} )}
</Box> </Box>
@@ -404,7 +411,7 @@ export default function Login() {
<Divider size="xs" color="#99c2ff" /> <Divider size="xs" color="#99c2ff" />
<Text size="xs" style={{ textAlign: "center" }}> <Text size="xs" style={{ textAlign: "center" }}>
© 2025 The Kangra Central Co-Operative Bank © 2025 The Kangra Central Co-Operative Bank Ltd.
</Text> </Text>
</div> </div>
</Providers> </Providers>

View File

@@ -171,7 +171,7 @@ export default function Login() {
mt="sm" mt="sm"
/> />
<Group mt="sm" align="center"> <Group mt="sm" align="center">
<Box style={{ backgroundColor: "#fff", fontSize: "18px", textDecoration: "line-through", padding: "4px 8px", fontFamily: "cursive" }}>{captcha}</Box> <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> <Button size="xs" variant="light" onClick={regenerateCaptcha}>Refresh</Button>
</Group> </Group>

View 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;
};

View File

@@ -1,387 +0,0 @@
"use client";
import React, { useState, useEffect, Suspense } from "react";
import { Text, Button, TextInput, PasswordInput, Title, Card, Group, Box, Image } from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { Providers } from "@/app/providers";
import { useRouter } from "next/navigation";
import NextImage from "next/image";
import logo from '@/app/image/logo1.jpg';
import frontPage from '@/app/image/EMandate.jpg';
import { generateCaptcha } from '@/app/captcha';
import styles from './Login.module.css';
import { useSearchParams } from "next/navigation";
function LoginEmandate() {
const router = useRouter();
const [CIF, SetCIF] = useState("");
const [psw, SetPsw] = useState("");
const [captcha, setCaptcha] = useState("");
const [inputCaptcha, setInputCaptcha] = useState("");
const [isLogging, setIsLogging] = useState(false);
const searchParams = useSearchParams();
const data = searchParams.get("data");
useEffect(() => {
if (data) {
console.log("URL parameter 'data':", data);
localStorage.setItem("Emendate_data", data);
}
}, [data]);
useEffect(() => {
const loadCaptcha = async () => {
const newCaptcha = await generateCaptcha();
setCaptcha(newCaptcha);
};
loadCaptcha();
}, []);
const regenerateCaptcha = () => {
// setCaptcha(generateCaptcha());
const loadCaptcha = async () => {
const newCaptcha = await generateCaptcha();
setCaptcha(newCaptcha);
};
loadCaptcha();
setInputCaptcha("");
};
async function handleMandateLogin(e: React.FormEvent) {
e.preventDefault();
const onlyDigit = /^\d{11}$/;
if (!onlyDigit.test(CIF)) {
// setError('Input value must be 11 digit');
notifications.show({
withBorder: true,
color: "red",
title: "Invalid UserId",
message: "UserID must be 11 digit",
autoClose: 5000,
});
return;
}
if (!inputCaptcha) {
notifications.show({
withBorder: true,
color: "red",
title: "Invalid Captcha",
message: "Please fill the Captcha filed",
autoClose: 5000,
});
return;
}
if (inputCaptcha !== captcha) {
notifications.show({
withBorder: true,
color: "red",
title: "Captcha Error",
message: "Please enter the correct captcha",
autoClose: 5000,
});
regenerateCaptcha();
return;
}
if (!CIF || !psw) {
notifications.show({
withBorder: true,
color: "red",
title: "Invalid Input",
message: "Please fill UserId and Password",
autoClose: 5000,
});
return;
}
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Login-Type': 'eMandate',
},
body: JSON.stringify({
customerNo: CIF,
password: psw,
}),
});
const data = await response.json();
if (!response.ok) {
notifications.show({
withBorder: true,
color: "red",
title: "Error",
message: data?.error || "Internal Server Error",
autoClose: 5000,
});
regenerateCaptcha()
localStorage.removeItem("mandate_token");
localStorage.clear();
sessionStorage.clear();
return;
}
setIsLogging(true);
if (response.ok) {
// console.log(data);
const token = data.token;
localStorage.setItem("mandate_token", token);
// localStorage.setItem("pswExpiryDate", data.loginPswExpiry);
if (data.FirstTimeLogin === true) {
notifications.show({
withBorder: true,
color: "red",
title: "Error",
message: "Please go to Internet Banking, set your credentials, and then try logging in here again.",
autoClose: 5000,
});
}
else {
router.push("/eMandate/mandate_page");
}
}
else {
regenerateCaptcha();
setIsLogging(false);
notifications.show({
withBorder: true,
color: "red",
title: "Wrong User Id or Password",
message: "Wrong User Id or Password",
autoClose: 5000,
});
}
}
catch (error: any) {
notifications.show({
withBorder: true,
color: "red",
title: "Error",
message: "Internal Server Error,Please try again Later",
autoClose: 5000,
});
return;
}
}
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>
</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",
}}
>
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&apos;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%); }
}
@media (max-width: 768px) {
.desktop-scroll-text { display: none; }
}
`}
</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' }}>
<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: "cursive" }}>{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: "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: "cursive",
}}
>
{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>
</div>
</div>
</Providers>
);
}
export default function Login() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LoginEmandate />
</Suspense>
);
}

View File

@@ -0,0 +1,458 @@
"use client";
import React, { useState, useEffect, Suspense } from "react";
import { Text, Button, TextInput, PasswordInput, Title, Card, Group, Box, Image } from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { Providers } from "@/app/providers";
import { useRouter } from "next/navigation";
import NextImage from "next/image";
import logo from '@/app/image/logo1.jpg';
import frontPage from '@/app/image/EMandate.jpg';
import { generateCaptcha } from '@/app/captcha';
import styles from './Login.module.css';
import { useSearchParams } from "next/navigation";
function LoginEmandate() {
const router = useRouter();
const [CIF, SetCIF] = useState("");
const [psw, SetPsw] = useState("");
const [captcha, setCaptcha] = useState("");
const [inputCaptcha, setInputCaptcha] = useState("");
const [isLogging, setIsLogging] = useState(false);
const searchParams = useSearchParams();
const data = searchParams.get("data");
const mandateReqDoc = searchParams.get("mandateReqDoc");
const mndtType = searchParams.get("mndtType");
// useEffect(() => {
// if (data) {
// console.log("URL parameter 'data':", data);
// localStorage.setItem("Emendate_data", data);
// localStorage.setItem("Emendate_req_doc", mandateReqDoc || "");
// localStorage.setItem("Emendate_type", mndtType || "");
// }
// }, [data]);
useEffect(() => {
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, mandateReqDoc, mndtType]);
useEffect(() => {
const loadCaptcha = async () => {
const newCaptcha = await generateCaptcha();
setCaptcha(newCaptcha);
};
loadCaptcha();
}, []);
const regenerateCaptcha = () => {
// setCaptcha(generateCaptcha());
const loadCaptcha = async () => {
const newCaptcha = await generateCaptcha();
setCaptcha(newCaptcha);
};
loadCaptcha();
setInputCaptcha("");
};
async function handleMandateLogin(e: React.FormEvent) {
e.preventDefault();
const onlyDigit = /^\d{11}$/;
if (!onlyDigit.test(CIF)) {
// setError('Input value must be 11 digit');
notifications.show({
withBorder: true,
color: "red",
title: "Invalid UserId",
message: "UserID must be 11 digit",
autoClose: 5000,
});
return;
}
if (!inputCaptcha) {
notifications.show({
withBorder: true,
color: "red",
title: "Invalid Captcha",
message: "Please fill the Captcha filed",
autoClose: 5000,
});
return;
}
if (inputCaptcha !== captcha) {
notifications.show({
withBorder: true,
color: "red",
title: "Captcha Error",
message: "Please enter the correct captcha",
autoClose: 5000,
});
regenerateCaptcha();
return;
}
if (!CIF || !psw) {
notifications.show({
withBorder: true,
color: "red",
title: "Invalid Input",
message: "Please fill UserId and Password",
autoClose: 5000,
});
return;
}
try {
setIsLogging(true);
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Login-Type': 'eMandate',
},
body: JSON.stringify({
customerNo: CIF,
password: psw,
}),
});
const data = await response.json();
if (!response.ok) {
notifications.show({
withBorder: true,
color: "red",
title: "Error",
message: data?.error || "Internal Server Error",
autoClose: 5000,
});
regenerateCaptcha()
localStorage.removeItem("mandate_token");
localStorage.clear();
sessionStorage.clear();
setIsLogging(false);
return;
}
if (response.ok) {
// console.log(data);
const token = data.token;
localStorage.setItem("mandate_token", token);
// localStorage.setItem("pswExpiryDate", data.loginPswExpiry);
if (data.FirstTimeLogin === true) {
notifications.show({
withBorder: true,
color: "red",
title: "Error",
message: "Please go to Internet Banking, set your credentials, and then try logging in here again.",
autoClose: 5000,
});
setIsLogging(false);
}
else {
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"),
// customer_no:CIF
}),
});
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 {
regenerateCaptcha();
setIsLogging(false);
notifications.show({
withBorder: true,
color: "red",
title: "Wrong User Id or Password",
message: "Wrong User Id or Password",
autoClose: 5000,
});
}
}
catch (error: any) {
notifications.show({
withBorder: true,
color: "red",
title: "Error",
message: "Internal Server Error,Please try again Later",
autoClose: 5000,
});
setIsLogging(false);
return;
}
}
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>
</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",
}}
>
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&apos;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%); }
}
@media (max-width: 768px) {
.desktop-scroll-text { display: none; }
}
`}
</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' }}>
<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: "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>
</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>
);
}
}
export default function Login() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LoginEmandate />
</Suspense>
);
}

View File

@@ -0,0 +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");
console.log("Received from SoftTech:", { data, mandateReqDoc, mndtType });
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;
}

View 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;
}

View 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>
);
}

View File

@@ -117,7 +117,10 @@ export default function MandatePage() {
localStorage.removeItem("mandate_token"); localStorage.removeItem("mandate_token");
localStorage.removeItem("user_name"); localStorage.removeItem("user_name");
localStorage.removeItem("userMobNo"); 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 () => { const handleFetchUserName = async () => {
@@ -430,7 +433,7 @@ export default function MandatePage() {
}} }}
> >
<Text c="dimmed" size="xs"> <Text c="dimmed" size="xs">
© 2025 The Kangra Central Co-Operative Bank © 2025 The Kangra Central Co-Operative Bank Ltd.
</Text> </Text>
</Box> </Box>

View 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;
}
};

View 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>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -9,7 +9,7 @@ import vision from '@/app/image/vision.jpg';
export default function CustomCarousel() { export default function CustomCarousel() {
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const images = [DICGC,vision,objective]; const images = [DICGC, vision, objective];
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(0);
const scrollToIndex = (index: number) => { const scrollToIndex = (index: number) => {
@@ -40,7 +40,7 @@ export default function CustomCarousel() {
}, [currentIndex]); }, [currentIndex]);
return ( return (
<Box style={{ position: 'relative', width: '90%', overflow: 'hidden',backgroundColor:"white" }}> <Box style={{ position: 'relative', width: '90%', overflow: 'hidden', backgroundColor: "white" }}>
{/* Scrollable container */} {/* Scrollable container */}
<Box <Box
ref={scrollRef} ref={scrollRef}
@@ -54,27 +54,29 @@ export default function CustomCarousel() {
{images.map((img, i) => ( {images.map((img, i) => (
<Box <Box
key={i} key={i}
onClick={() => {
if (i === 0) window.open("https://dicgc.org.in", "_blank");
}}
style={{ style={{
flex: '0 0 100%', flex: "0 0 100%",
scrollSnapAlign: 'start', scrollSnapAlign: "start",
height: '250px', height: "250px",
minWidth: '100%', minWidth: "100%",
maxWidth: '100%', cursor: i === 0 ? "pointer" : "default",
borderRadius: '8px',
backgroundColor: 'white',
}} }}
> >
<Image <Image
src={img.src} src={img.src}
alt={`Slide ${i + 1}`} alt={`Slide ${i + 1}`}
style={{ style={{
width: '100%', width: "100%",
height: '100%', height: "100%",
objectFit: 'contain', objectFit: "contain",
}} }}
/> />
</Box> </Box>
))} ))}
</Box> </Box>
{/* Left Scroll Button */} {/* Left Scroll Button */}
@@ -111,4 +113,3 @@ export default function CustomCarousel() {
</Box> </Box>
); );
} }

View File

@@ -7,9 +7,9 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; 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%); 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 { .header-text {
@@ -20,7 +20,6 @@
text-align: left; text-align: left;
} }
/* Desktop text */
.desktop-text { .desktop-text {
color: white; color: white;
font-family: Roboto, sans-serif; font-family: Roboto, sans-serif;

View File

@@ -8,10 +8,10 @@ import { sendOtp, verifyLoginOtp } from '@/app/_util/otp';
import NextImage from "next/image"; import NextImage from "next/image";
import styles from './page.module.css'; import styles from './page.module.css';
import logo from '@/app/image/logo1.jpg'; 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 dynamic from 'next/dynamic';
import { generateCaptcha } from '@/app/captcha'; import { generateCaptcha } from '@/app/captcha';
import { IconRefresh, IconShieldLockFilled } from "@tabler/icons-react"; import { IconAlertCircle, IconLock, IconPhone, IconRefresh, IconShieldLockFilled, IconUser } from "@tabler/icons-react";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { fetchAndStoreUserName } from "../_util/userdetails"; import { fetchAndStoreUserName } from "../_util/userdetails";
@@ -45,8 +45,9 @@ export default function Login() {
} }
try { try {
// await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: mobile }); const maskedCIF = CIF?.replace(/.(?=.{3})/g, '*');
await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: "7890544527" }); await sendOtp({ type: 'LOGIN_OTP', username: maskedCIF, mobileNumber: mobile });
// await sendOtp({ type: 'LOGIN_OTP', username: maskedCIF, mobileNumber: "7890544527" });
notifications.show({ notifications.show({
color: 'orange', color: 'orange',
title: 'OTP Required', title: 'OTP Required',
@@ -67,8 +68,8 @@ export default function Login() {
async function handleVerifyOtp(mobile?: string) { async function handleVerifyOtp(mobile?: string) {
try { try {
if (mobile) { if (mobile) {
// await verifyLoginOtp(otp, mobile); await verifyLoginOtp(otp, mobile);
await verifyLoginOtp(otp, '7890544527'); // await verifyLoginOtp(otp, '7890544527');
return true; return true;
} }
} }
@@ -77,6 +78,7 @@ export default function Login() {
title: `${err.message}`, title: `${err.message}`,
message: 'OTP verification failed. Please try again later.', message: 'OTP verification failed. Please try again later.',
color: 'red', color: 'red',
autoClose: 700,
}); });
return false; return false;
} }
@@ -440,9 +442,26 @@ export default function Login() {
)} )}
</Modal> </Modal>
{/* Main Screen */} {/* Main Screen */}
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "auto", paddingTop: "5%" }}> <div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "auto" }}>
{/* Header */} {/* Header */}
<Box className={styles.header}> <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 <Image
src={logo} src={logo}
component={NextImage} component={NextImage}
@@ -450,37 +469,26 @@ export default function Login() {
alt="ebanking" alt="ebanking"
style={{ width: "60px", height: "auto" }} style={{ width: "60px", height: "auto" }}
/> />
<Box className={styles['header-text']}> <div>
{/* Desktop */} <Title order={3} ref={headerRef} style={{ fontFamily: "Roboto", color: "white", marginBottom: 2 }}>
<Title className={styles['desktop-text']} ref={headerRef} order={2}>
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD. THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
</Title> </Title>
<Text className={styles['desktop-address']} size="xs"> <Text size="xs" c="white" style={{ opacity: 0.85 }}>
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 Head Office: Dharmshala, District Kangra (H.P), Pin: 176215
</Text> </Text>
</div>
</Group>
</Box> </Box>
</Box> <div >
<div style={{ marginTop: '10px' }}>
{/* Movable text */} {/* Movable text */}
<Box <Box
style={{ style={{
width: "100%", width: "100%",
height: "0.5%",
overflow: "hidden", overflow: "hidden",
background: "linear-gradient(90deg, #cef7e3ff 0%, #82e8b8ff 100%)",
whiteSpace: "nowrap", whiteSpace: "nowrap",
padding: "8px 0", padding: "8px 0",
// borderBottom: "2px solid #3a95bcff"
}} }}
> >
<Text <Text
@@ -514,9 +522,9 @@ export default function Login() {
<div style={{ <div style={{
display: "flex", height: "75vh", overflow: "hidden", position: "relative", display: "flex", height: "75vh", overflow: "hidden", position: "relative",
// background: 'linear-gradient(to right, #02081eff, #0a3d91)' // background: 'linear-gradient(to right, #02081eff, #0a3d91)'
background: 'linear-gradient(179deg,rgba(1, 13, 18, 1) 49%, rgba(77, 82, 79, 1) 87%)' background: 'linear-gradient(179deg,rgba(92, 175, 210, 1) 49%, rgba(77, 82, 79, 1) 87%)'
}}> }}>
<div style={{ flex: 1, backgroundColor: "#c1e0f0", position: "relative" }}> <Box style={{ flex: 1, backgroundColor: "#c1e0f0", position: "relative" }}>
<Image <Image
fit="cover" fit="cover"
src={frontPage} src={frontPage}
@@ -524,13 +532,39 @@ export default function Login() {
alt="ebanking" alt="ebanking"
style={{ width: "100%", height: "100%" }} style={{ width: "100%", height: "100%" }}
/> />
</div> </Box>
<Box w={{ base: "100%", md: "45%" }} p="lg"> <Box
<Card shadow="md" padding="xl" radius="md" style={{ maxWidth: 550, height: '70vh', justifyContent: 'space-between' }} > 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}> <form onSubmit={handleLogin}>
{/* <Title order={3} style={{ color: "#0a7228", textAlign: "center" }}>
Internet Banking Login
</Title> */}
<TextInput <TextInput
label="User ID / User Name" label="User ID / User Name"
placeholder="Enter your CIF No / User Name" placeholder="Enter your CIF No / User Name"
leftSection={<IconUser size={18} />}
value={CIF} value={CIF}
onInput={(e) => { onInput={(e) => {
// const input = e.currentTarget.value.replace(/\D/g, ""); // const input = e.currentTarget.value.replace(/\D/g, "");
@@ -545,6 +579,7 @@ export default function Login() {
<PasswordInput <PasswordInput
label="Password" label="Password"
placeholder="Enter your password" placeholder="Enter your password"
leftSection={<IconLock size={18} />}
value={psw} value={psw}
onChange={(e) => SetPsw(e.currentTarget.value)} onChange={(e) => SetPsw(e.currentTarget.value)}
withAsterisk withAsterisk
@@ -553,32 +588,49 @@ export default function Login() {
readOnly={isLogging} readOnly={isLogging}
// mt="sm" // mt="sm"
/> />
<Box style={{ textAlign: "right" }}> <Box style={{ textAlign: "right", marginTop: "0.5rem" }}>
{/* <Anchor <Anchor
style={{ fontSize: "14px", color: "#1c7ed6", cursor: "pointer" }} style={{ fontSize: "14px", color: "#1c7ed6", cursor: "pointer" }}
onClick={() => router.push("/ValidateUser")} onClick={() => router.push("/ValidateUser")}
> >
Forgot Password? Forgot Password?
</Anchor> */} </Anchor>
</Box> </Box>
<Group align="center"> <Group align="center">
<Box style={{ <Box style={{
backgroundColor: "#fff", fontSize: "18px", textDecoration: "line-through", padding: "4px 8px", fontFamily: "cursive", flex: 1,
padding: "5px",
background: "white",
borderRadius: "8px",
border: "2px dashed #dee6deff",
textAlign: "center",
fontWeight: 500,
fontSize: "22px",
letterSpacing: "6px",
color: "#0a7228",
fontFamily: "Verdana",
userSelect: "none", userSelect: "none",
pointerEvents: "none", textDecoration: "line-through"
}}
}}
onCopy={(e) => e.preventDefault()} onCopy={(e) => e.preventDefault()}
onContextMenu={(e) => e.preventDefault()} onContextMenu={(e) => e.preventDefault()}
> >
{captcha} {captcha}
</Box> </Box>
<Button
<Button size="xs" variant="light" onClick={regenerateCaptcha} disabled={otpRequired}>Refresh</Button> variant="light"
color="green"
onClick={regenerateCaptcha}
disabled={otpRequired}
leftSection={<IconRefresh size={16} />}
>
Refresh
</Button>
</Group> </Group>
<TextInput <TextInput
label="Enter CAPTCHA" label="Enter CAPTCHA"
placeholder="Enter above text" placeholder="Enter the text above"
value={inputCaptcha} value={inputCaptcha}
onChange={(e) => setInputCaptcha(e.currentTarget.value)} onChange={(e) => setInputCaptcha(e.currentTarget.value)}
withAsterisk withAsterisk
@@ -590,10 +642,11 @@ export default function Login() {
{otpRequired && ( {otpRequired && (
<Group align="end" gap="xs"> <Group align="end" gap="xs">
<PasswordInput <PasswordInput
label="Enter OTP" label="One-Time Password"
placeholder="Enter OTP" placeholder="Enter 6-digit OTP"
value={otp} value={otp}
maxLength={6} maxLength={6}
leftSection={<IconPhone size={18} />}
onChange={(e) => setOtp(e.currentTarget.value)} onChange={(e) => setOtp(e.currentTarget.value)}
withAsterisk withAsterisk
style={{ flex: 1 }} style={{ flex: 1 }}
@@ -605,30 +658,39 @@ export default function Login() {
px={10} px={10}
disabled={isLogging || otpVerified} disabled={isLogging || otpVerified}
onClick={() => handleSendOtp(mobile)} onClick={() => handleSendOtp(mobile)}
leftSection={<IconRefresh size={20} />}
style={{ alignSelf: "flex-end", marginBottom: 4 }} style={{ alignSelf: "flex-end", marginBottom: 4 }}
> >
<IconRefresh size={20} /> Resend Resend
</Button> </Button>
</Tooltip> </Tooltip>
</Group> </Group>
)} )}
<Button type="submit" fullWidth mt="sm" loading={isLogging} disabled={isLogging}> <Button type="submit" fullWidth mt="sm" loading={isLogging} disabled={isLogging}>
{isLogging ? "Processing..." : buttonLabel} {isLogging ? "Processing..." : buttonLabel}
</Button> </Button>
<Box mt="xs"> <Box mt="xs"
<Text size="sm"> style={{
<Text component="span" c="red" fw={600}>Note: </Text> background: "#cdffdfff",
<Text component="span" c="black"> borderRadius: "8px",
Existing users logging in to the new Internet Banking for the first time should use their CIF number to avoid login issues. border: "1px solid #42c442ff",
</Text> borderLeft: "4px solid #42c442ff"
}}>
<Group gap="xs" align="flex-start">
<IconAlertCircle size={20} color="#856404" style={{ marginTop: 1 }} />
<Box style={{ flex: 1 }}>
<Text size="sm" fw={600} c="#856404">Important Note</Text>
<Text size="sm" c="#151615ff" mt={4}>
Existing users logging in for the first time should use their CIF number to avoid login issues.
</Text> </Text>
</Box> </Box>
</Group>
</Box>
</form> </form>
</Card> </Card>
</Box> </Box>
</div> </div>
{/* Carousel and Notes */} {/* Carousel and Notes */}
<Flex direction={{ base: "column", md: "row" }} mt="md" px="md" py="sm" gap="sm"> <Flex direction={{ base: "column", md: "row" }} mt="md" px="md" py="sm" gap="sm">
<Box w={{ base: "100%", md: "85%" }}> <Box w={{ base: "100%", md: "85%" }}>
@@ -638,7 +700,13 @@ export default function Login() {
<Title order={2}> <IconShieldLockFilled />Security Notes :</Title> <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" 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> <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> </Box>
</Flex> </Flex>
{/* Footer */} {/* Footer */}
@@ -665,6 +733,6 @@ export default function Login() {
</Box> </Box>
</div> </div>
</div> </div>
</Providers> </Providers >
); );
} }