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
This commit is contained in:
2025-07-21 17:57:16 +05:30
parent d997c961ed
commit e621f10824
7 changed files with 1390 additions and 98 deletions

View File

@@ -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<boolean | null>(null);
const [accountData, setAccountData] = useState<accountData[]>([]);
const [selectedAccNo, setSelectedAccNo] = useState<string | null>(null);
const [beneficiaryAcc, setBeneficiaryAcc] = useState("");
const [showPayeeAcc, setShowPayeeAcc] = useState(true);
const [confirmBeneficiaryAcc, setConfirmBeneficiaryAcc] = useState("");
const [paymentMode, setPaymentMode] = useState('IMPS');
const [beneficiaryType, setBeneficiaryType] = useState<string | null>(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<string | null>(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 (
<>
<Modal
opened={showConfirmModel}
onClose={() => setConfirmModel(false)}
// title="Confirm Transaction"
centered
>
<Stack>
<Title order={4}>Confirm Transaction</Title>
<Text><strong>Debit Account:</strong> {selectedAccNo}</Text>
<Text><strong>Payee Account:</strong> {beneficiaryAcc}</Text>
<Text><strong>Payee Name:</strong> {beneficiaryName}</Text>
<Text><strong>Amount:</strong> {amount}</Text>
<Text><strong>Remarks:</strong> {remarks}</Text>
</Stack>
<Group justify="flex-end" mt="md">
<Button variant="default" onClick={() => setConfirmModel(false)}>Cancel</Button>
<Button
color="blue"
onClick={async () => {
setConfirmModel(false);
const otp = await handleGenerateOtp();
setShowOtpField(true);
notifications.show({
title: "OTP Sent",
message: `Check your registered device for OTP`,
color: "green",
autoClose: 5000,
});
}}
>
Confirm
</Button>
</Group>
</Modal>
{/* main content */}
<div style={{ maxHeight: "320px", overflowY: "auto" }}>
<Stack gap="xs">
<Group grow>
<Select
label="Select Debit Account Number"
placeholder="Choose account number"
data={accountOptions}
value={selectedAccNo}
onChange={setSelectedAccNo}
withAsterisk
readOnly={isVisibilityLocked}
/>
<TextInput
label="Payee Account No"
value={showPayeeAcc ? beneficiaryAcc : getFullMaskedAccount(beneficiaryAcc)}
onChange={(e) => {
const value = e.currentTarget.value;
if (/^\d*$/.test(value)) {
setBeneficiaryAcc(value);
setShowPayeeAcc(true);
}
}}
onBlur={() => setShowPayeeAcc(false)}
onFocus={() => setShowPayeeAcc(true)}
withAsterisk
readOnly={isVisibilityLocked}
/>
<TextInput
label="Confirm Payee Account No"
value={confirmBeneficiaryAcc}
onChange={(e) => {
const value = e.currentTarget.value;
if (/^\d*$/.test(value)) {
setConfirmBeneficiaryAcc(value);
}
}}
// onCopy={(e) => e.preventDefault()}
// onPaste={(e) => e.preventDefault()}
// onCut={(e) => e.preventDefault()}
withAsterisk
readOnly={isVisibilityLocked}
/>
<TextInput
label="Payee Name"
value={validationStatus === "success" && beneficiaryName ? beneficiaryName : ""}
disabled
readOnly
withAsterisk
/>
</Group>
<Group justify="space-between" >
<Text size="xs" c="green" style={{ visibility: selectedAccount ? "visible" : "hidden" }}>Available Balance :
{selectedAccount ? selectedAccount.stAvailableBalance : 0}
</Text>
<Group justify="center">
{validationStatus === "error" && <Text size="sm" fw={700} ta="right" c="red">{beneficiaryName}</Text>}
</Group>
</Group>
<Group grow>
<Radio.Group
name="payment-mode"
label="Select Payment Method"
value={paymentMode}
onChange={setPaymentMode}
withAsterisk
>
<Group>
<Radio value="IMPS" label="IMPS" />
<Radio value="RTGS" label="RTGS" />
<Radio value="NEFT" label="NEFT" />
</Group>
</Radio.Group>
<Select
label="Payee Account Type"
placeholder="Select type"
data={["Savings", "Current"]}
value={beneficiaryType}
onChange={setBeneficiaryType}
withAsterisk
readOnly={showOtpField}
/>
<TextInput
label="Amount"
type="number"
value={amount}
onChange={(e) => setAmount(e.currentTarget.value)}
error={
selectedAccount && Number(amount) > Number(selectedAccount.stAvailableBalance) ?
"Amount exceeds available balance" : false}
withAsterisk
readOnly={showOtpField}
/>
<TextInput
label="Remarks"
placeholder="Enter remarks"
value={remarks}
onChange={(e) => setRemarks(e.currentTarget.value)}
withAsterisk
readOnly={showOtpField}
/>
</Group>
<Group grow>
{showOtpField && (
<TextInput
label="OTP"
placeholder="Enter OTP"
type="otp"
value={otp}
onChange={(e) => setOtp(e.currentTarget.value)}
withAsterisk
disabled={showTxnPassword}
/>
)}
{showTxnPassword && (
<TextInput
label="Transaction Password"
placeholder="Enter transaction password"
type="password"
value={txnPassword}
onChange={(e) => setTxnPassword(e.currentTarget.value)}
withAsterisk
/>
)}
</Group>
<Group justify="flex-start">
<Button variant="filled" color="blue" onClick={handleValidate} disabled={validationStatus === "success"}>
Validate
</Button>
<Button
variant="filled"
color="blue"
onClick={handleProceed}
loading={isSubmitting}
disabled={validationStatus !== "success"}
>
{!showTxnPassword && showOtpField ? "Validate the OTP" : showTxnPassword ? "Proceed to Pay" : "Proceed"}
</Button>
</Group>
</Stack>
</div>
</>
);
}

View File

@@ -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() {
<TextInput
label="Payee Name"
value={validationStatus === "success" && beneficiaryName ? beneficiaryName : ""}
// disabled
disabled
readOnly
withAsterisk
/>
<Select
label="Beneficiary A/c Type"
label="Payee Account Type"
placeholder="Select type"
data={["Savings", "Current"]}
value={beneficiaryType}
@@ -445,9 +446,9 @@ export default function QuickPay() {
</Stack>
</div>
) : (
<Text size="lg" mt="md">
hii
</Text>
<div>
<OutsideQuickPay />
</div>
)}
</Paper>
</>

View File

@@ -0,0 +1,415 @@
"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 SendToBeneficiaryOthers from "./sendBeneficiaryOthers";
interface accountData {
stAccountNo: string;
stAccountType: string;
stAvailableBalance: string;
custname: string;
}
export const MockBeneficiaryData =
[
{
'stBankName': 'Kangra Central Co-operative Bank',
'stBenAccountNo': '50077736845',
'stBenName': 'RAJAT MAHARANA',
},
{
'stBankName': 'Kangra Central Co-operative Bank',
'stBenAccountNo': '50077742351',
'stBenName': 'RAJAT MAHARANA',
},
{
'stBankName': 'State Bank of India',
'stBenAccountNo': '50077742361',
'stIFSC': 'SBIN0004567',
'stBenName': 'Sachin Sharma',
},
{
'stBankName': 'ICICI',
'stBenAccountNo': '90088842361',
'stIFSC': 'ICICI0004567',
'stBenName': 'Eshika Paul',
},
]
export default function SendToBeneficiaryOwn() {
const router = useRouter();
const [bankType, setBankType] = useState("own");
const [authorized, setAuthorized] = useState<boolean | null>(null);
const [accountData, setAccountData] = useState<accountData[]>([]);
const [selectedAccNo, setSelectedAccNo] = useState<string | null>(null);
const [beneficiaryAcc, setBeneficiaryAcc] = useState<string | null>(null);
const [beneficiaryName, setBeneficiaryName] = useState<string | null>(null);
const [beneficiaryType, setBeneficiaryType] = useState<string | null>(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 [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) =>
bankType === 'own' ? ben_acc.stBankName === 'Kangra Central Co-operative Bank' : true)
.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);
}
else {
setBeneficiaryAcc('');
setBeneficiaryName('');
}
}
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 || !beneficiaryType || !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");
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('');
setBeneficiaryType(null);
setAmount('');
setRemarks('');
setIsVisibilityLocked(false);
setIsSubmitting(false);
setShowTxnPassword(false);
setShowOtpField(false);
}
};
if (!authorized) return null;
return (
<>
<Modal
opened={showConfirmModel}
onClose={() => setConfirmModel(false)}
// title="Confirm Transaction"
centered
>
<Stack>
<Title order={4}>Confirm Transaction</Title>
<Text><strong>Debit Account:</strong> {selectedAccNo}</Text>
<Text><strong>Beneficiary Account:</strong> {beneficiaryAcc}</Text>
<Text><strong>Beneficiary Name:</strong> {beneficiaryName}</Text>
<Text><strong>Beneficiary Account Type:</strong> {beneficiaryType}</Text>
<Text><strong>Amount:</strong> {amount}</Text>
<Text><strong>Remarks:</strong> {remarks}</Text>
</Stack>
<Group justify="flex-end" mt="md">
<Button variant="default" onClick={() => setConfirmModel(false)}>Cancel</Button>
<Button
color="blue"
onClick={async () => {
setConfirmModel(false);
const otp = await handleGenerateOtp();
setShowOtpField(true);
notifications.show({
title: "OTP Sent",
message: `Check your registered device for OTP`,
color: "green",
autoClose: 5000,
});
}}
>
Confirm
</Button>
</Group>
</Modal>
{/* main content */}
<Paper shadow="sm" radius="md" p="md" withBorder h={400}>
<Title order={3} mb="md">
Send To Beneficiary
</Title>
<Radio.Group value={bankType} onChange={setBankType} name="bankType" withAsterisk mb="md">
<Group justify="center">
<Radio value="own" label="Own Bank" />
<Radio value="outside" label="Outside Bank" />
</Group>
</Radio.Group>
{bankType === "own" ? (
<div style={{ maxHeight: "320px", overflowY: "auto" }}>
<Stack gap="xs">
<Group grow>
<Select
label="Select Debit Account Number"
placeholder="Choose account number"
data={accountOptions}
value={selectedAccNo}
onChange={setSelectedAccNo}
withAsterisk
readOnly={isVisibilityLocked}
/>
<Select
label="Beneficiary Account No"
placeholder="Choose beneficiary account number"
data={benAccountOption}
value={beneficiaryAcc}
onChange={handleBeneficiary}
withAsterisk
readOnly={isVisibilityLocked}
nothingFoundMessage='No account found'
/>
<TextInput
label="Beneficiary Name"
value={beneficiaryName ? beneficiaryName : ""}
disabled
readOnly
withAsterisk
/>
</Group>
<Group justify="space-between" >
<Text size="xs" c="green" style={{ visibility: selectedAccount ? "visible" : "hidden" }}>Available Balance :
{selectedAccount ? selectedAccount.stAvailableBalance : 0}
</Text>
</Group>
<Group grow>
<Select
label="Beneficiary Account Type"
placeholder="Select type"
data={["Savings", "Current"]}
value={beneficiaryType}
onChange={setBeneficiaryType}
withAsterisk
readOnly={showOtpField}
/>
<TextInput
label="Amount"
type="number"
value={amount}
onChange={(e) => setAmount(e.currentTarget.value)}
error={
selectedAccount && Number(amount) > Number(selectedAccount.stAvailableBalance) ?
"Amount exceeds available balance" : false}
withAsterisk
readOnly={showOtpField}
/>
<TextInput
label="Remarks"
placeholder="Enter remarks"
value={remarks}
onChange={(e) => setRemarks(e.currentTarget.value)}
withAsterisk
readOnly={showOtpField}
/>
</Group>
<Group grow>
{showOtpField && (
<TextInput
label="OTP"
placeholder="Enter OTP"
type="otp"
value={otp}
onChange={(e) => setOtp(e.currentTarget.value)}
withAsterisk
disabled={showTxnPassword}
/>
)}
{showTxnPassword && (
<TextInput
label="Transaction Password"
placeholder="Enter transaction password"
type="password"
value={txnPassword}
onChange={(e) => setTxnPassword(e.currentTarget.value)}
withAsterisk
/>
)}
</Group>
<Group justify="flex-start">
<Button
variant="filled"
color="blue"
onClick={handleProceed}
loading={isSubmitting}
>
{!showTxnPassword && showOtpField ? "Validate the OTP" : showTxnPassword ? "Proceed to Pay" : "Proceed"}
</Button>
</Group>
</Stack>
</div>
) : (
<div>
<SendToBeneficiaryOthers/>
</div>
)}
</Paper>
</>
);
}

View File

@@ -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<boolean | null>(null);
const [accountData, setAccountData] = useState<accountData[]>([]);
const [selectedAccNo, setSelectedAccNo] = useState<string | null>(null);
const [beneficiaryAcc, setBeneficiaryAcc] = useState<string | null>(null);
const [beneficiaryName, setBeneficiaryName] = useState<string | null>(null);
const [beneficiaryIFSC, setBeneficiaryIFSC] = useState<string | null>(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 (
<>
<Modal
opened={showConfirmModel}
onClose={() => setConfirmModel(false)}
// title="Confirm Transaction"
centered
>
<Stack>
<Title order={4}>Confirm Transaction</Title>
<Text><strong>Debit Account:</strong> {selectedAccNo}</Text>
<Text><strong>Beneficiary Account:</strong> {beneficiaryAcc}</Text>
<Text><strong>Beneficiary Name:</strong> {beneficiaryName}</Text>
<Text><strong>Payment Type:</strong> {paymentMode}</Text>
<Text><strong>Amount:</strong> {amount}</Text>
<Text><strong>Remarks:</strong> {remarks}</Text>
</Stack>
<Group justify="flex-end" mt="md">
<Button variant="default" onClick={() => setConfirmModel(false)}>Cancel</Button>
<Button
color="blue"
onClick={async () => {
setConfirmModel(false);
const otp = await handleGenerateOtp();
setShowOtpField(true);
notifications.show({
title: "OTP Sent",
message: `Check your registered device for OTP`,
color: "green",
autoClose: 5000,
});
}}
>
Confirm
</Button>
</Group>
</Modal>
{/* main content */}
<div style={{ maxHeight: "320px", overflowY: "auto" }}>
<Stack gap="xs">
<Group grow>
<Select
label="Select Debit Account Number"
placeholder="Choose account number"
data={accountOptions}
value={selectedAccNo}
onChange={setSelectedAccNo}
withAsterisk
readOnly={isVisibilityLocked}
/>
<Select
label="Beneficiary Account No"
placeholder="Choose beneficiary account number"
data={benAccountOption}
value={beneficiaryAcc}
onChange={handleBeneficiary}
withAsterisk
readOnly={isVisibilityLocked}
nothingFoundMessage='No account found'
/>
<TextInput
label="Beneficiary Name"
value={beneficiaryName ? beneficiaryName : ""}
disabled
readOnly
withAsterisk
/>
</Group>
<Group justify="space-between" >
<Text size="xs" c="green" style={{ visibility: selectedAccount ? "visible" : "hidden" }}>Available Balance :
{selectedAccount ? selectedAccount.stAvailableBalance : 0}
</Text>
</Group>
<Group grow>
<TextInput
label="Beneficiary IFSC Code"
value={beneficiaryIFSC ? beneficiaryIFSC : ""}
disabled
readOnly
withAsterisk
/>
<Radio.Group
name="payment-mode"
label="Select Payment Method"
value={paymentMode}
onChange={setPaymentMode}
withAsterisk
readOnly={isVisibilityLocked}
>
<Group>
<Radio value="IMPS" label="IMPS" />
<Radio value="RTGS" label="RTGS" />
<Radio value="NEFT" label="NEFT" />
</Group>
</Radio.Group>
<TextInput
label="Amount"
type="number"
value={amount}
onChange={(e) => setAmount(e.currentTarget.value)}
error={
selectedAccount && Number(amount) > Number(selectedAccount.stAvailableBalance) ?
"Amount exceeds available balance" : false}
withAsterisk
readOnly={showOtpField}
/>
<TextInput
label="Remarks"
placeholder="Enter remarks"
value={remarks}
onChange={(e) => setRemarks(e.currentTarget.value)}
withAsterisk
readOnly={showOtpField}
/>
</Group>
<Group grow>
{showOtpField && (
<TextInput
label="OTP"
placeholder="Enter OTP"
type="otp"
value={otp}
onChange={(e) => setOtp(e.currentTarget.value)}
withAsterisk
disabled={showTxnPassword}
/>
)}
{showTxnPassword && (
<TextInput
label="Transaction Password"
placeholder="Enter transaction password"
type="password"
value={txnPassword}
onChange={(e) => setTxnPassword(e.currentTarget.value)}
withAsterisk
/>
)}
</Group>
<Group justify="flex-start">
<Button
variant="filled"
color="blue"
onClick={handleProceed}
loading={isSubmitting}
>
{!showTxnPassword && showOtpField ? "Validate the OTP" : showTxnPassword ? "Proceed to Pay" : "Proceed"}
</Button>
</Group>
</Stack>
</div>
</>
);
}

View File

@@ -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<boolean | null>(null);
useEffect(() => {
const token = localStorage.getItem("access_token");
if (!token) {
setAuthorized(false);
router.push("/login");
} else {
setAuthorized(true);
}
}, []);
if (!authorized) return null;
return (
<Paper shadow="sm" radius="md" p="md" withBorder h={400}>
<Text>View beneficiary feature coming soon.</Text>
</Paper>
);
}

View File

@@ -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() {
<Providers>
<div>
<Title order={4} style={{ padding: "10px" }}>Accounts Overview</Title>
<Group
style={{ flex: 1, padding: "10px 10px 4px 10px", marginLeft: '10px', display: "flex", alignItems: "center", justifyContent: "left", height: "1vh" }}>
<Group style={{ flex: 1, padding: "10px 10px 4px 10px", marginLeft: '10px', display: "flex", alignItems: "center", justifyContent: "left", height: "1vh" }}>
<IconEye size={20} />
<Text fw={700} style={{ fontFamily: "inter", fontSize: '17px' }}>Show Balance </Text>
<Switch size="md" onLabel="ON" offLabel="OFF" checked={showBalance}
onChange={(event) => setShowBalance(event.currentTarget.checked)} />
</Group>
<div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "flex-start", marginLeft: '15px' }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "flex-start", marginLeft: '15px' }}>
<Group grow gap="xl">
<Paper p="md" radius="md" style={{ backgroundColor: '#c1e0f0', width: 350 }}>
{/* Deposit Account Card */}
<Paper p="md" radius="md" style={{
backgroundColor: '#c1e0f0', width: 350, height: 195, display: 'flex',
flexDirection: 'column', justifyContent: 'space-between'
}}>
<Group gap='xs'>
<IconBuildingBank size={25} />
<Text fw={700}>Deposit Account</Text>
<Select
// placeholder="Select A/C No"
data={depositAccounts.map(acc => ({
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
}
}}
/>
{depositAccounts.length > 0 ? (
<Select
data={depositAccounts.map(acc => ({
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
}
}}
/>
) : (
<Text c="dimmed" size="sm" ml="sm">No deposit account available</Text>
)}
</Group>
<Text c="dimmed">{Number(selectedDAData?.stAccountNo || 0)}</Text>
<Title order={2} mt="md">
{showBalance ? `${Number(selectedDAData?.stAvailableBalance || 0).toLocaleString('en-IN')}` : "****"}
</Title>
<Button fullWidth mt="xs" onClick={() => handleGetAccountStatement(selectedDA)}>Get Statement</Button>
{depositAccounts.length > 0 ? (
<>
<Text c="dimmed">{Number(selectedDAData?.stAccountNo || 0)}</Text>
<Title order={2} mt="md">
{showBalance ? `${Number(selectedDAData?.stAvailableBalance || 0).toLocaleString('en-IN')}` : "****"}
</Title>
<Button fullWidth mt="xs" onClick={() => handleGetAccountStatement(selectedDA)}>Get Statement</Button>
</>
) : (
<>
<Text c="dimmed" mt="md">Apply for a deposit account to get started</Text>
<Button fullWidth mt="xs">Apply Now</Button>
</>
)}
</Paper>
<Paper p="md" radius="md" style={{ backgroundColor: '#c1e0f0', width: 350 }}>
{/* Loan Account Card */}
<Paper p="md" radius="md" style={{
backgroundColor: '#c1e0f0', width: 350, height: 195, display: 'flex',
flexDirection: 'column', justifyContent: 'space-between'
}}
>
<Group gap='xs'>
<IconBuildingBank size={25} />
<Text fw={700}>Loan Account</Text>
<Select
placeholder="Select A/C No"
data={loanAccounts.map(acc => ({
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
}
}}
/>
{loanAccounts.length > 0 ? (
<Select
data={loanAccounts.map(acc => ({
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
}
}}
/>
) : (
<Text c="dimmed" size="sm" ml="sm">No loan account available</Text>
)}
</Group>
<Text c="dimmed">{Number(selectedLNData?.stAccountNo || 0)}</Text>
<Title order={2} mt="md">
{showBalance ? `${Number(selectedLNData?.stAvailableBalance || 0).toLocaleString('en-IN')}` : "****"}
</Title>
<Button fullWidth mt="xs" onClick={() => handleGetAccountStatement(selectedLN)}>Get Statement</Button>
{loanAccounts.length > 0 ? (
<>
<Text c="dimmed">{Number(selectedLNData?.stAccountNo || 0)}</Text>
<Title order={2} mt="md">
{showBalance ? `${Number(selectedLNData?.stAvailableBalance || 0).toLocaleString('en-IN')}` : "****"}
</Title>
<Button fullWidth mt="xs" onClick={() => handleGetAccountStatement(selectedLN)}>Get Statement</Button>
</>
) : (
<>
<Text c="dimmed" mt="md">Apply for a loan account to get started</Text>
<Button fullWidth mt="xs">Apply Now</Button>
</>
)}
</Paper>
{/* Important Links Card */}
<Paper p="md" radius="md" style={{ width: 300, backgroundColor: '#FFFFFF', marginLeft: '130px', border: '1px solid grey' }}>
<Title order={5} mb="sm">Important Links</Title>
{/* <Title order={5} mb="sm" style={{ textAlign: 'center' }}>Loan EMI Calculator</Title> */}
<Stack gap="xs">
<Button variant="light" color="blue" fullWidth>Loan EMI Calculator</Button>
<Button variant="light" color="blue" fullWidth>Branch Locator</Button>
<Button variant="light" color="blue" fullWidth>Customer Care</Button>
<Button variant="light" color="blue" fullWidth>FAQs</Button>
</Stack>
{/* <Group>
<TextInput
label="Loan Amount"
placeholder=""
/>
<TextInput
label="Interest Rate(%)"
placeholder=""
/>
<TextInput
label="Loan Tenure(Years)"
placeholder=""
/>
<Button fullWidth style={{textAlign:'center'}}>Calculate</Button>
</Group> */}
</Paper>
</Group>
</div>
<div
style={{
flex: 1,
marginTop: 0,
padding: "20px",
display: "flex",
// alignItems: "center",
justifyContent: "left",
}}
>
{/* Send Money Section */}
<div style={{ padding: "20px", display: "flex", justifyContent: "left" }}>
<Box>
<Title order={4}>Send Money</Title>
<Group mt="sm">
@@ -217,4 +227,6 @@ export default function Home() {
</Providers>
);
}
return null;
}

View File

@@ -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&apos;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.
</Text>
<style>