V 0.03
parent
877a55a3a8
commit
f13f9bc99b
Binary file not shown.
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 671 KiB |
@ -1,53 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
import {FC, useEffect} from 'react'
|
||||
import { useForm, SubmitHandler } from "react-hook-form";
|
||||
import Logo from '../assets/logo.png'
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useAppSelector, useAppDispatch } from '../hooks/redux';
|
||||
import { login } from '../store/reducers/UserSlice';
|
||||
|
||||
type Form = {
|
||||
email: string,
|
||||
password: string,
|
||||
};
|
||||
|
||||
const LoginForm: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const {isAuth} = useAppSelector(state => state.UserSlice);
|
||||
const { register, handleSubmit, formState: {isSubmitSuccessful} } = useForm<Form>();
|
||||
|
||||
useEffect(() => {
|
||||
if(isSubmitSuccessful) {
|
||||
if(isAuth) {
|
||||
navigate('/');
|
||||
}
|
||||
}
|
||||
}, [isSubmitSuccessful]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const onSubmit: SubmitHandler<Form> = async (data) => {
|
||||
await dispatch(login({email: data.email, password: data.password}));
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="flex items-center h-screen">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='bg-white container mx-auto flex flex-col p-10 max-w-2xl'>
|
||||
<img className='w-52 self-center mb-5' src={ Logo } alt="logo" />
|
||||
<h1 className='text-2xl font-semibold tracking-wider text-gray-800 capitalize '>Login to your account</h1>
|
||||
<p className='mt-4 text-gray-500 mb-5'>Let's get you all set up so you can verify your personal account and begin setting up your profile.</p>
|
||||
<div className="flex flex-col mb-5">
|
||||
<label className="block text-sm text-gray-600 " htmlFor="email">Email:</label>
|
||||
<input id='email' className='block w-full px-5 py-3 mt-2 text-gray-700 placeholder-gray-400 bg-white border border-gray-200 rounded-md focus:border-[#fbceb1] focus:ring-[#fbceb1] focus:outline-none focus:ring focus:ring-opacity-40' {...register("email", { required: true })} placeholder='Email'/>
|
||||
</div>
|
||||
<div className="flex flex-col mb-5">
|
||||
<label className="block text-sm text-gray-600 " htmlFor="password">Password:</label>
|
||||
<input id='password' className='block w-full px-5 py-3 mt-2 text-gray-700 placeholder-gray-400 bg-white border border-gray-200 rounded-md focus:border-[#fbceb1] focus:ring-[#fbceb1] focus:outline-none focus:ring focus:ring-opacity-40' {...register("password", { required: true })} placeholder='Password'/>
|
||||
</div>
|
||||
<button className='flex items-center justify-center mt-2 w-full px-6 py-3 text-sm tracking-wide text-white hover:text-[#fbceb1] capitalize transition-colors duration-300 transform bg-gray-800 rounded-md focus:outline-none focus:ring focus:ring-gray-300 focus:ring-opacity-50'>Login</button>
|
||||
<p className='mt-5 text-gray-700'>Dont have account? <Link className='text-[#fbceb1]' to={'/register'}>Sign up here.</Link></p>
|
||||
</form>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginForm;
|
||||
@ -0,0 +1,21 @@
|
||||
import { FC } from 'react'
|
||||
import Modal from '../UI/Modal';
|
||||
import { BiErrorCircle } from "@react-icons/all-files/bi/BiErrorCircle";
|
||||
|
||||
interface AuthErrorModalProps {
|
||||
modal: boolean,
|
||||
setModal: (bool: boolean) => void,
|
||||
error: string
|
||||
}
|
||||
|
||||
const AuthErrorModal: FC<AuthErrorModalProps> = ({ modal, setModal, error }) => {
|
||||
return (
|
||||
<Modal active={modal} setActive={setModal} className='items-center'>
|
||||
<BiErrorCircle className='text-red-500 text-8xl mb-3'/>
|
||||
<h2 className='text-4xl dark:text-white font-medium mb-3'>Error!</h2>
|
||||
<p className='text-lg dark:text-[#c7c7c7] text-center'>{error}</p>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default AuthErrorModal;
|
||||
@ -1,77 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
import {FC, useEffect} from 'react'
|
||||
import { useForm, SubmitHandler } from "react-hook-form";
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import Logo from '../assets/logo.png'
|
||||
import { useAppSelector, useAppDispatch } from '../hooks/redux';
|
||||
import { registration } from '../store/reducers/UserSlice';
|
||||
|
||||
const RegisterForm: FC = () => {
|
||||
|
||||
type Form = {
|
||||
email: string,
|
||||
name: string,
|
||||
sname: string,
|
||||
password: string,
|
||||
confirm_password: string
|
||||
};
|
||||
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const {isAuth} = useAppSelector(state => state.UserSlice);
|
||||
const { register, handleSubmit, watch, formState: {isSubmitSuccessful} } = useForm<Form>();
|
||||
|
||||
useEffect(() => {
|
||||
if(isSubmitSuccessful) {
|
||||
if(isAuth) {
|
||||
navigate('/');
|
||||
}
|
||||
}
|
||||
}, [isSubmitSuccessful]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const onSubmit: SubmitHandler<Form> = async (data) => {
|
||||
await dispatch(registration({email: data.email, name: data.name, sname: data.sname, password: data.password}));
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="flex items-center h-screen">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='bg-white container mx-auto flex flex-col p-10 max-w-2xl'>
|
||||
<img className='w-52 self-center mb-5' src={ Logo } alt="logo" />
|
||||
<h1 className='text-2xl font-semibold tracking-wider text-gray-800 capitalize '>Register To Get Started</h1>
|
||||
<p className='mt-4 text-gray-500 mb-5'>Let's get you all set up so you can verify your personal account and begin setting up your profile.</p>
|
||||
<div className="flex flex-col mb-5">
|
||||
<label className="block text-sm text-gray-600 " htmlFor="email">Email:</label>
|
||||
<input id='email' className='block w-full px-5 py-3 mt-2 text-gray-700 placeholder-gray-400 bg-white border border-gray-200 rounded-md focus:border-[#fbceb1] focus:ring-[#fbceb1] focus:outline-none focus:ring focus:ring-opacity-40' {...register("email", { required: true })} type='email' placeholder='Email'/>
|
||||
</div>
|
||||
<div className="flex flex-col mb-5">
|
||||
<label className="block text-sm text-gray-600 " htmlFor="name">First Name:</label>
|
||||
<input id='name' className='block w-full px-5 py-3 mt-2 text-gray-700 placeholder-gray-400 bg-white border border-gray-200 rounded-md focus:border-[#fbceb1] focus:ring-[#fbceb1] focus:outline-none focus:ring focus:ring-opacity-40' {...register("name", { required: true })} placeholder='First Name'/>
|
||||
</div>
|
||||
<div className="flex flex-col mb-5">
|
||||
<label className="block text-sm text-gray-600 " htmlFor="sname">Last Name:</label>
|
||||
<input id='sname' className='block w-full px-5 py-3 mt-2 text-gray-700 placeholder-gray-400 bg-white border border-gray-200 rounded-md focus:border-[#fbceb1] focus:ring-[#fbceb1] focus:outline-none focus:ring focus:ring-opacity-40' {...register("sname", { required: true })} type='text' placeholder='Last Name'/>
|
||||
</div>
|
||||
<div className="flex flex-col mb-5">
|
||||
<label className="block text-sm text-gray-600 " htmlFor="password">Password:</label>
|
||||
<input id='password' className='block w-full px-5 py-3 mt-2 text-gray-700 placeholder-gray-400 bg-white border border-gray-200 rounded-md focus:border-[#fbceb1] focus:ring-[#fbceb1] focus:outline-none focus:ring focus:ring-opacity-40' {...register("password", { required: true })} type='password' placeholder='Password'/>
|
||||
</div>
|
||||
<div className="flex flex-col mb-5">
|
||||
<label className="block text-sm text-gray-600 " htmlFor="password">Confirm Password:</label>
|
||||
<input id='password' className='block w-full px-5 py-3 mt-2 text-gray-700 placeholder-gray-400 bg-white border border-gray-200 rounded-md focus:border-[#fbceb1] focus:ring-[#fbceb1] focus:outline-none focus:ring focus:ring-opacity-40' {...register("confirm_password",
|
||||
{
|
||||
required: true,
|
||||
validate: (val: string) => {
|
||||
if (watch('password') != val) {
|
||||
return "Your passwords do no match";
|
||||
}
|
||||
},
|
||||
})} type='password' placeholder='Confirm Password'/>
|
||||
</div>
|
||||
<button className='flex items-center justify-center mt-2 w-full px-6 py-3 text-sm tracking-wide text-white hover:text-[#fbceb1] capitalize transition-colors duration-300 transform bg-gray-800 rounded-md focus:outline-none focus:ring focus:ring-gray-300 focus:ring-opacity-50'>Register</button>
|
||||
<p className='mt-5 text-gray-700'>Already have an account ? <Link className='text-[#fbceb1]' to={'/login'}>Log In here.</Link></p>
|
||||
</form>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default RegisterForm;
|
||||
@ -0,0 +1,13 @@
|
||||
import { FC, PropsWithChildren } from 'react'
|
||||
|
||||
interface ButtonProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const Button: FC<PropsWithChildren<ButtonProps>> = ({children, className, ...props}) => {
|
||||
return (
|
||||
<button className={['flex items-center justify-center w-full px-6 py-3 text-sm tracking-wide text-white hover:text-apricot capitalize transition-colors duration-300 transform bg-gray-800 rounded-md focus:outline-none focus:ring focus:ring-gray-300 focus:ring-opacity-50', className].join(' ')} {...props}>{children}</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default Button;
|
||||
@ -0,0 +1,32 @@
|
||||
import { FC } from 'react'
|
||||
import uniqid from 'uniqid';
|
||||
import { UseFormRegisterReturn } from 'react-hook-form';
|
||||
|
||||
interface InputProps {
|
||||
className?: string,
|
||||
wrapperClasses?: string
|
||||
labelClasses?: string,
|
||||
label?: string,
|
||||
name?: string,
|
||||
error?: string,
|
||||
placeholder?: string,
|
||||
type: string,
|
||||
register?: UseFormRegisterReturn
|
||||
}
|
||||
|
||||
const Input: FC<InputProps> = ({className, wrapperClasses, labelClasses, label, name, error, register, type, ...props}) => {
|
||||
const uid: string = uniqid();
|
||||
return (
|
||||
<div className={['flex flex-col relative', wrapperClasses].join(' ')}>
|
||||
{label &&
|
||||
<label className={['block text-sm text-gray-600 mb-2', labelClasses].join(' ')} htmlFor={uid}>{label}</label>
|
||||
}
|
||||
<input type={type} id={uid} name={name} {...register} className={['block w-full px-5 py-3 text-gray-700 placeholder-gray-400 bg-white border border-gray-200 rounded-md focus:border-apricot focus:ring-apricot focus:outline-none focus:ring focus:ring-opacity-40', className].join(' ')} {...props} />
|
||||
{error &&
|
||||
<p className="text-red-600 text-sm absolute -bottom-0.5 translate-y-full">{error}</p>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Input;
|
||||
@ -0,0 +1,36 @@
|
||||
import { FC, PropsWithChildren, useEffect, MouseEventHandler } from 'react'
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { IoMdClose } from "@react-icons/all-files/io/IoMdClose";
|
||||
import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock";
|
||||
|
||||
interface ModalProps {
|
||||
className?: string,
|
||||
active: boolean,
|
||||
setActive: (bool: boolean) => void,
|
||||
}
|
||||
|
||||
const Modal: FC<PropsWithChildren<ModalProps>> = ({className, children, active, setActive, ...props}) => {
|
||||
|
||||
useEffect(() => {
|
||||
active ? disableBodyScroll(document.body) : enableBodyScroll(document.body);
|
||||
}, [active])
|
||||
|
||||
const stopProp: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{active &&
|
||||
<motion.div className='min-h-full overflow-y-auto fixed w-full h-full bg-black p-3 bg-opacity-80 left-0 top-0 flex justify-center items-center z-30' initial={{ opacity: 0 }} exit={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.3 }} onClick={() => setActive(false)}>
|
||||
<motion.div className={['min-w-[320px] max-w-[500px] max-h-full overflow-y-auto w-full p-8 bg-white dark:bg-[#17212B] flex flex-col rounded-sm relative', className].join(' ')} initial={{ opacity: 0, scale: 0 }} exit={{ opacity: 0, scale: 0 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.5 }} onClick={e=> stopProp(e)} {...props}>
|
||||
<IoMdClose className='absolute right-3 top-3 text-2xl cursor-pointer dark:text-white' onClick={() => setActive(false)}/>
|
||||
{children}
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
|
||||
export default Modal;
|
||||
@ -0,0 +1,59 @@
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
import {FC, useEffect, useState} from 'react'
|
||||
import { useForm, SubmitHandler } from "react-hook-form";
|
||||
import Logo from '../assets/logo.png'
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useAppSelector, useAppDispatch } from '../hooks/redux';
|
||||
import { login } from '../store/reducers/UserSlice';
|
||||
import Input from '../components/UI/Input';
|
||||
import Button from '../components/UI/Button';
|
||||
import AuthError from '../components/Modals/AuthError';
|
||||
import { ServerError } from '../models/response/ServerError';
|
||||
|
||||
type Form = {
|
||||
email: string,
|
||||
password: string,
|
||||
};
|
||||
|
||||
const LoginPage: FC = () => {
|
||||
const [modal, setModal] = useState<boolean>(false);
|
||||
const [modalError, setModalError] = useState<string>('');
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const {isAuth} = useAppSelector(state => state.UserSlice);
|
||||
const { register, handleSubmit, formState: {errors, isSubmitSuccessful} } = useForm<Form>();
|
||||
|
||||
useEffect(() => {
|
||||
if(isSubmitSuccessful) {
|
||||
if(isAuth) {
|
||||
navigate('/');
|
||||
}
|
||||
}
|
||||
}, [isSubmitSuccessful]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const onSubmit: SubmitHandler<Form> = async (data) => {
|
||||
const response = await dispatch(login({email: data.email, password: data.password}));
|
||||
const res = response.payload as ServerError;
|
||||
if(res?.error) {
|
||||
setModal(true);
|
||||
setModalError(res.error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="flex items-center h-screen">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='bg-white container mx-auto flex flex-col p-10 max-w-2xl'>
|
||||
<img className='w-52 self-center mb-5' src={ Logo } alt="logo" />
|
||||
<h1 className='text-2xl font-semibold tracking-wider text-gray-800 capitalize '>Login to your account</h1>
|
||||
<p className='mt-4 text-gray-500 mb-5'>Let's get you all set up so you can verify your personal account and begin setting up your profile.</p>
|
||||
<Input wrapperClasses='mb-5' type="text" label='Email:' placeholder='Email' error={errors.email?.message} register={register('email', { required: "The field must be filled" })}/>
|
||||
<Input wrapperClasses='mb-5' type="text" label='Password:' placeholder='Password' error={errors.password?.message} register={register('password', { required: "The field must be filled" })}/>
|
||||
<Button>Login</Button>
|
||||
<p className='mt-5 text-gray-700'>Dont have account? <Link className='text-[#fbceb1]' to={'/register'}>Sign up here.</Link></p>
|
||||
</form>
|
||||
<AuthError modal={modal} setModal={setModal} error={modalError}/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginPage;
|
||||
@ -0,0 +1,73 @@
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
import {FC, useEffect, useState} from 'react'
|
||||
import { useForm, SubmitHandler } from "react-hook-form";
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import Logo from '../assets/logo.png'
|
||||
import { useAppSelector, useAppDispatch } from '../hooks/redux';
|
||||
import { registration } from '../store/reducers/UserSlice';
|
||||
import { ServerError } from '../models/response/ServerError';
|
||||
import { EmailValidation, PasswordValidation } from '../utils/ValidationRules';
|
||||
import Input from '../components/UI/Input';
|
||||
import Button from '../components/UI/Button';
|
||||
import AuthError from '../components/Modals/AuthError';
|
||||
|
||||
type Form = {
|
||||
email: string,
|
||||
name: string,
|
||||
sname: string,
|
||||
password: string,
|
||||
confirm_password: string
|
||||
};
|
||||
|
||||
const RegisterPage: FC = () => {
|
||||
const [modal, setModal] = useState<boolean>(false);
|
||||
const [modalError, setModalError] = useState<string>('');
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const {isAuth} = useAppSelector(state => state.UserSlice);
|
||||
const { register, handleSubmit, watch, formState: {errors, isSubmitSuccessful} } = useForm<Form>();
|
||||
|
||||
useEffect(() => {
|
||||
if(isSubmitSuccessful) {
|
||||
if(isAuth) {
|
||||
navigate('/');
|
||||
}
|
||||
}
|
||||
}, [isSubmitSuccessful]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const onSubmit: SubmitHandler<Form> = async (data) => {
|
||||
const response = await dispatch(registration({email: data.email, name: data.name, sname: data.sname, password: data.password}));
|
||||
const res = response.payload as ServerError;
|
||||
if(res?.error) {
|
||||
setModal(true);
|
||||
setModalError(res.error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="flex items-center h-screen">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='bg-white container mx-auto flex flex-col p-10 max-w-2xl'>
|
||||
<img className='w-52 self-center mb-5' src={ Logo } alt="logo" />
|
||||
<h1 className='text-2xl font-semibold tracking-wider text-gray-800 capitalize '>Register To Get Started</h1>
|
||||
<p className='mt-4 text-gray-500 mb-5'>Let's get you all set up so you can verify your personal account and begin setting up your profile.</p>
|
||||
<Input wrapperClasses='mb-5' type="email" label='Email:' placeholder='Email' error={errors.email?.message} register={register('email', EmailValidation)}/>
|
||||
<Input wrapperClasses='mb-5' type="text" label='First Name:' placeholder='First Name' error={errors.name?.message} register={register('name', { required: "The field must be filled" })}/>
|
||||
<Input wrapperClasses='mb-5' type="text" label='Last Name:' placeholder='Last Name' error={errors.sname?.message} register={register('sname', { required: "The field must be filled" })}/>
|
||||
<Input wrapperClasses='mb-5' type="password" label='Password:' placeholder='Password' error={errors.password?.message} register={register('password', PasswordValidation)}/>
|
||||
<Input wrapperClasses='mb-5' type="password" label='Confirm Password:' placeholder='Confirm Password' error={errors.confirm_password?.message} register={register('confirm_password', {
|
||||
required: "The field must be filled",
|
||||
validate: (val: string) => {
|
||||
if (watch('password') != val) {
|
||||
return "Your passwords do no match";
|
||||
}
|
||||
},
|
||||
})}/>
|
||||
<Button>Register</Button>
|
||||
<p className='mt-5 text-gray-700'>Already have an account ? <Link className='text-[#fbceb1]' to={'/login'}>Log In here.</Link></p>
|
||||
</form>
|
||||
<AuthError modal={modal} setModal={setModal} error={modalError}/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default RegisterPage;
|
||||
@ -0,0 +1,20 @@
|
||||
import { RegisterOptions } from "react-hook-form"
|
||||
|
||||
const EmailValidation = {
|
||||
required: "The field must be filled",
|
||||
pattern: {
|
||||
value: /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<,>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, //eslint-disable-line
|
||||
message: "Invalid email"
|
||||
},
|
||||
}
|
||||
|
||||
const PasswordValidation: RegisterOptions = {
|
||||
required: "The field must be filled",
|
||||
minLength: {
|
||||
value: 6,
|
||||
message: "Password must be more than 6 characters"
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
export { EmailValidation, PasswordValidation }
|
||||
Loading…
Reference in New Issue