From 8acc9430b31b2ba92146e4851af344818304c289 Mon Sep 17 00:00:00 2001 From: rockyeverlast Date: Wed, 9 Apr 2025 16:23:36 +0530 Subject: [PATCH] Profiule API integrated --- dev-dist/sw.js | 2 +- src/Pages/ForgotPassword.tsx | 18 +- src/Pages/Profile/ChangePassword.tsx | 268 ++++++++++++++++++--- src/Pages/Profile/EnterOTP.tsx | 320 +++++++++++++++++++------- src/Pages/Profile/EnterPassword.tsx | 148 +++++++++--- src/Pages/Profile/Profile.tsx | 81 +++---- src/Pages/SetNewPassword.tsx | 81 +++++-- src/Pages/VerifyEnterOTP.tsx | 74 +++++- src/Redux/Service/profile.password.ts | 69 ++++++ src/Redux/Store.tsx | 3 + src/components/EditableInput.tsx | 3 + 11 files changed, 846 insertions(+), 221 deletions(-) create mode 100644 src/Redux/Service/profile.password.ts diff --git a/dev-dist/sw.js b/dev-dist/sw.js index 01e5a6f..69b4925 100644 --- a/dev-dist/sw.js +++ b/dev-dist/sw.js @@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict'; "revision": "3ca0b8505b4bec776b69afdba2768812" }, { "url": "index.html", - "revision": "0.96d38pjn7gg" + "revision": "0.4sre1e6vpfo" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/src/Pages/ForgotPassword.tsx b/src/Pages/ForgotPassword.tsx index 0a3233d..449da3e 100644 --- a/src/Pages/ForgotPassword.tsx +++ b/src/Pages/ForgotPassword.tsx @@ -31,6 +31,10 @@ const ForgotPassword = () => { } = useForm(); const onSubmit = handleSubmit(async (data) => { + const username = import.meta.env.VITE_USER_NAME || ""; // Replace with actual username + const password = import.meta.env.VITE_PASSWORD || ""; // Replace with actual password + const basicAuth = `${username}:${password}`; // Encode to Base64 + setIsLoading(true); try { const res = await axios.post( @@ -38,12 +42,24 @@ const ForgotPassword = () => { { mobile_number: Number(data.mobileNumber), }, + { + headers: { + Authorization: `Basic ${basicAuth}`, + "Content-Type": "application/json", + }, + } ); if (res.status === 200) { navigate(`/forgot-password/verify?phone=${data.mobileNumber}`) } else { - alert(res.data.message || "Something went wrong"); + // alert(res.data.message || "Something went wrong"); + toaster.create({ + // title: error?.response?.data?.message, + title: res.data.message || "Something went wrong", + type: "error", + }) + setIsLoading(false); } console.log("============", res); diff --git a/src/Pages/Profile/ChangePassword.tsx b/src/Pages/Profile/ChangePassword.tsx index d3a74bf..95e3693 100644 --- a/src/Pages/Profile/ChangePassword.tsx +++ b/src/Pages/Profile/ChangePassword.tsx @@ -1,46 +1,246 @@ -import { Field, Input, Stack } from "@chakra-ui/react"; +import { DialogCloseTrigger, Field, IconButton, Input, Stack, Text } from "@chakra-ui/react"; import { Button } from "../../components/ui/button"; import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../components/ui/dialog"; -import EnterPassword from "./EnterPassword"; -function Changepassword() { +// import EnterPassword from "./EnterPassword"; +import { useForm } from "react-hook-form"; +import { useEffect, useState } from "react"; +import { toaster } from "../../components/ui/toaster"; +import { useNewPasswordSetMutation } from "../../Redux/Service/profile.password"; +import { LuEye, LuEyeOff } from "react-icons/lu"; +import { InputGroup } from "../../components/ui/input-group"; + +type EnterPasswordProps = { + onClose: () => void; + isOpen: boolean; +}; + +type FormData = { + new_password: string; + confirm_password: string; +}; + +function Changepassword({ onClose, isOpen }: EnterPasswordProps) { + const [showOldPassword, setShowOldPassword] = useState(false); + const [showNewPassword, setShowNewPassword] = useState(false); + const [newPasswordSet] = useNewPasswordSetMutation() + const [isLoading, setIsLoading] = useState(false); + const { + register, + handleSubmit, + formState: { errors }, + setError, + clearErrors, + watch, + reset, + } = useForm({ + defaultValues: { + new_password: '', + confirm_password: '' + } + }); + + const newPassword = watch("new_password"); + const confirmPassword = watch("confirm_password"); + + useEffect(() => { + if (newPassword && confirmPassword && newPassword !== confirmPassword) { + setError("confirm_password", { + type: "manual", + message: "Passwords do not match" + }); + } else if (confirmPassword) { + clearErrors("confirm_password"); + } + }, [newPassword, confirmPassword, setError, clearErrors]); + + + const onSubmit = async (data: FormData) => { + + if (data.new_password === '' || data.confirm_password === '') { + return + } + + if (data.new_password !== data.confirm_password) { + setError("confirm_password", { + type: "manual", + message: "Passwords do not match" + }); + return; + } + + console.log('ERROR', errors) + console.log('Change submitted:', data); + + clearErrors("confirm_password"); + + setIsLoading(true); + const payload = { + new_password: data.new_password, + confirm_password: data.confirm_password + } + + try { + const res = await newPasswordSet(payload).unwrap() + + if (res.status === 'success') { + toaster.create({ + title: "Password changed Successfully", + type: "success", + }); + onClose() + } else { + toaster.create({ + title: res.data.message || "Invalid password", + type: "error", + }); + + } + reset() + } catch (error: any) { + toaster.create({ + title: error.data.message || "Something went wrong", + type: "error", + }); + } finally { + setIsLoading(false); + } + }; return ( - - - - - - - + !open && onClose()} > - - CHANGE PASSWORD - - - - - New password - - - Confirm password - + + + - - - - - - - + + CHANGE PASSWORD + +
+ + + + New password + setShowOldPassword(!showOldPassword)} + _hover={{ bg: "transparent" }} + height={'fit-content'} + mr={2} + > + {showOldPassword ? : } + + }> + + + {/* setShowPassword(!showPassword)} + > + {showPassword ? : } + */} + {errors.new_password && ( + + {errors.new_password.message} + + )} + + + + Confirm password + setShowNewPassword(!showNewPassword)} + _hover={{ bg: "transparent" }} + height={'fit-content'} + mr={2} + > + {showNewPassword ? : } + + }> + + value === newPassword || "Passwords do not match", + })} + /> + + + {errors.confirm_password && ( + + {errors.confirm_password.message} + + )} + + + + + + {/* */} + + +
+ + + + ) } diff --git a/src/Pages/Profile/EnterOTP.tsx b/src/Pages/Profile/EnterOTP.tsx index 1e07d2c..7d27cbe 100644 --- a/src/Pages/Profile/EnterOTP.tsx +++ b/src/Pages/Profile/EnterOTP.tsx @@ -1,11 +1,87 @@ -import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../components/ui/dialog" +import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle } from "../../components/ui/dialog" import { Box, Input, Stack, Text } from "@chakra-ui/react" import { Button } from "../../components/ui/button"; -import { useState } from "react"; -import { BiSolidEdit } from "react-icons/bi"; +import { useEffect, useState } from "react"; +// import { BiSolidEdit } from "react-icons/bi"; +import { Toaster, toaster } from "../../components/ui/toaster"; +import { useGetProfileQuery, useResendOtpMutation, useVerifyOTPMutation } from "../../Redux/Service/profile.password"; +import { useForm } from "react-hook-form"; +import Changepassword from "./ChangePassword"; -function EnterOTP() { - const [otp, setOtp] = useState(["", "", "", ""]); +type EnterOTPProps = { + onClose: () => void; + isOpen: boolean; +}; + +type FormData = { + otp: string[]; +}; + + +function EnterOTP({ onClose, isOpen }: EnterOTPProps) { + const [isLoading, setIsLoading] = useState(false); + const [timer, setTimer] = useState(60); + const [isResendDisabled, setIsResendDisabled] = useState(true); + const [verifyOTP] = useVerifyOTPMutation() + const [otpSuccess, setOtpSuccess] = useState(false); + const [resendOtp] = useResendOtpMutation() + const { data } = useGetProfileQuery() + const id = data?.data.id + + + const { + register, + handleSubmit, + formState: { errors }, + reset, + } = useForm({ + defaultValues: { + otp: ["", "", "", ""] // Initialize with empty strings for each digit + } + }); + + useEffect(() => { + let interval: NodeJS.Timeout; + + if (timer > 0 && isResendDisabled) { + interval = setInterval(() => { + setTimer((prevTimer) => prevTimer - 1); + }, 1000); + } else if (timer === 0) { + setIsResendDisabled(false); + } + + return () => clearInterval(interval); + }, [timer, isResendDisabled]); + + const handleResendOTP = async () => { + setIsResendDisabled(true); + setTimer(60); // Reset timer to 1 minute + + try { + + const res = await resendOtp({ mobile_number: Number(id) }).unwrap() + + if (res.status === 200) { + toaster.create({ + title: "OTP resent successfully", + type: "success", + }); + } else { + toaster.create({ + title: res.data.message || "Failed to resend OTP", + type: "error", + }); + setIsResendDisabled(false); // Enable button if failed + } + } catch (error: any) { + toaster.create({ + title: error.response?.data?.message || "Failed to resend OTP", + type: "error", + }); + setIsResendDisabled(false); // Enable button if error + } + }; // Handle change for OTP inputs const handleChange = (e: React.ChangeEvent, index: number): void => { @@ -15,94 +91,174 @@ function EnterOTP() { if (/[^0-9]/.test(value)) return; // Update the OTP state with the new value - const newOtp = [...otp]; - newOtp[index] = value; - setOtp(newOtp); + // const newOtp = [...otp]; + // newOtp[index] = value; + // setOtp(newOtp); - // Move focus to the next input automatically - if (value && index < otp.length - 1) { + if (value && index < 3) { const nextInput = document.getElementById(`otp-input-${index + 1}`) as HTMLInputElement; if (nextInput) nextInput.focus(); } + + // Move focus to the next input automatically + // if (value && index < otp.length - 1) { + // const nextInput = document.getElementById(`otp-input-${index + 1}`) as HTMLInputElement; + // if (nextInput) nextInput.focus(); + // } + + if (value === "" && index > 0) { + const prevInput = document.getElementById(`otp-input-${index - 1}`) as HTMLInputElement; + if (prevInput) prevInput.focus(); + } + }; + + + const onSubmit = async (data: FormData) => { + console.log('ERROR', errors) + if (data.otp.length !== 4) { + toaster.create({ + title: "OTP must be 4 digits", + type: "error", + }); + return; + } + + const fullOtp = data.otp.join(''); + console.log('OTP submitted:', fullOtp); + + setIsLoading(true); + try { + const res = await verifyOTP({ otp: fullOtp }).unwrap() + + if (res.status === 'success') { + toaster.create({ + title: "OTP Verified Successfully", + type: "success", + }); + setOtpSuccess(true); + reset(); + } else { + toaster.create({ + title: res.data.message || "Invalid OTP", + type: "error", + }); + } + reset() + } catch (error: any) { + toaster.create({ + title: error.data?.message || "Something went wrong", + type: "error", + }); + setOtpSuccess(false); + } finally { + setIsLoading(false); + } }; return ( - - - - - - - - - - ENTER OTP - - - - OTP has been send to your E-mail Address - - - 9619565889 - - - - - - {/* 4 OTP Inputs */} - {otp.map((digit, index) => ( - handleChange(e, index)} - maxLength={1} // Only allows 1 character per input - /> - ))} - - - - Resend OTP - - - - - + <> + !open && onClose()}> + {/* - - {/* */} - - + */} + + + ENTER OTP + +
+ + OTP has been sent successfully + {/* + + 9619565889 + */} + + + + + {/* 4 OTP Inputs */} + {Array.from({ length: 4 }).map((_, index) => ( + handleChange(e, index) + })} + /> + ))} + + + {isResendDisabled ? ( + + Resend OTP in {timer} seconds + + ) : ( + + Resend OTP + + )} + + + + + + +
+ +
+ + {otpSuccess && ( { + setOtpSuccess(false); + onClose() + }} + isOpen={otpSuccess} + />)} + + ) } diff --git a/src/Pages/Profile/EnterPassword.tsx b/src/Pages/Profile/EnterPassword.tsx index 18eb230..ee9e442 100644 --- a/src/Pages/Profile/EnterPassword.tsx +++ b/src/Pages/Profile/EnterPassword.tsx @@ -1,53 +1,127 @@ -import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../components/ui/dialog" -import { Field, Input, Stack } from "@chakra-ui/react" +import { DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from "../../components/ui/dialog" +import { Field, Input, Stack, Text } from "@chakra-ui/react" +import { useForm } from "react-hook-form"; import { Button } from "../../components/ui/button"; import EnterOTP from "./EnterOTP"; +import { useProfilePasswordMutation } from "../../Redux/Service/profile.password"; +import { useState } from "react"; +import { Toaster, toaster } from "../../components/ui/toaster"; + +type FormData = { + password: string; +}; + function EnterPassword() { + const [profilePassword] = useProfilePasswordMutation(); + const [isSuccess, setIsSuccess] = useState(false); + const [isDialogOpen, setIsDialogOpen] = useState(false); + + const { + register, + handleSubmit, + formState: { errors }, + reset, + } = useForm(); + + const onSubmit = async (data: FormData) => { + if (!data.password) { + return + } + try { + await profilePassword({ password: data.password }).unwrap(); + setIsSuccess(true); + reset(); + setIsDialogOpen(false); + // Handle success (e.g., show a success message or redirect) + } catch (error: any) { + // Handle error (e.g., show an error message) + toaster.create({ + title: error?.data.message || "Invalid password", + type: "error", + }); + console.error("Password change failed:", error.data.message); + setIsSuccess(false); + } + }; return ( + <> - - - - - - - setIsDialogOpen(details.open)} > - - ENTER PASSWORD - + + - - - - Password - + + - - - {/* */} - - + + - {/* */} - - + + + + + {isSuccess && ( { + setIsSuccess(false); + setIsDialogOpen(false); + }} + isOpen={isSuccess} + />) + } + + ) } diff --git a/src/Pages/Profile/Profile.tsx b/src/Pages/Profile/Profile.tsx index fffe5c9..6b111fe 100644 --- a/src/Pages/Profile/Profile.tsx +++ b/src/Pages/Profile/Profile.tsx @@ -3,62 +3,63 @@ import { FaCamera } from "react-icons/fa"; import EditableInput from "../../components/EditableInput"; import MainFrame from "../../components/MainFrame"; import { Field } from "../../components/ui/field"; -import Changepassword from "./ChangePassword"; +// import Changepassword from "./ChangePassword"; +import EnterPassword from "./EnterPassword"; +import { useGetProfileQuery } from "../../Redux/Service/profile.password"; const Profile = () => { + const { data } = useGetProfileQuery() + console.log('PROFILE DATA:', data?.data); + return ( - + - + - - - alert("Avatar clicked!")}> - - - - - - - - - Ritesh Pandey - - Employee ID: #1245679 - - + + + alert("Avatar clicked!")}> + + + + + + + + + {`${data?.data?.first_name.charAt(0).toUpperCase()}${data?.data.first_name.slice(1)}`} + + {/* + Employee ID: #1245679 + */} + - - - + + {/* */} + - - - - + + + + - + - + - - - - - - + diff --git a/src/Pages/SetNewPassword.tsx b/src/Pages/SetNewPassword.tsx index fea65af..46f66e8 100644 --- a/src/Pages/SetNewPassword.tsx +++ b/src/Pages/SetNewPassword.tsx @@ -2,6 +2,7 @@ import { Box, Center, HStack, + IconButton, Image, Input, Stack, @@ -14,12 +15,18 @@ import { useNavigate } from "react-router-dom"; import logo from "../assets/logo.svg"; import { Button } from "../components/ui/button"; import { toaster, Toaster } from "../components/ui/toaster"; +import { InputGroup } from "../components/ui/input-group"; +import { LuEye, LuEyeOff } from "react-icons/lu"; const SetNewPassword = () => { const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); + const queryParams = new URLSearchParams(window.location.search); + const id = queryParams.get("id"); + const [showOldPassword, setShowOldPassword] = useState(false); + const [showNewPassword, setShowNewPassword] = useState(false); const handlePasswordSubmit = async () => { // Validation @@ -42,13 +49,13 @@ const SetNewPassword = () => { setIsLoading(true); try { - const res = await axios.post(`${import.meta.env.VITE_API_URL}/reset-password`, { + const res = await axios.post(`${import.meta.env.VITE_API_URL}/update-password`, { password: password, confirm_password: confirmPassword, - // id: id + id: Number(id) }); - if (res.status === 200) { + if (res.data.status === 'success') { toaster.create({ title: "Password updated successfully", type: "success", @@ -90,26 +97,58 @@ const SetNewPassword = () => { New password - setPassword(e.target.value)} - /> + setShowOldPassword(!showOldPassword)} + _hover={{ bg: "transparent" }} + height={'fit-content'} + mr={2} + > + {showOldPassword ? : } + + }> + setPassword(e.target.value)} + /> + Confirm password - setConfirmPassword(e.target.value)} - /> + setShowNewPassword(!showNewPassword)} + _hover={{ bg: "transparent" }} + height={'fit-content'} + mr={2} + > + {showNewPassword ? : } + + }> + setConfirmPassword(e.target.value)} + /> +