Wip: update the login page,

feat: update Otp in set login and transaction page.
Feat: Add validation for external account in add beneficiary.
This commit is contained in:
2025-08-14 17:05:21 +05:30
parent c9181881e0
commit 78426747e5
20 changed files with 687 additions and 474 deletions

View File

@@ -37,8 +37,8 @@ export default function Layout({ children }: { children: React.ReactNode }) {
}} }}
> >
<Stack style={{ background: '#228be6', height: '10%', alignItems: 'center' }}> <Stack style={{ background: '#228be6', height: '10%', alignItems: 'center' }}>
<Text fw={700} fs="italic" c='white' style={{ textAlign: 'center', marginTop: '10px' }}> <Text fw={700} c='white' style={{ textAlign: 'center', marginTop: '10px' }}>
Accounts My Accounts
</Text> </Text>
</Stack> </Stack>

View File

@@ -1,19 +1,9 @@
"use client"; "use client";
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { import { TextInput, Button, Grid, Text, PasswordInput } from '@mantine/core';
TextInput,
Button,
Select,
Title,
Paper,
Grid,
Group,
Radio,
Text,
PasswordInput,
} from '@mantine/core';
import { notifications } from '@mantine/notifications'; import { notifications } from '@mantine/notifications';
import { useRouter } from "next/navigation";
const MockOthersAccountValidation = const MockOthersAccountValidation =
[ [
@@ -31,8 +21,9 @@ const MockOthersAccountValidation =
}, },
] ]
export default function AddBeneficiaryOthers() {
const AddBeneficiaryOthers: React.FC = () => { const router = useRouter();
const [authorized, setAuthorized] = useState<boolean | null>(null);
const [bankName, setBankName] = useState(''); const [bankName, setBankName] = useState('');
const [ifsccode, setIfsccode] = useState(''); const [ifsccode, setIfsccode] = useState('');
const [branchName, setBranchName] = useState(''); const [branchName, setBranchName] = useState('');
@@ -40,24 +31,28 @@ const AddBeneficiaryOthers: React.FC = () => {
const [confirmAccountNo, setConfirmAccountNo] = useState(''); const [confirmAccountNo, setConfirmAccountNo] = useState('');
const [nickName, setNickName] = useState(''); const [nickName, setNickName] = useState('');
const [beneficiaryName, setBeneficiaryName] = useState<string | null>(null); const [beneficiaryName, setBeneficiaryName] = useState<string | null>(null);
const [otp, setOtp] = useState(''); const [otp, setOtp] = useState('');
const [generatedOtp, setGeneratedOtp] = useState(''); const [generatedOtp, setGeneratedOtp] = useState('');
const [otpSent, setOtpSent] = useState(false); const [otpSent, setOtpSent] = useState(false);
const [otpVerified, setOtpVerified] = useState(false); const [otpVerified, setOtpVerified] = useState(false);
const [validationStatus, setValidationStatus] = useState<'success' | 'error' | null>(null); const [validationStatus, setValidationStatus] = useState<'success' | 'error' | null>(null);
const [isVisibilityLocked, setIsVisibilityLocked] = useState(false); const [isVisibilityLocked, setIsVisibilityLocked] = useState(false);
const [showPayeeAcc, setShowPayeeAcc] = useState(true); const [showPayeeAcc, setShowPayeeAcc] = useState(true);
const getFullMaskedAccount = (acc: string) => { return "X".repeat(acc.length); }; const getFullMaskedAccount = (acc: string) => { return "X".repeat(acc.length); };
useEffect(() => {
const token = localStorage.getItem("access_token");
if (!token) {
setAuthorized(false);
router.push("/login");
} else {
setAuthorized(true);
}
}, []);
useEffect(() => { useEffect(() => {
const ifscTrimmed = ifsccode.trim(); const ifscTrimmed = ifsccode.trim();
if (ifscTrimmed.length === 11) { // Only call API when IFSC is valid length if (ifscTrimmed.length === 11) { // Only call API when IFSC is valid length
const fetchIfscDetails = async () => { const fetchIfscDetails = async () => {
try { try {
@@ -115,15 +110,6 @@ const AddBeneficiaryOthers: React.FC = () => {
return; return;
} }
// if ( !/^[A-Za-z ]{1,5}$/.test(bankName)) {
// notifications.show({
// color: "red",
// title: "Invalid Bank Name",
// message: "Use only alphabets/spaces, max 100 characters.",
// });
// return;
// }
const trimmedIfsc = ifsccode.trim().toUpperCase(); const trimmedIfsc = ifsccode.trim().toUpperCase();
const isValidIfscCode = (code: string) => { const isValidIfscCode = (code: string) => {
return /^[A-Z]{4}0[0-9]{6}$/.test(code); return /^[A-Z]{4}0[0-9]{6}$/.test(code);
@@ -161,12 +147,25 @@ const AddBeneficiaryOthers: React.FC = () => {
} }
// Validation for now // Validation for now
try { try {
const matched = MockOthersAccountValidation.find( // const matched = MockOthersAccountValidation.find(
(entry) => entry.stBenAccountNo === accountNo // (entry) => entry.stBenAccountNo === accountNo
); // );
if (matched) { const token = localStorage.getItem("access_token");
setBeneficiaryName(matched.stBenName); const response = await fetch(
`http://localhost:8080/api/beneficiary/validate/outside-bank?accountNo=${accountNo}&ifscCode=${ifsccode}&remitterName=""`,
{
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"); setValidationStatus("success");
setIsVisibilityLocked(true); setIsVisibilityLocked(true);
setOtpSent(true); setOtpSent(true);
@@ -190,55 +189,6 @@ const AddBeneficiaryOthers: React.FC = () => {
setBeneficiaryName("Something went wrong"); setBeneficiaryName("Something went wrong");
setValidationStatus("error"); setValidationStatus("error");
} }
// Later need to add api
// try {
// // API will be changed
// const token = localStorage.getItem("access_token");
// const response = await fetch(`/api/beneficiary/validate/within-bank?accountNumber=${accountNo}`, {
// 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);
// setOtpSent(true);
// await handleGenerateOtp();
// 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) {
// setBeneficiaryName("Invalid beneficiary account number");
// setValidationStatus("error");
// notifications.show({
// withBorder: true,
// color: "red",
// title: "Error",
// message: "Could not validate account number.",
// autoClose: 5000,
// });
// }
}; };
const verifyOtp = () => { const verifyOtp = () => {
@@ -271,6 +221,7 @@ const AddBeneficiaryOthers: React.FC = () => {
}); });
}; };
if (!authorized) return null;
return ( return (
<Grid gutter="md"> <Grid gutter="md">
@@ -278,9 +229,7 @@ const AddBeneficiaryOthers: React.FC = () => {
<TextInput <TextInput
label="IFSC Code" label="IFSC Code"
placeholder="e.g.,ABCD0123456" placeholder="e.g.,ABCD0123456"
//data={bankOptions}
value={ifsccode} value={ifsccode}
// onChange={(e) => setIfsccode(e.currentTarget.value)}
onChange={(e) => { onChange={(e) => {
let val = e.currentTarget.value.toUpperCase(); let val = e.currentTarget.value.toUpperCase();
val = val.replace(/[^A-Z0-9]/g, ""); // block special chars val = val.replace(/[^A-Z0-9]/g, ""); // block special chars
@@ -305,8 +254,6 @@ const AddBeneficiaryOthers: React.FC = () => {
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={4}> <Grid.Col span={4}>
<TextInput <TextInput
label="Branch Name" label="Branch Name"
@@ -422,4 +369,4 @@ const AddBeneficiaryOthers: React.FC = () => {
); );
}; };
export default AddBeneficiaryOthers;

View File

@@ -1,27 +1,20 @@
"use client"; "use client";
import React, { useState } from 'react'; import React, { useEffect, useState } from 'react';
import { import {TextInput,Button,Select,Title,Paper,Grid,Group,Radio,Text,PasswordInput} from '@mantine/core';
TextInput, import { useRouter } from "next/navigation";
Button,
Select,
Title,
Paper,
Grid,
Group,
Radio,
Text,
PasswordInput,
} from '@mantine/core';
import { notifications } from '@mantine/notifications'; import { notifications } from '@mantine/notifications';
import AddBeneficiaryOthers from './addBeneficiaryOthers'; import AddBeneficiaryOthers from './addBeneficiaryOthers';
import { generateOTP } from '@/app/OTPGenerator';
const bankOptions = [ const bankOptions = [
{ value: 'KCCB', label: 'KCCB - The Kangra Central Co-Operative Bank' }, { value: 'KCCB', label: 'KCCB - The Kangra Central Co-Operative Bank' },
]; ];
const AddBeneficiary: React.FC = () => { const AddBeneficiary: React.FC = () => {
const router = useRouter();
const [bankName, setBankName] = useState(''); const [bankName, setBankName] = useState('');
const [authorized, setAuthorized] = useState<boolean | null>(null);
const [bankType, setBankType] = useState('own'); const [bankType, setBankType] = useState('own');
const [accountNo, setAccountNo] = useState(''); const [accountNo, setAccountNo] = useState('');
const [confirmAccountNo, setConfirmAccountNo] = useState(''); const [confirmAccountNo, setConfirmAccountNo] = useState('');
@@ -37,11 +30,20 @@ const AddBeneficiary: React.FC = () => {
const getFullMaskedAccount = (acc: string) => { return "X".repeat(acc.length); }; const getFullMaskedAccount = (acc: string) => { return "X".repeat(acc.length); };
const handleGenerateOtp = async () => { const handleGenerateOtp = async () => {
// const value = await generateOTP(6);
const value = "123456"; // Or generate a random OTP const value = "123456"; // Or generate a random OTP
setGeneratedOtp(value); setGeneratedOtp(value);
return value; return value;
}; };
useEffect(() => {
const token = localStorage.getItem("access_token");
if (!token) {
setAuthorized(false);
router.push("/login");
} else {
setAuthorized(true);
}
}, []);
// const isValidBankName = (name: string) => { // const isValidBankName = (name: string) => {
// const regex = /^[A-Za-z\s]{3,5}$/; // const regex = /^[A-Za-z\s]{3,5}$/;
// return regex.test(name.trim()); // return regex.test(name.trim());
@@ -51,8 +53,6 @@ const AddBeneficiary: React.FC = () => {
// return /^[A-Z]{4}0[0-9]{6}$/.test(code); // return /^[A-Z]{4}0[0-9]{6}$/.test(code);
// }; // };
const validateAndSendOtp = async () => { const validateAndSendOtp = async () => {
if (!bankName || !accountNo || !confirmAccountNo) { if (!bankName || !accountNo || !confirmAccountNo) {
notifications.show({ notifications.show({
@@ -86,10 +86,6 @@ const AddBeneficiary: React.FC = () => {
// return; // return;
// } // }
if (accountNo.length < 10 || accountNo.length > 17) { if (accountNo.length < 10 || accountNo.length > 17) {
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
@@ -101,9 +97,6 @@ const AddBeneficiary: React.FC = () => {
return; return;
} }
if (accountNo !== confirmAccountNo) { if (accountNo !== confirmAccountNo) {
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
@@ -193,6 +186,7 @@ const AddBeneficiary: React.FC = () => {
}); });
}; };
if (!authorized) return null;
return ( return (
<Paper shadow="sm" radius="md" p="md" withBorder h={400}> <Paper shadow="sm" radius="md" p="md" withBorder h={400}>
@@ -254,9 +248,9 @@ const AddBeneficiary: React.FC = () => {
// disabled={isVisibilityLocked} // disabled={isVisibilityLocked}
readOnly={isVisibilityLocked} readOnly={isVisibilityLocked}
required required
onCopy={(e) => e.preventDefault()} onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()} onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()} onCut={(e) => e.preventDefault()}
/> />
{validationStatus === "error" && ( {validationStatus === "error" && (

View File

@@ -38,7 +38,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
}} }}
> >
<Stack style={{ background: '#228be6', height: '10%', alignItems: 'center' }}> <Stack style={{ background: '#228be6', height: '10%', alignItems: 'center' }}>
<Text fw={700} fs="italic" c='white' style={{ textAlign: 'center', marginTop: '10px' }}> <Text fw={700} c='white' style={{ textAlign: 'center', marginTop: '10px' }}>
Send Money Send Money
</Text> </Text>
</Stack> </Stack>

View File

@@ -6,6 +6,7 @@ import { notifications } from "@mantine/notifications";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { generateOTP } from '@/app/OTPGenerator'; import { generateOTP } from '@/app/OTPGenerator';
import OutsideQuickPay from "./outside_quick_pay"; import OutsideQuickPay from "./outside_quick_pay";
import { IconRefresh } from "@tabler/icons-react";
interface accountData { interface accountData {
stAccountNo: string; stAccountNo: string;
@@ -34,6 +35,8 @@ export default function QuickPay() {
const [validationStatus, setValidationStatus] = useState<"success" | "error" | null>(null); const [validationStatus, setValidationStatus] = useState<"success" | "error" | null>(null);
const [beneficiaryName, setBeneficiaryName] = useState<string | null>(null); const [beneficiaryName, setBeneficiaryName] = useState<string | null>(null);
const [showOtpField, setShowOtpField] = useState(false); const [showOtpField, setShowOtpField] = useState(false);
const [countdown, setCountdown] = useState(60);
const [timerActive, setTimerActive] = useState(false);
const [otp, setOtp] = useState(""); const [otp, setOtp] = useState("");
const [generateOtp, setGenerateOtp] = useState(""); const [generateOtp, setGenerateOtp] = useState("");
@@ -41,6 +44,8 @@ export default function QuickPay() {
// const value = await generateOTP(6); // const value = await generateOTP(6);
const value = "123456"; const value = "123456";
setGenerateOtp(value); setGenerateOtp(value);
setCountdown(60);
setTimerActive(true);
return value; return value;
} }
@@ -94,6 +99,22 @@ export default function QuickPay() {
} }
}, [authorized]); }, [authorized]);
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]);
async function handleValidate() { async function handleValidate() {
if (!selectedAccNo || !beneficiaryAcc || if (!selectedAccNo || !beneficiaryAcc ||
!confirmBeneficiaryAcc !confirmBeneficiaryAcc
@@ -257,6 +278,7 @@ export default function QuickPay() {
color: "red", color: "red",
}); });
} finally { } finally {
setValidationStatus(null);
setSelectedAccNo(null); setSelectedAccNo(null);
setBeneficiaryAcc(''); setBeneficiaryAcc('');
setBeneficiaryName(''); setBeneficiaryName('');
@@ -313,142 +335,161 @@ export default function QuickPay() {
{/* main content */} {/* main content */}
<Paper shadow="sm" radius="md" p="md" withBorder h={400}> <Paper shadow="sm" radius="md" p="md" withBorder h={400}>
<Title order={3} mb="md"> <Title order={3} mb="md">
Quick Pay Quick Pay - Own Bank
</Title> </Title>
<div style={{ maxHeight: "320px", overflowY: "auto" }}> <div style={{ maxHeight: "320px", overflowY: "auto" }}>
<Stack gap="xs"> <Stack gap="xs">
<Group grow> <Group grow>
<Select <Select
label="Select Debit Account Number" label="Select Debit Account Number"
placeholder="Choose account number" placeholder="Choose account number"
data={accountOptions} data={accountOptions}
value={selectedAccNo} value={selectedAccNo}
onChange={setSelectedAccNo} onChange={setSelectedAccNo}
withAsterisk withAsterisk
readOnly={isVisibilityLocked} readOnly={isVisibilityLocked}
/> />
<TextInput <TextInput
label="Payee Account No" label="Payee Account No"
value={showPayeeAcc ? beneficiaryAcc : getFullMaskedAccount(beneficiaryAcc)} value={showPayeeAcc ? beneficiaryAcc : getFullMaskedAccount(beneficiaryAcc)}
onChange={(e) => { onChange={(e) => {
const value = e.currentTarget.value; const value = e.currentTarget.value;
if (/^\d*$/.test(value)) { if (/^\d*$/.test(value)) {
setBeneficiaryAcc(value); setBeneficiaryAcc(value);
setShowPayeeAcc(true); setShowPayeeAcc(true);
} }
}} }}
onBlur={() => setShowPayeeAcc(false)} onBlur={() => setShowPayeeAcc(false)}
onFocus={() => setShowPayeeAcc(true)} onFocus={() => setShowPayeeAcc(true)}
withAsterisk withAsterisk
readOnly={isVisibilityLocked} readOnly={isVisibilityLocked}
/> />
<TextInput <TextInput
label="Confirm Payee Account No" label="Confirm Payee Account No"
value={confirmBeneficiaryAcc} value={confirmBeneficiaryAcc}
onChange={(e) => { onChange={(e) => {
const value = e.currentTarget.value; const value = e.currentTarget.value;
if (/^\d*$/.test(value)) { if (/^\d*$/.test(value)) {
setConfirmBeneficiaryAcc(value); setConfirmBeneficiaryAcc(value);
} }
}} }}
onCopy={(e) => e.preventDefault()} onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()} onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()} onCut={(e) => e.preventDefault()}
withAsterisk withAsterisk
readOnly={isVisibilityLocked} readOnly={isVisibilityLocked}
/> />
</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 justify="space-between" > </Group>
<Text size="xs" c="green" style={{ visibility: selectedAccount ? "visible" : "hidden" }}>Available Balance : <Group grow>
{selectedAccount ? selectedAccount.stAvailableBalance : 0} <TextInput
</Text> label="Payee Name"
<Group justify="center"> value={validationStatus === "success" && beneficiaryName ? beneficiaryName : ""}
{validationStatus === "error" && <Text size="sm" fw={700} ta="right" c="red">{beneficiaryName}</Text>} disabled
</Group> readOnly
</Group> withAsterisk
<Group grow> />
<TextInput
label="Payee Name"
value={validationStatus === "success" && beneficiaryName ? beneficiaryName : ""}
disabled
readOnly
withAsterisk
/>
<Select <Select
label="Payee Account Type" label="Payee Account Type"
placeholder="Select type" placeholder="Select type"
data={["Savings", "Current"]} data={["Savings", "Current"]}
value={beneficiaryType} value={beneficiaryType}
onChange={setBeneficiaryType} onChange={setBeneficiaryType}
withAsterisk withAsterisk
readOnly={showOtpField} readOnly={showOtpField}
/> />
<TextInput <TextInput
label="Amount" label="Amount"
type="number" type="number"
value={amount} value={amount}
onChange={(e) => setAmount(e.currentTarget.value)} onChange={(e) => setAmount(e.currentTarget.value)}
error={ error={
selectedAccount && Number(amount) > Number(selectedAccount.stAvailableBalance) ? selectedAccount && Number(amount) > Number(selectedAccount.stAvailableBalance) ?
"Amount exceeds available balance" : false} "Amount exceeds available balance" : false}
withAsterisk withAsterisk
readOnly={showOtpField} readOnly={showOtpField}
/> />
<TextInput <TextInput
label="Remarks" label="Remarks"
placeholder="Enter remarks" placeholder="Enter remarks"
value={remarks} value={remarks}
onChange={(e) => setRemarks(e.currentTarget.value)} onChange={(e) => setRemarks(e.currentTarget.value)}
// withAsterisk // withAsterisk
readOnly={showOtpField} readOnly={showOtpField}
withAsterisk withAsterisk
/> />
</Group> </Group>
<Group grow> <Group grow>
{showOtpField && ( {showOtpField && (
<>
<PasswordInput <PasswordInput
label="OTP" label="OTP"
placeholder="Enter OTP" placeholder="Enter OTP"
type="otp" type="otp"
value={otp} value={otp}
maxLength={6}
onChange={(e) => setOtp(e.currentTarget.value)} onChange={(e) => setOtp(e.currentTarget.value)}
withAsterisk withAsterisk
disabled={showTxnPassword} disabled={showTxnPassword}
/> />
)} {!showTxnPassword && (
{showTxnPassword && ( timerActive ? (
<TextInput <Text size="xs" c="dimmed">
label="Transaction Password" Resend OTP will be enabled in 00:{countdown < 10 ? `0${countdown}` : countdown} min
placeholder="Enter transaction password" </Text>
type="password" ) : (
value={txnPassword} <Button
onChange={(e) => setTxnPassword(e.currentTarget.value)} variant="subtle"
withAsterisk px={8}
/> onClick={handleGenerateOtp}
)} leftSection={<IconRefresh size={16} />}
</Group> >
Resend
</Button>
)
)}
</>
)}
{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"> <Group justify="flex-start">
<Button variant="filled" color="blue" onClick={handleValidate} disabled={validationStatus === "success"}> <Button variant="filled" color="blue" onClick={handleValidate} disabled={validationStatus === "success"}>
Validate Validate
</Button> </Button>
<Button <Button
variant="filled" variant="filled"
color="blue" color="blue"
onClick={handleProceed} onClick={handleProceed}
loading={isSubmitting} loading={isSubmitting}
disabled={validationStatus !== "success"} disabled={validationStatus !== "success"}
> >
{!showTxnPassword && showOtpField ? "Validate the OTP" : showTxnPassword ? "Proceed to Pay" : "Proceed"} {!showTxnPassword && showOtpField ? "Validate the OTP" : showTxnPassword ? "Proceed to Pay" : "Proceed"}
</Button> </Button>
</Group> </Group>
</Stack> </Stack>
</div> </div>
{/* ) : ( {/* ) : (
<div> <div>
<OutsideQuickPay /> <OutsideQuickPay />

View File

@@ -143,7 +143,7 @@ export default function Home() {
{/* Loan Account Card */} {/* Loan Account Card */}
<Paper p="md" radius="md" style={{ <Paper p="md" radius="md" style={{
backgroundColor: '#c1e0f0', width: 350, height: 195, display: 'flex', backgroundColor: '#c1e0f0', width: 355, height: 195, display: 'flex',
flexDirection: 'column', justifyContent: 'space-between' flexDirection: 'column', justifyContent: 'space-between'
}} }}
> >

View File

@@ -5,7 +5,7 @@ import { IconBook, IconCurrencyRupee, IconHome, IconLogout, IconPhoneFilled, Ico
import Link from 'next/link'; import Link from 'next/link';
import { useRouter, usePathname } from "next/navigation"; import { useRouter, usePathname } from "next/navigation";
import { Providers } from '../providers'; import { Providers } from '../providers';
import logo from '@/app/image/logo.jpg'; import logo from '@/app/image/logo1.jpg';
import NextImage from 'next/image'; import NextImage from 'next/image';
import { notifications } from '@mantine/notifications'; import { notifications } from '@mantine/notifications';
@@ -25,7 +25,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
async function handleFetchUserName() { async function handleFetchUserName() {
try { try {
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
const response = await fetch('api/customer', { const response = await fetch('/api/customer', {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -67,7 +67,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
async function handleFetchUserDetails(e: React.FormEvent) { async function handleFetchUserDetails(e: React.FormEvent) {
e.preventDefault(); e.preventDefault();
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
const response = await fetch('api/auth/user_details', { const response = await fetch('/api/auth/user_details', {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -124,7 +124,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
width: '100%', width: '100%',
display: "flex", display: "flex",
justifyContent: "flex-start", justifyContent: "flex-start",
background: "linear-gradient(15deg,rgba(2, 163, 85, 1) 55%, rgba(101, 101, 184, 1) 100%)", background: "linear-gradient(15deg,rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
}} }}
> >
<Image <Image
@@ -134,6 +134,19 @@ export default function RootLayout({ children }: { children: React.ReactNode })
alt="ebanking" alt="ebanking"
style={{ width: "100%", height: "100%" }} style={{ width: "100%", height: "100%" }}
/> />
<Title
order={2}
style={{
fontFamily: 'Roboto',
position: 'absolute',
top: '30%',
left: '6%',
color: 'White',
transition: "opacity 0.5s ease-in-out",
}}
>
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
</Title>
<Text <Text
style={{ style={{
position: 'absolute', position: 'absolute',
@@ -173,7 +186,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<Link key={item.href} href={item.href}> <Link key={item.href} href={item.href}>
<Button <Button
leftSection={<Icon size={20} />} leftSection={<Icon size={20} />}
variant={isActive ? "light" : "subtle"} variant={isActive ? "dark" : "subtle"}
color={isActive ? "blue" : undefined} color={isActive ? "blue" : undefined}
> >
{item.label} {item.label}
@@ -213,7 +226,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
}} }}
> >
<Text c="dimmed" size="xs"> <Text c="dimmed" size="xs">
© 2025 Kangra Central Co-Operative Bank © 2025 The Kangra Central Co-Operative Bank
</Text> </Text>
</Box> </Box>
</div> </div>

View File

@@ -14,7 +14,7 @@ import {
} from "@mantine/core"; } from "@mantine/core";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { IconEye, IconEyeOff, IconLock } from "@tabler/icons-react"; import { IconEye, IconEyeOff, IconLock } from "@tabler/icons-react";
import CaptchaImage from "@/app/SetPassword/CaptchaImage"; // import CaptchaImage from "@/app/SetPassword/CaptchaImage";
import { generateCaptcha } from "@/app/captcha"; import { generateCaptcha } from "@/app/captcha";
const ChangePassword: React.FC = () => { const ChangePassword: React.FC = () => {
@@ -193,7 +193,7 @@ const ChangePassword: React.FC = () => {
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 5 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 5 }}>
{/* CAPTCHA Image */} {/* CAPTCHA Image */}
<div style={{ display: 'flex', alignItems: 'center', height: 30 }}> <div style={{ display: 'flex', alignItems: 'center', height: 30 }}>
<CaptchaImage text={captcha} /> {/* <CaptchaImage text={captcha} /> */}
</div> </div>
{/* Refresh Button */} {/* Refresh Button */}

View File

@@ -38,7 +38,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
}} }}
> >
<Stack style={{ background: '#228be6', height: '10%', alignItems: 'center' }}> <Stack style={{ background: '#228be6', height: '10%', alignItems: 'center' }}>
<Text fw={700} fs="italic" c='white' style={{ textAlign: 'center', marginTop: '10px' }}> <Text fw={700} c='white' style={{ textAlign: 'center', marginTop: '10px' }}>
Settings Settings
</Text> </Text>
</Stack> </Stack>

View File

@@ -1,23 +0,0 @@
import React, { useEffect, useRef } from 'react';
const CaptchaImage = ({ text }: { text: string }) => {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas?.getContext('2d');
if (canvas && ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = '26px Arial';
ctx.fillStyle = '#000';
ctx.setTransform(1, 0.1, -0.1, 1, 0, 0);
ctx.fillText(text, 10, 30);
}
}, [text]);
return <canvas ref={canvasRef} width={120} height={40} />;
};
export default CaptchaImage;

View File

@@ -1,14 +1,15 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Text, Button, TextInput, PasswordInput, Title, Card, Box, Image } from "@mantine/core"; import { Text, Button, TextInput, PasswordInput, Title, Card, Box, Image, Group } from "@mantine/core";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { Providers } from "@/app/providers"; import { Providers } from "@/app/providers";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import NextImage from "next/image"; import NextImage from "next/image";
import logo from '@/app/image/logo.jpg'; import logo from '@/app/image/logo1.jpg';
import changePwdImage from '@/app/image/changepw.png'; import changePwdImage from '@/app/image/set_log_pass.jpg';
import CaptchaImage from './CaptchaImage'; import { IconLock, IconLogout, IconRefresh } from '@tabler/icons-react';
import { IconEye, IconEyeOff, IconLock, IconLogout } from '@tabler/icons-react'; import { generateCaptcha } from '@/app/captcha';
import { generateOTP } from "../OTPGenerator";
export default function SetLoginPwd() { export default function SetLoginPwd() {
const router = useRouter(); const router = useRouter();
@@ -16,33 +17,35 @@ export default function SetLoginPwd() {
const [captcha, setCaptcha] = useState(""); const [captcha, setCaptcha] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState("");
const [captchaInput, setCaptchaInput] = useState(''); const [captchaInput, setCaptchaInput] = useState("");
const [captchaError, setCaptchaError] = useState(''); const [captchaValidate, setCaptchaValidate] = useState(false);
const [confirmVisible, setConfirmVisible] = useState(false); const [otp, setOtp] = useState("");
const toggleConfirmVisibility = () => setConfirmVisible((v) => !v); const [countdown, setCountdown] = useState(60);
const [timerActive, setTimerActive] = useState(false);
const icon = <IconLock size={18} stroke={1.5} />; const icon = <IconLock size={18} stroke={1.5} />;
const [generateOtp, setGenerateOtp] = useState("");
async function handleGenerateOtp() {
// const value = await generateOTP(6);
const value = "123456";
setGenerateOtp(value);
setCountdown(60);
setTimerActive(true);
// return value;
}
async function handleLogout(e: React.FormEvent) { async function handleLogout(e: React.FormEvent) {
e.preventDefault(); e.preventDefault();
localStorage.removeItem("access_token"); localStorage.removeItem("access_token");
router.push("/login") router.push("/login")
} }
const regenerateCaptcha = () => {
useEffect(() => { const loadCaptcha = async () => {
generateCaptcha(); const newCaptcha = await generateCaptcha();
}, []); setCaptcha(newCaptcha);
};
const generateCaptcha = () => { loadCaptcha();
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; setCaptchaInput("");
let result = '';
for (let i = 0; i < 6; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
setCaptcha(result);
setCaptchaInput('');
setCaptchaError('');
}; };
async function handleSetLoginPassword(e: React.FormEvent) { async function handleSetLoginPassword(e: React.FormEvent) {
e.preventDefault(); e.preventDefault();
const pwdRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/; const pwdRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
@@ -50,36 +53,72 @@ export default function SetLoginPwd() {
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "red", color: "red",
title: "Both password fields are required.", title: "Field Required",
message: "Both password fields are required.", message: "Password and Confirm Password Both fields are required.",
autoClose: 5000, autoClose: 5000,
}); });
return; return;
// alert("Both password fields are required."); }
} else if (password !== confirmPassword) { if (!captchaInput) {
// alert("Passwords do not match.");
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "red", color: "red",
title: "Passwords do not match.", title: "Field Required",
message: "Please Enter Captcha details",
autoClose: 5000,
});
return;
}
if (password !== confirmPassword) {
notifications.show({
withBorder: true,
color: "red",
title: "Password Mismatch",
message: "Passwords do not match.", message: "Passwords do not match.",
autoClose: 5000, autoClose: 5000,
}); });
return; return;
} }
else if (!pwdRegex.test(password)) { if (!pwdRegex.test(password)) {
// alert("Password must contain at least 1 capital letter, 1 number, 1 special character, and be at least 8 characters long.");
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "red", color: "red",
title: "Password must contain at least 1 capital letter, 1 number, 1 special character, and be at least 8 characters long.", title: "Invalid Password",
message: "Password must contain at least 1 capital letter, 1 number, 1 special character, and be at least 8 characters long.", message: "Password must contain at least one capital letter, one number, one special character, and be 8-15 characters long.",
autoClose: 5000, autoClose: 5000,
}); });
return; return;
} }
else if (captchaInput !== captcha) { if (captchaInput !== captcha) {
setCaptchaError("Incorrect CAPTCHA."); notifications.show({
withBorder: true,
color: "red",
title: "Captcha Error",
message: "Please enter the correct captcha",
autoClose: 5000,
});
regenerateCaptcha();
return;
}
if (!captchaValidate) {
setCaptchaValidate(true);
handleGenerateOtp();
return;
}
if (!otp) {
notifications.show({
title: "Null Field",
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; return;
} }
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
@@ -116,6 +155,29 @@ export default function SetLoginPwd() {
router.push("/login"); router.push("/login");
} }
} }
useEffect(() => {
const loadCaptcha = async () => {
const newCaptcha = await generateCaptcha();
setCaptcha(newCaptcha);
};
loadCaptcha();
}, []);
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(() => { useEffect(() => {
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
@@ -136,16 +198,28 @@ export default function SetLoginPwd() {
position: 'fixed', width: '100%', height: '12%', top: 0, left: 0, zIndex: 100, position: 'fixed', width: '100%', height: '12%', top: 0, left: 0, zIndex: 100,
display: "flex", display: "flex",
justifyContent: "flex-start", justifyContent: "flex-start",
background: "linear-gradient(15deg,rgba(2, 163, 85, 1) 55%, rgba(101, 101, 184, 1) 100%)" background: "linear-gradient(15deg,rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)"
}}> }}>
<Image <Image
// radius="md"
fit="cover" fit="cover"
src={logo} src={logo}
component={NextImage} component={NextImage}
alt="ebanking" alt="ebanking"
style={{ width: "100%", height: "100%" }} style={{ width: "100%", height: "100%" }}
/> />
<Title
order={2}
style={{
fontFamily: 'Roboto',
position: 'absolute',
top: '30%',
left: '7%',
color: 'White',
transition: "opacity 0.5s ease-in-out",
}}
>
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
</Title>
<Button style={{ <Button style={{
position: 'absolute', position: 'absolute',
top: '50%', top: '50%',
@@ -157,80 +231,101 @@ export default function SetLoginPwd() {
leftSection={<IconLogout color='white' />} variant="subtle" onClick={handleLogout}>Logout</Button> leftSection={<IconLogout color='white' />} variant="subtle" onClick={handleLogout}>Logout</Button>
</Box> </Box>
<div> <div>
<Box style={{ display: "flex", justifyContent: "center", alignItems: "center", columnGap: "5rem" }} bg="#c1e0f0"> <Box style={{ display: "flex", justifyContent: "center", alignItems: "center" }} bg="#80868989">
<Image h="85vh" fit="contain" component={NextImage} src={changePwdImage} alt="Change Password Image" /> <Image h="85vh" w="100%" fit="contain" component={NextImage} src={changePwdImage} alt="Change Password Image" />
<Box h="100%" style={{ display: "flex", justifyContent: "center", alignItems: "center" }}> <Box h="100%" style={{ display: "flex", justifyContent: "center", alignItems: "center" }}>
<Card p="xl" w="40vw" h='82vh'> <Card p="xl" w="40vw" h='85vh'>
<Title order={3} <Title order={4}
// @ts-ignore // @ts-ignore
align="center" mb="md">Set Login Password</Title> align="center" mb="md">Set Login Password</Title>
<form onSubmit={handleSetLoginPassword}> <form onSubmit={handleSetLoginPassword}>
<PasswordInput <PasswordInput
label="Login Password" label="Login Password"
placeholder="Enter your password" placeholder="Enter your password"
required withAsterisk
id="loginPassword" id="loginPassword"
value={password} value={password}
minLength={8}
maxLength={15}
onChange={(e) => setPassword(e.currentTarget.value)} onChange={(e) => setPassword(e.currentTarget.value)}
onCopy={(e) => e.preventDefault()} onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()} onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()} onCut={(e) => e.preventDefault()}
readOnly={captchaValidate}
/> />
<PasswordInput <PasswordInput
label="Confirm Login Password" label="Confirm Login Password"
placeholder="Enter your password" placeholder="Enter your password"
required withAsterisk
rightSection={icon} rightSection={icon}
id="confirmPassword" id="confirmPassword"
value={confirmPassword} value={confirmPassword}
onChange={(e) => setConfirmPassword(e.currentTarget.value)} onChange={(e) => setConfirmPassword(e.currentTarget.value)}
type={confirmVisible ? 'text' : 'password'}
onCopy={(e) => e.preventDefault()} onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()} onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()} onCut={(e) => e.preventDefault()}
readOnly={captchaValidate}
/> />
<Group mt="sm" align="center">
{/* CAPTCHA */} <Box style={{ backgroundColor: "#fff", fontSize: "18px", textDecoration: "line-through", padding: "4px 8px", fontFamily: "cursive" }}>{captcha}</Box>
<div style={{ marginTop: 20 }}> <Button size="xs" variant="light" onClick={regenerateCaptcha}>Refresh</Button>
<label style={{ fontWeight: 600 }}>Enter CAPTCHA *</label> </Group>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 5 }}> <TextInput
<CaptchaImage text={captcha} /> label="Enter CAPTCHA"
<Button size="xs" variant="outline" onClick={generateCaptcha}>Refresh</Button> placeholder="Enter above text"
</div> value={captchaInput}
<TextInput onChange={(e) => setCaptchaInput(e.currentTarget.value)}
placeholder="Enter above text" withAsterisk
value={captchaInput} readOnly={captchaValidate}
onChange={(e) => setCaptchaInput(e.currentTarget.value)} />
required <Box style={{ height: 60 }}>
/> {captchaValidate && (
{captchaError && <p style={{ color: 'red', fontSize: '12px' }}>{captchaError}</p>} <Group gap="xs" align="flex-end">
</div> <PasswordInput
label="Enter OTP"
placeholder="Enter the OTP"
value={otp}
maxLength={6}
onChange={(e) => setOtp(e.currentTarget.value)}
withAsterisk
style={{ flex: 1 }}
/>
{timerActive ? (
<Text size="xs" c="dimmed">
Resend OTP will be enabled in 00:{countdown < 10 ? `0${countdown}` : countdown} min
</Text>
) : (
<Button
variant="subtle"
px={8}
onClick={handleGenerateOtp}
leftSection={<IconRefresh size={16} />}
>
Resend
</Button>
)}
</Group>
)}
</Box>
<Button <Button
type="submit" type="submit"
fullWidth fullWidth
mt="sm" mt="sm"
color="blue"
> >
Set {!captchaValidate ? "Validate" : "Set"}
</Button> </Button>
</form> </form>
<br></br>
<Box <Box
style={{ style={{
flex: 1, flex: 1,
borderLeft: '1px solid #ccc', borderLeft: '1px solid #ccc',
paddingLeft: 16, paddingLeft: 16,
minHeight: 90, minHeight: 80,
gap: "sm"
}} }}
> >
<Text size="sm"> <Text size="sm">
<strong>Note:</strong> Password will contains minimum one alphabet, one digit, one special symbol and total 8 charecters. <strong>Note:</strong> Password must contain at least one capital letter(A-Z), one digit(0-9), one special symbol(e.g.,@,#,$), and be 8-15 characters long.
</Text> </Text>
</Box> </Box>
</Card> </Card>
@@ -247,7 +342,7 @@ export default function SetLoginPwd() {
}} }}
> >
<Text c="dimmed" size="xs"> <Text c="dimmed" size="xs">
© 2025 Kangra Central Co-Operative Bank © 2025 The Kangra Central Co-Operative Bank
</Text> </Text>
</Box> </Box>
</div> </div>

View File

@@ -1,24 +0,0 @@
import React, { useEffect, useRef } from 'react';
const CaptchaImage = ({ text }: { text: string }) => {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas?.getContext('2d');
if (canvas && ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = '26px Arial';
ctx.fillStyle = '#000';
ctx.setTransform(1, 0.1, -0.1, 1, 0, 0);
ctx.fillText(text, 10, 30);
}
}, [text]);
return <canvas ref={canvasRef} width={120} height={40} />;
};
export default CaptchaImage;

View File

@@ -1,14 +1,14 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Text, Button, TextInput, PasswordInput, Title, Card, Box, Image } from "@mantine/core"; import { Text, Button, TextInput, PasswordInput, Title, Card, Box, Image, Group } from "@mantine/core";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { Providers } from "@/app/providers"; import { Providers } from "@/app/providers";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import NextImage from "next/image"; import NextImage from "next/image";
import logo from '@/app/image/logo.jpg'; import logo from '@/app/image/logo1.jpg';
import changePwdImage from '@/app/image/changepw.png'; import changePwdImage from '@/app/image/set_tran_pass.jpg';
import CaptchaImage from './CaptchaImage'; import { generateCaptcha } from '@/app/captcha';
import { IconEye, IconEyeOff, IconLock, IconLogout } from '@tabler/icons-react'; import { IconLock, IconLogout, IconRefresh } from '@tabler/icons-react';
export default function SetTransactionPwd() { export default function SetTransactionPwd() {
const router = useRouter(); const router = useRouter();
@@ -16,32 +16,59 @@ export default function SetTransactionPwd() {
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState("");
const [captcha, setCaptcha] = useState(""); const [captcha, setCaptcha] = useState("");
const [captchaInput, setCaptchaInput] = useState(''); const [captchaInput, setCaptchaInput] = useState("");
const [captchaError, setCaptchaError] = useState(''); const [captchaValidate, setCaptchaValidate] = useState(false);
const [confirmVisible, setConfirmVisible] = useState(false); const [otp, setOtp] = useState("");
const toggleConfirmVisibility = () => setConfirmVisible((v) => !v); const [countdown, setCountdown] = useState(60);
const [timerActive, setTimerActive] = useState(false);
const icon = <IconLock size={18} stroke={1.5} />; const icon = <IconLock size={18} stroke={1.5} />;
const [generateOtp, setGenerateOtp] = useState("");
async function handleGenerateOtp() {
// const value = await generateOTP(6);
const value = "123456";
setGenerateOtp(value);
setCountdown(60);
setTimerActive(true);
}
async function handleLogout(e: React.FormEvent) { async function handleLogout(e: React.FormEvent) {
e.preventDefault(); e.preventDefault();
localStorage.removeItem("access_token"); localStorage.removeItem("access_token");
router.push("/login") router.push("/login")
} }
const regenerateCaptcha = () => {
const loadCaptcha = async () => {
const newCaptcha = await generateCaptcha();
setCaptcha(newCaptcha);
};
loadCaptcha();
setCaptchaInput("");
};
useEffect(() => { useEffect(() => {
generateCaptcha(); const loadCaptcha = async () => {
const newCaptcha = await generateCaptcha();
setCaptcha(newCaptcha);
};
loadCaptcha();
}, []); }, []);
const generateCaptcha = () => { useEffect(() => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let interval: number | undefined;
let result = ''; if (timerActive && countdown > 0) {
for (let i = 0; i < 6; i++) { interval = window.setInterval(() => {
result += chars.charAt(Math.floor(Math.random() * chars.length)); setCountdown((prev) => prev - 1);
}, 1000);
} }
setCaptcha(result); if (countdown === 0) {
setCaptchaInput(''); if (interval) clearInterval(interval);
setCaptchaError(''); setTimerActive(false);
}; }
return () => {
if (interval) clearInterval(interval);
};
}, [timerActive, countdown]);
async function handleSetTransactionPassword(e: React.FormEvent) { async function handleSetTransactionPassword(e: React.FormEvent) {
e.preventDefault(); e.preventDefault();
@@ -50,36 +77,72 @@ export default function SetTransactionPwd() {
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "red", color: "red",
title: "Both password fields are required.", title: "Field Required",
message: "Both password fields are required.", message: "Both password fields are required.",
autoClose: 5000, autoClose: 5000,
}); });
return; return;
// alert("Both password fields are required."); }
} else if (password !== confirmPassword) { if (!captchaInput) {
// alert("Passwords do not match.");
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "red", color: "red",
title: "Passwords do not match.", title: "Field Required",
message: "Please Enter Captcha details",
autoClose: 5000,
});
return;
}
if (password !== confirmPassword) {
notifications.show({
withBorder: true,
color: "red",
title: "Password Mismatch",
message: "Passwords do not match.", message: "Passwords do not match.",
autoClose: 5000, autoClose: 5000,
}); });
return; return;
} }
else if (!pwdRegex.test(password)) { if (!pwdRegex.test(password)) {
// alert("Password must contain at least 1 capital letter, 1 number, 1 special character, and be at least 8 characters long.");
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "red", color: "red",
title: "Password must contain at least 1 capital letter, 1 number, 1 special character, and be at least 8 characters long.", title: "Invalid Password",
message: "Password must contain at least 1 capital letter, 1 number, 1 special character, and be at least 8 characters long.", message: "Password must contain at least one capital letter, one number, one special character, and be 8-15 characters long.",
autoClose: 5000, autoClose: 5000,
}); });
return; return;
} }
else if (captchaInput !== captcha) { if (captchaInput !== captcha) {
setCaptchaError("Incorrect CAPTCHA."); notifications.show({
withBorder: true,
color: "red",
title: "Captcha Error",
message: "Please enter the correct captcha",
autoClose: 5000,
});
regenerateCaptcha();
return;
}
if (!captchaValidate) {
setCaptchaValidate(true);
handleGenerateOtp();
return;
}
if (!otp) {
notifications.show({
title: "Null Field",
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; return;
} }
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
@@ -116,7 +179,6 @@ export default function SetTransactionPwd() {
router.push("/login"); router.push("/login");
} }
} }
useEffect(() => { useEffect(() => {
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
if (!token) { if (!token) {
@@ -136,16 +198,28 @@ export default function SetTransactionPwd() {
position: 'fixed', width: '100%', height: '12%', top: 0, left: 0, zIndex: 100, position: 'fixed', width: '100%', height: '12%', top: 0, left: 0, zIndex: 100,
display: "flex", display: "flex",
justifyContent: "flex-start", justifyContent: "flex-start",
background: "linear-gradient(15deg,rgba(2, 163, 85, 1) 55%, rgba(101, 101, 184, 1) 100%)" background: "linear-gradient(15deg,rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)"
}}> }}>
<Image <Image
// radius="md"
fit="cover" fit="cover"
src={logo} src={logo}
component={NextImage} component={NextImage}
alt="ebanking" alt="ebanking"
style={{ width: "100%", height: "100%" }} style={{ width: "100%", height: "100%" }}
/> />
<Title
order={2}
style={{
fontFamily: 'Roboto',
position: 'absolute',
top: '30%',
left: '7%',
color: 'White',
transition: "opacity 0.5s ease-in-out",
}}
>
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
</Title>
<Button style={{ <Button style={{
position: 'absolute', position: 'absolute',
top: '50%', top: '50%',
@@ -158,15 +232,15 @@ export default function SetTransactionPwd() {
</Button> </Button>
</Box> </Box>
<div> <div>
<Box style={{ display: "flex", justifyContent: "center", alignItems: "center", columnGap: "5rem" }} bg="#c1e0f0"> <Box style={{ display: "flex", justifyContent: "center", alignItems: "center" }} bg="#80868989">
<Image h="85vh" fit="contain" component={NextImage} src={changePwdImage} alt="Change Password Image" /> <Image h="85vh" fit="contain" component={NextImage} src={changePwdImage} alt="Change Password Image" />
<Box h="100%" style={{ display: "flex", justifyContent: "center", alignItems: "center" }}> <Box h="100%" style={{ display: "flex", justifyContent: "center", alignItems: "center" }}>
<Card p="xl" w="40vw" h='82vh'> <Card p="xl" w="40vw" h='85vh'>
<Text onClick={()=>router.push("/login")} <Text onClick={() => router.push("/login")}
style={{ style={{
position: 'absolute', top: '1rem', right: '2rem', cursor: 'pointer', fontWeight: 500, color: '#7091ecff',textDecoration:'underline' position: 'absolute', top: '1rem', right: '2rem', cursor: 'pointer', fontWeight: 500, color: '#7091ecff', textDecoration: 'underline'
}}> Skip now</Text> }}> Skip now</Text>
<Title order={3} <Title order={3}
// @ts-ignore // @ts-ignore
align="center" mb="md">Set Transaction Password</Title> align="center" mb="md">Set Transaction Password</Title>
@@ -174,52 +248,71 @@ export default function SetTransactionPwd() {
<PasswordInput <PasswordInput
label="Transaction Password" label="Transaction Password"
placeholder="Enter your Transaction password" placeholder="Enter your Transaction password"
required withAsterisk
id="loginPassword" id="loginPassword"
value={password} value={password}
onChange={(e) => setPassword(e.currentTarget.value)} onChange={(e) => setPassword(e.currentTarget.value)}
onCopy={(e) => e.preventDefault()} onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()} onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()} onCut={(e) => e.preventDefault()}
readOnly={captchaValidate}
/> />
<PasswordInput <PasswordInput
label="Confirm Transaction Password" label="Confirm Transaction Password"
placeholder="Re-enter your Transaction password" placeholder="Re-enter your Transaction password"
required withAsterisk
id="confirmPassword" id="confirmPassword"
rightSection={icon} rightSection={icon}
value={confirmPassword} value={confirmPassword}
type={confirmVisible ? 'text' : 'password'}
// rightSection={
// <button
// type="button"
// onClick={toggleConfirmVisibility}
// style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'grey' }}
// >
// {confirmVisible ? <IconEyeOff size={18} /> : <IconEye size={18} />}
// </button>
// }
onChange={(e) => setConfirmPassword(e.currentTarget.value)} onChange={(e) => setConfirmPassword(e.currentTarget.value)}
onCopy={(e) => e.preventDefault()} onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()} onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()} onCut={(e) => e.preventDefault()}
readOnly={captchaValidate}
/> />
{/* CAPTCHA */} {/* CAPTCHA */}
<div style={{ marginTop: 20 }}> <Group mt="sm" align="center">
<label style={{ fontWeight: 600 }}>Enter CAPTCHA *</label> <Box style={{ backgroundColor: "#fff", fontSize: "18px", textDecoration: "line-through", padding: "4px 8px", fontFamily: "cursive" }}>{captcha}</Box>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 5 }}> <Button size="xs" variant="light" onClick={regenerateCaptcha}>Refresh</Button>
<CaptchaImage text={captcha} /> </Group>
<Button size="xs" variant="outline" onClick={generateCaptcha}>Refresh</Button> <TextInput
</div> label="Enter CAPTCHA"
<TextInput placeholder="Enter above text"
placeholder="Enter above text" value={captchaInput}
value={captchaInput} onChange={(e) => setCaptchaInput(e.currentTarget.value)}
onChange={(e) => setCaptchaInput(e.currentTarget.value)} withAsterisk
required mt="sm"
/> readOnly={captchaValidate}
{captchaError && <p style={{ color: 'red', fontSize: '12px' }}>{captchaError}</p>} />
</div> <Box style={{ height: 60 }}>
{captchaValidate && (
<Group gap="xs" align="flex-end">
<PasswordInput
label="Enter OTP"
placeholder="Enter the OTP"
value={otp}
maxLength={6}
onChange={(e) => setOtp(e.currentTarget.value)}
withAsterisk
style={{ flex: 1 }}
/>
{timerActive ? (
<Text size="xs" c="dimmed">
Resend OTP will be enabled in 00:{countdown < 10 ? `0${countdown}` : countdown} min
</Text>
) : (
<Button
variant="subtle"
px={8}
onClick={handleGenerateOtp}
leftSection={<IconRefresh size={16} />}
>
Resend
</Button>
)}
</Group>
)}
</Box>
<Button <Button
type="submit" type="submit"
fullWidth fullWidth
@@ -228,10 +321,7 @@ export default function SetTransactionPwd() {
> >
Set Set
</Button> </Button>
</form> </form>
<br></br> <br></br>
<Box <Box
style={{ style={{
@@ -243,7 +333,7 @@ export default function SetTransactionPwd() {
}} }}
> >
<Text size="sm"> <Text size="sm">
<strong>Note:</strong> Password will contains minimum one alphabet, one digit, one special symbol and total 8 charecters. <strong>Note:</strong> Password must contain at least one capital letter(A-Z), one digit(0-9), one special symbol(e.g.,@,#,$), and be 8-15 characters long.
</Text> </Text>
</Box> </Box>
</Card> </Card>

View File

@@ -12,6 +12,23 @@ export const KccbTheme = createTheme({
primaryColor: 'kccb-colors', primaryColor: 'kccb-colors',
colors: { colors: {
'kccb-colors': KccbColors 'kccb-colors': KccbColors
} },
// primaryColor: 'kccb-colors' //Typography settings
fontFamily: "Inter, Roboto, sans-serif",
headings: {
fontFamily: "Inter, Roboto, sans-serif",
fontWeight: '700',
sizes: {
h1: { fontSize: "2.2rem", lineHeight: '1.2' },
h2: { fontSize: "1.8rem", lineHeight: '1.3' },
h3: { fontSize: "1.5rem", lineHeight: '1.35' },
},
},
fontSizes: {
xs: "0.75rem",
sm: "0.875rem",
md: "1rem",
lg: "1.125rem",
xl: "1.25rem",
},
}); });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 KiB

BIN
src/app/image/logo1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -40,7 +40,7 @@ export default function CustomCarousel() {
}, [currentIndex]); }, [currentIndex]);
return ( return (
<Box style={{ position: 'relative', width: '83%', overflow: 'hidden',backgroundColor:"white" }}> <Box style={{ position: 'relative', width: '90%', overflow: 'hidden',backgroundColor:"white" }}>
{/* Scrollable container */} {/* Scrollable container */}
<Box <Box
ref={scrollRef} ref={scrollRef}

View File

@@ -1,24 +1,25 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, memo, useRef } from "react";
import { Text, Button, TextInput, PasswordInput, Title, Card, Group, Flex, Box, Image, Anchor, Stack, Popover, ActionIcon } from "@mantine/core"; import { Text, Button, TextInput, PasswordInput, Title, Card, Group, Flex, Box, Image, Anchor, Tooltip } from "@mantine/core";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { Providers } from "@/app/providers"; import { Providers } from "@/app/providers";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import NextImage from "next/image"; import NextImage from "next/image";
import logo from '@/app/image/logo.jpg'; import logo from '@/app/image/logo1.jpg';
import frontPage from '@/app/image/ib_front.jpg'; import frontPage from '@/app/image/ib_front.jpg';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { generateCaptcha } from '@/app/captcha'; import { generateCaptcha } from '@/app/captcha';
import { IconShieldLockFilled } from "@tabler/icons-react";
export default function Login() { export default function Login() {
const router = useRouter(); const router = useRouter();
const [error, setError] = useState<string | null>(null);
const [CIF, SetCIF] = useState(""); const [CIF, SetCIF] = useState("");
const [psw, SetPsw] = useState(""); const [psw, SetPsw] = useState("");
const [captcha, setCaptcha] = useState(""); const [captcha, setCaptcha] = useState("");
const [inputCaptcha, setInputCaptcha] = useState(""); const [inputCaptcha, setInputCaptcha] = useState("");
const [isLogging, setIsLogging] = useState(false); const [isLogging, setIsLogging] = useState(false);
const ClientCarousel = dynamic(() => import('./clientCarousel'), { ssr: false }); const ClientCarousel = dynamic(() => import('./clientCarousel'), { ssr: false });
const headerRef = useRef<HTMLHeadingElement>(null);
useEffect(() => { useEffect(() => {
const loadCaptcha = async () => { const loadCaptcha = async () => {
@@ -42,7 +43,25 @@ export default function Login() {
e.preventDefault(); e.preventDefault();
const onlyDigit = /^\d{11}$/; const onlyDigit = /^\d{11}$/;
if (!onlyDigit.test(CIF)) { if (!onlyDigit.test(CIF)) {
setError('Input value must be 11 digit'); // setError('Input value must be 11 digit');
notifications.show({
withBorder: true,
color: "red",
title: "Invalid UserId",
message: "UserID must be 11 digit",
autoClose: 5000,
});
return;
}
if (!inputCaptcha) {
notifications.show({
withBorder: true,
color: "red",
title: "Invalid Captcha",
message: "Please fill the Captcha filed",
autoClose: 5000,
});
return;
} }
if (inputCaptcha !== captcha) { if (inputCaptcha !== captcha) {
notifications.show({ notifications.show({
@@ -55,6 +74,16 @@ export default function Login() {
regenerateCaptcha(); regenerateCaptcha();
return; return;
} }
if (!CIF || !psw) {
notifications.show({
withBorder: true,
color: "red",
title: "Invalid Input",
message: "Please fill UserId and Password",
autoClose: 5000,
});
return;
}
const response = await fetch('api/auth/login', { const response = await fetch('api/auth/login', {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -90,38 +119,75 @@ export default function Login() {
}); });
} }
} }
useEffect(() => {
const headerData = [
"THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.",
"कांगड़ा केन्द्रीय सहकारी बैंक सीमित",
];
let index = 0;
const interval = setInterval(() => {
index = (index + 1) % headerData.length;
if (headerRef.current) {
headerRef.current.textContent = headerData[index];
}
}, 2000);
return () => clearInterval(interval);
}, []);
return ( return (
<Providers> <Providers>
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "auto", paddingTop: "5%" }}> <div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "auto", paddingTop: "5%" }}>
{/* Header */} {/* Header */}
<Box style={{ <Box
position: 'fixed', width: '100%', height: '12%', top: 0, left: 0, zIndex: 100, style={{
display: "flex", position: 'fixed', width: '100%', height: '12%', top: 0, left: 0, zIndex: 100,
justifyContent: "flex-start", display: "flex",
background: "linear-gradient(15deg,rgba(2, 163, 85, 1) 55%, rgba(101, 101, 184, 1) 100%)", justifyContent: "flex-start",
// border: "1px solid black" background: "linear-gradient(15deg,rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
}}> }}>
<Image <Image
fit="cover"
src={logo} src={logo}
component={NextImage} component={NextImage}
fit="contain"
alt="ebanking" alt="ebanking"
style={{ width: "40%", height: "100%", objectFit: "contain", marginLeft: 0 }} style={{ width: "100%", height: "auto", flexShrink: 0 }}
/> />
<Text <Title ref={headerRef}
order={2}
style={{ style={{
fontFamily: 'Roboto',
position: 'absolute', position: 'absolute',
top: '50%', top: '30%',
left: '64%', left: '7%',
color: 'White',
transition: "opacity 0.5s ease-in-out",
}}
>
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
</Title>
<Text size="sm" c="white"
style={{
backgroundColor: '#1f1f14',
fontFamily: 'Roboto',
position: 'absolute',
top: '59%',
left: '72%',
color: 'white', color: 'white',
textShadow: '1px 1px 2px blue', textShadow: '1px 1px 2px blue',
}} }}
> >
{/* <IconBuildingBank/> */} Head Office : Dharmshala, District: Kangra(H.P), Pin: 176215
Head Office : Dharmshala, District: Kangra(H.P), Pincode: 176215
</Text> </Text>
{/* <Box style={{ position: "absolute", right: "1rem", top: "50%", transform: 'translateY(-50%)' }}>
<Tooltip
label='Head Office : Dharmshala, District: Kangra(H.P), Pin: 176215'
position="right"
withArrow>
<IconBuildingBank size={40} style={{ cursor: "pointer", color: "white" }} />
</Tooltip>
</Box> */}
</Box> </Box>
<div style={{ marginTop: '10px' }}> <div style={{ marginTop: '10px' }}>
{/* Movable text */} {/* Movable text */}
<Box <Box
@@ -148,7 +214,7 @@ export default function Login() {
KCC Bank never asks for User Id,Passwords and Pins through email or phone. 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. 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, user ID and password. 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. If you had shared 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. Inform the Bank/branch in which your account is maintained for resetting your password.
</Text> </Text>
<style> <style>
@@ -164,8 +230,7 @@ export default function Login() {
<div style={{ <div style={{
display: "flex", height: "75vh", overflow: "hidden", position: "relative", display: "flex", height: "75vh", overflow: "hidden", position: "relative",
// background: 'linear-gradient(to right, #02081eff, #0a3d91)' // background: 'linear-gradient(to right, #02081eff, #0a3d91)'
background:'linear-gradient(179deg,rgba(1, 13, 18, 1) 49%, rgba(181, 196, 187, 1) 87%)' background: 'linear-gradient(179deg,rgba(1, 13, 18, 1) 49%, rgba(77, 82, 79, 1) 87%)'
// background:'linear-gradient(179deg,rgba(1, 7, 10, 1) 48%, rgba(87, 94, 89, 1) 87%)'
}}> }}>
<div style={{ flex: 1, backgroundColor: "#c1e0f0", position: "relative" }}> <div style={{ flex: 1, backgroundColor: "#c1e0f0", position: "relative" }}>
<Image <Image
@@ -176,8 +241,8 @@ export default function Login() {
style={{ width: "100%", height: "100%" }} style={{ width: "100%", height: "100%" }}
/> />
</div> </div>
<Box w={{ base: "100%", md: "50%" }} p="lg"> <Box w={{ base: "100%", md: "45%" }} p="lg">
<Card shadow="md" padding="xl" radius="md" style={{ maxWidth: 400, margin: "0 auto", height: '68vh' }}> <Card shadow="md" padding="xl" radius="md" style={{ maxWidth: 550, margin: "0 auto", height: '68vh' }}>
<form onSubmit={handleLogin}> <form onSubmit={handleLogin}>
<TextInput <TextInput
label="User ID" label="User ID"
@@ -187,21 +252,20 @@ export default function Login() {
const input = e.currentTarget.value.replace(/\D/g, ""); const input = e.currentTarget.value.replace(/\D/g, "");
if (input.length <= 11) SetCIF(input); if (input.length <= 11) SetCIF(input);
}} }}
error={error} withAsterisk
required
/> />
<PasswordInput <PasswordInput
label="Password" label="Password"
placeholder="Enter your password" placeholder="Enter your password"
value={psw} value={psw}
onChange={(e) => SetPsw(e.currentTarget.value)} onChange={(e) => SetPsw(e.currentTarget.value)}
required withAsterisk
mt="sm" mt="sm"
/> />
<Box style={{ textAlign: "right"}}> <Box style={{ textAlign: "right" }}>
<Anchor <Anchor
style={{ fontSize: "14px", color: "#1c7ed6", cursor: "pointer" }} style={{ fontSize: "14px", color: "#1c7ed6", cursor: "pointer" }}
onClick={() => router.push("/ValidateUser")} onClick={() => router.push("/ValidateUser")}
> >
Forgot Password? Forgot Password?
</Anchor> </Anchor>
@@ -209,14 +273,13 @@ export default function Login() {
<Group mt="sm" align="center"> <Group mt="sm" align="center">
<Box style={{ backgroundColor: "#fff", fontSize: "18px", textDecoration: "line-through", padding: "4px 8px", fontFamily: "cursive" }}>{captcha}</Box> <Box style={{ backgroundColor: "#fff", fontSize: "18px", textDecoration: "line-through", padding: "4px 8px", fontFamily: "cursive" }}>{captcha}</Box>
<Button size="xs" variant="light" onClick={regenerateCaptcha}>Refresh</Button> <Button size="xs" variant="light" onClick={regenerateCaptcha}>Refresh</Button>
</Group> </Group>
<TextInput <TextInput
label="Enter CAPTCHA" label="Enter CAPTCHA"
placeholder="Enter above text" placeholder="Enter above text"
value={inputCaptcha} value={inputCaptcha}
onChange={(e) => setInputCaptcha(e.currentTarget.value)} onChange={(e) => setInputCaptcha(e.currentTarget.value)}
required withAsterisk
mt="sm" mt="sm"
/> />
<Button type="submit" fullWidth mt="md" disabled={isLogging}> <Button type="submit" fullWidth mt="md" disabled={isLogging}>
@@ -227,12 +290,12 @@ export default function Login() {
</Box> </Box>
</div> </div>
{/* Carousel and Notes */} {/* Carousel and Notes */}
<Flex direction={{ base: "column", md: "row" }} mt="lg" p="lg"> <Flex direction={{ base: "column", md: "row" }} mt="md" px="md" py="sm" gap="sm">
<Box w={{ base: "100%", md: "60%" }}> <Box w={{ base: "100%", md: "85%" }}>
<ClientCarousel /> <ClientCarousel />
</Box> </Box>
<Box w={{ base: "100%", md: "40%" }} p="md" style={{ textAlign: "center" }}> <Box w={{ base: "100%", md: "25%" }} p="md" style={{ textAlign: "center", border: '1px solid #5c5c3d' }}>
<Title order={2}>Security Notes :</Title> <Title order={2}> <IconShieldLockFilled />Security Notes :</Title>
<Text mt="sm" size="md">When you Login, Your User Id and Password travels in an encrypted and highly secured mode.</Text> <Text mt="sm" size="md">When you Login, Your User Id and Password travels in an encrypted and highly secured mode.</Text>
<Text mt="sm" fs="italic">For more information on Products and Services, Please Visit</Text> <Text mt="sm" fs="italic">For more information on Products and Services, Please Visit</Text>
<Anchor href="http://www.kccb.in/"> http://www.kccb.in/</Anchor> <Anchor href="http://www.kccb.in/"> http://www.kccb.in/</Anchor>