From 3444291e252da6b45e1e30d1d46e0b4d82dec529 Mon Sep 17 00:00:00 2001 From: "nabanita.jana" Date: Thu, 28 Aug 2025 10:13:44 +0530 Subject: [PATCH] feat(settings):add all settings pages --- .../settings/change_login_password/page.tsx | 348 ++++++++++-------- .../settings/change_txn_password/page.tsx | 263 +++++++++++++ src/app/(main)/settings/layout.tsx | 4 +- src/app/(main)/settings/page.tsx | 148 +++++++- .../(main)/settings/set_txn_password/page.tsx | 255 +++++++++++++ 5 files changed, 850 insertions(+), 168 deletions(-) create mode 100644 src/app/(main)/settings/change_txn_password/page.tsx create mode 100644 src/app/(main)/settings/set_txn_password/page.tsx diff --git a/src/app/(main)/settings/change_login_password/page.tsx b/src/app/(main)/settings/change_login_password/page.tsx index e86ff17..2d2c781 100644 --- a/src/app/(main)/settings/change_login_password/page.tsx +++ b/src/app/(main)/settings/change_login_password/page.tsx @@ -1,235 +1,265 @@ "use client"; import React, { useEffect, useState } from "react"; -import { - TextInput, - PasswordInput, - Button, - Title, - Paper, - Box, - Text, - Group, - Grid, -} from "@mantine/core"; +import { TextInput, PasswordInput, Button, Title, Paper, Group, Box } from "@mantine/core"; import { notifications } from "@mantine/notifications"; -import { IconEye, IconEyeOff, IconLock } from "@tabler/icons-react"; -// import CaptchaImage from "@/app/SetPassword/CaptchaImage"; +import { IconLock } from "@tabler/icons-react"; import { generateCaptcha } from "@/app/captcha"; -const ChangePassword: React.FC = () => { +export default function ChangePassword() { const [oldPassword, setOldPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [captcha, setCaptcha] = useState(""); - const [showOld, setShowOld] = useState(false); - const [showNew, setShowNew] = useState(false); - const [showConfirm, setShowConfirm] = useState(false); - // const [captchaCode] = useState("X7pK9"); - const [inputCaptcha, setInputCaptcha] = useState(""); - const [captchaInput, setCaptchaInput] = useState(''); - const [captchaError, setCaptchaError] = useState(''); + const [captchaInput, setCaptchaInput] = useState(""); + const [otp, setOtp] = useState(""); + const [otpValidated, setOtpValidated] = useState(false); + + const [step, setStep] = useState<"form" | "otp" | "final">("form"); // ✅ steps control const [passwordHistory] = useState(["Pass@1234", "OldPass@123", "MyPass#2023"]); const icon = ; - useEffect(() => { - const loadCaptcha = async () => { - const newCaptcha = await generateCaptcha(); - setCaptcha(newCaptcha); - }; - loadCaptcha(); + regenerateCaptcha(); }, []); - const regenerateCaptcha = () => { - // setCaptcha(generateCaptcha()); - const loadCaptcha = async () => { - const newCaptcha = await generateCaptcha(); - setCaptcha(newCaptcha); - }; - loadCaptcha(); - setInputCaptcha(""); + 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 = () => { - // 1. Check all mandatory fields - if (!oldPassword || !newPassword || !confirmPassword || !captcha) { + // 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; + } + + const actualOldPassword = "Pass@123"; + if (oldPassword !== actualOldPassword) { + notifications.show({ + title: "Old Password Incorrect", + message: "Entered old password does not match.", + color: "red", + }); + return; + } + + if (!validatePasswordPolicy(newPassword)) { + notifications.show({ + title: "Invalid Password", + message: + "Password must be at least 8 characters and contain alphanumeric and special characters.", + color: "red", + }); + return; + } + + if (passwordHistory.includes(newPassword)) { + notifications.show({ + title: "Password Reused", + message: "New password must be different from the last 3 passwords.", + 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 + setStep("otp"); notifications.show({ - title: "Missing Field", - message: "Please fill all mandatory fields.", - color: "red", + title: "OTP Sent", + message: "An OTP has been sent to your registered mobile.", + color: "blue", }); return; } - // 2. Validate old password (simulate real check) - const actualOldPassword = "OldPass@123"; // Simulated stored password - if (oldPassword !== actualOldPassword) { + // Step 2 → validate OTP + if (step === "otp") { + if (otp !== "123456") { + notifications.show({ + title: "Invalid OTP", + message: "Please enter the correct OTP.", + color: "red", + }); + return; + } + + setOtpValidated(true); + setStep("final"); notifications.show({ - title: "Old Password Incorrect", - message: "Entered old password does not match.", - color: "red", + title: "OTP Verified", + message: "OTP has been successfully verified.", + color: "green", }); return; } - // 3. New password validation - if (!validatePasswordPolicy(newPassword)) { + // Step 3 → Final Change Password + if (step === "final") { notifications.show({ - title: "Invalid Password", - message: - "Password must be at least 8 characters and contain alphanumeric and special characters.", - color: "red", + title: "Password Changed", + message: "Your password has been successfully updated.", + color: "green", }); - return; + resetForm(); } + }; - if (passwordHistory.includes(newPassword)) { - notifications.show({ - title: "Password Reused", - message: "New password must be different from the last 3 passwords.", - color: "red", - }); - return; - } - - // 4. Confirm password check - if (newPassword !== confirmPassword) { - notifications.show({ - title: "Password Mismatch", - message: "Confirm password does not match new password.", - color: "red", - }); - return; - } - - - // else if (captchaInput !== captcha) { - // setCaptchaError("Incorrect CAPTCHA."); - // return; - // } - // // Success - // notifications.show({ - // title: "Success", - // message: "Your password has been reset successfully.", - // color: "green", - // }); - - if (captchaInput !== captcha) { - setCaptchaError("Incorrect CAPTCHA."); - return; - } - - // ✅ Clear error if CAPTCHA is correct - setCaptchaError(""); - - // Success - notifications.show({ - title: "Success", - message: "Your password has been reset successfully.", - color: "green", - }); - + const resetForm = () => { + setOldPassword(""); + setNewPassword(""); + setConfirmPassword(""); + setCaptchaInput(""); + setOtp(""); + setOtpValidated(false); + setStep("form"); + regenerateCaptcha(); }; return ( - - - + <Paper shadow="sm" radius="md" p="md" withBorder> + <Title order={3} mb="sm"> Change Login Password - {/* Scrollable form section */} -
+ {/* Scrollable form area */} +
setOldPassword(e.currentTarget.value)} - visible={showOld} - onVisibilityChange={setShowOld} - required + withAsterisk mb="xs" + readOnly={step !== "form"} /> + + setNewPassword(e.currentTarget.value)} + withAsterisk + mb="xs" + readOnly={step !== "form"} + /> - setNewPassword(e.currentTarget.value)} - visible={showNew} - onVisibilityChange={setShowNew} - required - mb="xs" - /> + setConfirmPassword(e.currentTarget.value)} + withAsterisk + rightSection={icon} + mb="sm" + readOnly={step !== "form"} + onCopy={(e) => e.preventDefault()} + onPaste={(e) => e.preventDefault()} + onCut={(e) => e.preventDefault()} + /> - setConfirmPassword(e.currentTarget.value)} - visible={showConfirm} - onVisibilityChange={setShowConfirm} - required - rightSection={icon} - mb="sm" - - onCopy={(e) => e.preventDefault()} - onPaste={(e) => e.preventDefault()} - onCut={(e) => e.preventDefault()} - /> + {/* CAPTCHA */} -
- -
- {/* CAPTCHA Image */} -
- {/* */} +
+ +
+
+ {captcha}
- {/* Refresh Button */} - {/* TextInput */} setCaptchaInput(e.currentTarget.value)} - required - style={{ height: 30, flexGrow: 1 }} + withAsterisk + style={{ flexGrow: 1 }} + readOnly={step !== "form"} />
- -

- {captchaError || '\u00A0'} -

+ {step !== "form" && ( + setOtp(e.currentTarget.value)} + withAsterisk + mt="sm" + maxLength={6} + readOnly={otpValidated} + /> + )} +
- {/* Fixed Save Button */} - - + {/* Buttons */} + + + + - ); -}; - -export default ChangePassword; +} diff --git a/src/app/(main)/settings/change_txn_password/page.tsx b/src/app/(main)/settings/change_txn_password/page.tsx new file mode 100644 index 0000000..7b01af7 --- /dev/null +++ b/src/app/(main)/settings/change_txn_password/page.tsx @@ -0,0 +1,263 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { TextInput, PasswordInput, Button, Title, Paper, Group, Box } from "@mantine/core"; +import { notifications } from "@mantine/notifications"; +import { IconLock } from "@tabler/icons-react"; +import { generateCaptcha } from "@/app/captcha"; + +export default function ChangePassword() { + 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 [otpValidated, setOtpValidated] = useState(false); + + const [step, setStep] = useState<"form" | "otp" | "final">("form"); // ✅ steps control + const [passwordHistory] = useState(["Pass@1234", "OldPass@123", "MyPass#2023"]); + const icon = ; + + useEffect(() => { + regenerateCaptcha(); + }, []); + + 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 = () => { + // 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; + } + + const actualOldPassword = "Pass@123"; + if (oldPassword !== actualOldPassword) { + notifications.show({ + title: "Old Password Incorrect", + message: "Entered old password does not match.", + color: "red", + }); + return; + } + + if (!validatePasswordPolicy(newPassword)) { + notifications.show({ + title: "Invalid Password", + message: + "Password must be at least 8 characters and contain alphanumeric and special characters.", + color: "red", + }); + return; + } + + if (passwordHistory.includes(newPassword)) { + notifications.show({ + title: "Password Reused", + message: "New password must be different from the last 3 passwords.", + 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 + 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") { + if (otp !== "123456") { + 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; + } + + // Step 3 → Final Change Password + if (step === "final") { + notifications.show({ + title: "Password Changed", + message: "Your password has been successfully updated.", + color: "green", + }); + resetForm(); + } + }; + + const resetForm = () => { + setOldPassword(""); + setNewPassword(""); + setConfirmPassword(""); + setCaptchaInput(""); + setOtp(""); + setOtpValidated(false); + setStep("form"); + regenerateCaptcha(); + }; + + return ( + + + Change Transaction Password + + + {/* Scrollable form area */} +
+ setOldPassword(e.currentTarget.value)} + withAsterisk + mb="xs" + /> + + setNewPassword(e.currentTarget.value)} + withAsterisk + mb="xs" + /> + + setConfirmPassword(e.currentTarget.value)} + withAsterisk + rightSection={icon} + mb="sm" + readOnly={step !== "form"} + onCopy={(e) => e.preventDefault()} + onPaste={(e) => e.preventDefault()} + onCut={(e) => e.preventDefault()} + /> + + + + {/* CAPTCHA */} + +
+ +
+
+ {captcha} +
+ + + + setCaptchaInput(e.currentTarget.value)} + withAsterisk + style={{ flexGrow: 1 }} + readOnly={step !== "form"} + /> +
+
+ {step !== "form" && ( + setOtp(e.currentTarget.value)} + withAsterisk + mt="sm" + maxLength={6} + readOnly={otpValidated} + /> + )} + +
+ + {/* Buttons */} + + + + +
+ ); +} diff --git a/src/app/(main)/settings/layout.tsx b/src/app/(main)/settings/layout.tsx index ba3d1f4..2cf0bfa 100644 --- a/src/app/(main)/settings/layout.tsx +++ b/src/app/(main)/settings/layout.tsx @@ -13,8 +13,8 @@ export default function Layout({ children }: { children: React.ReactNode }) { const links = [ { label: "View Profile", href: "/settings" }, { label: "Change Login Password", href: "/settings/change_login_password" }, - { label: "Change transaction Password", href: "/settings/change_transaction_password" }, - { label: "Set transaction Password", href: "/settings/set_transaction_password" }, + { label: "Change transaction Password", href: "/settings/change_txn_password" }, + { label: "Set transaction Password", href: "/settings/set_txn_password" }, ]; useEffect(() => { const token = localStorage.getItem("access_token"); diff --git a/src/app/(main)/settings/page.tsx b/src/app/(main)/settings/page.tsx index b79c0c1..d70d0d2 100644 --- a/src/app/(main)/settings/page.tsx +++ b/src/app/(main)/settings/page.tsx @@ -1,11 +1,145 @@ "use client"; -import { Divider, Stack, Text } from '@mantine/core'; -import React, { useEffect, useState } from 'react'; + +import React, { useEffect, useState } from "react"; +import { + Paper, + Title, + Grid, + Text, + Anchor, + Divider, + Loader, + Center, +} from "@mantine/core"; +import { notifications } from "@mantine/notifications"; import { useRouter } from "next/navigation"; -export default function settings() { - return( - Hii - ) +// Response structure from backend +interface ProfileData { + custname: string; + cifNumber: string; + stBranchNo: string; + custdob: string; + mobileno: string; + id: string; + custaddress: string; + pincode: string; -} \ No newline at end of file +} + +export default function ViewProfile() { + const router = useRouter(); + const [profileData, setProfileData] = useState(null); + const [loading, setLoading] = useState(true); + + // Fetch API with same style as RootLayout + async function handleFetchProfile() { + try { + const token = localStorage.getItem("access_token"); + const response = await fetch("/api/customer", { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + + if (!response.ok) { + notifications.show({ + withBorder: true, + color: "red", + title: "Error", + message: "Unable to fetch profile details", + autoClose: 5000, + }); + return; + } + + const data = await response.json(); + + // If backend returns array → take first record + if (Array.isArray(data) && data.length > 0) { + setProfileData(data[0]); + } + } catch (err) { + notifications.show({ + withBorder: true, + color: "red", + title: "Please try again later", + message: "Something went wrong while fetching profile", + autoClose: 5000, + }); + } finally { + setLoading(false); + } + } + + useEffect(() => { + handleFetchProfile(); + }, []); + + const Row = ({ + label, + value, + link, + }: { + label: string; + value: string; + link?: string; + }) => ( + + + + {label} + + + + {link ? ( + + {value} + + ) : ( + {value} + )} + + + ); + + return ( + + + View Profile + + + + {loading ? ( +
+ +
+ ) : profileData ? ( + <> + + + + + + + + + + + + ) : ( + + No profile data found. + + )} +
+ ); +} diff --git a/src/app/(main)/settings/set_txn_password/page.tsx b/src/app/(main)/settings/set_txn_password/page.tsx new file mode 100644 index 0000000..707ee62 --- /dev/null +++ b/src/app/(main)/settings/set_txn_password/page.tsx @@ -0,0 +1,255 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { TextInput, PasswordInput, Button, Title, Paper, Group, Box } from "@mantine/core"; +import { notifications } from "@mantine/notifications"; +import { IconLock } from "@tabler/icons-react"; +import { generateCaptcha } from "@/app/captcha"; + +export default function ChangePassword() { + 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 [otpValidated, setOtpValidated] = useState(false); + + const [step, setStep] = useState<"form" | "otp" | "final">("form"); // ✅ steps control + const [passwordHistory] = useState(["Pass@1234", "OldPass@123", "MyPass#2023"]); + const icon = ; + + useEffect(() => { + regenerateCaptcha(); + }, []); + + 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 = () => { + // Step 1 → validate form + if (step === "form") { + if (!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 be at least 8 characters and contain alphanumeric and special characters.", + color: "red", + }); + return; + } + + if (passwordHistory.includes(newPassword)) { + notifications.show({ + title: "Password Reused", + message: "New password must be different from the last 3 passwords.", + 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 + 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") { + if (otp !== "123456") { + 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; + } + + // Step 3 → Final Change Password + if (step === "final") { + notifications.show({ + title: "Password Changed", + message: "Your password has been successfully updated.", + color: "green", + }); + resetForm(); + } + }; + + const resetForm = () => { + setOldPassword(""); + setNewPassword(""); + setConfirmPassword(""); + setCaptchaInput(""); + setOtp(""); + setOtpValidated(false); + setStep("form"); + regenerateCaptcha(); + }; + + return ( + + + Set Transaction Password + + + {/* Scrollable form area */} +
+ {/* setOldPassword(e.currentTarget.value)} + withAsterisk + mb="xs" + /> */} + + setNewPassword(e.currentTarget.value)} + withAsterisk + mb="xs" + /> + + setConfirmPassword(e.currentTarget.value)} + withAsterisk + rightSection={icon} + mb="sm" + readOnly={step !== "form"} + onCopy={(e) => e.preventDefault()} + onPaste={(e) => e.preventDefault()} + onCut={(e) => e.preventDefault()} + /> + + + + {/* CAPTCHA */} + +
+ +
+
+ {captcha} +
+ + + + setCaptchaInput(e.currentTarget.value)} + withAsterisk + style={{ flexGrow: 1 }} + readOnly={step !== "form"} + /> +
+
+ {step !== "form" && ( + setOtp(e.currentTarget.value)} + withAsterisk + mt="sm" + maxLength={6} + readOnly={otpValidated} + /> + )} + +
+ + {/* Buttons */} + + + + +
+ ); +}