From a4f510ad40ce99b631ce378f2483fbc50a65a99b Mon Sep 17 00:00:00 2001 From: "nabanita.jana" Date: Thu, 9 Oct 2025 15:17:07 +0530 Subject: [PATCH 1/2] wip: delete beneficiaty --- .../funds_transfer/view_beneficiary/page.tsx | 296 ++++++++++++------ src/app/login/page.tsx | 1 + 2 files changed, 197 insertions(+), 100 deletions(-) diff --git a/src/app/(main)/funds_transfer/view_beneficiary/page.tsx b/src/app/(main)/funds_transfer/view_beneficiary/page.tsx index 3b4628e..28ddf31 100644 --- a/src/app/(main)/funds_transfer/view_beneficiary/page.tsx +++ b/src/app/(main)/funds_transfer/view_beneficiary/page.tsx @@ -1,13 +1,17 @@ "use client"; import React, { useEffect, useState } from "react"; -import { Center, Group, Loader, Paper, ScrollArea, Table, Text, Title, TextInput, Button, } from "@mantine/core"; +import { Center, Group, Loader, Paper, ScrollArea, Table, Text, Title, TextInput, Button, Modal } from "@mantine/core"; import { notifications } from "@mantine/notifications"; import { useRouter } from "next/navigation"; import Image from "next/image"; import { getBankLogo } from "@/app/_util/getBankLogo"; import { IconTrash } from "@tabler/icons-react"; + + + + interface Beneficiary { accountNo: string; name: string; @@ -23,6 +27,105 @@ export default function ViewBeneficiary() { const [beneficiaries, setBeneficiaries] = useState([]); const [loading, setLoading] = useState(true); + const [opened, setOpened] = useState(false); + const [otpStep, setOtpStep] = useState(false); + const [otp, setOtp] = useState(""); + const [selectedAccount, setSelectedAccount] = useState(null); + + const openDeleteModal = (accountNo: string) => { + setSelectedAccount(accountNo); + setOpened(true); + setOtpStep(false); // Reset modal to confirmation step + setOtp(""); + }; + + + const handleModalConfirm = async () => { + try { + // const token = localStorage.getItem("access_token"); + + // // Generate OTP + // const otpResponse = await fetch("/api/otp/generate", { + // method: "POST", + // headers: { Authorization: `Bearer ${token}` }, + // }); + const otpResponse ="123456"; + + if (otpResponse !== otp) { + notifications.show({ + title: "Error", + message: "Failed to generate OTP.", + color: "red", + }); + return; + } + + setOtpStep(true); // move to OTP input step + } catch (err) { + notifications.show({ + title: "Error", + message: "Something went wrong.", + color: "red", + }); + } + }; + + // Step 3: Handle OTP verification & delete + const handleVerifyOtpAndDelete = async () => { + if (!otp || !selectedAccount) return; + + try { + const token = localStorage.getItem("access_token"); + + // Verify OTP + const verify = await fetch("/api/otp/verify", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ otp }), + }); + + if (!verify.ok) { + notifications.show({ + title: "Invalid OTP", + message: "Please enter the correct OTP.", + color: "red", + }); + return; + } + + // Delete beneficiary + const res = await fetch(`/api/beneficiary/${selectedAccount}`, { + method: "DELETE", + headers: { Authorization: `Bearer ${token}` }, + }); + + if (res.ok) { + setBeneficiaries((prev) => prev.filter((b) => b.accountNo !== selectedAccount)); + notifications.show({ + title: "Deleted", + message: "Beneficiary deleted successfully.", + color: "green", + }); + setOpened(false); + } else { + notifications.show({ + title: "Error", + message: "Failed to delete beneficiary.", + color: "red", + }); + } + } catch (err) { + notifications.show({ + title: "Error", + message: "Something went wrong.", + color: "red", + }); + } + }; + useEffect(() => { const token = localStorage.getItem("access_token"); if (!token) { @@ -69,60 +172,6 @@ export default function ViewBeneficiary() { if (!authorized) return null; - //add delete beneficiary - - - async function handleDeleteBeneficiary(accountNo: string) { - const isConfirmed = window.confirm("Are you sure you want to delete this beneficiary?"); - if (!isConfirmed) return; - - try { - const token = localStorage.getItem("access_token"); - - // Step 1: generate OTP - await fetch("/api/otp/generate", { - method: "POST", - headers: { Authorization: `Bearer ${token}` }, - }); - const otp = prompt("An OTP has been sent to your registered number. Please enter it:"); - - if (!otp) return; // cancelled - - // Step 2: verify OTP - const verify = await fetch("/api/otp/verify", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify({ otp }), - }); - if (!verify.ok) { - alert("Invalid OTP!"); - return; - } - - // Step 3: delete beneficiary - const res = await fetch(`/api/beneficiary/${accountNo}`, { - method: "DELETE", - headers: { Authorization: `Bearer ${token}` }, - }); - - if (res.ok) { - setBeneficiaries((prev) => prev.filter((b) => b.accountNo !== accountNo)); - alert("Beneficiary deleted successfully."); - } else { - alert("Error deleting beneficiary."); - } - } catch (err) { - alert("Something went wrong. Please try again."); - } - } - - - - - //add delete beneficiary return ( @@ -131,52 +180,99 @@ export default function ViewBeneficiary() { {beneficiaries.length === 0 ? ( No beneficiaries found. ) : ( - - - - - Bank - Account No - Name - Type - IFSC - Action Icon - - - - {beneficiaries.map((b, i) => ( - - - - - {b.bankName} - - - {b.accountNo} - {b.name} - {b.accountType} - {b.ifscCode} - {/* */} - - - handleDeleteBeneficiary(b.accountNo)} - /> - - + <> + +
+ + + Bank + Account No + Name + Type + IFSC + Action Icon - ))} - -
-
+ + + {beneficiaries.map((b, i) => ( + + + + {b.bankName} + {b.bankName} + + + {b.accountNo} + {b.name} + {b.accountType} + {b.ifscCode} + {/* */} + + + openDeleteModal(b.accountNo)} + /> + + + + ))} + + + + + { + setOpened(false); + setOtpStep(false); + setOtp(""); + }} + title="Delete Beneficiary" + centered + > + {!otpStep ? ( + <> + + Are you sure you want to delete this beneficiary? + + + + + + + ) : ( + <> + Enter OTP sent to your registered number: + setOtp(e.currentTarget.value)} + placeholder="Enter OTP" + /> + + + + + + )} + + + )} ); diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 5481617..1bf4976 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -12,6 +12,7 @@ import dynamic from 'next/dynamic'; import { generateCaptcha } from '@/app/captcha'; import { IconShieldLockFilled } from "@tabler/icons-react"; import dayjs from "dayjs"; +import { IconTrash } from "@tabler/icons-react"; export default function Login() { From 83ae6b055c0be78898c6659ca331d67598ff6046 Mon Sep 17 00:00:00 2001 From: "nabanita.jana" Date: Mon, 13 Oct 2025 11:55:34 +0530 Subject: [PATCH 2/2] feat: call the OTP Url in "send money","settings" --- src/app/(main)/funds_transfer/page.tsx | 57 +++++++-- .../funds_transfer/view_beneficiary/page.tsx | 120 ++++++++++++------ .../settings/change_login_password/page.tsx | 58 +++++++-- .../settings/change_txn_password/page.tsx | 57 +++++++-- .../(main)/settings/set_txn_password/page.tsx | 55 ++++++-- src/app/SetPassword/page.tsx | 66 ++++++++-- src/app/_util/otp.ts | 4 +- 7 files changed, 330 insertions(+), 87 deletions(-) diff --git a/src/app/(main)/funds_transfer/page.tsx b/src/app/(main)/funds_transfer/page.tsx index 27d035e..78b6087 100644 --- a/src/app/(main)/funds_transfer/page.tsx +++ b/src/app/(main)/funds_transfer/page.tsx @@ -9,6 +9,7 @@ import OutsideQuickPay from "./outside_quick_pay"; import { IconRefresh } from "@tabler/icons-react"; import Image from "next/image"; import img from '@/app/image/logo1.jpg' +import { sendOtp, verifyOtp } from "@/app/_util/otp"; interface accountData { stAccountNo: string; @@ -42,15 +43,53 @@ export default function QuickPay() { const [otp, setOtp] = useState(""); const [generateOtp, setGenerateOtp] = useState(""); - async function handleGenerateOtp() { - // const value = await generateOTP(6); - const value = "123456"; - setGenerateOtp(value); - setCountdown(180); - setTimerActive(true); - return value; + // async function handleGenerateOtp() { + // // const value = await generateOTP(6); + // const value = "123456"; + // setGenerateOtp(value); + // setCountdown(180); + // setTimerActive(true); + // return value; + // } + + async function handleSendOtp() { + const mobileNumber = localStorage.getItem('remitter_mobile_no'); + if (!mobileNumber) { + notifications.show({ + title: 'Error', + message: 'Mobile number not found.Contact to administrator', + color: 'red', + }); + return; + } + try { + await sendOtp({ type: 'BENEFICIARY_DELETE' }); + setShowOtpField(true); + setCountdown(180); + setTimerActive(true); + } catch (err: any) { + console.error('Send OTP failed', err); + notifications.show({ + title: 'Error', + message: err.message || 'Send OTP failed.Please try again later.', + color: 'red', + }); + } } + async function handleVerifyOtp() { + try { + await verifyOtp(otp); + return true; + } catch { + return false; + } + } + + + + + const selectedAccount = accountData.find((acc) => acc.stAccountNo === selectedAccNo); const getFullMaskedAccount = (acc: string) => { return "X".repeat(acc.length); }; @@ -335,7 +374,7 @@ export default function QuickPay() { color="blue" onClick={async () => { setConfirmModel(false); - const otp = await handleGenerateOtp(); + const otp = await handleSendOtp(); setShowOtpField(true); notifications.show({ title: "OTP Sent", @@ -484,7 +523,7 @@ export default function QuickPay() { ) )} diff --git a/src/app/(main)/funds_transfer/view_beneficiary/page.tsx b/src/app/(main)/funds_transfer/view_beneficiary/page.tsx index 28ddf31..02d1258 100644 --- a/src/app/(main)/funds_transfer/view_beneficiary/page.tsx +++ b/src/app/(main)/funds_transfer/view_beneficiary/page.tsx @@ -6,10 +6,8 @@ import { notifications } from "@mantine/notifications"; import { useRouter } from "next/navigation"; import Image from "next/image"; import { getBankLogo } from "@/app/_util/getBankLogo"; -import { IconTrash } from "@tabler/icons-react"; - - - +import { IconRefresh, IconTrash } from "@tabler/icons-react"; +import { sendOtp, verifyOtp } from "@/app/_util/otp"; interface Beneficiary { @@ -31,6 +29,41 @@ export default function ViewBeneficiary() { const [otpStep, setOtpStep] = useState(false); const [otp, setOtp] = useState(""); const [selectedAccount, setSelectedAccount] = useState(null); + const [countdown, setCountdown] = useState(180); + const [timerActive, setTimerActive] = useState(false); + + async function handleSendOtp() { + const mobileNumber = localStorage.getItem('remitter_mobile_no'); + if (!mobileNumber) { + notifications.show({ + title: 'Error', + message: 'Mobile number not found.Contact to administrator', + color: 'red', + }); + return; + } + try { + await sendOtp({ type: 'IMPS' }); // type will be "BENEFICIARY_DELETE" + setCountdown(180); + setTimerActive(true); + } catch (err: any) { + console.error('Send OTP failed', err); + notifications.show({ + title: 'Error', + message: err.message || 'Send OTP failed.Please try again later.', + color: 'red', + }); + } + } + + async function handleVerifyOtp() { + try { + await verifyOtp(otp); + return true; + } catch { + return false; + } + } const openDeleteModal = (accountNo: string) => { setSelectedAccount(accountNo); @@ -41,30 +74,15 @@ export default function ViewBeneficiary() { const handleModalConfirm = async () => { + if (!selectedAccount) return; + try { - // const token = localStorage.getItem("access_token"); - - // // Generate OTP - // const otpResponse = await fetch("/api/otp/generate", { - // method: "POST", - // headers: { Authorization: `Bearer ${token}` }, - // }); - const otpResponse ="123456"; - - if (otpResponse !== otp) { - notifications.show({ - title: "Error", - message: "Failed to generate OTP.", - color: "red", - }); - return; - } - - setOtpStep(true); // move to OTP input step + await handleSendOtp(); // send OTP to user's mobile + setOtpStep(true); // move modal to OTP input step } catch (err) { notifications.show({ title: "Error", - message: "Something went wrong.", + message: "Failed to send OTP. Please try again.", color: "red", }); } @@ -75,19 +93,8 @@ export default function ViewBeneficiary() { if (!otp || !selectedAccount) return; try { - const token = localStorage.getItem("access_token"); - - // Verify OTP - const verify = await fetch("/api/otp/verify", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify({ otp }), - }); - - if (!verify.ok) { + const isOtpValid = await handleVerifyOtp(); // verify OTP + if (!isOtpValid) { notifications.show({ title: "Invalid OTP", message: "Please enter the correct OTP.", @@ -96,7 +103,8 @@ export default function ViewBeneficiary() { return; } - // Delete beneficiary + // OTP is valid → delete beneficiary + const token = localStorage.getItem("access_token"); const res = await fetch(`/api/beneficiary/${selectedAccount}`, { method: "DELETE", headers: { Authorization: `Bearer ${token}` }, @@ -110,6 +118,8 @@ export default function ViewBeneficiary() { color: "green", }); setOpened(false); + setOtpStep(false); + setOtp(""); } else { notifications.show({ title: "Error", @@ -126,6 +136,7 @@ export default function ViewBeneficiary() { } }; + useEffect(() => { const token = localStorage.getItem("access_token"); if (!token) { @@ -162,6 +173,23 @@ export default function ViewBeneficiary() { fetchBeneficiaries(); }, []); + //new use effect + useEffect(() => { + let interval: NodeJS.Timeout | null = null; + if (timerActive && countdown > 0) { + interval = setInterval(() => { + setCountdown((prev) => prev - 1); + }, 1000); + } else if (countdown === 0) { + setTimerActive(false); + } + return () => { + if (interval) clearInterval(interval); + }; + }, [timerActive, countdown]); + + + if (loading) { return (
@@ -260,6 +288,22 @@ export default function ViewBeneficiary() { onChange={(e) => setOtp(e.currentTarget.value)} placeholder="Enter OTP" /> + + {/* Resend OTP Timer or Icon */} + {timerActive ? ( + + Resend OTP will be enabled in{" "} + {String(Math.floor(countdown / 60)).padStart(2, "0")}: + {String(countdown % 60).padStart(2, "0")} + + ) : ( + + )} +