diff --git a/src/app/ChangePassword/page.tsx b/src/app/ChangePassword/page.tsx
new file mode 100644
index 0000000..84f6b94
--- /dev/null
+++ b/src/app/ChangePassword/page.tsx
@@ -0,0 +1,408 @@
+"use client";
+
+import React, { useEffect, useState } from "react";
+import {
+ Text,
+ Button,
+ TextInput,
+ PasswordInput,
+ Title,
+ Card,
+ Box,
+ Image,
+ Group,
+} from "@mantine/core";
+import { notifications } from "@mantine/notifications";
+import { useRouter } from "next/navigation";
+import NextImage from "next/image";
+import logo from "@/app/image/logo1.jpg";
+import changePwdImage from "@/app/image/change_password.jpg"; // reuse background illustration
+import { generateCaptcha } from "@/app/captcha";
+import { IconLock, IconRefresh } from "@tabler/icons-react";
+
+export default function ChangePassword() {
+ const router = useRouter();
+
+ // states
+ 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 [generatedOtp, setGeneratedOtp] = useState("");
+ const [otpValidated, setOtpValidated] = useState(false);
+ const [countdown, setCountdown] = useState(180);
+ const [timerActive, setTimerActive] = useState(false);
+ const [step, setStep] = useState<"form" | "otp" | "final">("form");
+ const icon = ;
+
+ // OTP generation
+ const handleGenerateOtp = async () => {
+ const value = "123456"; // replace with backend OTP if available
+ setGeneratedOtp(value);
+ setCountdown(180);
+ setTimerActive(true);
+ return value;
+ };
+
+ 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]);
+
+ // captcha
+ const regenerateCaptcha = async () => {
+ const newCaptcha = await generateCaptcha();
+ setCaptcha(newCaptcha);
+ setCaptchaInput("");
+ };
+
+ // password policy
+ const validatePasswordPolicy = (password: string) => {
+ return /^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,15}$/.test(
+ password
+ );
+ };
+
+ // handle submit
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ 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:
+ "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.",
+ 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;
+ }
+
+ await handleGenerateOtp();
+ setStep("otp");
+ notifications.show({
+ title: "OTP Sent",
+ message: "An OTP has been sent to your registered mobile.",
+ color: "blue",
+ });
+ return;
+ }
+
+ if (step === "otp") {
+ if (otp !== generatedOtp) {
+ notifications.show({
+ title: "Invalid OTP",
+ message: "Please enter the correct OTP.",
+ color: "red",
+ });
+ return;
+ }
+ setOtpValidated(true);
+ setStep("final");
+ notifications.show({
+ title: "OTP Verified",
+ message: "OTP has been successfully verified.",
+ color: "green",
+ });
+ return;
+ }
+
+ if (step === "final") {
+ const token = localStorage.getItem("access_token");
+ if (!token) {
+ router.push("/login");
+ return;
+ }
+ try {
+ const response = await fetch("/api/auth/change/login_password", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify({
+ OldLPsw: oldPassword,
+ newLPsw: newPassword,
+ confirmLPsw: confirmPassword,
+ }),
+ });
+
+ if (response.status === 401) {
+ localStorage.clear();
+ sessionStorage.clear();
+ router.push("/login");
+ return;
+ }
+
+ const result = await response.json();
+ if (!response.ok) {
+ notifications.show({
+ title: "Failed",
+ message: result.error || "Failed to change login password",
+ color: "red",
+ autoClose: false,
+ });
+ } else {
+ notifications.show({
+ title: "Success",
+ message: "Login password changed successfully.",
+ color: "green",
+ });
+ resetForm();
+ router.push("/login");
+ }
+ } 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 (
+
+ {/* Header */}
+
+
+
+ THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
+
+
+
+ {/* Body */}
+
+
+
+
+
+ Change Password
+
+
+
+
+
+ Note: Login 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.
+
+
+
+
+
+
+ {/* Footer */}
+
+
+ © 2025 Kangra Central Co-Operative Bank
+
+
+
+ );
+}
diff --git a/src/app/image/change_password.jpg b/src/app/image/change_password.jpg
new file mode 100644
index 0000000..4494c8f
Binary files /dev/null and b/src/app/image/change_password.jpg differ
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
index 7eee408..ed630a3 100644
--- a/src/app/login/page.tsx
+++ b/src/app/login/page.tsx
@@ -10,6 +10,7 @@ import frontPage from '@/app/image/ib_front_1.jpg';
import dynamic from 'next/dynamic';
import { generateCaptcha } from '@/app/captcha';
import { IconShieldLockFilled } from "@tabler/icons-react";
+import dayjs from "dayjs";
export default function Login() {
const router = useRouter();
@@ -141,6 +142,24 @@ export default function Login() {
const token = data.token;
localStorage.setItem("access_token", token);
localStorage.setItem("pswExpiryDate", data.loginPswExpiry);
+
+ // Password Expiry Logic todo
+ if (data.loginPswExpiry && (dayjs(data.loginPswExpiry)).diff(dayjs(), "day") < 0) {
+ notifications.show({
+ withBorder: true,
+ color: "orange",
+ title: "Password Expired",
+ message: "Your password has expired, please set a new one.",
+ autoClose: 4000,
+ });
+
+
+
+ router.push("/ChangePassword");
+ return;
+ }
+
+
if (data.FirstTimeLogin === true) {
router.push("/SetPassword")
}