Skip to content

Commit

Permalink
Merge pull request #23 from atlp-rwanda/187354199-ft-password-reset
Browse files Browse the repository at this point in the history
finishing password reset
  • Loading branch information
niyontwali authored Jul 10, 2024
2 parents 69b2560 + e0a7f77 commit 6befa21
Show file tree
Hide file tree
Showing 11 changed files with 315 additions and 79 deletions.
14 changes: 13 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import Messages from './pages/admin/Messages';
import Settings from './pages/admin/Settings';

import CategoriesPage from './pages/CategoriesPage';
import ResetPassword from './pages/ResetPassword';
import NewPassword from './pages/NewPassword';
const App = () => {
const { data, error, isLoading } = useGetProductsQuery();
const dispatch = useDispatch();
Expand Down Expand Up @@ -69,7 +71,17 @@ const App = () => {
{
path: 'categories/:categoryId',
element: <CategoriesPage />,
}
},
{
path: '/reset-password',
children: [
{ path: '', element: <ResetPassword /> },
{
path: ':token',
element: <NewPassword />,
},
],
},
],
},
{
Expand Down
6 changes: 4 additions & 2 deletions src/components/authentication/LoginComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import GoogleIcon from '../../assets/googleIcon.svg';
import Button from '../common/Button';
import Input from '../common/Input';
import { loginSchema, LoginData } from '../../utils/schemas';
import { useNavigate, useLocation, useSearchParams } from 'react-router-dom';
import { useNavigate, useLocation, useSearchParams, Link } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import { setToken, setUser, setRole } from '../../redux/slices/userSlice';
import { useLoginUserMutation } from '../../services/authAPI';
Expand Down Expand Up @@ -120,7 +120,9 @@ const LoginComponent = () => {
{...register('password')}
error={errors.password && errors.password.message}
/>
<p className='text-sm text-end hover:cursor-pointer hover:underline'>Forget password</p>
<Link to={'/reset-password'}>
<p className='text-sm text-end hover:cursor-pointer hover:underline'>Forget password</p>
</Link>
<button
type='submit'
className='p-2 rounded-lg bg-greenColor hover:bg-darkGreen transition-all text-whiteColor font-bold'
Expand Down
7 changes: 5 additions & 2 deletions src/components/common/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cn } from "../../utils";
import { cn } from '../../utils';

interface ButtonProps {
text: string;
Expand All @@ -11,7 +11,10 @@ const Button = ({ text, type, className, onClick }: ButtonProps) => {
return (
<button
type={type}
className={cn('p-2 rounded-lg bg-greenColor hover:bg-darkGreen transition-all text-whiteColor font-bold', className)}
className={cn(
'p-2 rounded-lg bg-greenColor hover:bg-darkGreen transition-all text-whiteColor font-bold',
className
)}
onClick={onClick}
>
{text}
Expand Down
149 changes: 75 additions & 74 deletions src/components/footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,79 +1,80 @@
import FooterTitle from "../../containers/footer/FooterTitle"
import SocialIcon from "../../containers/footer/SocialIcon"
import FooterLink from "../../containers/footer/FooterLink"
import FooterTitle from '../../containers/footer/FooterTitle';
import SocialIcon from '../../containers/footer/SocialIcon';
import FooterLink from '../../containers/footer/FooterLink';

function Footer() {
return (
<>
<div className="w-full flex flex-col gap-2 bg-grayColor font-roboto 2xl:items-center ">
<div className="p-3 md:p-4 xl:px-10 2xl:w-[1440px] grid grid-cols-2 md:grid-cols-5 xl:grid-cols-6 gap-x-20 gap-y-5 sm:gap-5 md:gap-2">
<div className="flex flex-col md:row-span-1 md:col-start-1 md:col-end-3 gap-3">
<FooterTitle title={"mavericks"} />
<div className="leading-none text-xs md:text-base break-words flex flex-col gap-3 font-light flex-grow">
<p>
K309 St , Makuza plaza, Nyarugenge ,
Kigali, Rwanda
</p>
<p>
[email protected]
</p>
<p>+250 788888888</p>
</div>
<div className="flex gap-2">
<a href="#" target="_blank">
<SocialIcon name="instagram" />
</a>
<a href="#" target="_blank">
<SocialIcon name="facebook" />
</a>
<a href="#" target="_blank">
<SocialIcon name="twitter" />
</a>
</div>
</div>
<div className="flex flex-col gap-3">
<FooterTitle title={"company"} />
<div className="flex flex-col gap-1 font-light">
<FooterLink name={"about us"} />
<FooterLink name={"contact us"} />
</div>
</div>
<div className="flex flex-col gap-3">
<FooterTitle title={"shop"} />
<div className="flex flex-col gap-1 font-light">
<FooterLink name={"new arrival"} />
<FooterLink name={"all products"} />
<FooterLink name={"babies"} />
<FooterLink name={"father"} />
<FooterLink name={"electronics"} />
</div>
</div>
<div className="flex flex-col gap-3">
<FooterTitle title={"help"} />
<div className="flex flex-col gap-2 md:gap-1 font-light">
<FooterLink name={"customer services"} />
<FooterLink name={"my account"} />
<FooterLink name={"find store"} />
<FooterLink name={"legal & privacy"} />
</div>
</div>
<div className="flex flex-col items-center gap-3 row-span-1 md:row-span-1 col-span-2 md:col-start-3 md:col-end-6 lg:col-start-4 xl:col-start-6 xl:auto-cols-max">
<FooterTitle title={"subscribe"} />
<p className="hidden xl:flex leading-none text-base font-light">Be the first to get latest news about trends,Promotions and many more.</p>
<form className="w-full flex flex-col">
<div className=" w-full flex">
<label htmlFor="email"></label>
<input type="text" id="email" placeholder="Email address" className="p-2 flex-grow xl:w-3/4" />
<button type="submit" className="leading-none bg-greenColor text-whiteColor w-20">Join</button>
</div>
<span className="text-xs text-redColor text-left">Email is not valid</span>
</form>
</div>
</div>
<p className="p-3 md:p-4 xl:px-10 2xl:w-[1440px] text-xs text-center xl:text-left ">&copy; 2024 Mavericks Shop. All rights reserved.</p>
return (
<>
<div className='w-full flex flex-col gap-2 bg-grayColor font-roboto 2xl:items-center'>
<div className='p-3 md:p-4 xl:px-10 2xl:w-[1440px] grid grid-cols-2 md:grid-cols-5 xl:grid-cols-6 gap-x-20 gap-y-5 sm:gap-5 md:gap-2'>
<div className='flex flex-col md:row-span-1 md:col-start-1 md:col-end-3 gap-3'>
<FooterTitle title={'mavericks'} />
<div className='leading-none text-xs md:text-base break-words flex flex-col gap-3 font-light flex-grow'>
<p>K309 St , Makuza plaza, Nyarugenge , Kigali, Rwanda</p>
<p>[email protected]</p>
<p>+250 788888888</p>
</div>
</>
)
<div className='flex gap-2'>
<a href='#' target='_blank'>
<SocialIcon name='instagram' />
</a>
<a href='#' target='_blank'>
<SocialIcon name='facebook' />
</a>
<a href='#' target='_blank'>
<SocialIcon name='twitter' />
</a>
</div>
</div>
<div className='flex flex-col gap-3'>
<FooterTitle title={'company'} />
<div className='flex flex-col gap-1 font-light'>
<FooterLink name={'about us'} />
<FooterLink name={'contact us'} />
</div>
</div>
<div className='flex flex-col gap-3'>
<FooterTitle title={'shop'} />
<div className='flex flex-col gap-1 font-light'>
<FooterLink name={'new arrival'} />
<FooterLink name={'all products'} />
<FooterLink name={'babies'} />
<FooterLink name={'father'} />
<FooterLink name={'electronics'} />
</div>
</div>
<div className='flex flex-col gap-3'>
<FooterTitle title={'help'} />
<div className='flex flex-col gap-2 md:gap-1 font-light'>
<FooterLink name={'customer services'} />
<FooterLink name={'my account'} />
<FooterLink name={'find store'} />
<FooterLink name={'legal & privacy'} />
</div>
</div>
<div className='flex flex-col items-center gap-3 row-span-1 md:row-span-1 col-span-2 md:col-start-3 md:col-end-6 lg:col-start-4 xl:col-start-6 xl:auto-cols-max'>
<FooterTitle title={'subscribe'} />
<p className='hidden xl:flex leading-none text-base font-light'>
Be the first to get latest news about trends,Promotions and many more.
</p>
<form className='w-full flex flex-col'>
<div className=' w-full flex'>
<label htmlFor='email'></label>
<input type='text' id='email' placeholder='Email address' className='p-2 flex-grow xl:w-3/4' />
<button type='submit' className='leading-none bg-greenColor text-whiteColor w-20'>
Join
</button>
</div>
<span className='text-xs text-redColor text-left'>Email is not valid</span>
</form>
</div>
</div>
<p className='p-3 md:p-4 xl:px-10 2xl:w-[1440px] text-xs text-center xl:text-left '>
&copy; 2024 Mavericks Shop. All rights reserved.
</p>
</div>
</>
);
}

export default Footer
export default Footer;
81 changes: 81 additions & 0 deletions src/components/passwordReset/NewPasswordComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import 'react-toastify/dist/ReactToastify.css';
import { toast } from 'react-toastify';

import { cn } from '../../utils';
import Button from '../common/Button';
import Input from '../common/Input';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { resetPasswordSchema } from '../../utils/schemas';
import { useParams, useNavigate } from 'react-router-dom';

import { useNewPasswordMutation } from '../../services/resetPassword';

interface PasswordData {
newPassword: string;
passwordConfirm: string;
}
const NewPasswordComponent = () => {
const navigate = useNavigate();
const { token } = useParams<{ token: string }>();
const [mutate, { isLoading }] = useNewPasswordMutation();
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm<PasswordData>({ resolver: zodResolver(resetPasswordSchema) });

const onSubmit = async (data: PasswordData) => {
try {
const { newPassword } = data;
const res = await mutate({ newPassword, token });
if (res.error) {
const { data } = res.error as any;
return toast.error(data.error || 'Failed, please try again later!');
} else {
reset();
const {
data: { message },
} = res;
toast.success(message || 'Password reset successfully!');
setTimeout(() => navigate('/login'), 3000);
}
} catch (err) {
toast.error('An unexpected error occurred');
}
};
return (
<main className='flex-grow'>
<form
action='#'
className={cn(
' font-roboto shadow-xl mt-4 mb-12 lg:w-1/3 sm:mt-10 h-1/2 md:w-1/2 mx-auto py-12 px-4 flex flex-col gap-4 sm:border-darkGreen sm:border-2 rounded-lg sm:w-3/4 w-full'
)}
onSubmit={handleSubmit(onSubmit)}
>
<Input
type='password'
placeholder='Type your new Password'
label='New Password'
id='newPassword'
{...register('newPassword')}
/>
{errors.newPassword && <p className={cn('text-sm text-redColor -mt-4')}>{errors.newPassword.message}</p>}
<Input
type='password'
placeholder='Confirm your new Password'
label='Confirm Password'
id='passwordConfirm'
{...register('passwordConfirm')}
/>
{errors.passwordConfirm && (
<p className={cn('text-sm text-redColor -mt-4')}>{errors.passwordConfirm.message}</p>
)}
<Button type='submit' text={isLoading ? 'Loading...' : 'Change Password'} />
</form>
</main>
);
};

export default NewPasswordComponent;
63 changes: 63 additions & 0 deletions src/components/passwordReset/PasswordResetComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import 'react-toastify/dist/ReactToastify.css';
import { toast } from 'react-toastify';
import { useEffect } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';

import { cn } from '../../utils';
import Button from '../common/Button';
import Input from '../common/Input';
import { emailSchema } from '../../utils/schemas';
import { useResetPasswordMutation } from '../../services/resetPassword';
import { useAppSelector } from '../../hooks/customHooks';
interface UserData {
email: string;
}

const PasswordResetComponent = () => {
const token: string | undefined = useAppSelector(state => state.user.token)?.replace(/"/g, '');
const navigate = useNavigate();
useEffect(() => {
if (token !== undefined) {
navigate('/login');
}
}, []);
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm<UserData>({
resolver: zodResolver(emailSchema),
});
const [resetPassword, { isLoading }] = useResetPasswordMutation();

const onSubmit = async (data: UserData) => {
try {
const res = await resetPassword(data.email).unwrap();
toast.success(res.message || 'Password reset email sent, Please verify your email!');
reset();
} catch (err) {
toast.error('Failed to send email, check your provided email and try again!');
}
};
return (
<main className='flex-grow'>
<h1 className={cn('font-roboto text-center mt-6 sm:text-3xl text-xl')}>Forgot Password?</h1>
<form
action='#'
className={cn(
'font-roboto sm:shadow-xl mt-4 mb-12 sm:mt-10 h-1/2 md:w-1/2 lg:w-1/3 mx-auto py-12 px-4 flex flex-col gap-4 sm:border-darkGreen sm:border-2 rounded-lg sm:w-3/4 w-full'
)}
onSubmit={handleSubmit(onSubmit)}
>
<Input type='text' placeholder='Type your email' label='Email' id='email' {...register('email')} />
{errors.email && <p className={cn('text-sm text-redColor -mt-4')}>{errors.email.message}</p>}
<Button type='submit' text={!isLoading ? 'Reset Password' : 'Sending Email...'} />
</form>
</main>
);
};

export default PasswordResetComponent;
5 changes: 5 additions & 0 deletions src/hooks/customHooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { TypedUseSelectorHook, useSelector, useDispatch } from 'react-redux';
import { RootState, AppDispatch } from '../redux/store';

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
15 changes: 15 additions & 0 deletions src/pages/NewPassword.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Footer from '../components/footer/Footer';
import Navbar from '../components/navbar/Navbar';
import NewPasswordComponent from '../components/passwordReset/NewPasswordComponent';

const NewPassword = () => {
return (
<div className='min-h-screen flex flex-col'>
<Navbar />
<NewPasswordComponent />
<Footer />
</div>
);
};

export default NewPassword;
Loading

0 comments on commit 6befa21

Please sign in to comment.