mbeps / next_discussion_platform

Full Stack Website for Forum Discussion Platform using Next.JS and Firebase
https://circus-discussion.vercel.app
MIT License
49 stars 11 forks source link

REFACTOR: Signup #36

Closed mbeps closed 1 year ago

mbeps commented 1 year ago

Refactoring signup.tsx

import { Button, Flex, Input, Text } from "@chakra-ui/react";
import React, { useState } from "react";
import { useCreateUserWithEmailAndPassword } from "react-firebase-hooks/auth";
import { useSetRecoilState } from "recoil";
import { authModalState } from "../../../atoms/authModalAtom";
import { auth } from "../../../firebase/clientApp";
import { FIREBASE_ERRORS } from "../../../firebase/errors";

/**
 * Allows the user to create an account by inputting the required credentials (email and password).
 * There are 2 password fields to ensure that the user inputs the correct password.
 * If the 2 passwords do not match, the account is not created and an error is displayed.
 * If the email already exists, the account is not created and an error is displayed.
 *
 * A button to log in instead is available which would switch the modal to the log in view when clicked.
 * @returns Sign up components view for modal.
 * @see https://github.com/CSFrequency/react-firebase-hooks/tree/master/auth
 */
const SignUp = () => {
  const setAuthModalState = useSetRecoilState(authModalState); // Set global state
  const [signUpForm, setSignUpForm] = useState({
    email: "", // Initially empty email
    password: "", // Initially empty password
    confirmPassword: "", // Initially empty confirm password
  });
  const [error, setError] = useState(""); // Initially empty error
  const [
    createUserWithEmailAndPassword, // returns a function that returns the user, loading or error
    user,
    loading,
    userError,
  ] = useCreateUserWithEmailAndPassword(auth);

  /**
   * This function is used as the event handler for a form submission.
   * It will prevent the page from refreshing.
   * Checks if the password and confirm password fields match and the password requirements are met:
   *    - If they do not match, an error message is set and the function returns without creating a new user.
   *    - If the password does not meet the requirements, an error message is set and the function returns without creating a new user.
   *    - If the passwords match and the password meets the requirements, a new user is created using the email and password provided in the form.
   * @param event (React.FormEvent): the submit event triggered by the form
   * @returns None
   */
  const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault(); // Prevent the page from refreshing
    if (error) setError(""); // If there is an error, clear it
    if (signUpForm.password !== signUpForm.confirmPassword) {
      // If the password and confirm password don't match
      setError("Passwords don't match"); // Set error
      return; // Return so that the function doesn't continue
    }
    if (signUpForm.password.length < 8) {
      setError("Password must be at least 8 characters long");
      return;
    }
    if (!/\d/.test(signUpForm.password)) {
      setError("Password must contain at least 1 number");
      return;
    }
    if (!/[!@#$%^&*(),.?":{}|<>]/.test(signUpForm.password)) {
      setError("Password must contain at least 1 special character");
      return;
    }
    if (!/[A-Z]/.test(signUpForm.password)) {
      setError("Password must contain at least 1 capital letter");
      return;
    }

    createUserWithEmailAndPassword(signUpForm.email, signUpForm.password); // Create user with email and password
  }; // Function to execute when the form is submitted

  /**
   * Function to execute when the form is changed (when email and password are typed).
   * Multiple inputs use the same onChange function.
   * @param event(React.ChangeEvent<HTMLInputElement>) - the event that is triggered when the form is changed
   */
  const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    // Update form state
    setSignUpForm((prev) => ({
      ...prev, // Spread previous state because we don't want to lose the other input's value
      [event.target.name]: event.target.value, // Catch the name of the input that was changed and update the corresponding state
    }));
  };

  return (
    <form onSubmit={onSubmit}>
      <InputField
        name="email"
        placeholder="Email"
        type="email"
        onChange={onChange}
      />

      <InputField
        name="password"
        placeholder="Password"
        type="password"
        onChange={onChange}
      />

      <InputField
        name="confirmPassword"
        placeholder="Confirm Password"
        type="password"
        onChange={onChange}
      />

      {/* If there is error than the error is shown */}

      <Text textAlign="center" color="red" fontSize="10pt" fontWeight="800">
        {error ||
          FIREBASE_ERRORS[userError?.message as keyof typeof FIREBASE_ERRORS]}
      </Text>

      <Button
        width="100%"
        height="36px"
        mt={2}
        mb={2}
        type="submit"
        isLoading={loading} // If loading (from Firebase) is true, show loading spinner
      >
        {" "}
        {/* When the form is submitted, execute onSubmit function */}
        Sign Up
      </Button>

      <Flex fontSize="9pt" justifyContent="center">
        <Text mr={1}>Already a Clown? </Text>
        <Text
          color="red.500"
          fontWeight={700}
          cursor="pointer"
          onClick={() =>
            setAuthModalState((prev) => ({
              ...prev,
              view: "login",
            }))
          }
        >
          Log In
        </Text>
      </Flex>
    </form>
  );
};

export default SignUp;

interface InputFieldProps {
  name: string;
  placeholder: string;
  type: string;
  onChange: React.ChangeEventHandler<HTMLInputElement>;
}

const InputField: React.FC<InputFieldProps> = ({
  name,
  placeholder,
  type,
  onChange,
}) => {
  return (
    <Input
      required
      name={name}
      placeholder={placeholder}
      type={type}
      mb={2}
      onChange={onChange}
      fontSize="10pt"
      bg="gray.50"
      _placeholder={{ color: "gray.500" }}
      _hover={{
        bg: "white",
        borderColor: "red.400",
        border: "1px solid",
      }}
      _focus={{
        outline: "none",
        bg: "white",
        borderColor: "gray.500",
        border: "1px solid",
      }}
    />
  );
};

This code takes the common elements between all the atoms and creates a component. The unique elements such as placeholder, name, type and onChange are passed as props.

mbeps commented 1 year ago

The hover styling and focus styling are not working