fix: design in view profile and account overview

feat : page add for e mandate otp
This commit is contained in:
2025-12-06 13:54:20 +05:30
parent cf9faf2e82
commit ad758eb14d
25 changed files with 1027 additions and 175 deletions

View File

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

View File

@@ -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">

View File

@@ -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" }}
> >

View File

@@ -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">

View File

@@ -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"

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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">MonFri 10 AM 4 PM</Text> <Text size="sm" c="dimmed">MonSat 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>

View File

@@ -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 */}

View File

@@ -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>

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() {
@@ -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">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

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

@@ -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",

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

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.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

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

View File

@@ -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 */}