diff --git a/app/src/components/Dashboard/SidePannel.tsx b/app/src/components/Dashboard/SidePannel.tsx index 89ce5ea..0fa30d6 100644 --- a/app/src/components/Dashboard/SidePannel.tsx +++ b/app/src/components/Dashboard/SidePannel.tsx @@ -38,11 +38,13 @@ const SidePannel: React.FC = ({ setActiveTab, activeTab }) => { const handleCreateNewProject = async () => { const token = localStorage.getItem("token"); - const refreshToken = localStorage.getItem("refreshToken") - console.log('refreshToken: ', refreshToken); + const refreshToken = localStorage.getItem("refreshToken"); + console.log("refreshToken: ", refreshToken); try { const projectId = generateProjectId(); - useSocketStore.getState().initializeSocket(email, organization, token, refreshToken); + useSocketStore + .getState() + .initializeSocket(email, organization, token, refreshToken); //API for creating new Project // const project = await createProject( @@ -59,12 +61,12 @@ const SidePannel: React.FC = ({ setActiveTab, activeTab }) => { projectUuid: projectId, }; - console.log('projectSocket: ', projectSocket); + console.log("projectSocket: ", projectSocket); if (projectSocket) { const handleResponse = (data: any) => { if (data.message === "Project created successfully") { - setLoadingProgress(1) - navigate(`/${data.data.projectId}`); + setLoadingProgress(1); + navigate(`/projects/${data.data.projectId}`); } projectSocket.off("v1-project:response:add", handleResponse); // Clean up }; @@ -88,7 +90,8 @@ const SidePannel: React.FC = ({ setActiveTab, activeTab }) => {
{userName - ? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase() + ? userName.charAt(0).toUpperCase() + + userName.slice(1).toLowerCase() : "Anonymous"}
@@ -162,10 +165,14 @@ const SidePannel: React.FC = ({ setActiveTab, activeTab }) => { Settings -
{ - localStorage.clear(); - navigate("/"); - }}> +
{ + localStorage.clear(); + navigate("/"); + }} + > Log out
@@ -179,4 +186,4 @@ const SidePannel: React.FC = ({ setActiveTab, activeTab }) => { ); }; -export default SidePannel; \ No newline at end of file +export default SidePannel; diff --git a/app/src/components/forgotPassword/EmailInput.tsx b/app/src/components/forgotPassword/EmailInput.tsx index d3ca158..4df4d19 100644 --- a/app/src/components/forgotPassword/EmailInput.tsx +++ b/app/src/components/forgotPassword/EmailInput.tsx @@ -1,30 +1,39 @@ -import React from 'react'; +import React from "react"; interface Props { - email: string; - setEmail: (value: string) => void; - onSubmit: () => void; + email: string; + setEmail: (value: string) => void; + onSubmit: (e: React.FormEvent) => void; } const EmailInput: React.FC = ({ email, setEmail, onSubmit }) => { - return ( -
-

Forgot password

-

- Enter your email for the verification process, we will send a 4-digit code to your email. -

-
{ e.preventDefault(); onSubmit(); }}> - setEmail(e.target.value)} - required - /> - -
-
- ); + return ( +
+

Forgot password

+

+ Enter your email for the verification process, we will send a 4-digit + code to your email. +

+
{ + e.preventDefault(); + onSubmit(e); + }} + > + setEmail(e.target.value)} + required + /> + +
+
+ ); }; export default EmailInput; diff --git a/app/src/components/forgotPassword/OTPInput.tsx b/app/src/components/forgotPassword/OTPInput.tsx index e14b45e..3c6fef8 100644 --- a/app/src/components/forgotPassword/OTPInput.tsx +++ b/app/src/components/forgotPassword/OTPInput.tsx @@ -1,10 +1,95 @@ -import React, { useState, useRef, useEffect } from 'react'; +// import React, { useState, useRef, useEffect } from "react"; -const OTPInput: React.FC<{ length?: number; onComplete: (otp: string) => void }> = ({ length = 4, onComplete }) => { - const [otpValues, setOtpValues] = useState(Array(length).fill('')); +// const OTPInput: React.FC<{ +// length?: number; +// onComplete: (otp: string) => void; +// code: string; +// }> = ({ length = 4, onComplete, code }) => { +// const [otpValues, setOtpValues] = useState(Array(length).fill("")); +// const inputsRef = useRef<(HTMLInputElement | null)[]>([]); +// useEffect(() => { +// if (code) { +// console.log("code: ", code); + +// const codeString = String(code); // convert number → string +// setOtpValues(codeString.split("")); +// onComplete(codeString); +// } +// }, [code, length]); +// // Auto focus first input on mount +// useEffect(() => { +// inputsRef.current[0]?.focus(); +// }, []); + +// const handleChange = (value: string, index: number) => { +// if (/^[0-9]?$/.test(value)) { +// const newOtp = [...otpValues]; +// newOtp[index] = value; +// setOtpValues(newOtp); + +// if (value && index < length - 1) { +// inputsRef.current[index + 1]?.focus(); +// } + +// if (newOtp.every((digit) => digit !== "")) { +// console.log('newOtp.join(""): ', newOtp.join("")); +// onComplete(newOtp.join("")); +// } +// } +// }; + +// const handleKeyDown = ( +// e: React.KeyboardEvent, +// index: number +// ) => { +// if (e.key === "Backspace" && !otpValues[index] && index > 0) { +// inputsRef.current[index - 1]?.focus(); +// } +// }; + +// return ( +//
+// {otpValues.map((value, index) => ( +// handleChange(e.target.value, index)} +// onKeyDown={(e) => handleKeyDown(e, index)} +// ref={(el) => (inputsRef.current[index] = el)} +// /> +// ))} +//
+// ); +// }; + +// export default OTPInput; +import React, { useState, useRef, useEffect } from "react"; + +const OTPInput: React.FC<{ + length?: number; + onComplete: (otp: string) => void; + code: string | number; +}> = ({ length = 4, onComplete, code }) => { + const [otpValues, setOtpValues] = useState(Array(length).fill("")); const inputsRef = useRef<(HTMLInputElement | null)[]>([]); - // Auto focus first input on mount + // ✅ Pre-fill inputs if code is passed + useEffect(() => { + if (code) { + const codeString = String(code); + const filled = codeString.split("").slice(0, length); + const padded = filled.concat(Array(length - filled.length).fill("")); + setOtpValues(padded); + if (filled.length === length) { + onComplete(filled.join("")); + } + } + }, [code, length, onComplete]); + + // ✅ Focus first input on mount useEffect(() => { inputsRef.current[0]?.focus(); }, []); @@ -19,14 +104,18 @@ const OTPInput: React.FC<{ length?: number; onComplete: (otp: string) => void }> inputsRef.current[index + 1]?.focus(); } - if (newOtp.every((digit) => digit !== '')) { - onComplete(newOtp.join('')); + // ✅ Only trigger onComplete when all digits are filled + if (newOtp.every((digit) => digit !== "")) { + onComplete(newOtp.join("")); } } }; - const handleKeyDown = (e: React.KeyboardEvent, index: number) => { - if (e.key === 'Backspace' && !otpValues[index] && index > 0) { + const handleKeyDown = ( + e: React.KeyboardEvent, + index: number + ) => { + if (e.key === "Backspace" && !otpValues[index] && index > 0) { inputsRef.current[index - 1]?.focus(); } }; @@ -39,7 +128,7 @@ const OTPInput: React.FC<{ length?: number; onComplete: (otp: string) => void }> type="text" className="otp-input" maxLength={1} - value={value} + value={value ?? ""} onChange={(e) => handleChange(e.target.value, index)} onKeyDown={(e) => handleKeyDown(e, index)} ref={(el) => (inputsRef.current[index] = el)} diff --git a/app/src/components/forgotPassword/OTP_Verification.tsx b/app/src/components/forgotPassword/OTP_Verification.tsx index 8af7836..450f210 100644 --- a/app/src/components/forgotPassword/OTP_Verification.tsx +++ b/app/src/components/forgotPassword/OTP_Verification.tsx @@ -1,57 +1,77 @@ -import React, { useState } from 'react'; -import OTPInput from './OTPInput'; +import React, { FormEvent, useState } from "react"; +import OTPInput from "./OTPInput"; interface Props { - email: string; - timer: number; - setCode: (value: string) => void; - onSubmit: () => void; - resendCode: () => void; + email: string; + code: string; + timer: number; + setCode: (value: string) => void; + onSubmit: (e: React.FormEvent) => void; + resendCode: () => void; } -const OTPVerification: React.FC = ({ email, timer, setCode, onSubmit, resendCode }) => { - const [otp, setOtp] = useState(''); +const OTPVerification: React.FC = ({ + email, + timer, + setCode, + onSubmit, + resendCode, + code, +}) => { + const [otp, setOtp] = useState(""); - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - console.log('otp.length: ', otp.length); - if (otp.length === 4) { - onSubmit(); - } else { - alert('Please enter the 4-digit code'); - } - }; + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); - return ( -
-

Verification

-

- Enter the 4-digit code sent to {email}. -

-
- { setOtp(code); setCode(code); }} /> -
- {timer > 0 - ? `${String(Math.floor(timer / 60)).padStart(2, '0')}:${String(timer % 60).padStart(2, '0')}` - : ''} -
- - -
0 ? 'disabled' : ''}`} - onClick={timer === 0 ? resendCode : undefined} - style={{ cursor: timer === 0 ? 'pointer' : 'not-allowed', opacity: timer === 0 ? 1 : 0.5 }} - > - If you didn’t receive a code, Resend -
+ if (otp.length === 4) { + onSubmit(e); + } else { + alert("Please enter the 4-digit code"); + } + }; + + return ( +
+

Verification

+

+ Enter the 4-digit code sent to {email}. +

+
+ { + setOtp(code); + setCode(codes); + }} + code={code} + /> +
+ {timer > 0 + ? `${String(Math.floor(timer / 60)).padStart(2, "0")}:${String( + timer % 60 + ).padStart(2, "0")}` + : ""}
- ); + + +
0 ? "disabled" : ""}`} + onClick={timer === 0 ? resendCode : undefined} + style={{ + cursor: timer === 0 ? "pointer" : "not-allowed", + opacity: timer === 0 ? 1 : 0.5, + }} + > + If you didn’t receive a code, Resend +
+
+ ); }; export default OTPVerification; diff --git a/app/src/components/forgotPassword/PasswordSetup.tsx b/app/src/components/forgotPassword/PasswordSetup.tsx index 4c6af1c..f8f5658 100644 --- a/app/src/components/forgotPassword/PasswordSetup.tsx +++ b/app/src/components/forgotPassword/PasswordSetup.tsx @@ -1,77 +1,82 @@ -import React, { useState } from 'react'; -import { EyeIcon } from '../icons/ExportCommonIcons'; +import React, { useState } from "react"; +import { EyeIcon } from "../icons/ExportCommonIcons"; interface Props { - newPassword: string; - confirmPassword: string; - setNewPassword: (value: string) => void; - setConfirmPassword: (value: string) => void; - onSubmit: () => void; + newPassword: string; + confirmPassword: string; + setNewPassword: (value: string) => void; + setConfirmPassword: (value: string) => void; + onSubmit: (e: React.FormEvent) => void; } const PasswordSetup: React.FC = ({ - newPassword, - confirmPassword, - setNewPassword, - setConfirmPassword, - onSubmit + newPassword, + confirmPassword, + setNewPassword, + setConfirmPassword, + onSubmit, }) => { - const [showNewPassword, setShowNewPassword] = useState(false); - const [showConfirmPassword, setShowConfirmPassword] = useState(false); + const [showNewPassword, setShowNewPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); - return ( -
-

New Password

-

Set the new password for your account so you can login and access all features.

-
{ - e.preventDefault(); - if (newPassword !== confirmPassword) { - alert('Passwords do not match'); - return; - } - onSubmit(); - }} - > -
- setNewPassword(e.target.value)} - required - /> - -
- -
- setConfirmPassword(e.target.value)} - required - /> - -
- - -
+ return ( +
+

New Password

+

+ Set the new password for your account so you can login and access all + features. +

+
{ + e.preventDefault(); + if (newPassword !== confirmPassword) { + alert("Passwords do not match"); + return; + } + onSubmit(e); + }} + > +
+ setNewPassword(e.target.value)} + required + /> +
- ); + +
+ setConfirmPassword(e.target.value)} + required + /> + +
+ + +
+
+ ); }; export default PasswordSetup; diff --git a/app/src/pages/ForgotPassword.tsx b/app/src/pages/ForgotPassword.tsx index 8681996..c2d8374 100644 --- a/app/src/pages/ForgotPassword.tsx +++ b/app/src/pages/ForgotPassword.tsx @@ -1,92 +1,134 @@ -import React, { useState, useEffect } from 'react'; -import { LogoIconLarge } from '../components/icons/Logo'; -import EmailInput from '../components/forgotPassword/EmailInput'; -import OTPVerification from '../components/forgotPassword/OTP_Verification'; -import PasswordSetup from '../components/forgotPassword/PasswordSetup'; -import ConfirmationMessage from '../components/forgotPassword/ConfirmationMessgae'; +import React, { useState, useEffect, FormEvent } from "react"; +import { LogoIconLarge } from "../components/icons/Logo"; +import EmailInput from "../components/forgotPassword/EmailInput"; +import OTPVerification from "../components/forgotPassword/OTP_Verification"; +import PasswordSetup from "../components/forgotPassword/PasswordSetup"; +import ConfirmationMessage from "../components/forgotPassword/ConfirmationMessgae"; +import { changePasswordApi } from "../services/factoryBuilder/signInSignUp/changePasswordApi"; +import { checkEmailApi } from "../services/factoryBuilder/signInSignUp/checkEmailApi"; +import { verifyOtpApi } from "../services/factoryBuilder/signInSignUp/verifyOtpApi"; const ForgotPassword: React.FC = () => { - const [step, setStep] = useState(1); - const [email, setEmail] = useState(''); - const [code, setCode] = useState(''); - const [newPassword, setNewPassword] = useState(''); - const [confirmPassword, setConfirmPassword] = useState(''); - const [timer, setTimer] = useState(30); + const [step, setStep] = useState(1); + const [email, setEmail] = useState(""); + const [code, setCode] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [timer, setTimer] = useState(30); + const [resetToken, setResetToken] = useState(""); - useEffect(() => { - let countdown: NodeJS.Timeout; - if (step === 2 && timer > 0) { - countdown = setTimeout(() => setTimer(prev => prev - 1), 1000); - } - return () => clearTimeout(countdown); - }, [step, timer]); - - const handleSubmitEmail = () => { - setStep(2); - setTimer(30); + useEffect(() => { + let countdown: NodeJS.Timeout; + if (step === 2 && timer > 0) { + countdown = setTimeout(() => setTimer((prev) => prev - 1), 1000); } - const resendCode = () => { - // TODO: call API to resend code - setTimer(30); - }; + return () => clearTimeout(countdown); + }, [step, timer]); - return ( -
-
-
- -
+ const resendCode = async () => { + // TODO: call API to resend code + setTimer(30); + try { + const emailResponse = await checkEmailApi(email); + if (emailResponse.message == "OTP sent Successfully") { + setCode(emailResponse.OTP); + } + } catch {} + }; + const handleEmailSubmit = async (e: FormEvent) => { + setTimer(30); + try { + const emailResponse = await checkEmailApi(email); - {step === 1 && ( - <> - - Login - + if (emailResponse.message == "OTP sent Successfully") { + setStep(2); + setCode(emailResponse.OTP); + } + } catch {} + }; - )} + const handleOTPSubmit = async (e: FormEvent) => { + try { + const otpResponse = await verifyOtpApi(email, Number(code)); + if (otpResponse.message == "OTP verified successfully") { + setResetToken(otpResponse.resetToken); + setStep(3); + } else { + alert(otpResponse.message); + } + } catch {} + }; - {step === 2 && ( - <> - setStep(3)} - resendCode={resendCode} - /> - Login - + const handlePasswordSubmit = async (e: FormEvent) => { + try { + const passwordResponse = await changePasswordApi( + resetToken, + newPassword, + confirmPassword + ); + if (passwordResponse.message === "Password reset successfull!!") { + setStep(4); + } + } catch {} + }; - )} - - - {step === 3 && ( - <> - setStep(4)} - /> - Login - - - )} - - - {step === 4 && } - - -
+ return ( +
+
+
+
- ); + + {step === 1 && ( + <> + + + Login + + + )} + + {step === 2 && ( + <> + + + Login + + + )} + + {step === 3 && ( + <> + + + Login + + + )} + + {step === 4 && } +
+
+ ); }; export default ForgotPassword; diff --git a/app/src/pages/UserAuth.tsx b/app/src/pages/UserAuth.tsx index 8c45b77..06352a0 100644 --- a/app/src/pages/UserAuth.tsx +++ b/app/src/pages/UserAuth.tsx @@ -34,7 +34,7 @@ const UserAuth: React.FC = () => { useEffect(() => { initializeFingerprint(); - }, []) + }, []); const { userId, organization } = getUserData(); @@ -47,7 +47,6 @@ const UserAuth: React.FC = () => { setError(""); setOrganization(organization); setUserName(res.message.name); - // console.log(' res.userId: ', res.message.userId); localStorage.setItem("userId", res.message.userId); localStorage.setItem("email", res.message.email); localStorage.setItem("userName", res.message.name); @@ -55,33 +54,46 @@ const UserAuth: React.FC = () => { localStorage.setItem("refreshToken", res.message.refreshToken); try { - const projects = await recentlyViewed(organization, res.message.userId); + const projects = await recentlyViewed( + organization, + res.message.userId + ); if (res.message.isShare) { if (Object.values(projects.RecentlyViewed).length > 0) { - const recent_opend_projectID = (Object.values(projects?.RecentlyViewed || {})[0] as any)?._id; - if (Object.values(projects?.RecentlyViewed).filter((val: any) => val._id == recent_opend_projectID)) { - setLoadingProgress(1) - navigate(`/projects/${recent_opend_projectID}`) + const recent_opend_projectID = ( + Object.values(projects?.RecentlyViewed || {})[0] as any + )?._id; + if ( + Object.values(projects?.RecentlyViewed).filter( + (val: any) => val._id == recent_opend_projectID + ) + ) { + setLoadingProgress(1); + navigate(`/projects/${recent_opend_projectID}`); } else { - navigate("/Dashboard") + navigate("/Dashboard"); } } else { setLoadingProgress(1); navigate("/Dashboard"); } } - } catch (error) { console.error("Error fetching recent projects:", error); } } else if (res.message === "User Not Found!!! Kindly signup...") { setError("Account not found"); - } else if (res.message === "Email & Password is invalid...Check the credentials") { - setError(res.message) - } else if (res.message === "Already LoggedIn on another browser....Please logout!!!") { + } else if ( + res.message === "Email & Password is invalid...Check the credentials" + ) { + setError(res.message); + } else if ( + res.message === + "Already LoggedIn on another browser....Please logout!!!" + ) { setError("Already logged in on another browser. Please logout first."); navigate("/"); - setError("") + setError(""); // setError(""); // setOrganization(organization); // setUserName(res.ForceLogoutData.userName); @@ -179,6 +191,7 @@ const UserAuth: React.FC = () => { name="email" value={email} placeholder="Email" + autoComplete="email" onChange={(e) => setEmail(e.target.value)} required /> @@ -188,6 +201,7 @@ const UserAuth: React.FC = () => { type={showPassword ? "text" : "password"} value={password} placeholder="Password" + autoComplete="current-password" onChange={(e) => setPassword(e.target.value)} required /> @@ -201,7 +215,11 @@ const UserAuth: React.FC = () => {
- {isSignIn && Forgot password ?} + {isSignIn && ( + + Forgot password ? + + )} {!isSignIn && (
diff --git a/app/src/services/factoryBuilder/signInSignUp/changePasswordApi.ts b/app/src/services/factoryBuilder/signInSignUp/changePasswordApi.ts new file mode 100644 index 0000000..bc42635 --- /dev/null +++ b/app/src/services/factoryBuilder/signInSignUp/changePasswordApi.ts @@ -0,0 +1,29 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const changePasswordApi = async ( + resetToken: string, + newPassword: string, + confirmPassword: string +) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/V1/Auth/reset-password/${resetToken}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ newPassword, confirmPassword }), + } + ); + + const result = await response.json(); + + return result; + } catch (error) { + echo.error("Failed to create password"); + if (error instanceof Error) { + return { error: error.message }; + } else { + return { error: "An unknown error occurred" }; + } + } +}; diff --git a/app/src/services/factoryBuilder/signInSignUp/checkEmailApi.ts b/app/src/services/factoryBuilder/signInSignUp/checkEmailApi.ts new file mode 100644 index 0000000..6d8138a --- /dev/null +++ b/app/src/services/factoryBuilder/signInSignUp/checkEmailApi.ts @@ -0,0 +1,25 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const checkEmailApi = async (Email: string) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/V1/Auth/forgetPassword`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ Email }), + } + ); + + const result = await response.json(); + + return result; + } catch (error) { + echo.error("Failed to create password"); + if (error instanceof Error) { + return { error: error.message }; + } else { + return { error: "An unknown error occurred" }; + } + } +}; diff --git a/app/src/services/factoryBuilder/signInSignUp/verifyOtpApi.ts b/app/src/services/factoryBuilder/signInSignUp/verifyOtpApi.ts new file mode 100644 index 0000000..9b289e5 --- /dev/null +++ b/app/src/services/factoryBuilder/signInSignUp/verifyOtpApi.ts @@ -0,0 +1,28 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const verifyOtpApi = async (Email: string, Otp: number) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/V1/Auth/validate-otp`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + Email, + otp: Number(Otp), + }), + } + ); + + const result = await response.json(); + + return result; + } catch (error) { + echo.error("Failed to create password"); + if (error instanceof Error) { + return { error: error.message }; + } else { + return { error: "An unknown error occurred" }; + } + } +};