DEV Community

Cover image for Hire+Plus! For Employees Here's how I built it (UI - Auth)
AjeaS
AjeaS

Posted on

Hire+Plus! For Employees Here's how I built it (UI - Auth)

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;
Enter fullscreen mode Exit fullscreen mode

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: '',
};
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

Screenshot

Login component 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: '',
};
Enter fullscreen mode Exit fullscreen mode

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 */})
}
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

Screenshot

Sign up component

That's all for the UI/Auth portion of the project, stay tuned!

Top comments (0)