Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

finishing password reset #23

Merged
merged 1 commit into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading