diff --git a/app/(main)/register/Register.tsx b/app/(main)/register/Register.tsx new file mode 100644 index 00000000..6c8d02b9 --- /dev/null +++ b/app/(main)/register/Register.tsx @@ -0,0 +1,261 @@ +// Copyright (c) Gridiron Survivor. +// Licensed under the MIT License. + +'use client'; +import { AlertVariants } from '@/components/AlertNotification/Alerts.enum'; +import { Button } from '@/components/Button/Button'; +import { Control, useForm, useWatch, SubmitHandler } from 'react-hook-form'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '../../../components/Form/Form'; +import { Input } from '@/components/Input/Input'; +import { registerAccount } from '@/api/apiFunctions'; +import { toast } from 'react-hot-toast'; +import { useAuthContext } from '@/context/AuthContextProvider'; +import { useRouter } from 'next/navigation'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import Alert from '@/components/AlertNotification/AlertNotification'; +import LinkCustom from '@/components/LinkCustom/LinkCustom'; +import Logo from '@/components/Logo/Logo'; +import logo from '/public/assets/logo-colored-outline.svg'; +import React, { JSX, useEffect, useState } from 'react'; +import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner'; + +const RegisterUserSchema = z + .object({ + email: z + .string() + .min(1, { message: 'Please enter an email address' }) + .email({ message: 'Please enter a valid email address' }), + password: z + .string() + .min(1, { message: 'Please enter a password' }) + .min(8, { message: 'Password must be at least 8 characters' }), + confirmPassword: z + .string() + .min(1, { message: 'Please confirm your password' }) + .min(8, { message: 'Password must be at least 8 characters' }), + }) + .refine((data) => data.password === data.confirmPassword, { + message: "Passwords don't match", + path: ['confirmPassword'], + }); + +type RegisterUserSchemaType = z.infer; + +/** + * Renders the registration page. + * @returns {JSX.Element} The rendered registration page. + */ +const Register = (): JSX.Element => { + const router = useRouter(); + const { login, isSignedIn } = useAuthContext(); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (isSignedIn) { + router.push('/league/all'); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isSignedIn]); + + const form = useForm({ + resolver: zodResolver(RegisterUserSchema), + }); + + /** + * The current value of the 'email' field in the form. + * @type {string} + */ + const email: string = useWatch({ + control: form.control, + name: 'email', + defaultValue: '', + }); + + /** + * The current value of the 'password' field in the form. + * @type {string} + */ + const password: string = useWatch({ + control: form.control, + name: 'password', + defaultValue: '', + }); + + /** + * The current value of the 'confirmPassword' field in the form. + * @type {string} + */ + const confirmPassword: string = useWatch({ + control: form.control, + name: 'confirmPassword', + defaultValue: '', + }); + + /** + * A function that handles form submission. + * @param {RegisterUserSchemaType} data - The data submitted in the form. + * @returns {Promise} Promise that resolves after form submission is processed. + */ + const onSubmit: SubmitHandler = async ( + data: RegisterUserSchemaType, + ): Promise => { + try { + setIsLoading(true); + await registerAccount(data); + await login(data); + toast.custom( + , + ); + } catch (error) { + console.error('Registration Failed', error); + toast.custom( + , + ); + } finally { + setIsLoading(false); + } + }; + + const isDisabled = !email || !password || password !== confirmPassword; + + return ( +
+
+ +
+

+ Thank you... fantasy football draft, for letting me know that even + in my fantasies, I am bad at sports. +

+

Jimmy Fallon

+
+
+
+
+

+ Register A New Account +

+

+ If you have an existing account{' '} + Login! +

+
+ +
+ + } + name="email" + render={({ field }) => ( + + + Email + + + + + {form.formState.errors?.email && ( + + {form.formState.errors?.email.message} + + )} + + )} + /> + } + name="password" + render={({ field }) => ( + + + Password + + + + + {form.formState.errors?.password && ( + + {form.formState.errors?.password.message} + + )} + + )} + /> + } + name="confirmPassword" + render={({ field }) => ( + + + Confirm Password + + + + + {form.formState.errors?.confirmPassword && ( + + {form.formState.errors?.confirmPassword.message} + + )} + + )} + /> + +
+
+ ); +}; + +export default Register; \ No newline at end of file diff --git a/app/(main)/register/page.test.tsx b/app/(main)/register/page.test.tsx index e03874c0..36d7d66a 100644 --- a/app/(main)/register/page.test.tsx +++ b/app/(main)/register/page.test.tsx @@ -5,7 +5,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { toast } from 'react-hot-toast'; import Alert from '@/components/AlertNotification/AlertNotification'; import React, { useState as useStateMock } from 'react'; -import Register from './page'; +import RegisterPage from './page'; const mockLogin = jest.fn(); const mockPush = jest.fn(); @@ -57,7 +57,7 @@ describe('Register', () => { .spyOn(React, 'useState') .mockImplementation(() => [false, setIsLoading]); - render(); + render(); confirmPasswordInput = screen.getByTestId('confirm-password'); continueButton = screen.getByTestId('continue-button'); @@ -98,7 +98,7 @@ describe('Register', () => { it('redirects to /league/all when the button is clicked', async () => { mockUseAuthContext.isSignedIn = true; - render(); + render(); await waitFor(() => { expect(mockPush).toHaveBeenCalledWith('/league/all'); @@ -177,7 +177,7 @@ describe('Register loading spinner', () => { setIsLoading, ]); - render(); + render(); await waitFor(() => { expect(screen.queryByTestId('loading-spinner')).toBeInTheDocument(); @@ -189,7 +189,7 @@ describe('Register loading spinner', () => { setIsLoading, ]); - render(); + render(); await waitFor(() => { expect(screen.queryByTestId('loading-spinner')).not.toBeInTheDocument(); diff --git a/app/(main)/register/page.tsx b/app/(main)/register/page.tsx index 4d1b8e8d..32323ed3 100644 --- a/app/(main)/register/page.tsx +++ b/app/(main)/register/page.tsx @@ -1,248 +1,21 @@ // Copyright (c) Gridiron Survivor. // Licensed under the MIT License. -'use client'; -import { AlertVariants } from '@/components/AlertNotification/Alerts.enum'; -import { Button } from '@/components/Button/Button'; -import { Control, useForm, useWatch, SubmitHandler } from 'react-hook-form'; -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from '../../../components/Form/Form'; -import { Input } from '@/components/Input/Input'; -import { registerAccount } from '@/api/apiFunctions'; -import { toast } from 'react-hot-toast'; -import { useAuthContext } from '@/context/AuthContextProvider'; -import { useRouter } from 'next/navigation'; -import { z } from 'zod'; -import { zodResolver } from '@hookform/resolvers/zod'; -import Alert from '@/components/AlertNotification/AlertNotification'; -import LinkCustom from '@/components/LinkCustom/LinkCustom'; -import Logo from '@/components/Logo/Logo'; -import logo from '/public/assets/logo-colored-outline.svg'; -import React, { JSX, useEffect, useState } from 'react'; -import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner'; +import { JSX } from 'react'; +import { Metadata } from 'next'; +import Register from './Register'; -const RegisterUserSchema = z - .object({ - email: z - .string() - .min(1, { message: 'Please enter an email address' }) - .email({ message: 'Please enter a valid email address' }), - password: z - .string() - .min(1, { message: 'Please enter a password' }) - .min(8, { message: 'Password must be at least 8 characters' }), - confirmPassword: z - .string() - .min(1, { message: 'Please confirm your password' }) - .min(8, { message: 'Password must be at least 8 characters' }), - }) - .refine((data) => data.password === data.confirmPassword, { - message: "Passwords don't match", - path: ['confirmPassword'], - }); - -type RegisterUserSchemaType = z.infer; +export const metadata: Metadata = { + title: 'Registration | Gridiron Survivor', + description: 'Fantasy Football Survivor Pool', +}; /** * Renders the registration page. * @returns {JSX.Element} The rendered registration page. */ -const Register = (): JSX.Element => { - const router = useRouter(); - const { login, isSignedIn } = useAuthContext(); - const [isLoading, setIsLoading] = useState(false); - - useEffect(() => { - if (isSignedIn) { - router.push('/league/all'); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isSignedIn]); - - const form = useForm({ - resolver: zodResolver(RegisterUserSchema), - }); - - /** - * The current value of the 'email' field in the form. - * @type {string} - */ - const email: string = useWatch({ - control: form.control, - name: 'email', - defaultValue: '', - }); - - /** - * The current value of the 'password' field in the form. - * @type {string} - */ - const password: string = useWatch({ - control: form.control, - name: 'password', - defaultValue: '', - }); - - /** - * The current value of the 'confirmPassword' field in the form. - * @type {string} - */ - const confirmPassword: string = useWatch({ - control: form.control, - name: 'confirmPassword', - defaultValue: '', - }); - - /** - * A function that handles form submission. - * @param {RegisterUserSchemaType} data - The data submitted in the form. - * @returns {Promise} Promise that resolves after form submission is processed. - */ - const onSubmit: SubmitHandler = async ( - data: RegisterUserSchemaType, - ): Promise => { - try { - setIsLoading(true); - await registerAccount(data); - await login(data); - toast.custom( - , - ); - } catch (error) { - console.error('Registration Failed', error); - toast.custom( - , - ); - } finally { - setIsLoading(false); - } - }; - - const isDisabled = !email || !password || password !== confirmPassword; - - return ( -
-
- -
-

- Thank you... fantasy football draft, for letting me know that even - in my fantasies, I am bad at sports. -

-

Jimmy Fallon

-
-
-
-
-

- Register A New Account -

-

- If you have an existing account{' '} - Login! -

-
- -
- - } - name="email" - render={({ field }) => ( - - - - - {form.formState.errors?.email && ( - - {form.formState.errors?.email.message} - - )} - - )} - /> - } - name="password" - render={({ field }) => ( - - - - - {form.formState.errors?.password && ( - - {form.formState.errors?.password.message} - - )} - - )} - /> - } - name="confirmPassword" - render={({ field }) => ( - - - - - {form.formState.errors?.confirmPassword && ( - - {form.formState.errors?.confirmPassword.message} - - )} - - )} - /> - -
-
- ); +const RegisterPage = (): JSX.Element => { + return ; }; - -export default Register; + +export default RegisterPage; \ No newline at end of file diff --git a/components/Label/Label.test.tsx b/components/Label/Label.test.tsx new file mode 100644 index 00000000..c9c77800 --- /dev/null +++ b/components/Label/Label.test.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { Label } from './Label'; + +describe('Label', () => { + const disabledVariants = [true, false]; + + disabledVariants.forEach((disabled) => { + it(`renders correctly when disabled is ${disabled}`, () => { + render( + + ); + + const label = screen.getByTestId('test-label'); + expect(label).toBeInTheDocument(); + + const expectedClass = disabled ? 'opacity-50 cursor-not-allowed' : 'peer-aria-checked:border-accent peer-hover:border-white'; + expect(label).toHaveClass(expectedClass); + }); + }); + + it('applies additional custom classes correctly', () => { + const extraClasses = 'custom-class-label extra-custom-class'; + render( + + ); + + const labelCustom = screen.getByTestId('custom-class-label'); + expect(labelCustom).toBeInTheDocument(); + expect(labelCustom).toHaveClass(extraClasses); + }); +}); +