From 6204626e3ae0be49fe5d374f4aa56d658d550c40 Mon Sep 17 00:00:00 2001 From: Corina Murg <115652409+CorinaMurg@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:26:29 -0400 Subject: [PATCH 1/2] fix (#273): a11y audit signup page (#303) Fixes: #273 ### Changes: - add `title` element (completed by Chris). This involved setting up a server-side page, now responsible for rendering the `Register` component and setting its metadata (title and description). This approach is what Next.js expects. It wants to integrate the metadata into the server response when the page is initially loaded. - add `FormLabel` to input fields - create test for `Label` component ### Remaining (until design updates): - change input border color to meet contrast ratio --------- Co-authored-by: Cor-Ina <115652409+Cor-Ina@users.noreply.github.com> Co-authored-by: Chris Nowicki <102450568+chris-nowicki@users.noreply.github.com> Co-authored-by: Ryan Furrer Co-authored-by: Shashi Lo <362527+shashilo@users.noreply.github.com> --- app/(main)/register/Register.tsx | 261 ++++++++++++++++++++++++++++++ app/(main)/register/page.test.tsx | 10 +- app/(main)/register/page.tsx | 249 ++-------------------------- components/Label/Label.test.tsx | 40 +++++ 4 files changed, 317 insertions(+), 243 deletions(-) create mode 100644 app/(main)/register/Register.tsx create mode 100644 components/Label/Label.test.tsx 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); + }); +}); + From a267371079408d8e4c55521d8a2f85a0f9078390 Mon Sep 17 00:00:00 2001 From: Chris Nowicki <102450568+chris-nowicki@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:03:32 -0400 Subject: [PATCH 2/2] chris/feat(#381): Implement User Role Access (#458) fixes #381 Added access for SUPER ADMINS only. This has to be a field added in the database manually under the user > labels data field. This should only apply to GIS Developers. ## VIDEO https://github.com/user-attachments/assets/d65b83ce-4ef4-41d5-a2f9-30f55547ebfb --------- Co-authored-by: Shashi Lo <362527+shashilo@users.noreply.github.com> --- api/apiFunctions.interface.ts | 10 +++++ api/apiFunctions.ts | 5 ++- app/(main)/league/all/page.test.tsx | 7 ++++ app/(main)/league/all/page.tsx | 11 ++++-- .../UpdateEmailForm/UpdateEmailForm.tsx | 2 +- context/AuthContextProvider.tsx | 38 ++++++++++++------- store/dataStore.test.ts | 7 ++++ store/dataStore.ts | 5 ++- utils/utils.test.ts | 28 ++++++++++++++ utils/utils.ts | 19 ++++++++++ 10 files changed, 112 insertions(+), 20 deletions(-) diff --git a/api/apiFunctions.interface.ts b/api/apiFunctions.interface.ts index ed8352ba..e5d1f6fb 100644 --- a/api/apiFunctions.interface.ts +++ b/api/apiFunctions.interface.ts @@ -9,6 +9,16 @@ export interface IAccountData { } export interface IUser { documentId: string; + // for the appwrite auth collection + id: string; + email: string; + leagues: string[]; + labels: string[]; +} + +export interface ICollectionUser { + documentId: string; + // for the custom user collection id: string; email: string; leagues: string[]; diff --git a/api/apiFunctions.ts b/api/apiFunctions.ts index 93041dd3..2f3b072d 100644 --- a/api/apiFunctions.ts +++ b/api/apiFunctions.ts @@ -8,6 +8,7 @@ import { ILeague, IGameWeek, IUser, + ICollectionUser, IWeeklyPicks, INFLTeam, IRecoveryToken, @@ -149,7 +150,9 @@ export async function updateUserEmail({ * @param userId - The user ID * @returns {Models.DocumentList | Error} - The user object or an error */ -export async function getCurrentUser(userId: IUser['id']): Promise { +export async function getCurrentUser( + userId: IUser['id'], +): Promise { try { const user = await databases.listDocuments( appwriteConfig.databaseId, diff --git a/app/(main)/league/all/page.test.tsx b/app/(main)/league/all/page.test.tsx index b1a783c8..8625ee47 100644 --- a/app/(main)/league/all/page.test.tsx +++ b/app/(main)/league/all/page.test.tsx @@ -32,6 +32,7 @@ jest.mock('@/store/dataStore', () => ({ id: '1234', email: 'test@test.com', leagues: ['league1'], + labels: [], }, allLeagues: [ { @@ -82,6 +83,7 @@ describe('Leagues Component', () => { leagues: [], }, allLeagues: [], + labels: [], }); render(); @@ -105,6 +107,7 @@ describe('Leagues Component', () => { leagues: [], }, allLeagues: [], + labels: [], }); render(); @@ -121,6 +124,7 @@ describe('Leagues Component', () => { email: 'test@test.com', id: '123', leagues: [], + labels: [], }, allLeagues: [ { @@ -150,6 +154,7 @@ describe('Leagues Component', () => { email: 'test@test.com', id: '123', leagues: [], + labels: [], }; const league = { @@ -202,6 +207,7 @@ describe('Leagues Component', () => { user.id, user.email, [...user.leagues, league.leagueId], + user.labels, ); expect(toast.custom).toHaveBeenCalledWith( { email: 'test@test.com', id: '123', leagues: [], + labels: [], }; const league = { diff --git a/app/(main)/league/all/page.tsx b/app/(main)/league/all/page.tsx index 56c04323..8e8a77de 100644 --- a/app/(main)/league/all/page.tsx +++ b/app/(main)/league/all/page.tsx @@ -100,10 +100,13 @@ const Leagues = (): JSX.Element => { }); setLeagues([...leagues, league]); - updateUser(user.documentId, user.id, user.email, [ - ...user.leagues, - league.leagueId, - ]); + updateUser( + user.documentId, + user.id, + user.email, + [...user.leagues, league.leagueId], + user.labels, + ); toast.custom( { />, ); - updateUser(user.documentId, user.id, email, user.leagues); + updateUser(user.documentId, user.id, email, user.leagues, user.labels); form.reset({ email: email || '', password: '' }); } catch (error) { console.error('Email Update Failed', error); diff --git a/context/AuthContextProvider.tsx b/context/AuthContextProvider.tsx index d607d6b0..eb1adb9b 100644 --- a/context/AuthContextProvider.tsx +++ b/context/AuthContextProvider.tsx @@ -8,10 +8,11 @@ import { account } from '@/api/config'; import { useRouter } from 'next/navigation'; import { useDataStore } from '@/store/dataStore'; import type { DataStore } from '@/store/dataStore'; -import { IUser } from '@/api/apiFunctions.interface'; +import { ICollectionUser, IUser } from '@/api/apiFunctions.interface'; import { getCurrentUser } from '@/api/apiFunctions'; import { loginAccount, logoutHandler } from './AuthHelper'; import { usePathname } from 'next/navigation'; +import { isAuthRequiredPath } from '@/utils/utils'; type UserCredentials = { email: string; @@ -50,8 +51,12 @@ export const AuthContextProvider = ({ getUser(); return; } + setIsSignedIn(true); - }, [user]); + if (pathname.startsWith('/admin')) { + !user.labels.includes('admin') && router.push('/'); + } + }, [user, pathname]); /** * Authenticate and set session state @@ -81,11 +86,7 @@ export const AuthContextProvider = ({ */ const getUser = async (): Promise => { if (!isSessionInLocalStorage()) { - if ( - pathname !== '/register' && - pathname !== '/account/recovery' && - pathname !== '/recover-password' - ) { + if (isAuthRequiredPath(pathname)) { router.push('/login'); } return; @@ -93,14 +94,25 @@ export const AuthContextProvider = ({ try { const user = await account.get(); - const userData: IUser = await getCurrentUser(user.$id); + const userData: ICollectionUser = await getCurrentUser(user.$id); + + const currentUser: IUser = { + documentId: userData.documentId, + id: userData.id, + email: userData.email, + leagues: userData.leagues, + labels: user.labels, + }; + updateUser( - userData.documentId, - userData.id, - userData.email, - userData.leagues, + currentUser.documentId, + currentUser.id, + currentUser.email, + currentUser.leagues, + user.labels, ); - return userData; + + return currentUser; } catch (error) { resetUser(); setIsSignedIn(false); diff --git a/store/dataStore.test.ts b/store/dataStore.test.ts index ab4f9acf..d3012f1f 100644 --- a/store/dataStore.test.ts +++ b/store/dataStore.test.ts @@ -7,6 +7,7 @@ const userData = { id: '123', email: 'test@email.com', leagues: ['123456'], + labels: ['admin'], }; const NFLTeams = [ @@ -61,11 +62,14 @@ describe('Data Store', () => { userData.userId, userData.userEmail, userData.leagues, + userData.labels, ); }); expect(result.current.user.id).toBe(userData.userId); expect(result.current.user.email).toBe(userData.userEmail); + expect(result.current.user.labels).toStrictEqual(userData.labels); + expect(result.current.user.leagues).toStrictEqual(userData.leagues); }); it('Checks the reset user state matches default', () => { const { result } = renderHook(() => useDataStore()); @@ -76,12 +80,15 @@ describe('Data Store', () => { userData.userId, userData.userEmail, userData.leagues, + userData.labels, ); result.current.resetUser(); }); expect(result.current.user.id).toBe(''); expect(result.current.user.email).toBe(''); + expect(result.current.user.labels).toStrictEqual([]); + expect(result.current.user.leagues).toStrictEqual([]); }); }); diff --git a/store/dataStore.ts b/store/dataStore.ts index 1acc84e5..e0cef710 100644 --- a/store/dataStore.ts +++ b/store/dataStore.ts @@ -34,6 +34,7 @@ interface IDataStoreAction { id: IUser['id'], email: IUser['email'], leagues: IUser['leagues'], + labels: IUser['labels'], ) => void; updateWeeklyPicks: ({ leagueId, @@ -63,6 +64,7 @@ const initialState: IDataStoreState = { id: '', email: '', leagues: [], + labels: [], }, weeklyPicks: { leagueId: '', @@ -111,13 +113,14 @@ export const useDataStore = create((set) => ({ * @param selectedLeagues - The user selected league * @returns {void} */ - updateUser: (documentId, id, email, leagues): void => + updateUser: (documentId, id, email, leagues, labels): void => set( produce((state: IDataStoreState) => { state.user.documentId = documentId; state.user.id = id; state.user.email = email; state.user.leagues = [...leagues]; + state.user.labels = [...labels]; }), ), /** diff --git a/utils/utils.test.ts b/utils/utils.test.ts index 408a3b89..1c471924 100644 --- a/utils/utils.test.ts +++ b/utils/utils.test.ts @@ -5,6 +5,7 @@ import { getUserPick, parseUserPick, getUserLeagues, + isAuthRequiredPath, } from './utils'; import { getCurrentLeague, getAllWeeklyPicks } from '@/api/apiFunctions'; @@ -179,6 +180,33 @@ describe('utils', () => { }); }); }); + describe('isAuthRequiredPath', () => { + it('should return false for non-auth paths', () => { + expect(isAuthRequiredPath('/register')).toBe(false); + expect(isAuthRequiredPath('/account/recovery')).toBe(false); + expect(isAuthRequiredPath('/recover-password')).toBe(false); + }); + + it('should return true for auth-required paths', () => { + expect(isAuthRequiredPath('/')).toBe(true); + expect(isAuthRequiredPath('/dashboard')).toBe(true); + expect(isAuthRequiredPath('/profile')).toBe(true); + expect(isAuthRequiredPath('/settings')).toBe(true); + }); + + it('should handle edge cases', () => { + expect(isAuthRequiredPath('')).toBe(true); + expect(isAuthRequiredPath('/register/')).toBe(true); // Trailing slash + expect(isAuthRequiredPath('/REGISTER')).toBe(true); // Case sensitivity + expect(isAuthRequiredPath('/register/subpage')).toBe(true); + }); + + it('should handle unusual inputs', () => { + expect(isAuthRequiredPath(' /register ')).toBe(true); // Spaces + expect(isAuthRequiredPath('/account/recovery?param=value')).toBe(true); // Query parameters + }); + }); + xdescribe('getUserLeagues', () => { it('should return the list of leagues the user is a part of', async () => { (getCurrentLeague as jest.Mock).mockResolvedValue(mockLeague); diff --git a/utils/utils.ts b/utils/utils.ts index 51aee08e..ca28a1da 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -147,6 +147,15 @@ export const getUserEntries = async (userId: IUser['id'], leagueId: ILeague['lea return await getCurrentUserEntries(userId, leagueId); } +/** + * Check if the route is an /admin route + * @param path - The path to check + * @returns {boolean} - Whether the route is an /admin route + */ +export const isAdminRoute = (path: string): boolean => { + return path.startsWith('/admin'); +}; + /** * Returns if the team has already been picked by the user * @param teamName - The team name @@ -167,3 +176,13 @@ export const hasTeamBeenPicked = (teamName: string, selectedTeams: string[]): bo export const getNFLTeamLogo = (NFLTeams: INFLTeam[], teamName: string): string => { return NFLTeams.find((teams) => teams.teamName === teamName)?.teamLogo as string; } + +/** + * Checks if the current path requires authentication + * @param pathname - The current path + * @returns {boolean} - Whether the current path requires authentication + */ +export const isAuthRequiredPath = (pathname: string): boolean => { + const nonAuthPaths = ['/register', '/account/recovery', '/recover-password']; + return !nonAuthPaths.includes(pathname); +};