Files
IB/src/app/(main)/settings/change_txn_password/page.tsx
2025-12-06 13:54:20 +05:30

382 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import React, { useEffect, useState } from "react";
import { TextInput, PasswordInput, Button, Title, Paper, Group, Box, Text } from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { IconLock, IconRefresh } from "@tabler/icons-react";
import { generateCaptcha } from "@/app/captcha";
import { useRouter } from "next/navigation";
import { sendOtp, verifyOtp } from "@/app/_util/otp";
export default function ChangePassword() {
const router = useRouter();
const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [captcha, setCaptcha] = useState("");
const [captchaInput, setCaptchaInput] = useState("");
const [otp, setOtp] = useState("");
const [countdown, setCountdown] = useState(180);
const [timerActive, setTimerActive] = useState(false);
const [generatedOtp, setGeneratedOtp] = useState('');
const [otpValidated, setOtpValidated] = useState(false);
const [step, setStep] = useState<"form" | "otp" | "final">("form");
const icon = <IconLock size={18} stroke={1.5} />;
const [showOtpField, setShowOtpField] = useState(false);
// const handleGenerateOtp = async () => {
// const value = "123456"; // Or generate a random OTP
// setGeneratedOtp(value);
// setCountdown(180);
// setTimerActive(true);
// return value;
// };
async function handleSendOtp() {
const mobileNumber = localStorage.getItem('remitter_mobile_no');
if (!mobileNumber) {
notifications.show({
title: 'Error',
message: 'Mobile number not found.Contact to administrator',
color: 'red',
});
return;
}
try {
await sendOtp({ type: 'CHANGE_TPWORD' });
setShowOtpField(true);
setCountdown(180);
setTimerActive(true);
} catch (err: any) {
console.error('Send OTP failed', err);
notifications.show({
title: 'Error',
message: err.message || 'Send OTP failed.Please try again later.',
color: 'red',
});
}
}
async function handleVerifyOtp() {
try {
await verifyOtp(otp);
return true;
} catch {
return false;
}
}
useEffect(() => {
regenerateCaptcha();
}, []);
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]);
const regenerateCaptcha = async () => {
const newCaptcha = await generateCaptcha();
setCaptcha(newCaptcha);
setCaptchaInput("");
};
const validatePasswordPolicy = (password: string) => {
return /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$/.test(password);
};
const handleSubmit = async () => {
// Step 1 → validate form
if (step === "form") {
if (!oldPassword || !newPassword || !confirmPassword || !captchaInput) {
notifications.show({
title: "Missing Field",
message: "Please fill all mandatory fields.",
color: "red",
});
return;
}
if (!validatePasswordPolicy(newPassword)) {
notifications.show({
title: "Invalid Password",
message:
"Your new password must be 815 characters long and contain at least one number and one special character.",
color: "red",
});
return;
}
if (newPassword !== confirmPassword) {
notifications.show({
title: "Password Mismatch",
message: "Confirm password does not match new password.",
color: "red",
});
return;
}
if (captchaInput !== captcha) {
notifications.show({
title: "Invalid Captcha",
message: "Please enter correct Captcha",
color: "red",
});
regenerateCaptcha();
return;
}
// Passed → move to OTP
await handleSendOtp();
setStep("otp");
notifications.show({
title: "OTP Sent",
message: "An OTP has been sent to your registered mobile.",
color: "blue",
});
return;
}
// Step 2 → validate OTP
if (step === "otp") {
const verified = await handleVerifyOtp();
if (!verified) {
notifications.show({
title: "Invalid OTP",
message: "The OTP entered does not match",
color: "red",
});
return;
}
setOtpValidated(true);
setStep("final");
notifications.show({
title: "OTP Verified",
message: "OTP has been successfully verified.",
color: "green",
});
return;
}
// Step 3 → Final Change Password
if (step === "final") {
const token = localStorage.getItem("access_token");
if (!token) {
router.push("/login");
return;
}
try {
const response = await fetch("/api/auth/change/transaction_password", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "IB",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
OldTPsw: oldPassword,
newTPsw: newPassword,
confirmTPsw: confirmPassword
}),
});
if (response.status === 401) {
localStorage.removeItem("access_token");
localStorage.clear();
sessionStorage.clear();
router.push("/login");
return;
}
const result = await response.json();
// console.log(result);
if (!response.ok) {
notifications.show({
title: "Failed",
message: result.error || "Failed to set transaction password",
color: "Red",
autoClose: false,
});
}
if (response.ok) {
notifications.show({
title: "Success",
message: "Transaction password change successfully.",
color: "green",
});
}
resetForm();
} catch (err: any) {
notifications.show({
title: "Error",
message: err.message || "Server error, please try again later",
color: "red",
});
}
}
};
const resetForm = () => {
setOldPassword("");
setNewPassword("");
setConfirmPassword("");
setCaptchaInput("");
setOtp("");
setOtpValidated(false);
setStep("form");
regenerateCaptcha();
};
return (
<Paper shadow="sm" radius="md" p="md" withBorder h={500}>
<Title order={4} mb="sm">
Change Transaction Password
</Title>
{/* Scrollable form area */}
<div style={{ overflowY: "auto", maxHeight: "280px" }}>
<Group grow>
<PasswordInput
label="Old Password"
placeholder="Enter old password"
value={oldPassword}
onChange={(e) => setOldPassword(e.currentTarget.value)}
withAsterisk
mb="xs"
/>
<PasswordInput
label="New Password"
placeholder="Enter new password"
value={newPassword}
onChange={(e) => setNewPassword(e.currentTarget.value)}
withAsterisk
mb="xs"
/>
<PasswordInput
label="Confirm Transaction Password"
placeholder="Re-enter new Transaction password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.currentTarget.value)}
withAsterisk
rightSection={icon}
mb="sm"
readOnly={step !== "form"}
onCopy={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()}
/>
</Group>
{/* CAPTCHA */}
<div style={{ marginTop: 5 }}>
<label style={{ display: "block", marginBottom: 4, fontSize: "14px" }}>
Enter CAPTCHA <span style={{ color: "red" }}>*</span>
</label>
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 5 }}>
<div
style={{
fontSize: "18px",
letterSpacing: "3px",
background: "#f3f4f6",
padding: "6px 12px",
borderRadius: "6px",
border: "1px solid #d1d5db",
userSelect: "none",
textDecoration: "line-through",
fontFamily: "Verdana",
}}
>
{captcha}
</div>
<Button
size="xs"
variant="outline"
onClick={regenerateCaptcha}
style={{ height: 30, padding: "0 10px", lineHeight: "1" }}
disabled={step !== "form"}
>
Refresh
</Button>
<TextInput
placeholder="Enter above text"
value={captchaInput}
onChange={(e) => setCaptchaInput(e.currentTarget.value)}
withAsterisk
style={{ flexGrow: 1 }}
readOnly={step !== "form"}
/>
</div>
</div>
<Group grow>
{step !== "form" && (
<>
<Group gap="xs" align="flex-end">
<PasswordInput
label="OTP"
placeholder="Enter OTP"
type="otp"
value={otp}
maxLength={6}
onChange={(e) => setOtp(e.currentTarget.value)}
withAsterisk
disabled={otpValidated}
style={{ flex: 1 }}
/>
{!otpValidated && (
timerActive ? (
<Text size="xs" c="dimmed" style={{ minWidth: "120px" }}>
Resend OTP will be enabled in{" "}
{String(Math.floor(countdown / 60)).padStart(2, "0")}:
{String(countdown % 60).padStart(2, "0")}
</Text>
) : (
<IconRefresh
size={22}
style={{ cursor: "pointer", color: "blue", marginBottom: "6px" }}
onClick={handleSendOtp}
/>
)
)}
</Group>
</>
)}
</Group>
<Group mt="md" gap="sm">
<Button onClick={handleSubmit}>
{step === "form" && "Submit"}
{step === "otp" && "Validate OTP"}
{step === "final" && "Change Password"}
</Button>
<Button variant="outline" color="gray" onClick={resetForm}>
Reset
</Button>
</Group>
</div>
<Text size="sm" style={{ marginTop: "40px" }}>
<Text span c='red' fw={600}>Note : </Text>{""}
<Text span >Your new Login password must be 815 characters long and contain at least one number and one special character.</Text>
</Text>
</Paper>
);
}