fix: design in view profile and account overview
feat : page add for e mandate otp
This commit is contained in:
@@ -2,16 +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 {
|
||||||
import { useSearchParams } from "next/navigation";
|
IconCreditCard,
|
||||||
|
IconWallet,
|
||||||
|
IconTrendingUp,
|
||||||
|
IconBuilding,
|
||||||
|
IconCircleCheck,
|
||||||
|
IconAlertCircle,
|
||||||
|
IconUser,
|
||||||
|
IconFileText,
|
||||||
|
IconCircleDot,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
|
||||||
interface accountData {
|
interface accountData {
|
||||||
stAccountNo: string;
|
stAccountNo: string;
|
||||||
@@ -19,23 +39,21 @@ 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 searchParams = useSearchParams();
|
const [loading, setLoading] = useState(false);
|
||||||
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,9 +69,6 @@ export default function AccountDetails() {
|
|||||||
value: acc.stAccountNo,
|
value: acc.stAccountNo,
|
||||||
}));
|
}));
|
||||||
setAccountOptions(options);
|
setAccountOptions(options);
|
||||||
if (passedAccNo) {
|
|
||||||
handleAccountSelection(passedAccNo);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [authorized]);
|
}, [authorized]);
|
||||||
@@ -62,6 +77,8 @@ export default function AccountDetails() {
|
|||||||
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", {
|
||||||
@@ -77,11 +94,10 @@ export default function AccountDetails() {
|
|||||||
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.stAvailableBalance) + 20000
|
||||||
).toFixed(2); // dummy logic
|
).toFixed(2);
|
||||||
}
|
}
|
||||||
setAccountDetails(matched);
|
setAccountDetails(matched);
|
||||||
} else {
|
} else {
|
||||||
@@ -102,79 +118,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={500}>
|
|
||||||
<Title order={3} mb="md">
|
|
||||||
Account Details
|
|
||||||
</Title>
|
|
||||||
|
|
||||||
<Stack gap="md">
|
<Grid gutter="md">
|
||||||
<Select
|
{/* Left side – Account Selector */}
|
||||||
label="Select Account Number"
|
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||||
placeholder="Choose account number"
|
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
||||||
data={accountOptions}
|
<Title order={4} mb="md">
|
||||||
value={selectedAccNo}
|
Account Selector
|
||||||
onChange={handleAccountSelection}
|
</Title>
|
||||||
searchable
|
<Select
|
||||||
/>
|
label="Select Account Number"
|
||||||
|
placeholder="Choose an account number"
|
||||||
|
data={accountOptions}
|
||||||
|
value={selectedAccNo}
|
||||||
|
onChange={handleAccountSelection}
|
||||||
|
searchable
|
||||||
|
size="sm"
|
||||||
|
leftSection={<IconCreditCard size={20} />}
|
||||||
|
styles={{
|
||||||
|
input: {
|
||||||
|
borderRadius: rem(12),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Grid.Col>
|
||||||
|
|
||||||
{accountDetails && (
|
{/* 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"
|
||||||
</Group>
|
withBorder
|
||||||
|
h={500}
|
||||||
|
style={{ display: "flex", flexDirection: "column", overflow: "auto" }}
|
||||||
|
>
|
||||||
|
<Title order={4} mb="md">
|
||||||
|
Account Details
|
||||||
|
</Title>
|
||||||
|
|
||||||
<Group p="apart">
|
{/* Loading State */}
|
||||||
<Text size="sm" fw={500} c="dimmed">Account Type</Text>
|
{loading && (
|
||||||
<Text size="md">{accountDetails.stAccountType}</Text>
|
<Center style={{ flex: 1 }}>
|
||||||
</Group>
|
<Stack align="center" gap="md">
|
||||||
|
<Loader size="lg" type="dots" />
|
||||||
|
<Text c="dimmed">Loading account details...</Text>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
<Group p="apart">
|
{/* Account Details */}
|
||||||
<Text size="sm" fw={500} c="dimmed">Description</Text>
|
{!loading && accountDetails && (
|
||||||
<Text size="md">{accountDetails.stBookingNumber}</Text>
|
<Stack gap="sm" style={{ flex: 1 }}>
|
||||||
</Group>
|
{/* Account Header Card */}
|
||||||
|
<Paper
|
||||||
{/* Show Loan-specific fields */}
|
shadow="md"
|
||||||
{accountDetails.stAccountType.toUpperCase().includes("LN") ? (
|
p="xs"
|
||||||
<>
|
radius="md"
|
||||||
<Group p="apart">
|
style={{
|
||||||
<Text size="sm" fw={500} c="dimmed">Approved Balance</Text>
|
background: "linear-gradient(56deg, rgba(24,140,186,1) 0%, rgba(62,230,132,1) 86%)",
|
||||||
<Text size="md" c="gray.8">
|
}}
|
||||||
₹{parseFloat(accountDetails.stApprovedAmount || "0").toLocaleString("en-IN", {
|
>
|
||||||
minimumFractionDigits: 2,
|
<Group justify="space-between" align="flex-start" wrap="wrap" mb="xs">
|
||||||
})}
|
<Group gap="sm">
|
||||||
</Text>
|
<ThemeIcon size={48} radius="md" variant="white" color={accountColor}>
|
||||||
|
<AccountIcon size={28} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Box>
|
||||||
|
<Text size="xs" c="white" opacity={0.8}>
|
||||||
|
Account Number
|
||||||
|
</Text>
|
||||||
|
<Title order={3} c="white" style={{ letterSpacing: "0.5px" }}>
|
||||||
|
{accountDetails.stAccountNo}
|
||||||
|
</Title>
|
||||||
|
</Box>
|
||||||
</Group>
|
</Group>
|
||||||
<Group p="apart">
|
<Badge size="md" variant="white" color={accountColor} radius="md">
|
||||||
<Text size="sm" fw={500} c="dimmed">Available Balance</Text>
|
{accountDetails.stAccountType}
|
||||||
<Text size="md" c="red">
|
</Badge>
|
||||||
– ₹{parseFloat(accountDetails.stAvailableBalance).toLocaleString("en-IN", {
|
</Group>
|
||||||
minimumFractionDigits: 2,
|
|
||||||
})}
|
<Divider color="white" opacity={0.2} my="sm" />
|
||||||
</Text>
|
|
||||||
</Group>
|
<Box>
|
||||||
</>
|
<Text size="xs" c="white" opacity={0.8} mb={4}>
|
||||||
|
Description
|
||||||
|
</Text>
|
||||||
|
<Text c="white">{accountDetails.stBookingNumber}</Text>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Balance Cards */}
|
||||||
|
{isLoanAccount ? (
|
||||||
|
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="sm">
|
||||||
|
{/* Approved Balance */}
|
||||||
|
<Card shadow="sm" padding="md" radius="md" withBorder>
|
||||||
|
<Group gap="xs" mb="xs">
|
||||||
|
<ThemeIcon size={32} radius="md" variant="light" color="teal">
|
||||||
|
<IconCircleCheck size={20} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
Approved Balance
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Title order={3} c="dark">
|
||||||
|
₹
|
||||||
|
{parseFloat(accountDetails.stApprovedAmount || "0").toLocaleString(
|
||||||
|
"en-IN",
|
||||||
|
{
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Title>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Outstanding Amount */}
|
||||||
|
<Card shadow="sm" padding="md" radius="md" withBorder>
|
||||||
|
<Group gap="xs" mb="xs">
|
||||||
|
<ThemeIcon size={32} radius="md" variant="light" color="red">
|
||||||
|
<IconAlertCircle size={20} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
Outstanding Amount
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Title order={3} c="red">
|
||||||
|
– ₹
|
||||||
|
{parseFloat(accountDetails.stAvailableBalance).toLocaleString(
|
||||||
|
"en-IN",
|
||||||
|
{
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Title>
|
||||||
|
</Card>
|
||||||
|
</SimpleGrid>
|
||||||
) : (
|
) : (
|
||||||
<Group p="apart">
|
<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} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
Available Balance
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Title order={2} c="teal">
|
||||||
|
₹
|
||||||
|
{parseFloat(accountDetails.stAvailableBalance).toLocaleString("en-IN", {
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Title>
|
||||||
</Group>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Account Information */}
|
||||||
|
<Paper shadow="sm" p="md" radius="md" withBorder>
|
||||||
|
<Title order={5} mb="md">
|
||||||
|
Account Information
|
||||||
|
</Title>
|
||||||
|
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
|
||||||
|
<Stack gap={4}>
|
||||||
|
<Group gap="xs">
|
||||||
|
<ThemeIcon size={18} radius="xl" variant="light" color="gray">
|
||||||
|
<IconUser size={12} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
Account Holder
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Text size="sm">{accountDetails.custname}</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack gap={4}>
|
||||||
|
<Group gap="xs">
|
||||||
|
<ThemeIcon size={18} radius="xl" variant="light" color="gray">
|
||||||
|
<IconCreditCard size={12} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
Account Type
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Text size="sm">{accountDetails.stAccountType}</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack gap={4}>
|
||||||
|
<Group gap="xs">
|
||||||
|
<ThemeIcon size={18} radius="xl" variant="light" color="gray">
|
||||||
|
<IconFileText size={12} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
Booking Number
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Text size="sm">{accountDetails.stBookingNumber}</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack gap={4}>
|
||||||
|
<Group gap="xs">
|
||||||
|
<ThemeIcon size={18} radius="xl" variant="light" color="gray">
|
||||||
|
<IconCircleDot size={12} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
Status
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Badge
|
||||||
|
color="teal"
|
||||||
|
variant="light"
|
||||||
|
size="md"
|
||||||
|
leftSection={<IconCircleDot size={10} />}
|
||||||
|
>
|
||||||
|
Active
|
||||||
|
</Badge>
|
||||||
|
</Stack>
|
||||||
|
</SimpleGrid>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
)}
|
||||||
)}
|
|
||||||
</Stack>
|
{/* Empty State */}
|
||||||
</Paper>
|
{!loading && !accountDetails && (
|
||||||
|
<Center style={{ flex: 1 }}>
|
||||||
|
<Stack align="center" gap="md">
|
||||||
|
<ThemeIcon size={56} radius="xl" variant="light" color="gray">
|
||||||
|
<IconCreditCard size={28} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Text c="dimmed" size="sm">
|
||||||
|
{selectedAccNo
|
||||||
|
? "No account details available"
|
||||||
|
: "Please select an account to view details"}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
"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 { generateExcel } from "@/app/_components/statement_download/CsvGenerator";
|
import { generateExcel } from "@/app/_components/statement_download/CsvGenerator";
|
||||||
import { useMediaQuery } from "@mantine/hooks";
|
import { useMediaQuery } from "@mantine/hooks";
|
||||||
@@ -169,7 +169,7 @@ export default function AccountStatementPage() {
|
|||||||
{/* 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={500}>
|
<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"
|
||||||
@@ -203,7 +203,7 @@ 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={500} 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>
|
||||||
@@ -230,9 +230,9 @@ export default function AccountStatementPage() {
|
|||||||
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_branch_no") || "",
|
||||||
localStorage.getItem("remitter_cif_no") || "",
|
localStorage.getItem("remitter_cif_no") || "",
|
||||||
localStorage.getItem("remitter_address") || ""
|
localStorage.getItem("remitter_address") || ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -247,9 +247,9 @@ export default function AccountStatementPage() {
|
|||||||
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_branch_no") || "",
|
||||||
localStorage.getItem("remitter_cif_no") || "",
|
localStorage.getItem("remitter_cif_no") || "",
|
||||||
localStorage.getItem("remitter_address") || ""
|
localStorage.getItem("remitter_address") || ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -278,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">
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export default function AccountSummary() {
|
|||||||
return (
|
return (
|
||||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
<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" }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ const AddBeneficiary: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
<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">
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ export default function ViewBeneficiary() {
|
|||||||
return (
|
return (
|
||||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
||||||
<Group justify="space-between" align="center" mb="md" wrap="wrap">
|
<Group justify="space-between" align="center" mb="md" wrap="wrap">
|
||||||
<Title order={3}>My Beneficiaries</Title>
|
<Title order={4}>My Beneficiaries</Title>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
color="green"
|
color="green"
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ export default function QuickPay() {
|
|||||||
</Modal>
|
</Modal>
|
||||||
{/* main content */}
|
{/* main content */}
|
||||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
<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>
|
||||||
|
|
||||||
|
|||||||
@@ -397,7 +397,7 @@ export default function SendToBeneficiaryOwn() {
|
|||||||
|
|
||||||
{/* main content */}
|
{/* main content */}
|
||||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
<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">
|
||||||
|
|||||||
@@ -472,16 +472,24 @@ 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>
|
||||||
|
|||||||
@@ -303,16 +303,16 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
{/* NAVBAR — desktop unchanged, mobile scrollable */}
|
{/* NAVBAR — desktop unchanged, mobile scrollable */}
|
||||||
<Group
|
<Group
|
||||||
style={{
|
style={{
|
||||||
background: "#d3f3bcff",
|
background: "#c8eeacff",
|
||||||
boxShadow: "0 6px 6px rgba(0,0,0,0.06)",
|
boxShadow: "0 6px 6px rgba(0,0,0,0.06)",
|
||||||
position: "sticky",
|
position: "sticky",
|
||||||
top: isMobile ? 60 : 85,
|
top: isMobile ? 60 : 85,
|
||||||
zIndex: 150,
|
zIndex: 150,
|
||||||
|
|
||||||
/* MOBILE FIX 👉 make it scrollable */
|
/* MOBILE FIX make it scrollable */
|
||||||
overflowX: isMobile ? "auto" : "visible",
|
overflowX: isMobile ? "auto" : "visible",
|
||||||
whiteSpace: isMobile ? "nowrap" : "normal",
|
whiteSpace: isMobile ? "nowrap" : "normal",
|
||||||
padding: isMobile ? "6px 4px" : "0.8rem",
|
padding: isMobile ? "6px 4px" : "0.05rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{navItems.map((item) => {
|
{navItems.map((item) => {
|
||||||
@@ -324,7 +324,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
<Group
|
<Group
|
||||||
gap={8}
|
gap={8}
|
||||||
style={{
|
style={{
|
||||||
padding: isMobile ? "10px 14px" : "12px 24px",
|
padding: isMobile ? "10px 14px" : "14px 16px",
|
||||||
// borderRadius: isMobile ? 6 : 8,
|
// borderRadius: isMobile ? 6 : 8,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
transition: "0.2s ease",
|
transition: "0.2s ease",
|
||||||
@@ -434,7 +434,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Text size="sm" fw={500}>The Kangra Central</Text>
|
<Text size="sm" fw={500}>The Kangra Central</Text>
|
||||||
<Text size="xs">Co-operative Bank Ltd</Text>
|
<Text size="sm" fw={500}>Co-operative Bank Ltd</Text>
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
@@ -445,18 +445,42 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||||
<Text size="sm" fw={500} mb="md">Quick Links</Text>
|
<Text size="sm" fw={500} mb="md">Quick Links</Text>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Anchor href="#" size="sm" c="dimmed">About Us</Anchor>
|
<Anchor
|
||||||
<Anchor href="#" size="sm" c="dimmed">Products & Services</Anchor>
|
href="https://kccbhp.bank.in/about-us/history-of-kccb/"
|
||||||
<Anchor href="#" size="sm" c="dimmed">Help & Support</Anchor>
|
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>
|
</Stack>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
||||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||||
<Text size="sm" fw={500} mb="md">Contact Us</Text>
|
<Text size="sm" fw={500} mb="md">Contact Us</Text>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Text size="sm" c="dimmed">Phone: +91-1800-1808008</Text>
|
<Text size="sm" c="dimmed">Phone: +91-1800-1808008 </Text>
|
||||||
<Text size="sm" c="dimmed">Mon–Fri 10 AM – 4 PM</Text>
|
<Text size="sm" c="dimmed">Mon–Sat 10 AM – 5 PM</Text>
|
||||||
<Text size="sm" c="dimmed">Sat 10 AM – 2 PM</Text>
|
<Text size="sm" c="dimmed">(The Second and fourth Saturdays are holidays)</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ export default function ChangePassword() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
<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 */}
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ export default function ChangePassword() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
<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>
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
@@ -40,7 +41,7 @@ export default function ViewProfile() {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-Login-Type": "IB",
|
"X-Login-Type": "IB",
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -79,39 +80,90 @@ 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;
|
||||||
<Grid align="flex-start" gutter="xs" mb={6}>
|
showValue?: boolean;
|
||||||
<Grid.Col span={3}>
|
onToggle?: () => void;
|
||||||
<Text c="dimmed" size="sm" fw={500}>
|
}) => {
|
||||||
{label}
|
const displayValue = masked && !showValue ? maskValue(value) : value;
|
||||||
</Text>
|
|
||||||
</Grid.Col>
|
return (
|
||||||
<Grid.Col span={9}>
|
<Grid align="flex-start" gutter="xs" mb={6}>
|
||||||
{link ? (
|
<Grid.Col span={3}>
|
||||||
<Anchor size="sm" href={link} target="_blank" rel="noopener noreferrer">
|
<Text c="dimmed" size="sm" fw={500}>
|
||||||
{value}
|
{label}
|
||||||
</Anchor>
|
</Text>
|
||||||
) : (
|
</Grid.Col>
|
||||||
<Text size="sm">{value}</Text>
|
<Grid.Col span={9}>
|
||||||
)}
|
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||||
</Grid.Col>
|
{link ? (
|
||||||
</Grid>
|
<Anchor size="sm" href={link} target="_blank" rel="noopener noreferrer">
|
||||||
);
|
{displayValue}
|
||||||
|
</Anchor>
|
||||||
|
) : (
|
||||||
|
<Text size="sm">{displayValue}</Text>
|
||||||
|
)}
|
||||||
|
{masked && onToggle && (
|
||||||
|
<ActionIcon
|
||||||
|
variant="subtle"
|
||||||
|
size="sm"
|
||||||
|
onClick={onToggle}
|
||||||
|
aria-label={showValue ? "Hide" : "Show"}
|
||||||
|
>
|
||||||
|
{showValue ? <IconEyeOff size={16} /> : <IconEye size={16} />}
|
||||||
|
</ActionIcon>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper shadow="xs" radius="md" p="md" withBorder h={500}>
|
<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">
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ export default function SetTransactionLimit() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
<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>
|
||||||
|
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ export default function ChangePassword() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500} >
|
<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>
|
||||||
|
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ export default function SetPreferredNameSimple() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
|
<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>
|
||||||
|
|
||||||
|
|||||||
40
src/app/eMandate/authUtils.ts
Normal file
40
src/app/eMandate/authUtils.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
|
||||||
|
export const fetchUserDetails = async () => {
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem("mandate_token");
|
||||||
|
|
||||||
|
const response = await fetch("/api/customer", {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Login-Type": "eMandate",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error("Failed");
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (Array.isArray(data) && data.length > 0) {
|
||||||
|
const name = data[0].custname;
|
||||||
|
const mobile = data[0].mobileno;
|
||||||
|
|
||||||
|
localStorage.setItem("user_name", name);
|
||||||
|
localStorage.setItem("userMobNo", mobile);
|
||||||
|
|
||||||
|
return { name, mobile };
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
notifications.show({
|
||||||
|
title: "Please try again later",
|
||||||
|
message: "Unable to fetch user details",
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -167,12 +167,13 @@ function LoginEmandate() {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
localStorage.setItem("Validate_data", result);
|
||||||
console.log("Validate Result : ", result);
|
console.log("Validate Result : ", result);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
router.push("/eMandate/mandate_page");
|
router.push("/eMandate/otp_page");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log(result);
|
console.log("validation failed: response",result);
|
||||||
notifications.show({
|
notifications.show({
|
||||||
withBorder: true,
|
withBorder: true,
|
||||||
color: "red",
|
color: "red",
|
||||||
|
|||||||
65
src/app/eMandate/otpUtils.ts
Normal file
65
src/app/eMandate/otpUtils.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
|
||||||
|
export const sendOtp = async (mobile: string) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/otp/send", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Login-Type": "eMandate",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
mobileNumber: mobile,
|
||||||
|
type: "EMandate",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error("Failed");
|
||||||
|
|
||||||
|
notifications.show({
|
||||||
|
color: "green",
|
||||||
|
title: "OTP Sent",
|
||||||
|
message: "An OTP has been sent to your registered mobile number",
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
notifications.show({
|
||||||
|
color: "red",
|
||||||
|
title: "Error",
|
||||||
|
message: "Failed to send OTP",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const verifyOtp = async (otp: string, mobile: string) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/otp/verify?mobileNumber=${mobile}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Login-Type": "eMandate",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ otp }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error("Invalid OTP");
|
||||||
|
|
||||||
|
notifications.show({
|
||||||
|
color: "green",
|
||||||
|
title: "Success",
|
||||||
|
message: "OTP verified successfully!",
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
notifications.show({
|
||||||
|
color: "red",
|
||||||
|
title: "Verification Failed",
|
||||||
|
message: "Invalid OTP",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
394
src/app/eMandate/otp_page/page.tsx
Normal file
394
src/app/eMandate/otp_page/page.tsx
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
"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) {
|
||||||
|
setTimeout(() => router.push("/eMandate/mandate_page"), 1500);
|
||||||
|
} else {
|
||||||
|
setOtp("");
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsVerifying(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (authorized === null) {
|
||||||
|
return (
|
||||||
|
<Center style={{ height: "100vh" }}>
|
||||||
|
<Loader size="lg" color="green" />
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Providers>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
minHeight: "100vh",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
// background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
||||||
|
background: 'linear-gradient(179deg, #3faa56ff 49%, #3aa760ff 80%)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* HEADER */}
|
||||||
|
<Paper
|
||||||
|
radius={0}
|
||||||
|
shadow="md"
|
||||||
|
style={{
|
||||||
|
position: "sticky",
|
||||||
|
top: 0,
|
||||||
|
zIndex: 100,
|
||||||
|
background: "linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
||||||
|
// padding: isMobile ? "0.75rem 1rem" : "1rem 1.5rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Container size="xl">
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Group gap="md">
|
||||||
|
<Image
|
||||||
|
src={logo}
|
||||||
|
component={NextImage}
|
||||||
|
fit="contain"
|
||||||
|
alt="KCC Bank Logo"
|
||||||
|
style={{
|
||||||
|
width: isMobile ? "50px" : "70px",
|
||||||
|
height: isMobile ? "50px" : "70px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!isMobile && (
|
||||||
|
<Title
|
||||||
|
order={2}
|
||||||
|
c="white"
|
||||||
|
style={{
|
||||||
|
fontSize: "clamp(1rem, 2vw, 1.5rem)",
|
||||||
|
fontFamily: "Roboto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||||
|
</Title>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{isMobile ? (
|
||||||
|
<ActionIcon
|
||||||
|
variant="subtle"
|
||||||
|
color="white"
|
||||||
|
size="lg"
|
||||||
|
onClick={handleLogout}
|
||||||
|
title="Logout"
|
||||||
|
>
|
||||||
|
<IconLogout size={24} />
|
||||||
|
</ActionIcon>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="white"
|
||||||
|
color="gray"
|
||||||
|
onClick={handleLogout}
|
||||||
|
leftSection={<IconLogout size={20} />}
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
</Container>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* WELCOME BAR */}
|
||||||
|
<Paper
|
||||||
|
radius={0}
|
||||||
|
p={isMobile ? "sm" : "md"}
|
||||||
|
bg="white"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ borderBottom: "3px solid #0a7228" }}
|
||||||
|
>
|
||||||
|
<Container size="xl">
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Text
|
||||||
|
fw={600}
|
||||||
|
size={isMobile ? "lg" : "xl"}
|
||||||
|
style={{ fontFamily: "Inter" }}
|
||||||
|
>
|
||||||
|
Welcome, {custname ?? "User"}
|
||||||
|
</Text>
|
||||||
|
<Divider />
|
||||||
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* MAIN CONTENT */}
|
||||||
|
<Box style={{ flex: 1, padding: "2rem 1rem" }}>
|
||||||
|
<Container size="sm">
|
||||||
|
<Center>
|
||||||
|
<Paper
|
||||||
|
shadow="xl"
|
||||||
|
radius="xl"
|
||||||
|
p={isMobile ? "xl" : "2rem"}
|
||||||
|
style={{
|
||||||
|
maxWidth: "500px",
|
||||||
|
width: "100%",
|
||||||
|
background: "rgba(255, 255, 255, 0.95)",
|
||||||
|
backdropFilter: "blur(10px)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack gap="xl" align="center">
|
||||||
|
{/* Icon */}
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
width: "80px",
|
||||||
|
height: "80px",
|
||||||
|
borderRadius: "50%",
|
||||||
|
background:
|
||||||
|
"linear-gradient(135deg, #0a7228 0%, #2563eb 100%)",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconShieldCheck size={48} color="white" />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<Stack gap="xs" align="center">
|
||||||
|
<Title
|
||||||
|
order={2}
|
||||||
|
ta="center"
|
||||||
|
style={{
|
||||||
|
fontSize: isMobile ? "1.5rem" : "2rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
OTP Verification
|
||||||
|
</Title>
|
||||||
|
<Text size="sm" c="dimmed" ta="center">
|
||||||
|
Enter the 6-digit OTP sent to your registered mobile
|
||||||
|
number
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" fw={600} c="blue">
|
||||||
|
{localStorage.getItem("userMobNo")?.replace(
|
||||||
|
/(\d{2})(\d{4})(\d{4})/,
|
||||||
|
"+91 $1****$3"
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* OTP Input */}
|
||||||
|
<Stack gap="md" align="center" w="100%">
|
||||||
|
<PinInput
|
||||||
|
size={isMobile ? "lg" : "xl"}
|
||||||
|
length={6}
|
||||||
|
value={otp}
|
||||||
|
onChange={setOtp}
|
||||||
|
placeholder="○"
|
||||||
|
type="number"
|
||||||
|
oneTimeCode
|
||||||
|
styles={{
|
||||||
|
input: {
|
||||||
|
fontSize: isMobile ? "1.5rem" : "2rem",
|
||||||
|
fontWeight: 600,
|
||||||
|
borderColor: "#0a7228",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Timer */}
|
||||||
|
<Group gap="xs">
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{canResend ? (
|
||||||
|
"Didn't receive OTP?"
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Resend OTP in{" "}
|
||||||
|
<Text component="span" fw={600} c="blue">
|
||||||
|
{timer}s
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Verify Button */}
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
size="lg"
|
||||||
|
onClick={handleVerifyOtp}
|
||||||
|
loading={isVerifying}
|
||||||
|
disabled={otp.length !== 6 || isVerifying}
|
||||||
|
gradient={{ from: "teal", to: "blue", deg: 60 }}
|
||||||
|
variant="gradient"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
{isVerifying ? "Verifying..." : "Verify OTP"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Resend Button */}
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
variant="light"
|
||||||
|
size="md"
|
||||||
|
onClick={handleResendOtp}
|
||||||
|
disabled={!canResend || isResending}
|
||||||
|
loading={isResending}
|
||||||
|
leftSection={<IconRefresh size={18} />}
|
||||||
|
>
|
||||||
|
{isResending ? "Sending..." : "Resend OTP"}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Info */}
|
||||||
|
<Paper p="md" radius="md" bg="blue.0" w="100%">
|
||||||
|
<Text size="xs" c="blue.9" ta="center">
|
||||||
|
<Text component="span" fw={600}>
|
||||||
|
Note:{" "}
|
||||||
|
</Text>
|
||||||
|
Please do not share your OTP with anyone. KCC Bank
|
||||||
|
will never ask for your OTP.
|
||||||
|
</Text>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Center>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* FOOTER */}
|
||||||
|
<Paper
|
||||||
|
radius={0}
|
||||||
|
p="md"
|
||||||
|
bg="gray.9"
|
||||||
|
style={{
|
||||||
|
borderTop: "1px solid #ddd",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Container size="xl">
|
||||||
|
<Text size="xs" c="white" ta="center">
|
||||||
|
© 2025 The Kangra Central Co-Operative Bank Ltd. All rights
|
||||||
|
reserved.
|
||||||
|
</Text>
|
||||||
|
</Container>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</Providers>
|
||||||
|
);
|
||||||
|
}
|
||||||
BIN
src/app/image/DICGC_image - Copy.jpg
Normal file
BIN
src/app/image/DICGC_image - Copy.jpg
Normal file
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 |
BIN
src/app/image/ib_front_3.jpg
Normal file
BIN
src/app/image/ib_front_3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 101 KiB |
@@ -1,46 +1,46 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Box, Image, ActionIcon } from '@mantine/core';
|
import { Box, Image, ActionIcon } from '@mantine/core';
|
||||||
import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react';
|
import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react';
|
||||||
import { useRef, useState, useEffect } from 'react';
|
import { useRef, useState, useEffect } from 'react';
|
||||||
import DICGC from '@/app/image/DICGC_image.jpg';
|
import DICGC from '@/app/image/DICGC_image.jpg';
|
||||||
import objective from '@/app/image/objective.jpg';
|
import objective from '@/app/image/objective.jpg';
|
||||||
import vision from '@/app/image/vision.jpg';
|
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) => {
|
||||||
const container = scrollRef.current;
|
const container = scrollRef.current;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
const slideWidth = container.offsetWidth; // full width per slide
|
const slideWidth = container.offsetWidth; // full width per slide
|
||||||
container.scrollTo({
|
container.scrollTo({
|
||||||
left: slideWidth * index,
|
left: slideWidth * index,
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollRight = () => {
|
const scrollRight = () => {
|
||||||
const nextIndex = (currentIndex + 1) % images.length;
|
const nextIndex = (currentIndex + 1) % images.length;
|
||||||
setCurrentIndex(nextIndex);
|
setCurrentIndex(nextIndex);
|
||||||
scrollToIndex(nextIndex);
|
scrollToIndex(nextIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollLeft = () => {
|
const scrollLeft = () => {
|
||||||
const prevIndex = (currentIndex - 1 + images.length) % images.length;
|
const prevIndex = (currentIndex - 1 + images.length) % images.length;
|
||||||
setCurrentIndex(prevIndex);
|
setCurrentIndex(prevIndex);
|
||||||
scrollToIndex(prevIndex);
|
scrollToIndex(prevIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollToIndex(currentIndex);
|
scrollToIndex(currentIndex);
|
||||||
}, [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,29 +54,31 @@ 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 */}
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="filled"
|
variant="filled"
|
||||||
@@ -92,7 +94,7 @@ export default function CustomCarousel() {
|
|||||||
>
|
>
|
||||||
<IconChevronLeft size={24} />
|
<IconChevronLeft size={24} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
|
||||||
{/* Right Scroll Button */}
|
{/* Right Scroll Button */}
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="filled"
|
variant="filled"
|
||||||
@@ -111,4 +113,3 @@ export default function CustomCarousel() {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ 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_2.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 { IconRefresh, IconShieldLockFilled } from "@tabler/icons-react";
|
||||||
@@ -534,8 +534,30 @@ export default function Login() {
|
|||||||
style={{ width: "100%", height: "100%" }}
|
style={{ width: "100%", height: "100%" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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}>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="User ID / User Name"
|
label="User ID / User Name"
|
||||||
@@ -627,7 +649,7 @@ export default function Login() {
|
|||||||
{isLogging ? "Processing..." : buttonLabel}
|
{isLogging ? "Processing..." : buttonLabel}
|
||||||
</Button>
|
</Button>
|
||||||
<Box mt="xs">
|
<Box mt="xs">
|
||||||
<Text size="sm">
|
<Text size="md">
|
||||||
<Text component="span" c="red" fw={600}>Note: </Text>
|
<Text component="span" c="red" fw={600}>Note: </Text>
|
||||||
<Text component="span" c="black">
|
<Text component="span" c="black">
|
||||||
Existing users logging in to the new Internet Banking for the first time should use their CIF number to avoid login issues.
|
Existing users logging in to the new Internet Banking for the first time should use their CIF number to avoid login issues.
|
||||||
@@ -638,6 +660,7 @@ export default function Login() {
|
|||||||
</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%" }}>
|
||||||
@@ -647,7 +670,14 @@ 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 */}
|
||||||
|
|||||||
Reference in New Issue
Block a user