From e621f108248f42071d0fc6c5d58d1737ade1f7a5 Mon Sep 17 00:00:00 2001 From: "tomosa.sarkar" Date: Mon, 21 Jul 2025 17:57:16 +0530 Subject: [PATCH] feat: Implemented page for quick pay and send to beneficiary feature for own and outside bank ci: Modify the design of login page ci : modify the logic of home page, If no account is present then statement button change to apply now --- .../funds_transfer/outside_quick_pay.tsx | 451 ++++++++++++++++++ src/app/(main)/funds_transfer/page.tsx | 13 +- .../funds_transfer/send_beneficiary/page.tsx | 415 ++++++++++++++++ .../sendBeneficiaryOthers.tsx | 383 +++++++++++++++ .../funds_transfer/view_beneficiary/page.tsx | 30 ++ src/app/(main)/home/page.tsx | 188 ++++---- src/app/login/page.tsx | 8 +- 7 files changed, 1390 insertions(+), 98 deletions(-) create mode 100644 src/app/(main)/funds_transfer/outside_quick_pay.tsx create mode 100644 src/app/(main)/funds_transfer/send_beneficiary/page.tsx create mode 100644 src/app/(main)/funds_transfer/send_beneficiary/sendBeneficiaryOthers.tsx create mode 100644 src/app/(main)/funds_transfer/view_beneficiary/page.tsx diff --git a/src/app/(main)/funds_transfer/outside_quick_pay.tsx b/src/app/(main)/funds_transfer/outside_quick_pay.tsx new file mode 100644 index 0000000..b6c1daa --- /dev/null +++ b/src/app/(main)/funds_transfer/outside_quick_pay.tsx @@ -0,0 +1,451 @@ +"use client"; + +import React, { useEffect, useRef, useState } from "react"; +import { Button, Group, Modal, Paper, Radio, ScrollArea, Select, Stack, Text, TextInput, Title } from "@mantine/core"; +import { notifications } from "@mantine/notifications"; +import { useRouter } from "next/navigation"; +import { generateOTP } from '@/app/OTPGenerator'; + +interface accountData { + stAccountNo: string; + stAccountType: string; + stAvailableBalance: string; + custname: string; +} + +export default function OutsideQuickPay() { + const router = useRouter(); + const [authorized, setAuthorized] = useState(null); + const [accountData, setAccountData] = useState([]); + const [selectedAccNo, setSelectedAccNo] = useState(null); + const [beneficiaryAcc, setBeneficiaryAcc] = useState(""); + const [showPayeeAcc, setShowPayeeAcc] = useState(true); + const [confirmBeneficiaryAcc, setConfirmBeneficiaryAcc] = useState(""); + const [paymentMode, setPaymentMode] = useState('IMPS'); + const [beneficiaryType, setBeneficiaryType] = useState(null); + const [isVisibilityLocked, setIsVisibilityLocked] = useState(false); + const [amount, setAmount] = useState(""); + const [remarks, setRemarks] = useState(""); + const [showConfirmModel, setConfirmModel] = useState(false); + const [showTxnPassword, setShowTxnPassword] = useState(false); + const [txnPassword, setTxnPassword] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + const [validationStatus, setValidationStatus] = useState<"success" | "error" | null>(null); + const [beneficiaryName, setBeneficiaryName] = useState(null); + const [showOtpField, setShowOtpField] = useState(false); + const [otp, setOtp] = useState(""); + const [generateOtp, setGenerateOtp] = useState(""); + + async function handleGenerateOtp() { + // const value = await generateOTP(6); + const value = "123456"; + setGenerateOtp(value); + return value; + } + + const selectedAccount = accountData.find((acc) => acc.stAccountNo === selectedAccNo); + const getFullMaskedAccount = (acc: string) => { return "X".repeat(acc.length); }; + + const accountOptions = accountData.map((acc) => ({ + value: acc.stAccountNo, + label: `${acc.stAccountNo} (${acc.stAccountType})`, + })); + + const FetchAccountDetails = async () => { + try { + const token = localStorage.getItem("access_token"); + const response = await fetch("/api/customer", { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + const data = await response.json(); + if (response.ok && Array.isArray(data)) { + const filterSAaccount = data.filter((acc) => acc.stAccountType === 'SA'); + setAccountData(filterSAaccount); + } + } catch { + notifications.show({ + withBorder: true, + color: "red", + title: "Please try again later", + message: "Unable to Fetch, Please try again later", + autoClose: 5000, + }); + } + }; + + useEffect(() => { + const token = localStorage.getItem("access_token"); + if (!token) { + setAuthorized(false); + router.push("/login"); + } else { + setAuthorized(true); + } + }, []); + + useEffect(() => { + if (authorized) { + FetchAccountDetails(); + } + }, [authorized]); + + async function handleValidate() { + if (!selectedAccNo || !beneficiaryAcc || + !confirmBeneficiaryAcc + ) { + notifications.show({ + title: "Validation Error", + message: "Please fill debit account, beneficiary account number and confirm beneficiary account number", + color: "red", + }); + return; + } + if (beneficiaryAcc.length < 10 || beneficiaryAcc.length > 17) { + notifications.show({ + title: "Invalid Account Number", + message: "Please Enter valid account Number", + color: "red", + }); + return; + } + if (beneficiaryAcc !== confirmBeneficiaryAcc) { + notifications.show({ + title: "Mismatch", + message: "Beneficiary account numbers do not match", + color: "red", + }); + return; + } + + try { + const token = localStorage.getItem("access_token"); + // Need to change with proper API + const response = await fetch(`/api/beneficiary/validate/within-bank?accountNumber=${beneficiaryAcc}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + const data = await response.json(); + + if (response.ok && data?.name) { + setBeneficiaryName(data.name); + setValidationStatus("success"); + setIsVisibilityLocked(true); + } else { + setBeneficiaryName("Invalid account number"); + setValidationStatus("error"); + setBeneficiaryAcc(""); + setConfirmBeneficiaryAcc(""); + } + } catch { + setBeneficiaryName("Invalid account number"); + setValidationStatus("error"); + } + }; + + async function handleProceed() { + if (!selectedAccNo || !beneficiaryAcc || !confirmBeneficiaryAcc || !paymentMode || !beneficiaryType || !amount || !remarks) { + notifications.show({ + title: "Validation Error", + message: "Please fill all required fields", + color: "red", + }); + return; + } + + if (validationStatus !== "success") { + notifications.show({ + title: "Validation Required", + message: "Please validate beneficiary before proceeding", + color: "red", + }); + return; + } + if (parseInt(amount) <= 0) { + notifications.show({ + title: "Invalid amount", + message: "Amount Can not be less than Zero", + color: "red", + }); + return; + + } + + if (!showOtpField && !showTxnPassword && !showConfirmModel) { + setConfirmModel(true); + return; + } + + if (!otp) { + notifications.show({ + title: "Enter OTP", + message: "Please enter the OTP", + color: "red", + }); + return; + } + if (otp !== generateOtp) { + notifications.show({ + title: "Invalid OTP", + message: "The OTP entered does not match", + color: "red", + }); + return; + } + + if (!showTxnPassword) { + setShowTxnPassword(true); + return; + } + + if (!txnPassword) { + notifications.show({ + title: "Missing field", + message: "Please Enter Transaction Password Before Proceed", + color: "red", + }); + return; + } + try { + setIsSubmitting(true); + const token = localStorage.getItem("access_token"); + // Need to change with proper API + const res = await fetch("/api/payment/transfer", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + fromAccount: selectedAccNo, + toAccount: beneficiaryAcc, + toAccountType: beneficiaryType, + amount: amount, + narration: remarks, + tpassword: txnPassword, + }), + }); + const result = await res.json(); + if (res.ok) { + notifications.show({ + title: "Success", + message: "Transaction successful", + color: "green", + }); + setShowTxnPassword(false); + setTxnPassword(""); + setShowOtpField(false); + setOtp(""); + setValidationStatus(null); + setBeneficiaryName(null); + } else { + notifications.show({ + title: "Error", + message: result?.error || "Transaction failed", + color: "red", + }); + } + } catch { + notifications.show({ + title: "Error", + message: "Something went wrong", + color: "red", + }); + } finally { + setIsSubmitting(false); + } + }; + + if (!authorized) return null; + + return ( + <> + setConfirmModel(false)} + // title="Confirm Transaction" + centered + > + + Confirm Transaction + Debit Account: {selectedAccNo} + Payee Account: {beneficiaryAcc} + Payee Name: {beneficiaryName} + Amount: ₹ {amount} + Remarks: {remarks} + + + + + + + {/* main content */} +
+ + + + + setAmount(e.currentTarget.value)} + error={ + selectedAccount && Number(amount) > Number(selectedAccount.stAvailableBalance) ? + "Amount exceeds available balance" : false} + withAsterisk + readOnly={showOtpField} + /> + + setRemarks(e.currentTarget.value)} + withAsterisk + readOnly={showOtpField} + /> + + + {showOtpField && ( + setOtp(e.currentTarget.value)} + withAsterisk + disabled={showTxnPassword} + /> + )} + {showTxnPassword && ( + setTxnPassword(e.currentTarget.value)} + withAsterisk + /> + )} + + + + + + + +
+ + ); +} diff --git a/src/app/(main)/funds_transfer/page.tsx b/src/app/(main)/funds_transfer/page.tsx index da91d5e..ad242df 100644 --- a/src/app/(main)/funds_transfer/page.tsx +++ b/src/app/(main)/funds_transfer/page.tsx @@ -5,6 +5,7 @@ import { Button, Group, Modal, Paper, Radio, ScrollArea, Select, Stack, Text, Te import { notifications } from "@mantine/notifications"; import { useRouter } from "next/navigation"; import { generateOTP } from '@/app/OTPGenerator'; +import OutsideQuickPay from "./outside_quick_pay"; interface accountData { stAccountNo: string; @@ -93,7 +94,6 @@ export default function QuickPay() { } }, [authorized]); - async function handleValidate() { if (!selectedAccNo || !beneficiaryAcc || !confirmBeneficiaryAcc @@ -369,12 +369,13 @@ export default function QuickPay() { + + + setAmount(e.currentTarget.value)} + error={ + selectedAccount && Number(amount) > Number(selectedAccount.stAvailableBalance) ? + "Amount exceeds available balance" : false} + withAsterisk + readOnly={showOtpField} + /> + + setRemarks(e.currentTarget.value)} + withAsterisk + readOnly={showOtpField} + /> + + + {showOtpField && ( + setOtp(e.currentTarget.value)} + withAsterisk + disabled={showTxnPassword} + /> + )} + {showTxnPassword && ( + setTxnPassword(e.currentTarget.value)} + withAsterisk + /> + )} + + + + + + + + ) : ( +
+ +
+ )} + + + ); +} diff --git a/src/app/(main)/funds_transfer/send_beneficiary/sendBeneficiaryOthers.tsx b/src/app/(main)/funds_transfer/send_beneficiary/sendBeneficiaryOthers.tsx new file mode 100644 index 0000000..38b124a --- /dev/null +++ b/src/app/(main)/funds_transfer/send_beneficiary/sendBeneficiaryOthers.tsx @@ -0,0 +1,383 @@ +"use client"; + +import React, { useEffect, useRef, useState } from "react"; +import { Button, Group, Modal, Paper, Radio, ScrollArea, Select, Stack, Text, TextInput, Title } from "@mantine/core"; +import { notifications } from "@mantine/notifications"; +import { useRouter } from "next/navigation"; +import { generateOTP } from '@/app/OTPGenerator'; +import { MockBeneficiaryData } from './page'; + +interface accountData { + stAccountNo: string; + stAccountType: string; + stAvailableBalance: string; + custname: string; +} + +export default function SendToBeneficiaryOthers() { + const router = useRouter(); + const [authorized, setAuthorized] = useState(null); + const [accountData, setAccountData] = useState([]); + const [selectedAccNo, setSelectedAccNo] = useState(null); + const [beneficiaryAcc, setBeneficiaryAcc] = useState(null); + const [beneficiaryName, setBeneficiaryName] = useState(null); + const [beneficiaryIFSC, setBeneficiaryIFSC] = useState(null); + const [paymentMode, setPaymentMode] = useState('IMPS'); + const [isVisibilityLocked, setIsVisibilityLocked] = useState(false); + const [amount, setAmount] = useState(""); + const [remarks, setRemarks] = useState(""); + const [showConfirmModel, setConfirmModel] = useState(false); + const [showTxnPassword, setShowTxnPassword] = useState(false); + const [txnPassword, setTxnPassword] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + const [showOtpField, setShowOtpField] = useState(false); + const [otp, setOtp] = useState(""); + const [generateOtp, setGenerateOtp] = useState(""); + + async function handleGenerateOtp() { + // const value = await generateOTP(6); + const value = "123456"; + setGenerateOtp(value); + return value; + } + const selectedAccount = accountData.find((acc) => acc.stAccountNo === selectedAccNo); + const accountOptions = accountData.map((acc) => ({ + value: acc.stAccountNo, + label: `${acc.stAccountNo} (${acc.stAccountType})`, + })); + + const benAccountOption = MockBeneficiaryData.filter((ben_acc) => + ben_acc.stBankName !== 'Kangra Central Co-operative Bank') + .map((ben_acc) => ({ + value: ben_acc.stBenAccountNo, + label: ben_acc.stBenAccountNo, + + })); + const handleBeneficiary = (benAcc: string | null) => { + if (benAcc) { + setBeneficiaryAcc(benAcc); + const selected = MockBeneficiaryData.find((item) => item.stBenAccountNo === benAcc); + if (selected) + setBeneficiaryName(selected.stBenName); + setBeneficiaryIFSC(selected?.stIFSC ?? ''); + } + else { + setBeneficiaryAcc(''); + setBeneficiaryName(''); + setBeneficiaryIFSC(''); + } + } + + const FetchAccountDetails = async () => { + try { + const token = localStorage.getItem("access_token"); + const response = await fetch("/api/customer", { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + const data = await response.json(); + if (response.ok && Array.isArray(data)) { + const filterSAaccount = data.filter((acc) => acc.stAccountType === 'SA'); + setAccountData(filterSAaccount); + } + } catch { + notifications.show({ + withBorder: true, + color: "red", + title: "Please try again later", + message: "Unable to Fetch, Please try again later", + autoClose: 5000, + }); + } + }; + + useEffect(() => { + const token = localStorage.getItem("access_token"); + if (!token) { + setAuthorized(false); + router.push("/login"); + } else { + setAuthorized(true); + } + }, []); + + useEffect(() => { + if (authorized) { + FetchAccountDetails(); + } + }, [authorized]); + + async function handleProceed() { + if (!selectedAccNo || !beneficiaryAcc! || !beneficiaryName || !beneficiaryIFSC || !paymentMode || !amount || !remarks) { + notifications.show({ + title: "Validation Error", + message: `Please fill all required fields`, + color: "red", + }); + return; + } + + if (parseInt(amount) <= 0) { + notifications.show({ + title: "Invalid amount", + message: "Amount Can not be less than Zero", + color: "red", + }); + return; + } + + if (!showOtpField && !showTxnPassword && !showConfirmModel) { + setConfirmModel(true); + setIsVisibilityLocked(true); + return; + } + + if (!otp) { + notifications.show({ + title: "Enter OTP", + message: "Please enter the OTP", + color: "red", + }); + return; + } + if (otp !== generateOtp) { + notifications.show({ + title: "Invalid OTP", + message: "The OTP entered does not match", + color: "red", + }); + return; + } + + if (!showTxnPassword) { + setShowTxnPassword(true); + return; + } + + if (!txnPassword) { + notifications.show({ + title: "Missing field", + message: "Please Enter Transaction Password Before Proceed", + color: "red", + }); + return; + } + try { + setIsSubmitting(true); + const token = localStorage.getItem("access_token"); + // Need to change the API + const res = await fetch("/api/payment/transfer", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + fromAccount: selectedAccNo, + toAccount: beneficiaryAcc, + // toAccountType: beneficiaryType, + amount: amount, + narration: remarks, + tpassword: txnPassword, + }), + }); + const result = await res.json(); + if (res.ok) { + notifications.show({ + title: "Success", + message: "Transaction successful", + color: "green", + }); + setShowTxnPassword(false); + setTxnPassword(""); + setShowOtpField(false); + setOtp(""); + setBeneficiaryName(''); + } else { + notifications.show({ + title: "Error", + message: result?.error || "Transaction failed", + color: "red", + }); + } + } catch { + notifications.show({ + title: "Error", + message: "Something went wrong", + color: "red", + }); + } finally { + setSelectedAccNo(null); + setBeneficiaryAcc(null); + setBeneficiaryName(''); + setPaymentMode(''); + setAmount(''); + setRemarks(''); + setIsVisibilityLocked(false); + setIsSubmitting(false); + setShowTxnPassword(false); + setShowOtpField(false); + } + }; + + if (!authorized) return null; + + return ( + <> + setConfirmModel(false)} + // title="Confirm Transaction" + centered + > + + Confirm Transaction + Debit Account: {selectedAccNo} + Beneficiary Account: {beneficiaryAcc} + Beneficiary Name: {beneficiaryName} + Payment Type: {paymentMode} + Amount: ₹ {amount} + Remarks: {remarks} + + + + + + + {/* main content */} +
+ + + + + + + Available Balance : + {selectedAccount ? selectedAccount.stAvailableBalance : 0} + + + + + + + + + + + + + setAmount(e.currentTarget.value)} + error={ + selectedAccount && Number(amount) > Number(selectedAccount.stAvailableBalance) ? + "Amount exceeds available balance" : false} + withAsterisk + readOnly={showOtpField} + /> + + setRemarks(e.currentTarget.value)} + withAsterisk + readOnly={showOtpField} + /> + + + {showOtpField && ( + setOtp(e.currentTarget.value)} + withAsterisk + disabled={showTxnPassword} + /> + )} + {showTxnPassword && ( + setTxnPassword(e.currentTarget.value)} + withAsterisk + /> + )} + + + + + + +
+ + ); +} diff --git a/src/app/(main)/funds_transfer/view_beneficiary/page.tsx b/src/app/(main)/funds_transfer/view_beneficiary/page.tsx new file mode 100644 index 0000000..0ab6f4d --- /dev/null +++ b/src/app/(main)/funds_transfer/view_beneficiary/page.tsx @@ -0,0 +1,30 @@ +"use client"; + +import React, { useEffect, useRef, useState } from "react"; +import { Button, Group, Modal, Paper, Radio, ScrollArea, Select, Stack, Text, TextInput, Title } from "@mantine/core"; +import { notifications } from "@mantine/notifications"; +import { useRouter } from "next/navigation"; + +export default function ViewBeneficiary() { + const router = useRouter(); + const [authorized, setAuthorized] = useState(null); + + useEffect(() => { + const token = localStorage.getItem("access_token"); + if (!token) { + setAuthorized(false); + router.push("/login"); + } else { + setAuthorized(true); + } + }, []); + + if (!authorized) return null; + + return ( + + View beneficiary feature coming soon. + + + ); +} diff --git a/src/app/(main)/home/page.tsx b/src/app/(main)/home/page.tsx index ec94132..919a132 100644 --- a/src/app/(main)/home/page.tsx +++ b/src/app/(main)/home/page.tsx @@ -1,10 +1,9 @@ "use client"; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Button, Input, Group, Stack, Text, Title, Box, Select, Paper, Switch } from '@mantine/core'; import { IconBuildingBank, IconEye } from '@tabler/icons-react'; import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; import { Providers } from "../../providers"; import { notifications } from '@mantine/notifications'; @@ -28,9 +27,7 @@ export default function Home() { const selectedLNData = loanAccounts.find(acc => acc.stAccountNo === selectedLN); const [showBalance, setShowBalance] = useState(false); - async function handleFetchUserDetails() { - // e.preventDefault(); try { const token = localStorage.getItem("access_token"); const response = await fetch('api/customer', { @@ -49,10 +46,10 @@ export default function Home() { if (firstDeposit) setSelectedDA(firstDeposit.stAccountNo); if (firstLoan) setSelectedLN(firstLoan.stAccountNo); } + } else { + throw new Error(); } - else { throw new Error(); } - } - catch { + } catch { notifications.show({ withBorder: true, color: "red", @@ -72,8 +69,7 @@ export default function Home() { if (!token) { SetAuthorized(false); router.push("/login"); - } - else { + } else { SetAuthorized(true); } }, []); @@ -89,112 +85,126 @@ export default function Home() {
Accounts Overview - + Show Balance setShowBalance(event.currentTarget.checked)} /> -
+
- + {/* Deposit Account Card */} + Deposit Account - ({ + value: acc.stAccountNo, + label: `${acc.stAccountType}- ${acc.stAccountNo}` + }))} + value={selectedDA} + // @ts-ignore + onChange={setSelectedDA} + size="xs" + styles={{ + input: { + backgroundColor: "white", + color: "black", + marginLeft: 5, + width: 140 + } + }} + /> + ) : ( + No deposit account available + )} - {Number(selectedDAData?.stAccountNo || 0)} - - {showBalance ? `₹${Number(selectedDAData?.stAvailableBalance || 0).toLocaleString('en-IN')}` : "****"} - - + + {depositAccounts.length > 0 ? ( + <> + {Number(selectedDAData?.stAccountNo || 0)} + + {showBalance ? `₹${Number(selectedDAData?.stAvailableBalance || 0).toLocaleString('en-IN')}` : "****"} + + + + ) : ( + <> + Apply for a deposit account to get started + + + )} - + + {/* Loan Account Card */} + Loan Account - ({ + value: acc.stAccountNo, + label: `${acc.stAccountType}- ${acc.stAccountNo}` + }))} + value={selectedLN} + // @ts-ignore + onChange={setSelectedLN} + size="xs" + styles={{ + input: { + backgroundColor: "white", + color: "black", + marginLeft: 30, + width: 140 + } + }} + /> + ) : ( + No loan account available + )} - {Number(selectedLNData?.stAccountNo || 0)} - - {showBalance ? `₹${Number(selectedLNData?.stAvailableBalance || 0).toLocaleString('en-IN')}` : "****"} - - + + {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 */} Important Links - {/* Loan EMI Calculator */} - {/* - - - - - */}
-
+ + {/* Send Money Section */} +
Send Money @@ -217,4 +227,6 @@ export default function Home() { ); } + + return null; } \ No newline at end of file diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 24087db..78fe811 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -143,11 +143,11 @@ export default function Login() { }} > ⚠️ Always login to our Net Banking site directly or through Banks website. - ⚠️ Do not disclose your UserId and Password to any third party and keep Your UserId and Password strictly confidential. - ⚠️ KCC Bank never asks for UserId,Passwords and Pins through email or phone. + ⚠️ Do not disclose your User Id and Password to any third party and keep Your User Id and Password strictly confidential. + ⚠️ KCC Bank never asks for User Id,Passwords and Pins through email or phone. ⚠️ Be ware of Phishing mails with links to fake bank's websites asking for personal information are in circulation. - ⚠️ Please DO NOT Click on the links given in the emails asking for personal details like bank account number, userID and password. - ⚠️ If you had shard your UserId and Password through such mails or links, please change your Password immediately. + ⚠️ Please DO NOT Click on the links given in the emails asking for personal details like bank account number, user ID and password. + ⚠️ If you had shard your User Id and Password through such mails or links, please change your Password immediately. ⚠️ Inform the Bank/branch in which your account is maintained for resetting your password.