;
export const GetUserInfoDocument = {
kind: 'Document',
definitions: [
diff --git a/src/hooks/userHooks.ts b/src/hooks/userHooks.ts
index 89a4580..755b169 100644
--- a/src/hooks/userHooks.ts
+++ b/src/hooks/userHooks.ts
@@ -41,7 +41,11 @@ export const useLoadUser = () => {
refetchHandler: refetch,
});
console.error('failed retrieving user info, backing to login');
- if (!pathName.match('/login') && typeof window !== 'undefined') {
+ if (
+ !pathName.match('/login') &&
+ !pathName.match('/reset-password') &&
+ typeof window !== 'undefined'
+ ) {
router.push(`/login?orgUrl=${pathName}`);
}
},
diff --git a/src/module/auth/forgot-password-form/ForgotPasswordFormGroup.test.tsx b/src/module/auth/forgot-password-form/ForgotPasswordFormGroup.test.tsx
new file mode 100644
index 0000000..6c357a0
--- /dev/null
+++ b/src/module/auth/forgot-password-form/ForgotPasswordFormGroup.test.tsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import { render, fireEvent, waitFor } from '@testing-library/react';
+import { FormProvider, useForm } from 'react-hook-form';
+import ForgotPasswordFormGroup from './ForgotPasswordFormGroup';
+
+const localStorageMock = (() => {
+ let store: { [key: string]: string } = {};
+ return {
+ getItem: jest.fn((key) => store[key] || null),
+ setItem: jest.fn((key, value) => {
+ store[key] = value.toString();
+ }),
+ clear: jest.fn(() => {
+ store = {};
+ }),
+ };
+})();
+Object.defineProperty(window, 'localStorage', { value: localStorageMock });
+
+const Wrapper = ({ children }: { children: React.ReactNode }) => {
+ const methods = useForm();
+ return {children};
+};
+
+describe('ForgotPasswordFormGroup', () => {
+ beforeEach(() => {
+ localStorageMock.clear();
+ jest.clearAllMocks();
+ });
+
+ it('renders without crashing', () => {
+ const { getByPlaceholderText } = render(, { wrapper: Wrapper });
+ expect(getByPlaceholderText('Email')).toBeInTheDocument();
+ });
+
+ it('uses email from localStorage if available', () => {
+ localStorageMock.getItem.mockReturnValue('test@example.com');
+ const { getByPlaceholderText } = render(, { wrapper: Wrapper });
+ expect(getByPlaceholderText('Email')).toHaveAttribute('value', 'test@example.com');
+ });
+
+ it('displays an error for empty email', async () => {
+ const { getByPlaceholderText, findByText } = render(, {
+ wrapper: Wrapper,
+ });
+ const emailInput = getByPlaceholderText('Email');
+
+ fireEvent.change(emailInput, { target: { value: '' } });
+ fireEvent.blur(emailInput);
+
+ await waitFor(() => {
+ expect(findByText('This is required field')).resolves.toBeInTheDocument();
+ });
+ });
+
+ it('displays an error for invalid email format', async () => {
+ const { getByPlaceholderText, findByText } = render(, {
+ wrapper: Wrapper,
+ });
+ const emailInput = getByPlaceholderText('Email');
+
+ fireEvent.change(emailInput, { target: { value: 'invalidemail' } });
+ fireEvent.blur(emailInput);
+
+ await waitFor(() => {
+ expect(findByText('Entered value does not match email format')).resolves.toBeInTheDocument();
+ });
+ });
+
+ it('accepts valid email input', async () => {
+ const { getByPlaceholderText, queryByText } = render(, {
+ wrapper: Wrapper,
+ });
+ const emailInput = getByPlaceholderText('Email');
+
+ fireEvent.change(emailInput, { target: { value: 'valid@example.com' } });
+ fireEvent.blur(emailInput);
+
+ await waitFor(() => {
+ expect(queryByText('This is required field')).not.toBeInTheDocument();
+ expect(queryByText('Entered value does not match email format')).not.toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/module/auth/forgot-password-form/ForgotPasswordFormGroup.tsx b/src/module/auth/forgot-password-form/ForgotPasswordFormGroup.tsx
new file mode 100644
index 0000000..60df2e4
--- /dev/null
+++ b/src/module/auth/forgot-password-form/ForgotPasswordFormGroup.tsx
@@ -0,0 +1,48 @@
+import {
+ FormGroupField,
+ FormGroupIcon,
+ FormGroupLabel,
+} from '@/shared/components/form/FormElements';
+import { LastFormGroup } from '@/shared/components/account/AccountElements';
+import AccountOutlineIcon from 'mdi-react/AccountOutlineIcon';
+import FormField from '@/shared/components/form/FormHookField';
+import { emailPattern } from '@/shared/utils/helpers';
+import { EMAIL } from '@/shared/constants/storage';
+import { useFormContext } from 'react-hook-form';
+
+export default function ForgotPasswordFormGroup() {
+ const {
+ control,
+ formState: { errors },
+ } = useFormContext();
+ const localEmail = (typeof window !== 'undefined' ? localStorage.getItem(EMAIL) : '') || '';
+
+ return (
+ <>
+
+ Email
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/module/auth/forgot-password-form/FormLayout.test.tsx b/src/module/auth/forgot-password-form/FormLayout.test.tsx
new file mode 100644
index 0000000..64ad73a
--- /dev/null
+++ b/src/module/auth/forgot-password-form/FormLayout.test.tsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { MockedProvider } from '@apollo/client/testing';
+import { USER_FORGOT_PASSWORD } from '@/graphql/auth';
+import FormLayout from './FormLayout';
+import { act } from 'react-dom/test-utils';
+
+jest.mock('next/link', () => {
+ return ({ children }: { children: React.ReactNode }) => children;
+});
+
+jest.mock('@/hooks/useTitle', () => ({
+ useTitle: jest.fn(),
+}));
+
+const mocks = [
+ {
+ request: {
+ query: USER_FORGOT_PASSWORD,
+ variables: { email: 'test@example.com' },
+ },
+ result: {
+ data: {
+ forgotPassword: {
+ code: 200,
+ message: 'Password reset email sent',
+ data: null,
+ },
+ },
+ },
+ },
+];
+
+describe('FormLayout', () => {
+ it('renders without crashing', () => {
+ render(
+
+
+
+ );
+ const loginLink = screen.getByText((content, element) => {
+ if (
+ element &&
+ element.tagName.toLowerCase() === 'p' &&
+ content.includes('Back to') &&
+ content.includes('Login')
+ ) {
+ return true;
+ }
+ return false;
+ });
+ expect(loginLink).toBeInTheDocument();
+ expect(screen.getByText('Submit')).toBeInTheDocument();
+ });
+
+ it('displays form fields correctly', () => {
+ render(
+
+
+
+ );
+ expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();
+ });
+
+ it('submits form and displays success message', async () => {
+ render(
+
+
+
+ );
+
+ fireEvent.change(screen.getByPlaceholderText('Email'), {
+ target: { value: 'test@example.com' },
+ });
+
+ await act(async () => {
+ fireEvent.click(screen.getByText('Submit'));
+ });
+
+ await waitFor(
+ () => {
+ expect(screen.getByText(/If the email is associated with an account/)).toBeInTheDocument();
+ },
+ { timeout: 3000 }
+ );
+ });
+});
diff --git a/src/module/auth/forgot-password-form/FormLayout.tsx b/src/module/auth/forgot-password-form/FormLayout.tsx
new file mode 100644
index 0000000..84d4449
--- /dev/null
+++ b/src/module/auth/forgot-password-form/FormLayout.tsx
@@ -0,0 +1,82 @@
+'use client';
+
+import { useState } from 'react';
+import { useMutation } from '@apollo/client';
+import { AccountButton, AccountHaveAccount } from '@/shared/components/account/AccountElements';
+import { FormContainer } from '@/shared/components/form/FormElements';
+import { FormProvider, useForm } from 'react-hook-form';
+import Link from 'next/link';
+import styled from 'styled-components';
+import { colorBlue } from '@/styles/palette';
+import { USER_FORGOT_PASSWORD } from '@/graphql/auth';
+import { useTitle } from '@/hooks/useTitle';
+import ForgotPasswordFormGroup from './ForgotPasswordFormGroup';
+
+type FormData = { email: string };
+
+// region STYLES
+const BackToLogin = styled(AccountHaveAccount)`
+ margin-top: 0;
+`;
+
+const ResponseMessage = styled.div`
+ color: ${colorBlue};
+ margin-bottom: 14px;
+`;
+
+const FormLayout = () => {
+ useTitle('Forgot Password - BeeQuant');
+
+ const [responseMessage, setResponseMessage] = useState('');
+ const [forgotPassword] = useMutation(USER_FORGOT_PASSWORD);
+
+ const methods = useForm({
+ defaultValues: {
+ email: '',
+ },
+ });
+ const { handleSubmit } = methods;
+
+ const onSubmit = async (data: FormData) => {
+ try {
+ await forgotPassword({
+ variables: {
+ email: data.email,
+ },
+ });
+ setResponseMessage(
+ 'If the email is associated with an account, a reset email will be sent shortly. Please check your mailbox.'
+ );
+ } catch (err) {
+ console.error('Error during password reset request:', err);
+ setResponseMessage(
+ 'An error occurred while processing your request. Please try again later.'
+ );
+ }
+ };
+
+ return (
+
+
+ {responseMessage && (
+
+ {responseMessage}
+
+ )}
+
+
+ Submit
+
+
+
+
+ Back to
+
+ Login
+
+
+
+ );
+};
+
+export default FormLayout;
diff --git a/src/module/auth/forgot-password-form/forgot-password-form.component.test.tsx b/src/module/auth/forgot-password-form/forgot-password-form.component.test.tsx
new file mode 100644
index 0000000..8bae164
--- /dev/null
+++ b/src/module/auth/forgot-password-form/forgot-password-form.component.test.tsx
@@ -0,0 +1,63 @@
+import { render, screen } from '@testing-library/react';
+import { BrowserRouter as Router } from 'react-router-dom';
+import ForgotPasswordForm from './forgot-password-form.component';
+
+interface IconProps {
+ className?: string;
+}
+
+jest.mock('mdi-react/TrendingUpIcon', () => {
+ return {
+ __esModule: true,
+ default: ({ className }: IconProps) => Mocked Trending Up Icon
,
+ };
+});
+
+jest.mock('mdi-react/TrendingDownIcon', () => {
+ return {
+ __esModule: true,
+ default: ({ className }: IconProps) => (
+ Mocked Trending Down Icon
+ ),
+ };
+});
+
+jest.mock('mdi-react/AccountOutlineIcon', () => {
+ return {
+ __esModule: true,
+ default: () => Mocked Account Outline Icon
,
+ };
+});
+
+jest.mock('./FormLayout', () => ({
+ __esModule: true,
+ default: () => (
+
+
+
+
+ ),
+}));
+
+describe('ForgotPasswordForm', () => {
+ it('should render the forgot password page without crashing', () => {
+ const { container } = render(
+
+
+
+ );
+ expect(container).toBeTruthy();
+ });
+
+ it('should render the correct elements and text', () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByText(/Enter Your Email/i)).toBeInTheDocument();
+ expect(screen.getByText(/BeeQuant AI/i)).toBeInTheDocument();
+ expect(screen.getByText(/Trading smart, trading with BeeQuant AI/i)).toBeInTheDocument();
+ });
+});
diff --git a/src/module/auth/forgot-password-form/forgot-password-form.component.tsx b/src/module/auth/forgot-password-form/forgot-password-form.component.tsx
new file mode 100644
index 0000000..03e6f7a
--- /dev/null
+++ b/src/module/auth/forgot-password-form/forgot-password-form.component.tsx
@@ -0,0 +1,34 @@
+'use client';
+import {
+ AccountWrap,
+ AccountContent,
+ AccountCard,
+ AccountHead,
+ AccountLogo,
+ AccountLogoAccent,
+ AccountTitle,
+} from '@/shared/components/account/AccountElements';
+import FormLayout from './FormLayout';
+
+export default function ForgotPasswordForm() {
+ return (
+
+
+
+
+
+ Enter Your Email
+
+
+ BeeQuant
+ AI
+
+
+ Trading smart, trading with BeeQuant AI
+
+
+
+
+
+ );
+}
diff --git a/src/module/auth/login-form/LoginFormGroup.tsx b/src/module/auth/login-form/LoginFormGroup.tsx
index 5321662..f184398 100644
--- a/src/module/auth/login-form/LoginFormGroup.tsx
+++ b/src/module/auth/login-form/LoginFormGroup.tsx
@@ -68,7 +68,7 @@ export default function LoginFormGroup() {
defaultValue=""
/>
- Forgot a password?
+ Forgot password?
diff --git a/src/module/auth/reset-password-form/FormLayout.test.tsx b/src/module/auth/reset-password-form/FormLayout.test.tsx
new file mode 100644
index 0000000..8526a9c
--- /dev/null
+++ b/src/module/auth/reset-password-form/FormLayout.test.tsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { MockedProvider } from '@apollo/client/testing';
+import { USER_RESET_PASSWORD } from '@/graphql/auth';
+import FormLayout from './FormLayout';
+
+jest.mock('next/link', () => {
+ return ({ children }: { children: React.ReactNode }) => {
+ return children;
+ };
+});
+
+jest.mock('@/hooks/useTitle', () => ({
+ useTitle: jest.fn(),
+}));
+
+jest.mock('./ResetPasswordSuccess', () => {
+ return function ResetPasswordSuccess() {
+ return Reset Password Success
;
+ };
+});
+
+const mocks = [
+ {
+ request: {
+ query: USER_RESET_PASSWORD,
+ variables: {
+ input: {
+ newPassword: 'newPassword123',
+ resetToken: 'validToken',
+ },
+ },
+ },
+ result: {
+ data: {
+ resetPassword: {
+ code: 200,
+ message: 'Password reset successful',
+ data: null,
+ },
+ },
+ },
+ },
+];
+
+describe('FormLayout', () => {
+ beforeEach(() => {
+ if (window.location) {
+ delete (window as any).location;
+ }
+ window.location = { search: '?token=validToken' } as Location;
+ });
+
+ it('renders without crashing', () => {
+ render(
+
+
+
+ );
+ const loginLink = screen.getByText((content, element) => {
+ if (
+ element &&
+ element.tagName.toLowerCase() === 'p' &&
+ content.includes('Back to') &&
+ content.includes('Login')
+ ) {
+ return true;
+ }
+ return false;
+ });
+ expect(loginLink).toBeInTheDocument();
+ expect(screen.getByText('Reset Password')).toBeInTheDocument();
+ expect(screen.getByText('BeeQuant')).toBeInTheDocument();
+ expect(screen.getByText('AI')).toBeInTheDocument();
+ expect(screen.getByText('Trading smart, trading with BeeQuant AI')).toBeInTheDocument();
+ });
+
+ it('displays form fields correctly', () => {
+ render(
+
+
+
+ );
+ expect(screen.getByPlaceholderText('Password')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Confirm Password')).toBeInTheDocument();
+ });
+});
diff --git a/src/module/auth/reset-password-form/FormLayout.tsx b/src/module/auth/reset-password-form/FormLayout.tsx
new file mode 100644
index 0000000..696a077
--- /dev/null
+++ b/src/module/auth/reset-password-form/FormLayout.tsx
@@ -0,0 +1,100 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import {
+ AccountHaveAccount,
+ AccountHead,
+ AccountLogo,
+ AccountLogoAccent,
+ AccountTitle,
+} from '@/shared/components/account/AccountElements';
+import { useMutation } from '@apollo/client';
+import { USER_RESET_PASSWORD } from '@/graphql/auth';
+import { useTitle } from '@/hooks/useTitle';
+import Link from 'next/link';
+import styled from 'styled-components';
+import { colorBlue } from '@/styles/palette';
+import ResetPasswordFormGroup from './ResetPasswordFormGroup';
+import ResetPasswordSuccess from './ResetPasswordSuccess';
+
+// region STYLES
+const BackToLogin = styled(AccountHaveAccount)`
+ margin-top: 0;
+`;
+
+const FailMessage = styled.div`
+ color: ${colorBlue};
+ margin-bottom: 14px;
+`;
+
+export default function FormLayout() {
+ const [resetPassword] = useMutation(USER_RESET_PASSWORD);
+ const [step, setStep] = useState('form');
+ const [token, setToken] = useState('');
+ const [failMessage, setFailMessage] = useState('');
+
+ useTitle('Reset Password - BeeQuant');
+
+ useEffect(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+ const extractedToken = urlParams.get('token');
+ if (extractedToken) {
+ setToken(extractedToken);
+ }
+ }, []);
+
+ const onSuccess = async (data: { password: string }) => {
+ try {
+ const response = await resetPassword({
+ variables: {
+ input: {
+ newPassword: data.password,
+ resetToken: token,
+ },
+ },
+ });
+
+ if (response.data?.resetPassword?.code === 200) {
+ setStep('success');
+ }
+ const message = response.data?.resetPassword?.message;
+ setFailMessage(message || 'Password reset request failed.');
+ } catch (e) {
+ console.log('error:', e);
+ setFailMessage('Password reset request failed.');
+ }
+ };
+
+ if (step === 'success') {
+ return ;
+ }
+
+ return (
+ <>
+
+
+ Reset Password
+
+
+ BeeQuant
+ AI
+
+
+ Trading smart, trading with BeeQuant AI
+
+ {failMessage && (
+
+ {failMessage}
+
+ )}
+
+
+
+ Back to
+
+ Login
+
+
+ >
+ );
+}
diff --git a/src/module/auth/reset-password-form/ResetPasswordFormGroup.test.tsx b/src/module/auth/reset-password-form/ResetPasswordFormGroup.test.tsx
new file mode 100644
index 0000000..1231c4b
--- /dev/null
+++ b/src/module/auth/reset-password-form/ResetPasswordFormGroup.test.tsx
@@ -0,0 +1,125 @@
+import React from 'react';
+import { render, fireEvent, waitFor } from '@testing-library/react';
+import { FormProvider, useForm } from 'react-hook-form';
+import ResetPasswordFormGroup from './ResetPasswordFormGroup';
+
+const Wrapper = ({ children }: { children: React.ReactNode }) => {
+ const methods = useForm();
+ return {children};
+};
+
+describe('ResetPasswordFormGroup', () => {
+ const mockOnSuccess = jest.fn();
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders without crashing', () => {
+ const { getByPlaceholderText } = render(, {
+ wrapper: Wrapper,
+ });
+ expect(getByPlaceholderText('Password')).toBeInTheDocument();
+ expect(getByPlaceholderText('Confirm Password')).toBeInTheDocument();
+ });
+
+ it('displays an error for empty password', async () => {
+ const { getByPlaceholderText, findByText } = render(
+ ,
+ { wrapper: Wrapper }
+ );
+ const passwordInput = getByPlaceholderText('Password');
+
+ fireEvent.change(passwordInput, { target: { value: '' } });
+ fireEvent.blur(passwordInput);
+
+ await waitFor(() => {
+ expect(findByText('This field is required')).resolves.toBeInTheDocument();
+ });
+ });
+
+ it('displays an error for invalid password format', async () => {
+ const { getByPlaceholderText, findByText } = render(
+ ,
+ { wrapper: Wrapper }
+ );
+ const passwordInput = getByPlaceholderText('Password');
+
+ fireEvent.change(passwordInput, { target: { value: 'weak' } });
+ fireEvent.blur(passwordInput);
+
+ await waitFor(() => {
+ expect(
+ findByText(
+ 'must contain 8 to 32 characters, including letter, number and special character'
+ )
+ ).resolves.toBeInTheDocument();
+ });
+ });
+
+ it('displays an error when passwords do not match', async () => {
+ const { getByPlaceholderText, getByText, findByText } = render(
+ ,
+ { wrapper: Wrapper }
+ );
+ const passwordInput = getByPlaceholderText('Password');
+ const confirmPasswordInput = getByPlaceholderText('Confirm Password');
+
+ fireEvent.change(passwordInput, { target: { value: 'ValidPass1!' } });
+ fireEvent.change(confirmPasswordInput, { target: { value: 'DifferentPass1!' } });
+ fireEvent.click(getByText('Submit'));
+
+ await waitFor(() => {
+ expect(findByText('The passwords do not match')).resolves.toBeInTheDocument();
+ });
+ });
+
+ it('calls onSuccess when form is submitted with valid data', async () => {
+ const { getByPlaceholderText, getByText } = render(
+ ,
+ { wrapper: Wrapper }
+ );
+ const passwordInput = getByPlaceholderText('Password');
+ const confirmPasswordInput = getByPlaceholderText('Confirm Password');
+
+ fireEvent.change(passwordInput, { target: { value: 'ValidPass1!' } });
+ fireEvent.change(confirmPasswordInput, { target: { value: 'ValidPass1!' } });
+ fireEvent.click(getByText('Submit'));
+
+ await waitFor(() => {
+ expect(mockOnSuccess).toHaveBeenCalledWith({
+ password: 'ValidPass1!',
+ confirmPassword: 'ValidPass1!',
+ });
+ });
+ });
+
+ it('shows password requirements when password field is focused', () => {
+ const { getByPlaceholderText, getByText } = render(
+ ,
+ { wrapper: Wrapper }
+ );
+ const passwordInput = getByPlaceholderText('Password');
+
+ fireEvent.focus(passwordInput);
+
+ expect(
+ getByText(': 8 to 32 characters, including letter, number and special character')
+ ).toBeInTheDocument();
+ });
+
+ it('hides password requirements when password field loses focus', () => {
+ const { getByPlaceholderText, queryByText } = render(
+ ,
+ { wrapper: Wrapper }
+ );
+ const passwordInput = getByPlaceholderText('Password');
+
+ fireEvent.focus(passwordInput);
+ fireEvent.blur(passwordInput);
+
+ expect(
+ queryByText(': 8 to 32 characters, including letter, number and special character')
+ ).not.toBeInTheDocument();
+ });
+});
diff --git a/src/module/auth/reset-password-form/ResetPasswordFormGroup.tsx b/src/module/auth/reset-password-form/ResetPasswordFormGroup.tsx
new file mode 100644
index 0000000..c9ecada
--- /dev/null
+++ b/src/module/auth/reset-password-form/ResetPasswordFormGroup.tsx
@@ -0,0 +1,139 @@
+import { passwordPatten } from '@/shared/utils/helpers';
+import PasswordField from '@/shared/components/form/Password';
+import { useForm, Controller, SubmitHandler } from 'react-hook-form';
+import {
+ FormGroup,
+ FormGroupField,
+ FormGroupLabel,
+ FormContainer,
+} from '@/shared/components/form/FormElements';
+import { AccountButton, LastFormGroup } from '@/shared/components/account/AccountElements';
+import { useState } from 'react';
+
+interface FormValues {
+ password: string;
+ confirmPassword: string;
+}
+
+type ResetPasswordFormProps = {
+ onSuccess: (data: any) => void;
+};
+
+type IsFocused = {
+ password: boolean;
+ confirmPassword: boolean;
+};
+
+const ResetPasswordFormGroup = ({ onSuccess }: ResetPasswordFormProps) => {
+ const { handleSubmit, control, watch } = useForm({
+ mode: 'onSubmit',
+ });
+
+ const onSubmit: SubmitHandler = (data) => {
+ if (data.password !== data.confirmPassword) {
+ alert('Passwords do not match.');
+ return;
+ }
+ onSuccess(data);
+ };
+
+ const [isFocused, setIsFocused] = useState({
+ password: false,
+ confirmPassword: false,
+ });
+
+ const handleFocus = (fieldName: string) => {
+ setIsFocused((prevIsFocused) => ({
+ ...prevIsFocused,
+ [fieldName]: true,
+ }));
+ };
+
+ const handleBlur = (fieldName: string) => {
+ setIsFocused((prevIsFocused) => ({
+ ...prevIsFocused,
+ [fieldName]: false,
+ }));
+ };
+
+ return (
+
+
+
+ Password
+
+ {isFocused.password &&
+ ': 8 to 32 characters, including letter, number and special character'}
+
+
+
+ (
+ handleBlur(field.name) }}
+ meta={{
+ touched: !!fieldState.error,
+ error: fieldState.error?.message,
+ }}
+ placeholder="Password"
+ keyIcon
+ isAboveError
+ onFocus={() => handleFocus(field.name)}
+ />
+ )}
+ defaultValue=""
+ />
+
+
+
+
+ Confirm Password
+ {isFocused.confirmPassword && ''}
+
+
+ {
+ const { password } = watch();
+ return password === value || 'The passwords do not match';
+ },
+ },
+ }}
+ render={({ field, fieldState }) => (
+
+ )}
+ defaultValue=""
+ />
+
+
+
+ Submit
+
+
+ );
+};
+
+export default ResetPasswordFormGroup;
diff --git a/src/module/auth/reset-password-form/ResetPasswordSuccess.test.tsx b/src/module/auth/reset-password-form/ResetPasswordSuccess.test.tsx
new file mode 100644
index 0000000..becce3e
--- /dev/null
+++ b/src/module/auth/reset-password-form/ResetPasswordSuccess.test.tsx
@@ -0,0 +1,47 @@
+import { screen, render } from '@testing-library/react';
+import { BrowserRouter as Router } from 'react-router-dom';
+import ResetPasswordSuccess from './ResetPasswordSuccess';
+
+jest.mock('@/shared/components/account/AccountElements', () => ({
+ AccountButton: (props: any) => ,
+ AccountHead: (props: any) => {props.children}
,
+ AccountLogo: (props: any) => {props.children}
,
+ AccountLogoAccent: (props: any) => {props.children},
+ AccountTitle: (props: any) => {props.children}
,
+}));
+
+describe('ResetPasswordSuccess component', () => {
+ it('should render successfully and show success message', () => {
+ render(
+
+
+
+ );
+
+ const successMessage = screen.getByText(/Your password has been reset/i);
+ expect(successMessage).toBeInTheDocument();
+ });
+
+ it('should render success image', () => {
+ render(
+
+
+
+ );
+ const image = screen.getByAltText('success');
+ expect(image).toBeInTheDocument();
+ expect(image).toHaveAttribute('src', 'img/success.png');
+ });
+
+ it('should render button with correct link', () => {
+ const { getByText } = render(
+
+
+
+ );
+ const button = getByText('Back to Login');
+ expect(button).toBeInTheDocument();
+ const linkElement = button.closest('a');
+ expect(linkElement).toHaveAttribute('href', '/login');
+ });
+});
diff --git a/src/module/auth/reset-password-form/ResetPasswordSuccess.tsx b/src/module/auth/reset-password-form/ResetPasswordSuccess.tsx
new file mode 100644
index 0000000..851f23d
--- /dev/null
+++ b/src/module/auth/reset-password-form/ResetPasswordSuccess.tsx
@@ -0,0 +1,37 @@
+import styled from 'styled-components';
+import Link from 'next/link';
+import {
+ AccountButton,
+ AccountHead,
+ AccountLogo,
+ AccountLogoAccent,
+ AccountTitle,
+} from '@/shared/components/account/AccountElements';
+
+const AccountImage = styled.img`
+ max-width: 500px;
+ width: 100%;
+ margin-bottom: 40px;
+`;
+
+const ResetPasswordSuccess = () => (
+ <>
+
+
+
+
+
+ Cool !
+
+
+
+ Your password has been reset
+
+
+
+ Back to Login
+
+ >
+);
+
+export default ResetPasswordSuccess;
diff --git a/src/module/auth/reset-password-form/reset-password-form.component.tsx b/src/module/auth/reset-password-form/reset-password-form.component.tsx
new file mode 100644
index 0000000..ee7c164
--- /dev/null
+++ b/src/module/auth/reset-password-form/reset-password-form.component.tsx
@@ -0,0 +1,20 @@
+'use client';
+
+import {
+ AccountWrap,
+ AccountContent,
+ AccountCard,
+} from '@/shared/components/account/AccountElements';
+import FormLayout from './FormLayout';
+
+export default function ResetPasswordForm() {
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/routes/routeConfig.ts b/src/routes/routeConfig.ts
index 11968d1..a3cb16a 100644
--- a/src/routes/routeConfig.ts
+++ b/src/routes/routeConfig.ts
@@ -13,6 +13,8 @@ import ExchangeDetails from 'app/(protected)/crypto/exchange/details/page';
import PriceDetails from 'app/(protected)/crypto/price/details/page';
import BotDetail from 'app/(protected)/bot/details/page';
import BotCreate from 'app/(protected)/bot/create/page';
+import ForgotPassword from 'app/(auth)/forgot-password/page';
+import ResetPassword from 'app/(auth)/reset-password/page';
interface IRoute {
path: string;
@@ -30,6 +32,8 @@ export const ROUTE_KEY = {
LOGIN: 'login',
REGISTER: 'register',
SETTINGS: 'settings',
+ FORGOT_PASSWORD: 'forgot_password',
+ RESET_PASSWORD: 'reset_password',
BOT_DASHBOARD: 'bot_dashboard',
BOT_MANAGEMENT: 'bot_management',
BOT_CREATE: 'bot_create',
@@ -53,6 +57,21 @@ export const PUBLIC_ROUTE_CONFIG: Record = {
title: 'Register - BeeQuant',
component: Register,
},
+
+ [ROUTE_KEY.FORGOT_PASSWORD]: {
+ path: '/forgot',
+ name: 'Forgot Password',
+ title: 'Forgot Password - BeeQuant',
+ component: ForgotPassword,
+ },
+
+ [ROUTE_KEY.RESET_PASSWORD]: {
+ path: '/reset-password',
+ name: 'Reset Password',
+ title: 'Reset Password - BeeQuant',
+ component: ResetPassword,
+ },
+
[ROUTE_KEY.PAGE_404]: {
path: '/404',
name: '404',