Files
IB/src/app/(main)/funds_transfer/add_beneficiary/addBeneficiaryOthers.tsx
2025-11-04 10:50:41 +05:30

488 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import React, { useEffect, useState } from 'react';
import { TextInput, Button, Grid, Text, PasswordInput, Loader, Group, Select, Stack } from '@mantine/core';
import { notifications } from '@mantine/notifications';
import { useRouter } from "next/navigation";
import { IconRefresh } from '@tabler/icons-react';
import { sendOtp, verifyOtp } from '@/app/_util/otp';
export default function AddBeneficiaryOthers() {
const router = useRouter();
const [authorized, setAuthorized] = useState<boolean | null>(null);
const [loading, setLoading] = useState(false);
const [bankName, setBankName] = useState('');
const [ifsccode, setIfsccode] = useState('');
const [branchName, setBranchName] = useState('');
const [accountNo, setAccountNo] = useState('');
const [confirmAccountNo, setConfirmAccountNo] = useState('');
const [beneficiaryType, setBeneficiaryType] = useState<string | null>(null);
const [nickName, setNickName] = useState('');
const [beneficiaryName, setBeneficiaryName] = useState<string | null>(null);
const [otp, setOtp] = useState('');
const [otpSent, setOtpSent] = useState(false);
const [otpVerified, setOtpVerified] = useState(false);
const [countdown, setCountdown] = useState(180);
const [timerActive, setTimerActive] = useState(false);
const [validationStatus, setValidationStatus] = useState<'success' | 'error' | null>(null);
const [isVisibilityLocked, setIsVisibilityLocked] = useState(false);
const [showPayeeAcc, setShowPayeeAcc] = useState(true);
const getFullMaskedAccount = (acc: string) => { return "X".repeat(acc.length); };
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_ADD', beneficiary: beneficiaryName || undefined, ifsc: ifsccode });
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;
}
}
// Countdown effect
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(() => {
const token = localStorage.getItem("access_token");
if (!token) {
setAuthorized(false);
router.push("/login");
} else {
setAuthorized(true);
}
}, []);
useEffect(() => {
const ifscTrimmed = ifsccode.trim();
if (ifscTrimmed.length === 11) { // Only call API when IFSC is valid length
const fetchIfscDetails = async () => {
try {
const token = localStorage.getItem("access_token");
const response = await fetch(
`/api/beneficiary/ifsc-details?ifscCode=${ifscTrimmed}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "IB",
Authorization: `Bearer ${token}`,
},
}
);
if (!response.ok) {
throw new Error("Failed to fetch IFSC details");
}
const data = await response.json();
if (data && data.bank_name && data.branch_name) {
setBankName(data.bank_name);
setBranchName(data.branch_name);
} else {
setBranchName("No result found");
setBankName("");
}
} catch (error) {
console.error("Error fetching IFSC details:", error);
setBranchName("No result found");
setBankName("");
}
};
fetchIfscDetails();
}
}, [ifsccode]);
const validateAndSendOtp = async () => {
if (!bankName || !ifsccode || !branchName || !accountNo || !confirmAccountNo || !beneficiaryType) {
notifications.show({
withBorder: true,
color: "red",
title: "Missing Field",
message: "All fields must be completed.",
autoClose: 5000,
});
return;
}
const trimmedIfsc = ifsccode.trim().toUpperCase();
const isValidIfscCode = (code: string) => {
return /^[A-Z]{4}0[0-9]{6}$/.test(code);
};
if (!isValidIfscCode(trimmedIfsc)) {
notifications.show({
title: "Invalid IFSC Code",
message: "Must be 11 characters: 4 uppercase letters, 0, then 6 digits (e.g., HDFC0123456)",
color: "red",
});
return;
}
if (accountNo.length < 10 || accountNo.length > 17) {
notifications.show({
withBorder: true,
color: "red",
title: "Invalid Account Number",
message: "Enter a valid account number (1017 digits).",
autoClose: 5000,
});
return;
}
if (accountNo !== confirmAccountNo) {
notifications.show({
withBorder: true,
color: "red",
title: "Mismatch",
message: "Account numbers do not match.",
autoClose: 5000,
});
return;
}
// Validation for now
try {
setLoading(true);
const token = localStorage.getItem("access_token");
const response = await fetch(`/api/beneficiary/validate/outside-bank?accountNo=${accountNo}&ifscCode=${ifsccode}&remitterName=""`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "IB",
Authorization: `Bearer ${token}`,
},
}
);
const data = await response.json();
if (response.ok && data?.name) {
setBeneficiaryName(data.name);
setValidationStatus("success");
setIsVisibilityLocked(true);
setOtpSent(true);
const otp = await handleSendOtp();
notifications.show({
withBorder: true,
color: "green",
title: "OTP Sent",
message: "OTP has been sent to your registered mobile number.",
autoClose: 5000,
});
} else {
setBeneficiaryName("Invalid beneficiary account number");
setValidationStatus("error");
setAccountNo("");
setConfirmAccountNo("");
}
} catch (error) {
console.error("Validation error:", error);
setBeneficiaryName("Something went wrong");
setValidationStatus("error");
}
finally {
setLoading(false);
}
};
const verify_otp = async () => {
if (!otp) {
notifications.show({
withBorder: true,
color: "Red",
title: "Null Field",
message: "Please Enter Valid OTP",
autoClose: 5000,
});
return;
}
const verified = await handleVerifyOtp();
if (!verified) {
notifications.show({
title: "Invalid OTP",
message: "The OTP entered does not match",
color: "red",
});
return;
}
setOtpVerified(true);
notifications.show({
title: "OTP Verified",
message: "OTP successfully verified.",
color: "green",
});
};
const AddBen = async () => {
try {
const token = localStorage.getItem("access_token");
const response = await fetch(`/api/beneficiary`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "IB",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
accountNo: accountNo,
ifscCode: ifsccode,
accountType: beneficiaryType,
name: beneficiaryName
}),
}
);
const data = await response.json();
if (response.ok) {
notifications.show({
withBorder: true,
color: "green",
title: "Added",
message: "Beneficiary Added successfully",
autoClose: 5000,
});
return;
}
else {
notifications.show({
withBorder: true,
color: "Red",
title: "Error",
message: data?.error,
autoClose: 5000,
});
return;
}
}
catch {
notifications.show({
title: "Error",
message: "Something went wrong",
color: "red",
});
}
finally {
setBankName('');
setBranchName('');
setIfsccode('');
setAccountNo('');
setConfirmAccountNo('');
setBeneficiaryName('');
setNickName('');
setBeneficiaryType(null);
setIsVisibilityLocked(false);
setOtpSent(false);
}
};
if (!authorized) return null;
return (
<Grid gutter="md" style={{ padding: '5px' }}>
<Grid.Col span={4}>
<TextInput
label="IFSC Code"
placeholder="e.g.,ABCD0123456"
value={ifsccode}
onChange={(e) => {
let val = e.currentTarget.value.toUpperCase();
val = val.replace(/[^A-Z0-9]/g, ""); // block special chars
setIfsccode(val);
}}
maxLength={11}
withAsterisk
/>
</Grid.Col>
<Grid.Col span={4}>
<TextInput
label="Bank Name"
value={bankName}
disabled
onChange={(e) => {
let val = e.currentTarget.value.replace(/[^A-Za-z ]/g, "");
setBankName(val.slice(0, 100));
}}
withAsterisk
/>
</Grid.Col>
<Grid.Col span={4}>
<TextInput
label="Branch Name"
value={branchName}
disabled
maxLength={100}
withAsterisk
/>
</Grid.Col>
<Grid.Col span={4}>
<TextInput
label="Beneficiary Account Number"
placeholder="Enter account number"
value={showPayeeAcc ? accountNo : getFullMaskedAccount(accountNo)}
onChange={(e) => {
const value = e.currentTarget.value;
if (/^\d*$/.test(value) && value.length <= 17) {
setAccountNo(value);
setShowPayeeAcc(true);
}
}}
onBlur={() => setShowPayeeAcc(false)}
onFocus={() => setShowPayeeAcc(true)}
readOnly={isVisibilityLocked}
maxLength={17}
withAsterisk
/>
</Grid.Col>
<Grid.Col span={4}>
<TextInput
label="Confirm Beneficiary Account Number"
placeholder="Re-enter account number"
value={confirmAccountNo}
onChange={(e) => {
const value = e.currentTarget.value;
if (/^\d*$/.test(value) && value.length <= 17) {
setConfirmAccountNo(value);
}
}}
maxLength={17}
readOnly={isVisibilityLocked}
withAsterisk
onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()}
/>
{validationStatus === "error" && (
<Text c="red" size="sm" style={{ marginLeft: '1rem', whiteSpace: 'nowrap' }}>
{beneficiaryName}
</Text>
)}
</Grid.Col>
<Grid.Col span={4}>
<Select
label="Beneficiary Account Type"
placeholder="Select type"
data={["Savings", "Current"]}
value={beneficiaryType}
onChange={setBeneficiaryType}
readOnly={isVisibilityLocked}
withAsterisk
/>
</Grid.Col>
<Grid.Col span={4}>
<TextInput
label="Beneficiary Name"
value={validationStatus === "success" && beneficiaryName ? beneficiaryName : ""}
disabled
maxLength={100}
required
/>
</Grid.Col>
<Grid.Col span={4}>
<TextInput
label="Nick Name (Optional)"
placeholder="Enter nickname (optional)"
value={nickName}
onChange={(e) => setNickName(e.currentTarget.value)}
/>
</Grid.Col>
{!otpSent && (
<Grid.Col >
<Group gap="sm">
<Button onClick={validateAndSendOtp} disabled={loading}>Validate</Button>
{loading && (
<>
<Loader size='sm' type="bars"></Loader>
<Text>Looking for Beneficiary Name</Text>
</>
)}
</Group>
</Grid.Col>
)}
{otpSent && (
<>
<Grid.Col span={3}>
<PasswordInput
label="Enter OTP"
placeholder="Enter OTP"
value={otp}
onChange={(e) => setOtp(e.currentTarget.value)}
maxLength={6}
disabled={otpVerified}
withAsterisk
style={{ flex: 1 }}
/>
{!otpVerified && (
timerActive ? (
<Text size="xs" c="dimmed" style={{ minWidth: "120px" }}>
Resend OTP will be enabled in{" "}
{String(Math.floor(countdown / 60)).padStart(2, "0")}:
{String(countdown % 60).padStart(2, "0")}
</Text>
) : (
<IconRefresh
size={22}
style={{ cursor: "pointer", color: "blue", marginBottom: "6px" }}
onClick={handleSendOtp}
/>
)
)}
</Grid.Col >
<Grid.Col >
{!otpVerified ? (
<Button onClick={verify_otp}>Validate OTP</Button>
) : (
<Button color="blue" size="sm" onClick={AddBen}>
Add
</Button>
)}
</Grid.Col>
</>
)}
</Grid>
);
};