diff --git a/TODO.md b/TODO.md index e3f34cf..785ccab 100644 --- a/TODO.md +++ b/TODO.md @@ -22,6 +22,7 @@ - >view rights - Forget Password - >For Migration if user not have password - +- E-mandate +- Make every page responsive diff --git a/instruction.md b/instruction.md index 4b6d9e4..fd03323 100644 --- a/instruction.md +++ b/instruction.md @@ -5,14 +5,46 @@ - Download **AWS Session Manager Plugin** - Generate **Key for KCCB** +____________________________________________________________ +### Production: (Run in systemctl) + - cd /etc/systemd/system + + - vi IB.service + ``` + [Unit] + Description= Internet Banking Frontened Application in Node + After=network.target + + [Service] + # Use absolute path for node or npm + User=ib + Group=ib + ExecStart=/usr/bin/npm start + WorkingDirectory=/home/ib/IB + Restart=on-failure + RestartSec=5 + Environment=NODE_ENV=production + Environment=PORT=3000 + SuccessExitStatus=143 + + [Install] + WantedBy=multi-user.target + + + ``` + - sudo systemctl status IB + - sudo systemctl start IB + - sudo systemctl stop IB + - sudo systemctl restart IB + --- ## Machine ```bash -UAT (IB- frontend) : i-0b55435e15425f1c3 -Linux : i-0c850dcf8b85b1447 -Prod : i-088e64c3435cb5078 +UAT (IB- frontend Test) : i-0b55435e15425f1c3 +Linux : i-0c850dcf8b85b1447 (Test) +Prod : i-088e64c3435cb5078 (For IB & MB) ``` ## 2. Port Forwarding diff --git a/src/app/(main)/accounts/account_statement/accountStatement.tsx b/src/app/(main)/accounts/account_statement/accountStatement.tsx index 054e235..a8d05f8 100644 --- a/src/app/(main)/accounts/account_statement/accountStatement.tsx +++ b/src/app/(main)/accounts/account_statement/accountStatement.tsx @@ -1,5 +1,5 @@ "use client"; -import { Paper, Select, Title, Button, Text, Grid, ScrollArea, Table, Divider, Center, Loader, Stack, Group } from "@mantine/core"; +import { Paper, Select, Title, Button, Text, Grid, ScrollArea, Table, Divider, Center, Loader, Stack, Group, Card } from "@mantine/core"; import { DateInput } from '@mantine/dates'; import { useEffect, useRef, useState } from "react"; import { useSearchParams } from "next/navigation"; @@ -8,10 +8,11 @@ import dayjs from 'dayjs'; import { IconFileSpreadsheet, IconFileText, IconFileTypePdf } from "@tabler/icons-react"; import { generatePDF } from "@/app/_components/statement_download/PdfGenerator"; import { generateCSV } from "@/app/_components/statement_download/CsvGenerator"; +import { useMediaQuery } from "@mantine/hooks"; export default function AccountStatementPage() { - const pdfRef = useRef(null); + // const pdfRef = useRef(null); const [accountOptions, setAccountOptions] = useState<{ value: string; label: string }[]>([]); const [selectedAccNo, setSelectedAccNo] = useState(null); const [startDate, setStartDate] = useState(null); @@ -21,6 +22,7 @@ export default function AccountStatementPage() { const passedAccNo = searchParams.get("accNo"); const [loading, setLoading] = useState(false); const [availableBalance, setAvailableBalance] = useState(null); + const isMobile = useMediaQuery("(max-width: 768px)"); useEffect(() => { const saved = sessionStorage.getItem("accountData"); @@ -108,12 +110,12 @@ export default function AccountStatementPage() { }); return; } - if (end.diff(start, "day") > 90) { + if (end.diff(start, "day") > 30) { notifications.show({ withBorder: true, color: "red", title: "Invalid Date range", - message: "End date must be within 90 days from start date", + message: "End date must be within 30 days from start date", autoClose: 4000, }); return; @@ -179,12 +181,16 @@ export default function AccountStatementPage() { value={startDate} onChange={setStartDate} placeholder="Enter start date" + minDate={dayjs("1920-01-01").toDate()} + maxDate={dayjs().toDate()} /> + ); + })} + + + + )} + + + {/* Content Area */} + {children} - - + + ); } } diff --git a/src/app/(main)/accounts/page.tsx b/src/app/(main)/accounts/page.tsx index 9cc6bda..55ad973 100644 --- a/src/app/(main)/accounts/page.tsx +++ b/src/app/(main)/accounts/page.tsx @@ -1,6 +1,7 @@ "use client"; -import { Group, Paper, ScrollArea, Table, Text, Title } from "@mantine/core"; +import { Group, Paper, ScrollArea, Stack, Table, Text, Title } from "@mantine/core"; +import { useMediaQuery } from "@mantine/hooks"; import { notifications } from "@mantine/notifications"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; @@ -16,6 +17,7 @@ export default function AccountSummary() { const router = useRouter(); const [authorized, setAuthorized] = useState(null); const [accountData, setAccountData] = useState([]); + const isMobile = useMediaQuery("(max-width: 768px)"); async function FetchAccountDetails() { try { @@ -61,7 +63,8 @@ export default function AccountSummary() { const cellStyle = { border: "1px solid #ccc", - padding: "10px", + padding: isMobile ? "8px" : "10px", + fontSize: isMobile ? "13px" : "14px", }; // Filter accounts @@ -96,7 +99,7 @@ export default function AccountSummary() { - + <Title order={isMobile ? 5 : 4} mb="sm"> {title} @@ -117,25 +120,38 @@ export default function AccountSummary() { ); - if (authorized) { - return ( - + - <Title order={3} mb="md">Account Summary + Account Summary + + + {/* ✅ Responsive layout: Group for desktop, Stack for mobile */} + {isMobile ? ( + + {depositAccounts.length > 0 && + renderTable("Deposit Accounts (INR)", renderRows(depositAccounts))} + {loanAccounts.length > 0 && + renderTable("Loan Accounts (INR)", renderRows(loanAccounts))} + + ) : ( - {/* Left table for Deposit Accounts */} - {depositAccounts.length > 0 && renderTable("Deposit Accounts (INR)", renderRows(depositAccounts))} - - {/* Right table for Loan Accounts (only shown if available) */} - {loanAccounts.length > 0 && renderTable("Loan Accounts (INR)", renderRows(loanAccounts))} + {depositAccounts.length > 0 && + renderTable("Deposit Accounts (INR)", renderRows(depositAccounts))} + {loanAccounts.length > 0 && + renderTable("Loan Accounts (INR)", renderRows(loanAccounts))} - - * Book Balance includes uncleared effects. - - - ); - } + )} - return null; + + * Book Balance includes uncleared effects. + + + ); } diff --git a/src/app/(main)/funds_transfer/layout.tsx b/src/app/(main)/funds_transfer/layout.tsx index 28de9c4..115c329 100644 --- a/src/app/(main)/funds_transfer/layout.tsx +++ b/src/app/(main)/funds_transfer/layout.tsx @@ -1,14 +1,19 @@ "use client"; -import { Divider, Stack, Text } from '@mantine/core'; +import { Box, Burger, Button, Divider, Drawer, Stack, Text } from '@mantine/core'; import { usePathname } from 'next/navigation'; import Link from 'next/link'; import React, { useEffect, useState } from 'react'; import { useRouter } from "next/navigation"; +import Image from "next/image"; +import logo from "@/app/image/logo1.jpg"; +import { useMediaQuery } from '@mantine/hooks'; export default function Layout({ children }: { children: React.ReactNode }) { const [authorized, SetAuthorized] = useState(null); const router = useRouter(); const pathname = usePathname(); + const isMobile = useMediaQuery("(max-width: 768px)"); + const [drawerOpened, setDrawerOpened] = useState(false); const links = [ { label: " Quick Pay", href: "/funds_transfer" }, @@ -29,45 +34,145 @@ export default function Layout({ children }: { children: React.ReactNode }) { if (authorized) { return ( -
-
- - - Send Money - - + + {/* Desktop Sidebar */} + {!isMobile && ( + + + + Send Money + + - - {links.map(link => { - const isActive = pathname === link.href; - return ( - - {link.label} - - ); - })} - -
+ + {links.map((link) => { + const isActive = pathname === link.href; + return ( + + {link.label} + + ); + })} + + + )} -
+ {/* Mobile: Burger & Drawer */} + {isMobile && ( + <> + {/* Top header with burger and title */} + + setDrawerOpened(!drawerOpened)} + size="sm" + color="white" + /> + + Send Money + + + + setDrawerOpened(false)} + padding="md" + size="75%" + overlayProps={{ color: "black", opacity: 0.55, blur: 3 }} + styles={{ + root: { + backgroundColor: "#e6f5ff", // soft background for drawer + // borderLeft: "4px solid #228be6", + // borderRadius: "8px", + }, + }} + > + {/* Logo and Drawer Header */} + + <> + KCCB Logo + + Send Money + + + + + {/* Menu Items */} + + {links.map((link) => { + const isActive = pathname === link.href; + + return ( + + ); + })} + + + + )} + + + {/* Content Area */} + {children} -
-
+ + ); } } diff --git a/src/app/(main)/funds_transfer/page.tsx b/src/app/(main)/funds_transfer/page.tsx index 7dbb20d..62a2295 100644 --- a/src/app/(main)/funds_transfer/page.tsx +++ b/src/app/(main)/funds_transfer/page.tsx @@ -266,6 +266,8 @@ export default function QuickPay() { setOtp(""); setValidationStatus(null); setBeneficiaryName(null); + setTimerActive(false); + setCountdown(180); } else { notifications.show({ title: "Error", @@ -293,6 +295,8 @@ export default function QuickPay() { setShowTxnPassword(false); setShowOtpField(false); setIsSubmitting(false); + setTimerActive(false); + setCountdown(180); } }; diff --git a/src/app/(main)/home/page.tsx b/src/app/(main)/home/page.tsx index 48c9757..4f58504 100644 --- a/src/app/(main)/home/page.tsx +++ b/src/app/(main)/home/page.tsx @@ -7,6 +7,7 @@ import { useRouter } from "next/navigation"; import { Providers } from "../../providers"; import { notifications } from '@mantine/notifications'; import dayjs from 'dayjs'; +import { useMediaQuery } from '@mantine/hooks'; interface accountData { stAccountNo: string; @@ -19,6 +20,7 @@ interface accountData { export default function Home() { const [authorized, SetAuthorized] = useState(null); const router = useRouter(); + const isMobile = useMediaQuery("(max-width: 768px)"); const [accountData, SetAccountData] = useState([]); const depositAccounts = accountData.filter(acc => acc.stAccountType !== "LN"); const [selectedDA, setSelectedDA] = useState(depositAccounts[0]?.stAccountNo || ""); @@ -27,7 +29,7 @@ export default function Home() { const [selectedLN, setSelectedLN] = useState(loanAccounts[0]?.stAccountNo || ""); const selectedLNData = loanAccounts.find(acc => acc.stAccountNo === selectedLN); const [showBalance, setShowBalance] = useState(false); - const PassExpiryRemains =(dayjs(localStorage.getItem("pswExpiryDate"))).diff(dayjs(),"day") + const PassExpiryRemains = (dayjs(localStorage.getItem("pswExpiryDate"))).diff(dayjs(), "day") // If back and forward button is clicked useEffect(() => { @@ -42,11 +44,11 @@ export default function Home() { router.push("/login"); }; const handleBeforeUnload = () => { - // logout on tab close / refresh - localStorage.removeItem("access_token"); - sessionStorage.removeItem("access_token"); - localStorage.clear(); - sessionStorage.clear(); + // logout on tab close / refresh + localStorage.removeItem("access_token"); + sessionStorage.removeItem("access_token"); + localStorage.clear(); + sessionStorage.clear(); }; window.addEventListener("popstate", handlePopState); window.addEventListener("beforeunload", handleBeforeUnload); @@ -112,29 +114,62 @@ export default function Home() { if (authorized) { return ( -
- Accounts Overview - - - Show Balance - setShowBalance(event.currentTarget.checked)} /> + + + Accounts Overview + + + {/* Show Balance Switch */} + + + + Show Balance + + setShowBalance(event.currentTarget.checked)} + /> -
- + + {/* Cards Section */} + {isMobile ? ( + {/* Deposit Account Card */} - - - - Deposit Account + + + + + Deposit Account + {depositAccounts.length > 0 ? ( ({ + value: acc.stAccountNo, + label: `${acc.stAccountType}- ${acc.stAccountNo}`, + }))} + value={selectedLN} + // @ts-ignore + onChange={setSelectedLN} + size="xs" + styles={{ + input: { + backgroundColor: "white", + color: "black", + marginLeft: 5, + width: "120px", + }, + }} + /> + ) : ( + + No loan account available + + )} + + + {loanAccounts.length > 0 ? ( + <> + + {Number(selectedLNData?.stAccountNo || 0)} + + + {showBalance + ? `₹${Number(selectedLNData?.stAvailableBalance || 0).toLocaleString("en-IN")}` + : "****"} + + + + ) : ( + <> + + Apply for a loan account to get started + + + + )} + + + {/* Important Links Card */} + + + Quick Links + + + + + + + + + + ) : ( + + {/* Desktop Cards */} + {/* Deposit Account Card */} + + + + Deposit Account + {depositAccounts.length > 0 ? ( + ({ + data={loanAccounts.map((acc) => ({ value: acc.stAccountNo, - label: `${acc.stAccountType}- ${acc.stAccountNo}` + label: `${acc.stAccountType}- ${acc.stAccountNo}`, }))} value={selectedLN} // @ts-ignore @@ -194,12 +391,14 @@ export default function Home() { backgroundColor: "white", color: "black", marginLeft: 30, - width: 140 - } + width: 140, + }, }} /> ) : ( - No loan account available + + No loan account available + )} @@ -207,42 +406,60 @@ export default function Home() { <> {Number(selectedLNData?.stAccountNo || 0)} - {showBalance ? `₹${Number(selectedLNData?.stAvailableBalance || 0).toLocaleString('en-IN')}` : "****"} + {showBalance ? `₹${Number(selectedLNData?.stAvailableBalance || 0).toLocaleString("en-IN")}` : "****"} - + ) : ( <> - Apply for a loan account to get started - + + Apply for a loan account to get started + + )} {/* Important Links Card */} - - Quick Links + + + Quick Links + - - - - + + + + -
-
+ )} + + {/* Notes Section */} + ** Book Balance includes uncleared effect. - ** Click on the "Show Balance"to display balance for the Deposit and Loan account. - ** Your Password will expire in {PassExpiryRemains} days. - + ** Click on the "Show Balance" to display balance for the Deposit and Loan account. + + ** Your Password will expire in {PassExpiryRemains} days. + -
-
+ +
); } - return null; } \ No newline at end of file diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 1e05f12..83f3e14 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -8,7 +8,7 @@ import { Providers } from '../providers'; import logo from '@/app/image/logo1.jpg'; import NextImage from 'next/image'; import { notifications } from '@mantine/notifications'; -import { useDisclosure } from '@mantine/hooks'; +import { useDisclosure, useMediaQuery } from '@mantine/hooks'; export default function RootLayout({ children }: { children: React.ReactNode }) { const router = useRouter(); @@ -16,7 +16,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) const [authorized, SetAuthorized] = useState(null); const [userLastLoginDetails, setUserLastLoginDetails] = useState(null); const [custname, setCustname] = useState(null); - + const isMobile = useMediaQuery("(max-width: 768px)"); const [opened, { open, close }] = useDisclosure(false); @@ -167,104 +167,84 @@ export default function RootLayout({ children }: { children: React.ReactNode }) -
+ + + {/* HEADER */} - ebanking - - THE KANGRA CENTRAL CO-OPERATIVE BANK LTD. - - - Toll Free No : 1800-180-8008 - + {/* Logo */} + + ebanking + + + {/* Title & Phone */} + + + THE KANGRA CENTRAL CO-OPERATIVE BANK LTD. + + + Toll Free No : 1800-180-8008 + + -
- - + <Stack gap={isMobile ? 2 : 0} align={isMobile ? "flex-start" : "flex-start"}> + <Title order={isMobile ? 5 : 4} style={{ fontFamily: "inter", fontSize: isMobile ? "18px" : "22px" }}> Welcome, {custname ?? null} - + Last logged in at {userLastLoginDetails ? new Date(userLastLoginDetails).toLocaleString() : "N/A"} - + {navItems.map((item) => { const isActive = pathname.startsWith(item.href); const Icon = item.icon; return ( ); })} - {/* */} - + + - - Are you sure you want to logout? @@ -273,30 +253,31 @@ export default function RootLayout({ children }: { children: React.ReactNode }) - + -
+
- + -
{children} -
+ - + + {/* FOOTER */} - + © 2025 The Kangra Central Co-Operative Bank -
+
diff --git a/src/app/(main)/settings/layout.tsx b/src/app/(main)/settings/layout.tsx index bc1b7bb..2b21fca 100644 --- a/src/app/(main)/settings/layout.tsx +++ b/src/app/(main)/settings/layout.tsx @@ -1,14 +1,19 @@ "use client"; -import { Divider, Stack, Text } from '@mantine/core'; +import { Box, Burger, Button, Divider, Drawer, Stack, Text } from '@mantine/core'; import { usePathname } from 'next/navigation'; import Link from 'next/link'; import React, { useEffect, useState } from 'react'; import { useRouter } from "next/navigation"; +import { useMediaQuery } from '@mantine/hooks'; +import Image from "next/image"; +import logo from "@/app/image/logo1.jpg"; export default function Layout({ children }: { children: React.ReactNode }) { const [authorized, SetAuthorized] = useState(null); const router = useRouter(); const pathname = usePathname(); + const isMobile = useMediaQuery("(max-width: 768px)"); + const [drawerOpened, setDrawerOpened] = useState(false); const links = [ { label: "View Profile", href: "/settings" }, @@ -30,45 +35,143 @@ export default function Layout({ children }: { children: React.ReactNode }) { if (authorized) { return ( -
-
- - - Settings - - + + {/* Desktop Sidebar */} + {!isMobile && ( + + + + Settings + + - - {links.map(link => { - const isActive = pathname === link.href; - return ( - - {link.label} - - ); - })} - -
+ + {links.map((link) => { + const isActive = pathname === link.href; + return ( + + {link.label} + + ); + })} + + + )} -
+ {/* Mobile: Burger & Drawer */} + {isMobile && ( + <> + {/* Top header with burger and title */} + + setDrawerOpened(!drawerOpened)} + size="sm" + color="white" + /> + + Settings + + + + setDrawerOpened(false)} + padding="md" + size="75%" + overlayProps={{ color: "black", opacity: 0.55, blur: 3 }} + styles={{ + root: { + backgroundColor: "#e6f5ff", // soft background for drawer + }, + }} + > + {/* Logo and Drawer Header */} + + <> + KCCB Logo + + Settings + + + + + {/* Menu Items */} + + {links.map((link) => { + const isActive = pathname === link.href; + + return ( + + ); + })} + + + + )} + + + {/* Content Area */} + {children} -
-
+ + ); } } diff --git a/src/app/eMandate/login/Login.module.css b/src/app/eMandate/login/Login.module.css new file mode 100644 index 0000000..68faac2 --- /dev/null +++ b/src/app/eMandate/login/Login.module.css @@ -0,0 +1,89 @@ + +/* Header */ +.header { + position: fixed; + top: 0; + left: 0; + width: 100%; + z-index: 100; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem 1rem; + background: linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%); +} + +.header-text { + flex: 1; +} + +/* Desktop header text */ +.desktop-text { + color: white; +} + +.mobile-text { + color: white; + display: none; +} + +/* Movable scrolling text - desktop only */ +.desktop-scroll-text { + width: 100%; + overflow: hidden; + white-space: nowrap; + padding: 8px 0; +} + +.desktop-scroll-text span { + display: inline-block; + padding-left: 100%; + animation: scroll-left 60s linear infinite; + font-weight: bold; + color: #004d99; +} + +@keyframes scroll-left { + 0% { transform: translateX(0%); } + 100% { transform: translateX(-100%); } +} + +/* Main Login Section */ +.login-container { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + min-height: 75vh; + padding: 1rem; +} + +.login-image { + flex: 1 1 400px; + min-width: 300px; + height: 400px; + margin: 1rem; +} + +.login-card { + flex: 1 1 300px; + min-width: 280px; + margin: 1rem; +} + +/* Responsive - Mobile */ +@media (max-width: 768px) { + .desktop-text { display: none; } + .mobile-text { display: block; } + + .desktop-scroll-text { display: none; } + + .login-container { + flex-direction: column; + } + + .login-image { + height: 250px; + margin: 0.5rem 0; + } +} diff --git a/src/app/eMandate/login/page.tsx b/src/app/eMandate/login/page.tsx index f9e156d..ca3e101 100644 --- a/src/app/eMandate/login/page.tsx +++ b/src/app/eMandate/login/page.tsx @@ -1,6 +1,6 @@ "use client"; import React, { useState, useEffect, memo, useRef } from "react"; -import { Text, Button, TextInput, PasswordInput, Title, Card, Group, Flex, Box, Image, Anchor, Tooltip, Modal } from "@mantine/core"; +import { Text, Button, TextInput, PasswordInput, Title, Card, Group, Flex, Box, Image, Anchor, Tooltip, Modal, Stack } from "@mantine/core"; import { notifications } from "@mantine/notifications"; import { Providers } from "@/app/providers"; import { useRouter } from "next/navigation"; @@ -8,6 +8,7 @@ import NextImage from "next/image"; import logo from '@/app/image/logo1.jpg'; import frontPage from '@/app/image/EMandate.jpg'; import { generateCaptcha } from '@/app/captcha'; +import styles from './Login.module.css'; export default function Login() { @@ -88,6 +89,7 @@ export default function Login() { method: 'POST', headers: { 'Content-Type': 'application/json', + 'X-Login-Type': 'emandate', }, body: JSON.stringify({ customerNo: CIF, @@ -103,33 +105,33 @@ export default function Login() { message: data?.error || "Internal Server Error", autoClose: 5000, }); - localStorage.removeItem("access_token"); + regenerateCaptcha() + localStorage.removeItem("mandate_token"); localStorage.clear(); sessionStorage.clear(); return; - } setIsLogging(true); if (response.ok) { - console.log(data); + // console.log(data); const token = data.token; - localStorage.setItem("emandate_token", token); + localStorage.setItem("mandate_token", token); // localStorage.setItem("pswExpiryDate", data.loginPswExpiry); if (data.FirstTimeLogin === true) { notifications.show({ withBorder: true, color: "red", title: "Error", - message: "Please set your credential into Internet Banking before login.", + message: "Please go to Internet Banking, set your credentials, and then try logging in here again.", autoClose: 5000, }); } else { - router.push("/mandate_page"); + router.push("/eMandate/mandate_page"); } - } else { + regenerateCaptcha(); setIsLogging(false); notifications.show({ withBorder: true, @@ -157,58 +159,21 @@ export default function Login() { {/* Main Screen */}
{/* Header */} - - ebanking - - THE KANGRA CENTRAL CO-OPERATIVE BANK LTD. - - - Head Office : Dharmshala, District: Kangra(H.P), Pin: 176215 - + + + ebanking + + THE KANGRA CENTRAL CO-OPERATIVE BANK LTD. + Head Office: Dharmshala, District Kangra (H.P), Pin: 176215 + THE KANGRA CENTRAL + CO-OPERATIVE BANK LTD. +
{/* Movable text */} + {/* Main */} -
+
-
+
+ + {/* Mobile layout */} + + +
+ + E-Mandate Login + + + { + const input = e.currentTarget.value.replace(/\D/g, ""); + if (input.length <= 11) SetCIF(input); + }} + withAsterisk + mt="sm" + /> + + SetPsw(e.currentTarget.value)} + withAsterisk + mt="sm" + /> + + + + {captcha} + + + + + setInputCaptcha(e.currentTarget.value)} + withAsterisk + mt="sm" + /> + + + +
+
+ {/* Footer */} void; +}) => { + const [agreed, setAgreed] = useState(false); + + const handleClick = (action: "accept" | "reject") => { + if (!agreed) { + notifications.show({ + withBorder: true, + icon: , + color: "red", + title: "Error", + message: + "Please agree to the debit of mandate processing charges first.", + autoClose: 4000, + }); + return; + } + onAction(mandate.id, action); + }; + + return ( + + {mandate.category} + + Amount: ₹{mandate.amount} + + Max Amount: ₹{mandate.maxAmount} + Name: {mandate.name} + Frequency: {mandate.frequency} + First Collection: {mandate.firstCollection} + Final Collection: {mandate.finalCollection} + + setAgreed(e.currentTarget.checked)} + /> + + + + + + + ); +}; + +export default function MandatePage() { + const router = useRouter(); + const [authorized, setAuthorized] = useState(null); + const [custname, setCustname] = useState(null); + const isMobile = useMediaQuery("(max-width: 768px)"); + + // OTP Modal states + const [otpModalOpen, setOtpModalOpen] = useState(false); + const [otp, setOtp] = useState(""); + const [pendingAction, setPendingAction] = useState<{ id: string; action: "accept" | "reject" } | null>(null); + + useEffect(() => { + const token = localStorage.getItem("mandate_token"); + if (!token) { + setAuthorized(false); + router.push("/eMandate/login"); + } else { + setAuthorized(true); + handleFetchUserName(); + } + }, []); + + const handleLogout = () => { + localStorage.removeItem("mandate_token"); + localStorage.removeItem("user_name"); + localStorage.removeItem("userMobNo"); + router.push("/eMandate/login"); + }; + + const handleFetchUserName = async () => { + try { + const token = localStorage.getItem("mandate_token"); + const response = await fetch("/api/customer", { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + if (!response.ok) throw new Error(); + const data = await response.json(); + if (Array.isArray(data) && data.length > 0) { + const name = data[0].custname; + const mobileNumber = data[0].mobileno; + localStorage.setItem("user_name", name); + localStorage.setItem("userMobNo", mobileNumber); + setCustname(name); + } + } catch { + notifications.show({ + withBorder: true, + color: "red", + title: "Please try again later", + message: "Unable to Fetch, Please try again later", + autoClose: 5000, + }); + } + }; + + // Dummy mandates + const [mandates] = useState([ + { + id: "1", + category: "Insurance Premium", + amount: 11743, + maxAmount: 11743, + name: "LIFE INSURANCE CORPORATION", + frequency: "YEAR", + firstCollection: "2025-09-20", + finalCollection: "2038-03-28", + }, + { + id: "2", + category: "Loan EMI", + amount: 8500, + maxAmount: 8500, + name: "XYZ BANK", + frequency: "MONTH", + firstCollection: "2025-10-01", + finalCollection: "2030-10-01", + }, + ]); + + // STEP 1: When Accept/Reject pressed → call send OTP API + const handleMandateAction = async (id: string, action: "accept" | "reject") => { + try { + const response = await fetch("/api/otp/send", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + mobileNumber: localStorage.getItem("userMobNo"), + type: 'EMandate' + }), + }); + const data = await response.json(); + console.log(data) + if (!response.ok) throw new Error("Failed to send OTP"); + + notifications.show({ + withBorder: true, + color: "green", + title: "OTP Sent", + message: "An OTP has been sent to your registered mobile number.", + autoClose: 4000, + }); + + setPendingAction({ id, action }); + setOtp(""); + setOtpModalOpen(true); + } catch (err) { + notifications.show({ + withBorder: true, + color: "red", + title: "Error", + message: "Failed to send OTP. Please try again.", + autoClose: 5000, + }); + } + }; + + // Resend OTP + const handleResendOtp = async () => { + try { + const response = await fetch("/api/otp/send", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + mobileNumber: localStorage.getItem("userMobNo"), + type: "EMandate", + }), + }); + + if (!response.ok) throw new Error("Failed to resend OTP"); + + notifications.show({ + withBorder: true, + color: "blue", + title: "OTP Resent", + message: "A new OTP has been sent to your registered mobile number.", + autoClose: 4000, + }); + } catch { + notifications.show({ + withBorder: true, + color: "red", + title: "Error", + message: "Failed to resend OTP. Please try again.", + autoClose: 5000, + }); + } + }; + + // STEP 2: Verify OTP and complete action + const handleOtpSubmit = async () => { + if (!otp) { + notifications.show({ + withBorder: true, + color: "red", + title: "Invalid Input", + message: "Please enter OTP before proceed", + autoClose: 5000, + }); + } + if (!pendingAction) return; + try { + const response = await fetch(`/api/otp/verify?mobileNumber=${localStorage.getItem("userMobNo")}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + otp: otp, + }), + }); + + if (!response.ok) throw new Error("Invalid OTP"); + + notifications.show({ + withBorder: true, + color: "green", + title: "Success", + message: `Mandate ${pendingAction.action}ed successfully!`, + autoClose: 4000, + }); + + setOtpModalOpen(false); + setPendingAction(null); + } catch { + notifications.show({ + withBorder: true, + color: "red", + title: "Error", + message: "Invalid OTP. Please try again.", + autoClose: 4000, + }); + } + }; + + if (!authorized) return null; + + return ( + +
+ {/* HEADER */} + + + ebanking + {!isMobile && ( + + THE KANGRA CENTRAL CO-OPERATIVE BANK LTD. + + )} + + + {isMobile ? ( + + + + ) : ( + + + + +
+
+ ); +} diff --git a/src/app/login/page.module.css b/src/app/login/page.module.css index 7fb5753..8255e0f 100644 --- a/src/app/login/page.module.css +++ b/src/app/login/page.module.css @@ -1,62 +1,75 @@ -.root { - width: 100vw; - height: 100vh; - - } - -.title { - color: light-dark(var(--mantine-color-black), var(--mantine-color-white)); - font-family: - Greycliff CF, - var(--mantine-font-family); -} - - - .mobileImage { - @media (min-width: 48em) { - display: none; - } - } - -.desktopImage { - object-fit: cover; - width: 100%; - height: 100%; - @media (max-width: 47.99em) { - display: none; - } - } - -.carousel-wrapper { - width: 100%; - max-width: 800px; - margin: 0 auto; - overflow: hidden; -} - -.gradient-control { - width: 15%; - height: 100%; - position: absolute; +.header { + position: fixed; top: 0; - z-index: 2; - background-color: transparent; + left: 0; + width: 100%; + z-index: 100; display: flex; align-items: center; + justify-content: space-between; + padding: 0.5rem 1rem; + background: linear-gradient(15deg, rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%); + flex-wrap: wrap; /* allow wrapping on mobile */ +} + +.header-text { + display: flex; + flex-direction: column; justify-content: center; - opacity: 0.6; + flex: 1; + text-align: left; +} + +/* Desktop text */ +.desktop-text { color: white; - pointer-events: all; + font-family: Roboto, sans-serif; + font-size: 1.5rem; + line-height: 1.2; } - -/* First control is left */ -.gradient-control:first-of-type { - left: 0; - background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.0001)); + +.desktop-address { + font-family: Roboto, sans-serif; + color: white; + font-size: 0.9rem; + text-shadow: 1px 1px 2px blue; + margin-top: 0.25rem; } - -/* Last control is right */ -.gradient-control:last-of-type { - right: 0; - background-image: linear-gradient(to left, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.0001)); + +/* Mobile styles */ +.mobile-text { + color: white; + font-family: Roboto, sans-serif; + font-size: 1rem; + line-height: 1.2; + display: none; + text-align: center; +} + +/* Media query for mobile */ +@media screen and (max-width: 768px) { + .header { + justify-content: center; + padding: 0.5rem 0.75rem; + } + + .header-logo { + width: 50px; + margin-bottom: 0.5rem; + } + + .header-text { + text-align: center; + flex-direction: column; + } + + .desktop-text, + .desktop-address { + display: none; + } + + .mobile-text { + display: block; + font-size: 0.9rem; + } } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index ed630a3..5481617 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -5,6 +5,7 @@ import { notifications } from "@mantine/notifications"; import { Providers } from "@/app/providers"; import { useRouter } from "next/navigation"; import NextImage from "next/image"; +import styles from './page.module.css'; import logo from '@/app/image/logo1.jpg'; import frontPage from '@/app/image/ib_front_1.jpg'; import dynamic from 'next/dynamic'; @@ -12,6 +13,7 @@ import { generateCaptcha } from '@/app/captcha'; import { IconShieldLockFilled } from "@tabler/icons-react"; import dayjs from "dayjs"; + export default function Login() { const router = useRouter(); const [CIF, SetCIF] = useState(""); @@ -130,6 +132,7 @@ export default function Login() { message: data?.error || "Internal Server Error", autoClose: 5000, }); + regenerateCaptcha(); localStorage.removeItem("access_token"); localStorage.clear(); sessionStorage.clear(); @@ -169,6 +172,7 @@ export default function Login() { } else { + regenerateCaptcha(); setIsLogging(false); notifications.show({ withBorder: true, @@ -237,7 +241,7 @@ export default function Login() { }); const data = await res.json(); - console.log(data) + // console.log(data) if (!res.ok) { notifications.show({ color: "red", @@ -330,54 +334,34 @@ export default function Login() { {/* Main Screen */}
{/* Header */} - + ebanking - - THE KANGRA CENTRAL CO-OPERATIVE BANK LTD. - - - Head Office : Dharmshala, District: Kangra(H.P), Pin: 176215 - - {/* - - - - */} + + {/* Desktop */} + + THE KANGRA CENTRAL CO-OPERATIVE BANK LTD. + + + Head Office: Dharmshala, District Kangra (H.P), Pin: 176215 + + + {/* Mobile */} + + THE KANGRA CENTRAL + + + CO-OPERATIVE BANK LTD. + + + Head Office: Dharmshala, District Kangra (H.P), Pin: 176215 + +