Skip to content
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
40 changes: 8 additions & 32 deletions components/modal/auth/InputField.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,15 @@
import { Input } from "@chakra-ui/react";

/**
* @param {string} name - Name of the input field
* @param {string} placeholder - Placeholder text
* @param {string} type - Type of the input field
* @param {React.ChangeEventHandler<HTMLInputElement>} onChange - On change event handler
*/
interface InputFieldProps {
name: string;
placeholder: string;
type: string;
onChange: React.ChangeEventHandler<HTMLInputElement>;
}
import { Input, InputProps } from "@chakra-ui/react";
import React, { forwardRef } from "react";

/**
* Input field for various forms used in the Auth modal component.
* @param {string} name - Name of the input field
* @param {string} placeholder - Placeholder text
* @param {string} type - Type of the input field
* @param {React.ChangeEventHandler<HTMLInputElement>} onChange - On change event handler
*
* @returns {React.FC} - Input field component
*/
const InputField: React.FC<InputFieldProps> = ({
name,
placeholder,
type,
onChange,
}) => {
const InputField = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
return (
<Input
required
name={name}
placeholder={placeholder}
type={type}
{...props}
ref={ref}
mb={2}
onChange={onChange}
fontSize="10pt"
bg={{ base: "gray.50", _dark: "gray.800" }}
borderColor={{ base: "gray.200", _dark: "gray.600" }}
Expand All @@ -53,6 +27,8 @@ const InputField: React.FC<InputFieldProps> = ({
}}
/>
);
};
});

InputField.displayName = "InputField";

export default InputField;
84 changes: 31 additions & 53 deletions components/modal/auth/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Button, Flex, Input, Text } from "@chakra-ui/react";
import { Button, Flex, Text } from "@chakra-ui/react";
import { useSetAtom } from "jotai";
import React, { useState } from "react";
import React from "react";
import { useSignInWithEmailAndPassword } from "react-firebase-hooks/auth";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { authModalStateAtom } from "../../../atoms/authModalAtom";
import { auth } from "../../../firebase/clientApp";
import { FIREBASE_ERRORS } from "../../../firebase/errors";
import InputField from "./InputField";
import { PasswordInput } from "@/components/ui/password-input";
import { loginSchema, LoginInput } from "@/schema/auth";

type LoginProps = {};

Expand All @@ -25,66 +28,34 @@ type LoginProps = {};
*/
const Login: React.FC<LoginProps> = () => {
const setAuthModalState = useSetAtom(authModalStateAtom); // Set global state
const [loginForm, setLoginForm] = useState({
email: "", // Initially empty email
password: "", // Initially empty password
});

const [signInWithEmailAndPassword, user, loading, error] =
useSignInWithEmailAndPassword(auth);

/**
* This function is used as the event handler for a form submission.
* It will prevent the page from refreshing.
* Automatically checks if the user with the email exists and if the password is correct.
* @param {React.FormEvent<HTMLFormElement>} event - the submit event triggered by the form
*/
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); // Prevent page from reloading

signInWithEmailAndPassword(loginForm.email, loginForm.password); // Sign in 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
setLoginForm((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
}));
};
const {
register,
handleSubmit,
formState: { errors, isValid },
} = useForm<LoginInput>({
resolver: zodResolver(loginSchema),
mode: "onChange",
});

/**
* Determines whether the button is disabled or not.
* The button is disabled if the email or password is empty.
* @returns {boolean} - Whether the button is disabled or not
*/
const isButtonDisabled = () => {
return (
!loginForm.email || !loginForm.password
// signUpForm.confirmPassword !== signUpForm.password
);
const onSubmit = (data: LoginInput) => {
signInWithEmailAndPassword(data.email, data.password);
};

return (
<form onSubmit={onSubmit}>
<InputField
name="email"
placeholder="Email"
type="email"
onChange={onChange}
/>
<form onSubmit={handleSubmit(onSubmit)}>
<InputField placeholder="Email" type="email" {...register("email")} />
{errors.email && (
<Text color="red.500" fontSize="10pt" mt={1}>
{errors.email.message}
</Text>
)}

<PasswordInput
name="password"
placeholder="Password"
onChange={onChange}
required
rootProps={{ mb: 2 }}
rootProps={{ mb: 2, mt: 2 }}
fontSize="10pt"
bg={{ base: "gray.50", _dark: "gray.800" }}
borderColor={{ base: "gray.200", _dark: "gray.600" }}
Expand All @@ -100,13 +71,20 @@ const Login: React.FC<LoginProps> = () => {
border: "1px solid",
borderColor: { base: "red.500", _dark: "red.400" },
}}
{...register("password")}
/>
{errors.password && (
<Text color="red.500" fontSize="10pt" mt={1}>
{errors.password.message}
</Text>
)}

<Text
textAlign="center"
color={{ base: "red.500", _dark: "red.400" }}
fontSize="10pt"
fontWeight="800"
mt={2}
>
{FIREBASE_ERRORS[error?.code as keyof typeof FIREBASE_ERRORS]}
</Text>
Expand All @@ -118,7 +96,7 @@ const Login: React.FC<LoginProps> = () => {
mb={2}
type="submit"
loading={loading}
disabled={isButtonDisabled()}
disabled={!isValid}
>
{" "}
{/* When the form is submitted, execute onSubmit function */}
Expand Down
48 changes: 23 additions & 25 deletions components/modal/auth/ResetPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,52 @@ import { Button, Flex, Icon, Image, Input, Text } from "@chakra-ui/react";
import { useSetAtom } from "jotai";
import React, { useState } from "react";
import { useSendPasswordResetEmail } from "react-firebase-hooks/auth";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { BsDot } from "react-icons/bs";
import { authModalStateAtom } from "../../../atoms/authModalAtom";
import { auth } from "../../../firebase/clientApp";
import { resetPasswordSchema, ResetPasswordInput } from "@/schema/auth";

/**
* Allows the user to reset their password.
* Takes the email as the input and sends the user an email from Firebase to reset the password.
* Once the email is submitted, a new view is shown telling the user to check their email.
* @returns {React.FC} - Reset Password view in the authentication modal
*
* @see https://github.com/CSFrequency/react-firebase-hooks/tree/master/auth
*/
const ResetPassword: React.FC = () => {
const setAuthModalState = useSetAtom(authModalStateAtom);
const [email, setEmail] = useState("");
const [success, setSuccess] = useState(false);
const [sendPasswordResetEmail, sending, error] =
useSendPasswordResetEmail(auth);

/**
* This function is used as the event handler for a form submission.
* It will prevent the page from refreshing.
* Sends the email from Firebase to the email that was inputted in the form.
* @param {React.FormEvent<HTMLFormElement>} event - the submit event triggered by the form
*/
const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); // Prevent page from reloading
const {
register,
handleSubmit,
formState: { errors, isValid },
} = useForm<ResetPasswordInput>({
resolver: zodResolver(resetPasswordSchema),
mode: "onChange",
});

await sendPasswordResetEmail(email); // try to send email
setSuccess(true); // once the email is successfully send
const onSubmit = async (data: ResetPasswordInput) => {
await sendPasswordResetEmail(data.email);
setSuccess(true);
};

return (
<Flex direction="column" alignItems="center" width="100%">
<Image src="/images/logo.svg" height="40px" mb={2} alt="Website logo" />
<Text fontWeight={700} mb={2}>
Reset your password
</Text>
{/* Go to next page once the email is successfully sent */}
{success ? (
<Text mb={4}>Check your email</Text>
) : (
// While the email has not been sent, show the form
<>
<Text fontSize="sm" textAlign="center" mb={2}>
Enter the email associated with your account and we will send you a
reset link
</Text>
<form onSubmit={onSubmit} style={{ width: "100%" }}>
<form onSubmit={handleSubmit(onSubmit)} style={{ width: "100%" }}>
<Input
required
name="email"
placeholder="Email"
type="email"
mb={2}
onChange={(event) => setEmail(event.target.value)}
fontSize="10pt"
_placeholder={{ color: "gray.500" }}
_hover={{
Expand All @@ -71,7 +62,13 @@ const ResetPassword: React.FC = () => {
borderColor: "blue.500",
}}
bg={{ base: "gray.50", _dark: "gray.800" }}
{...register("email")}
/>
{errors.email && (
<Text color="red.500" fontSize="10pt" mt={1}>
{errors.email.message}
</Text>
)}
<Text
textAlign="center"
fontSize="10pt"
Expand All @@ -86,6 +83,7 @@ const ResetPassword: React.FC = () => {
mt={2}
type="submit"
loading={sending}
disabled={!isValid}
>
Reset Password
</Button>
Expand Down
Loading
Loading