Overview: All views and functionality related to authentication, all funcs called are coming from the authSlice
reducer.
Auth Route Page
inside routes > auth > auth-page.tsx
Outlet will render whatever is nested inside the auth/employees
route.
import { Outlet } from 'react-router';
const AuthPage = () => {
return <Outlet />;
};
export default AuthPage;
Auth Components
Sign-in
inside components > sign-in > sign-in.component.tsx
I imported ChangeEvent
, FormEvent
type interfaces from react to define my funcs with typescript. defaultFormFields
is default state of form fields.
import { ChangeEvent, FormEvent, useState } from 'react';
import BeatLoader from 'react-spinners/BeatLoader';
import { useAppDispatch, useAppSelector } from '../../app/hooks';
import { signInWithGoogle, signInWithEmailAndPassword } from '../../app/features/auth/authSlice';
import { resetError } from '../../app/features/auth/authSlice';
import { useNavigate } from 'react-router-dom';
const defaultFormFields = {
email: '',
password: '',
};
Functionality
At the top: I'll render any possible sign-in errors or loading states with signInError
and isLoading
props from auth
state. I'll navigate to new routes on success using navigate
func from react-router-dom
. I handle the form states with formFields
and setFormFields
.
At the bottom:
handleChange
- handle form field changes
resetFormFields
- resets form after submission
handleSubmit
- sends form data to redux action, resets the form, redirects to the main app.
signInGooglePopup
- calls redux action to login using google, resets form, and redirects to the main app.
const SignIn = () => {
const { signInError, isLoading } = useAppSelector((state) => state.auth);
const navigate = useNavigate();
const dispatch = useAppDispatch();
const [formFields, setFormFields] = useState(defaultFormFields);
const { email, password } = formFields;
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
setFormFields({ ...formFields, [name]: value });
};
const resetFormFields = () => {
setFormFields(defaultFormFields);
};
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
dispatch(signInWithEmailAndPassword(formFields))
.unwrap()
.then(() => {
resetFormFields();
navigate('/app');
})
.catch((error) => {
resetFormFields();
});
};
const signInGooglePopup = async () => {
dispatch(signInWithGoogle())
.unwrap()
.then(() => {
resetFormFields();
navigate('/app');
})
.catch((error) => {
dispatch(resetError());
});
};
return (// removed for simplicity);
};
export default SignIn;
UI
const SignIn = () => {
// removed for simplicity
return (
<div className="items-center px-5 mt-10">
<div className="flex flex-col w-full max-w-md p-6 mx-auto my-6 transition duration-500 ease-in-out transform rounded-lg md:mt-0 secondary-bg-color">
<div>
<div className="mb-8 mt-4">
<h1 className="text-2xl lg:text-3xl text-center">
Already have an account?
</h1>
<p className="text-center font-normal my-2 font-color">
Sign in with your email and password
</p>
</div>
{/* Error Handling */}
{signInError && (
<div className="text-center text-red-600 mb-5 text-lg">
{signInError}
</div>
)}
<div>
{/* Form Starts */}
<form className="space-y-6" onSubmit={handleSubmit}>
{/* Email Field */}
<div>
<label
htmlFor="email"
className="block text-sm font-medium font-color"
>
{' '}
Email address{' '}
</label>
<div className="mt-2">
<input
id="email"
onChange={handleChange}
value={email}
name="email"
type="email"
required
placeholder="e.g example@yahoo.com"
className="font-color primary-bg-color block w-full px-5 py-3 text-base text-neutral-600 placeholder-gray-500 transition duration-500 ease-in-out transform border border-transparent rounded-lg bg-gray-100 focus:outline-none focus:border-transparent focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-500"
/>
</div>
</div>
{/* Password Field */}
<div className="space-y-1">
<label
htmlFor="pass"
className="block text-sm font-medium font-color mb-2"
>
{' '}
Password{' '}
</label>
<div>
<input
id="password"
minLength={6}
onChange={handleChange}
value={password}
name="password"
type="password"
data-testid="password"
required
placeholder="********"
className="font-color primary-bg-color block w-full px-5 py-3 text-base text-neutral-600 placeholder-gray-500 transition duration-500 ease-in-out transform border border-transparent rounded-lg bg-gray-100 focus:outline-none focus:border-transparent focus:ring-2 focus:ring-bg-indigo-700 focus:ring-offset-2 focus:ring-offset-gray-300"
/>
</div>
</div>
{/* Submit Button */}
<div>
<button
type="submit"
className="flex items-center justify-center w-full px-10 py-4 text-base font-bold text-center text-white transition duration-500 ease-in-out transform rounded-xl focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 bg-indigo-700"
>
{isLoading ? (
<div className="text-center z-index">
<BeatLoader color={'white'} loading={true} />
</div>
) : (
<p>Sign in</p>
)}
</button>
</div>
</form>
{/* Google Sign-in */}
<div className="relative my-4">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-500"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 text-neutral-800 bg-white">
Or continue with
</span>
</div>
</div>
<div>
<button
onClick={signInGooglePopup}
className="w-full items-center block px-10 py-3.5 text-base font-medium text-center text-blue-600 transition duration-500 ease-in-out transform border-2 border-gray-300 shadow-md rounded-xl focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
>
<div className="flex items-center justify-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 48 48"
width="24px"
height="24px"
>
<path
fill="#fbc02d"
d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12 s5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24s8.955,20,20,20 s20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"
/>
<path
fill="#e53935"
d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039 l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"
/>
<path
fill="#4caf50"
d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36 c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"
/>
<path
fill="#1565c0"
d="M43.611,20.083L43.595,20L42,20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571 c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z"
/>
</svg>
<span className="ml-4 text-white"> Log in with Google</span>
</div>
</button>
</div>
</div>
</div>
</div>
</div>
);
};
export default SignIn;
Screenshot
Sign-up
inside components > sign-up > sign-up.component.tsx
defaultFormFields
is the default state of form fields
import { ChangeEvent, FormEvent, useState } from 'react';
import { useNavigate } from 'react-router';
import { BeatLoader } from 'react-spinners';
import { signUpUserEmailAndPassword, setSignupError } from '../../app/features/auth/authSlice';
import { useAppDispatch, useAppSelector } from '../../app/hooks';
const defaultFormFields = {
displayName: '',
email: '',
password: '',
confirmPassword: '',
};
Functionality
Does majority of the same thing as sign-in form, handle form change and submission, redirect to main app. However, if passwords don't match setSignupError
will dispatch with an error.
const Signup = () => {
const dispatch = useAppDispatch();
const { isLoading, signUpError } = useAppSelector((state) => state.auth);
const navigate = useNavigate();
const [formFields, setFormFields] = useState(defaultFormFields);
const { email, password, displayName, confirmPassword } = formFields;
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
setFormFields({ ...formFields, [name]: value });
};
const resetFormFields = () => {
setFormFields(defaultFormFields);
};
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (password !== confirmPassword) {
dispatch(setSignupError('Passwords must match'));
return;
}
dispatch(
signUpUserEmailAndPassword({
email,
password,
displayName,
})
)
.unwrap()
.then(() => {
resetFormFields();
navigate('/app');
})
.catch((error) => {
resetFormFields();
});
};
return ({/* removed for simplicity */})
}
UI
const Signup = () => {
{/* removed for simplicity */}
return (
<div className="items-center px-5 mt-5">
<div className="flex flex-col w-full max-w-md p-6 mx-auto transition duration-500 ease-in-out transform rounded-lg md:mt-0 secondary-bg-color">
<div>
<div className="mb-8 mt-4">
<h1 className="text-2xl lg:text-3xl text-center">
Dont have an account?
</h1>
<p className="text-center font-normal my-2 font-color">
Sign up with your email and password
</p>
</div>
{signUpError && (
<div className="text-center text-red-600 mb-5 text-lg">
{signUpError}
</div>
)}
<div>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label
htmlFor="name"
className="block text-sm font-medium font-color"
>
Name
</label>
<div className="mt-2">
<input
value={displayName}
onChange={handleChange}
id="name"
name="displayName"
type="text"
autoComplete="current-name"
required
placeholder="Enter your name"
className="primary-bg-color block w-full px-5 py-3 text-base text-neutral-400 placeholder-gray-400 transition duration-500 ease-in-out transform border border-transparent rounded-lg bg-gray-100 focus:outline-none focus:border-transparent focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-300"
/>
</div>
</div>
<div>
<label
htmlFor="email"
className="block text-sm font-medium font-color"
>
Email address
</label>
<div className="mt-2">
<input
value={email}
onChange={handleChange}
id="email"
name="email"
type="email"
autoComplete="current-email"
required
placeholder="Enter your email"
className="primary-bg-color block w-full px-5 py-3 text-base text-neutral-400 placeholder-gray-400 transition duration-500 ease-in-out transform border border-transparent rounded-lg bg-gray-100 focus:outline-none focus:border-transparent focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-300"
/>
</div>
</div>
<div className="space-y-1">
<label
htmlFor="password"
className="block text-sm font-medium font-color"
>
Password
</label>
<div className="mt-2">
<input
id="password"
name="password"
type="password"
onChange={handleChange}
value={password}
minLength={6}
required
data-testid="pass"
placeholder="********"
className="primary-bg-color block w-full px-5 py-3 text-base text-neutral-400 placeholder-gray-300 transition duration-500 ease-in-out transform border border-transparent rounded-lg bg-gray-100 focus:outline-none focus:border-transparent focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-300"
/>
</div>
</div>
<div className="space-y-1">
<label
htmlFor="confirmPassword"
className="block text-sm font-medium font-color"
>
Confirm Password
</label>
<div className="mt-1">
<input
id="confirmPassword"
name="confirmPassword"
type="password"
onChange={handleChange}
value={confirmPassword}
data-testid="confirmPass"
minLength={6}
required
placeholder="********"
className="primary-bg-color block w-full px-5 py-3 text-base text-neutral-400 placeholder-gray-300 transition duration-500 ease-in-out transform border border-transparent rounded-lg bg-gray-100 focus:outline-none focus:border-transparent focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-300"
/>
</div>
</div>
<div>
<button
type="submit"
className="bg-indigo-700 flex items-center justify-center w-full px-10 py-4 text-base font-medium text-center text-white transition duration-500 ease-in-out transform rounded-xl hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{isLoading ? (
<div className="text-center z-index">
<BeatLoader color={'white'} loading={true} />
</div>
) : (
<p>Sign up</p>
)}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
);
};
export default Signup;
Screenshot
That's all for the UI/Auth portion of the project, stay tuned!
Top comments (0)