From d62ca2387e3f86da75416505f441b4c914206094 Mon Sep 17 00:00:00 2001 From: "tomosa.sarkar" Date: Tue, 4 Nov 2025 10:50:41 +0530 Subject: [PATCH 1/2] fix : add ben feature --- .../funds_transfer/add_beneficiary/addBeneficiaryOthers.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(main)/funds_transfer/add_beneficiary/addBeneficiaryOthers.tsx b/src/app/(main)/funds_transfer/add_beneficiary/addBeneficiaryOthers.tsx index 6dae4f2..02afe0d 100644 --- a/src/app/(main)/funds_transfer/add_beneficiary/addBeneficiaryOthers.tsx +++ b/src/app/(main)/funds_transfer/add_beneficiary/addBeneficiaryOthers.tsx @@ -99,7 +99,7 @@ export default function AddBeneficiaryOthers() { try { const token = localStorage.getItem("access_token"); const response = await fetch( - `http://localhost:8080/api/beneficiary/ifsc-details?ifscCode=${ifscTrimmed}`, + `/api/beneficiary/ifsc-details?ifscCode=${ifscTrimmed}`, { method: "GET", headers: { From a40a5f621f187bdea863c99d591c9758912f71c2 Mon Sep 17 00:00:00 2001 From: "nabanita.jana" Date: Tue, 4 Nov 2025 12:43:53 +0530 Subject: [PATCH 2/2] feat : user can set the transaction limit --- src/app/(main)/settings/layout.tsx | 2 + .../(main)/settings/set_txn_limit/page.tsx | 370 ++++++++++++++++++ src/app/_util/otp.ts | 2 +- src/app/login/page.tsx | 6 +- 4 files changed, 376 insertions(+), 4 deletions(-) create mode 100644 src/app/(main)/settings/set_txn_limit/page.tsx diff --git a/src/app/(main)/settings/layout.tsx b/src/app/(main)/settings/layout.tsx index ea197bd..780ab76 100644 --- a/src/app/(main)/settings/layout.tsx +++ b/src/app/(main)/settings/layout.tsx @@ -21,6 +21,8 @@ export default function Layout({ children }: { children: React.ReactNode }) { { label: "Change transaction Password", href: "/settings/change_txn_password" }, { label: "Set transaction Password", href: "/settings/set_txn_password" }, { label: "Preferred Name", href: "/settings/user_name" }, + { label: "Set Transaction Limit ", href: "/settings/set_txn_limit" }, + ]; useEffect(() => { const token = localStorage.getItem("access_token"); diff --git a/src/app/(main)/settings/set_txn_limit/page.tsx b/src/app/(main)/settings/set_txn_limit/page.tsx new file mode 100644 index 0000000..a0598be --- /dev/null +++ b/src/app/(main)/settings/set_txn_limit/page.tsx @@ -0,0 +1,370 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { + TextInput, + Button, + Title, + Paper, + Group, + Text, +} from "@mantine/core"; +import { notifications } from "@mantine/notifications"; +import { IconRefresh, IconCurrencyRupee } from "@tabler/icons-react"; +import { generateCaptcha } from "@/app/captcha"; +import { useRouter } from "next/navigation"; +import { sendOtp, verifyOtp } from "@/app/_util/otp"; +import { getDailyLimit } from "@/app/_util/transactionLimit"; + +export default function SetTransactionLimit() { + const [limit, setLimit] = useState(""); + const [captcha, setCaptcha] = useState(""); + const [captchaInput, setCaptchaInput] = useState(""); + const [otp, setOtp] = useState(""); + const [otpValidated, setOtpValidated] = useState(false); + const [countdown, setCountdown] = useState(180); + const [timerActive, setTimerActive] = useState(false); + const [step, setStep] = useState<"form" | "otp" | "final">("form"); + const [dailyLimit, setDailyLimit] = useState(null); + const router = useRouter(); + + const icon = ; + + const FetchDailyLimit = async () => { + try { + const token = localStorage.getItem("access_token"); + if (!token) return; + const data = await getDailyLimit(token); + if (data) { + setDailyLimit(data.dailyLimit); + + } else { + setDailyLimit(null); + } + } catch { + setDailyLimit(null); + + } + }; + + async function handleSendOtp() { + const mobileNumber = localStorage.getItem("remitter_mobile_no"); + if (!mobileNumber) { + notifications.show({ + title: "Error", + message: "Mobile number not found. Contact administrator.", + color: "red", + }); + return; + } + try { + await sendOtp({ type: "TLIMIT" }); + setCountdown(180); + setTimerActive(true); + notifications.show({ + title: "OTP Sent", + message: "An OTP has been sent to your registered mobile.", + color: "blue", + }); + } catch (err: any) { + console.error("Send OTP failed", err); + notifications.show({ + title: "Error", + message: err.message || "Send OTP failed. Try again later.", + color: "red", + }); + } + } + + async function handleVerifyOtp() { + try { + await verifyOtp(otp); + return true; + } catch { + return false; + } + } + const regenerateCaptcha = async () => { + const newCaptcha = await generateCaptcha(); + setCaptcha(newCaptcha); + setCaptchaInput(""); + }; + + useEffect(() => { + let interval: number | undefined; + if (timerActive && countdown > 0) { + interval = window.setInterval(() => { + setCountdown((prev) => prev - 1); + }, 1000); + } + if (countdown === 0) { + if (interval) clearInterval(interval); + setTimerActive(false); + } + return () => { + if (interval) clearInterval(interval); + }; + }, [timerActive, countdown]); + + useEffect(() => { + regenerateCaptcha(); + FetchDailyLimit(); + }, []); + + const handleSubmit = async () => { + if (step === "form") { + if (!limit || !captchaInput) { + notifications.show({ + title: "Missing Field", + message: "Please fill all mandatory fields.", + color: "red", + }); + return; + } + + + + if (isNaN(Number(limit)) || Number(limit) <= 0) { + notifications.show({ + title: "Invalid Limit", + message: "Please enter a valid positive numeric transaction limit.", + color: "red", + }); + return; + } + + if (captchaInput !== captcha) { + notifications.show({ + title: "Invalid Captcha", + message: "Please enter correct Captcha", + color: "red", + }); + regenerateCaptcha(); + return; + } + + await handleSendOtp(); + setStep("otp"); + return; + } + + if (step === "otp") { + const verified = await handleVerifyOtp(); + if (!verified) { + notifications.show({ + title: "Invalid OTP", + message: "The OTP entered does not match.", + color: "red", + }); + return; + } + + setOtpValidated(true); + setStep("final"); + notifications.show({ + title: "OTP Verified", + message: "OTP has been successfully verified.", + color: "green", + }); + return; + } + + if (step === "final") { + const token = localStorage.getItem("access_token"); + if (!token) { + router.push("/login"); + return; + } + + try { + const response = await fetch("/api/customer/daily-limit", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Login-Type": "IB", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + amount: Number(limit), + }), + }); + + const result = await response.json(); + + if (!response.ok) { + throw new Error(result.message || "Failed to set transaction limit"); + } + + notifications.show({ + title: "Success", + message: `Transaction limit set successfully to ₹${limit}.`, + color: "green", + }); + resetForm(); + await FetchDailyLimit(); //refresh + const otp_sended = await sendOtp({ type: "TLIMIT_SET", amount: Number(limit) }); + + } catch (err: any) { + notifications.show({ + title: "Error", + message: err.message || "Server error, please try again later", + color: "red", + }); + } + } + }; + + const resetForm = () => { + setLimit(""); + setCaptchaInput(""); + setOtp(""); + setOtpValidated(false); + setStep("form"); + regenerateCaptcha(); + }; + + return ( + + + Set Transaction Limit + + +
+ + { + const val = e.currentTarget.value; + // Only allow digits + if (/^\d*$/.test(val)) { + setLimit(val); + } + }} + leftSection={icon} + withAsterisk + mb="sm" + readOnly={step !== "form"} + /> + + {dailyLimit !== null ? ( + + Your transaction limit per day is Rs. {dailyLimit.toFixed(2)} + + ) : ( + + • No daily limit set for this user + + )} + {/* CAPTCHA */} +
+ +
+
+ {captcha} +
+ + + + setCaptchaInput(e.currentTarget.value)} + withAsterisk + style={{ flexGrow: 1 }} + readOnly={step !== "form"} + /> +
+
+ + + {step !== "form" && ( + + setOtp(e.currentTarget.value)} + withAsterisk + disabled={otpValidated} + style={{ flex: 1 }} + /> + {!otpValidated && + (timerActive ? ( + + Resend OTP will be enabled in{" "} + {String(Math.floor(countdown / 60)).padStart(2, "0")}: + {String(countdown % 60).padStart(2, "0")} + + ) : ( + + ))} + + )} + +
+ + + + + + + + + Note :{" "} + + + Please enter your desired transaction limit. OTP verification is required to + confirm changes. + + +
+ ); +} diff --git a/src/app/_util/otp.ts b/src/app/_util/otp.ts index 7a70463..ac27665 100644 --- a/src/app/_util/otp.ts +++ b/src/app/_util/otp.ts @@ -15,7 +15,7 @@ interface SendOtpPayload { function getStoredMobileNumber(): string { const mobileNumber = localStorage.getItem('remitter_mobile_no'); - // const mobileNumber = "7890544527"; + // const mobileNumber = "6297421727"; if (!mobileNumber) throw new Error('Mobile number not found.'); return mobileNumber; } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 8c4526a..766a5ea 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -46,7 +46,7 @@ export default function Login() { try { await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: mobile }); - // await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: "7890544527" }); + // await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: "6297421727" }); notifications.show({ color: 'orange', title: 'OTP Required', @@ -68,7 +68,7 @@ export default function Login() { try { if (mobile) { await verifyLoginOtp(otp, mobile); - // await verifyLoginOtp(otp, '7890544527'); + // await verifyLoginOtp(otp, '6297421727'); return true; } } @@ -296,7 +296,7 @@ export default function Login() { message: "Internal Server Error, Please try again later", autoClose: 5000, }); - } + } // finally { // // Ensure we always stop loader if still active // setIsLogging(false);