Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4e6700e
Fix: Updated page.test file to match Ryan's file in his "add-lockout"…
alexappleget Sep 25, 2024
0c5ebe8
Fix: Made changes in entry/all page.tsx to match Ryan's past branch.
alexappleget Sep 25, 2024
c93a773
Fix: Updated LeagueEntries interface file to match Ryan's old branch
alexappleget Sep 25, 2024
9973ae7
Fix: Updated League entry tests to take in Ryan's changes from his ol…
alexappleget Sep 25, 2024
6fdee85
Fix: Updated LeagueEntries to bring in Ryan's old branch changes.
alexappleget Sep 25, 2024
ad04f2c
Fix: Updated LinkCustom test file to match Ryan's.
alexappleget Sep 25, 2024
988de13
Fix: Updated LinkCustom file to match Ryan's
alexappleget Sep 25, 2024
a576cab
Fix: Finalized changes from Ryan's branch. All tests pass.
alexappleget Sep 26, 2024
5c69b6e
Fix: Adjusted lockout logic.
alexappleget Sep 26, 2024
7f9a693
Fix: Adjusted lockout times and added comments.
alexappleget Sep 26, 2024
b9bdd9f
Merge remote-tracking branch 'origin/develop' into Alex+Ryan/add-lock…
alexappleget Sep 26, 2024
fa1c5ea
Merge remote-tracking branch 'origin/develop' into Alex+Ryan/add-lock…
alexappleget Sep 26, 2024
c25250a
Merge remote-tracking branch 'origin/develop' into Alex+Ryan/add-lock…
alexappleget Sep 30, 2024
cf4af9e
Fix: added lockout to onWeeklyChange function
alexappleget Sep 30, 2024
a7d2a11
Fix: editted the trycatch{} around the if else statement in onWeeklyC…
alexappleget Sep 30, 2024
86cacb5
Fix: removed isLockedOut prop
alexappleget Sep 30, 2024
41a72a5
Merge remote-tracking branch 'origin/develop' into Alex+Ryan/add-lock…
alexappleget Oct 7, 2024
b97abe0
Fix: Fixed testing. Tests were failing because there were duplicate i…
alexappleget Oct 7, 2024
3730ab6
Fix: Created custom hook for lockout to be usable across the applicat…
alexappleget Oct 8, 2024
3ce0434
Merge remote-tracking branch 'origin/develop' into Alex+Ryan/add-lock…
alexappleget Oct 8, 2024
2bfc389
Fix: Removed eslint comment.
alexappleget Oct 8, 2024
42eb061
Fix: Created testing for the useHook made for lockout.
alexappleget Oct 8, 2024
2deb8ba
Fix: moved files for useLockout() hook
alexappleget Oct 8, 2024
d8b2f69
Fix: Fixed eslint error.
alexappleget Oct 8, 2024
303f8a4
Fix: deleted prop from tests as it no longer exists.
alexappleget Oct 8, 2024
a8ff6a1
Merge remote-tracking branch 'origin/develop' into Alex+Ryan/add-lock…
alexappleget Oct 10, 2024
0996cd7
Fix: Handled PR comments
alexappleget Oct 10, 2024
b99ebb0
Merge remote-tracking branch 'origin/develop' into Alex+Ryan/add-lock…
alexappleget Oct 12, 2024
bd81e39
Fix: Handled Braydon's PR comments and updated tests
alexappleget Oct 12, 2024
220d865
Merge branch 'develop' into Alex+Ryan/add-lockout-period
ryanfurrer Oct 14, 2024
d32c663
refactor: sorted imports and added additional line break in test.
ryanfurrer Oct 14, 2024
52beb96
test: remove isLockedOutProp from LeagueEntries test to reflect compo…
ryanfurrer Oct 14, 2024
5cbe943
feat: adjust lockout period to start Saturday @ 12:00am
ryanfurrer Oct 14, 2024
0b0ec0b
test: adjust test for testing lockout from Friday to Saturday.
ryanfurrer Oct 15, 2024
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
53 changes: 33 additions & 20 deletions app/(main)/league/[leagueId]/entry/[entryId]/week/Week.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,40 @@
// Licensed under the MIT License.

'use client';
import React, { JSX, useEffect, useState } from 'react';
import { AlertVariants } from '@/components/AlertNotification/Alerts.enum';
import { ChevronLeft } from 'lucide-react';
import { cn, getNFLTeamLogo } from '@/utils/utils';
import { Control, FormProvider, useForm } from 'react-hook-form';
import {
FormControl,
FormField,
FormItem,
FormControl,
FormMessage,
} from '@/components/Form/Form';
import { FormProvider, Control, useForm } from 'react-hook-form';
import { z } from 'zod';
import { IWeekProps } from './Week.interface';
import { zodResolver } from '@hookform/resolvers/zod';
import { useDataStore } from '@/store/dataStore';
import { ISchedule } from './WeekTeams.interface';
import {
getAllWeeklyPicks,
getCurrentUserEntries,
getCurrentLeague,
getCurrentUserEntries,
getGameWeek,
} from '@/api/apiFunctions';
import { ILeague } from '@/api/apiFunctions.interface';
import WeekTeams from './WeekTeams';
import GlobalSpinner from '@/components/GlobalSpinner/GlobalSpinner';
import { onWeeklyPickChange } from './WeekHelper';
import Alert from '@/components/AlertNotification/AlertNotification';
import { AlertVariants } from '@/components/AlertNotification/Alerts.enum';
import { ISchedule } from './WeekTeams.interface';
import { IWeekProps } from './Week.interface';
import { NFLTeams } from '@/api/apiFunctions.enum';
import { onWeeklyPickChange } from './WeekHelper';
import { useAuthContext } from '@/context/AuthContextProvider';
import { cn, getNFLTeamLogo } from '@/utils/utils';
import Image from 'next/image';
import { useDataStore } from '@/store/dataStore';
import { useRouter } from 'next/navigation';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import Alert from '@/components/AlertNotification/AlertNotification';
import GlobalSpinner from '@/components/GlobalSpinner/GlobalSpinner';
import Image from 'next/image';
import LinkCustom from '@/components/LinkCustom/LinkCustom';
import { ChevronLeft } from 'lucide-react';
import React, { JSX, useEffect, useState } from 'react';
import toast from 'react-hot-toast';
import useIsUserLockedOut from '@/hooks/useIsUserLockedOut';
import WeekTeams from './WeekTeams';

/**
* Renders the weekly picks page.
Expand All @@ -54,6 +56,7 @@ const Week = ({ entry, league, NFLTeams, week }: IWeekProps): JSX.Element => {
useDataStore((state) => state);
const { isSignedIn } = useAuthContext();
const router = useRouter();
const lockedOut = useIsUserLockedOut();

/**
* Fetches the current game week.
Expand Down Expand Up @@ -217,9 +220,19 @@ const Week = ({ entry, league, NFLTeams, week }: IWeekProps): JSX.Element => {
};

try {
await onWeeklyPickChange(params);
setUserPick(teamSelect);
router.push(`/league/${league}/entry/all`);
if (lockedOut) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to nest this if statement in the try block?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. So what it does is run the api functions inside the try block. But, if its during the lockdown period it will block the api functions from running and instead run the toast notification. It is a way to stop hackers from hitting the api's when they shouldn't.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@braydoncoyer please let me know if you need more info on this

@braydoncoyer braydoncoyer Oct 23, 2024

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexappleget @ryandotfurrer Yep, I get the purpose of the Try block. I think I was more asking if the try block could be moved inside the conditional to better represent which part of the code may cause/throw an error.

I'll leave this as a nit because as-is this likely works. However, I'm always a big fan of being more specific about which part of the code may need to have errors caught.

if (lockedOut) {
  ... code
} else {
  try {
    await onWeeklyPickChange(params);
    setUserPick(teamSelect);
    ...
  } catch (error) {
   ...
  }
  console.error(params);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any new unit tests for this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add these in ASAP.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shashilo can you elaborate what test exactly you're looking for?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a conditional statement, so you need to have a positive and negative unit tests for this. Checking that both conditions are accurate when their conditions are met.

toast.custom(
<Alert
variant={AlertVariants.Error}
message={`Team selection has been locked for the week!`}
/>,
);
} else {
await onWeeklyPickChange(params);
setUserPick(teamSelect);
router.push(`/league/${league}/entry/all`);
}
Comment thread
choir241 marked this conversation as resolved.
console.error(params);
} catch (error) {
console.error('Submission error:', error);
}
Expand Down
8 changes: 4 additions & 4 deletions app/(main)/league/[leagueId]/entry/all/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ describe('League entries page (Entry Component)', () => {
screen.queryByTestId('add-new-entry-button'),
).not.toBeInTheDocument();
});
it('should display "Make Pick" button when no pick is set', async () => {
it('should display "Make Pick" link when no pick is set', async () => {
mockGetCurrentUserEntries.mockResolvedValueOnce([
{
$id: '123',
Expand All @@ -311,13 +311,13 @@ describe('League entries page (Entry Component)', () => {
render(<Entry params={{ leagueId: '123' }} />);

await waitFor(() => {
expect(screen.getByTestId('league-entry-pick-button')).toHaveTextContent(
expect(screen.getByTestId('league-entry-pick-link')).toHaveTextContent(
'Make Pick',
);
});
});

it('should render team logo and change button to "Change Pick" when a pick is made', async () => {
it('should render team logo and change link to "Change Pick" when a pick is made', async () => {
mockUseDataStore.mockReturnValue({
...mockUseDataStore(),
currentWeek: 1,
Expand All @@ -339,7 +339,7 @@ describe('League entries page (Entry Component)', () => {
'/_next/image?url=%2Fpackers-logo.png&w=96&q=75',
);

expect(screen.getByTestId('league-entry-pick-button')).toHaveTextContent(
expect(screen.getByTestId('league-entry-pick-link')).toHaveTextContent(
'Change Pick',
);
});
Expand Down
2 changes: 1 addition & 1 deletion app/(main)/league/[leagueId]/entry/all/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,10 @@ const Entry = ({
return (
<section key={entry.$id}>
<LeagueEntries
key={entry.$id}
entryName={entry.name}
isEliminated={entry.eliminated}
isPickSet={isPickSet}
key={entry.$id}
linkUrl={linkUrl}
userPickHistory={userPickHistory}
selectedTeamLogo={selectedTeamLogo}
Expand Down
20 changes: 7 additions & 13 deletions components/LeagueEntries/LeagueEntries.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ describe('LeagueEntries', () => {
);
const leagueEntryNumber = screen.getByTestId('league-entry-number');
const entryStatus = screen.getByTestId('entry-status');
const leagueEntryPickButton = screen.getByTestId(
'league-entry-pick-button',
);
const leagueEntryPickLink = screen.getByTestId('league-entry-pick-link');
const userHistoryPicks = screen.queryByTestId('user-pick-history');

expect(entryStatus).toHaveTextContent('alive');
expect(leagueEntryContainerCard).toBeInTheDocument();
expect(leagueEntryNumber).toHaveTextContent('Entry 1');
expect(leagueEntryPickButton).toHaveTextContent('Make Pick');
expect(leagueEntryPickLink).toHaveTextContent('Make Pick');
expect(userHistoryPicks).not.toBeInTheDocument();
});

Expand All @@ -40,14 +38,12 @@ describe('LeagueEntries', () => {
);
const leagueEntryNumber = screen.getByTestId('league-entry-number');
const entryStatus = screen.getByTestId('entry-status');
const leagueEntryPickButton = screen.getByTestId(
'league-entry-pick-button',
);
const leagueEntryPickLink = screen.getByTestId('league-entry-pick-link');

expect(entryStatus).toHaveTextContent('alive');
expect(leagueEntryContainerCard).toBeInTheDocument();
expect(leagueEntryNumber).toHaveTextContent('Entry 2');
expect(leagueEntryPickButton).toHaveTextContent('Change Pick');
expect(leagueEntryPickLink).toHaveTextContent('Change Pick');
expect(screen.queryByTestId('user-pick-history')).toBeInTheDocument();
});

Expand Down Expand Up @@ -92,16 +88,14 @@ describe('LeagueEntries', () => {
);
const leagueEntryNumber = screen.getByTestId('league-entry-number');
const entryStatus = screen.getByTestId('entry-status');
const leagueEntryPickButton = screen.getByTestId(
'league-entry-pick-button',
);
const leagueLink = screen.getByTestId('league-entry-pick-button-link');
const leagueEntryPickLink = screen.getByTestId('league-entry-pick-link');
const leagueLink = screen.getByTestId('league-entry-pick-link');
const leagueEntryLogo = screen.getByTestId('league-entry-logo');

expect(leagueEntryContainerCard).toBeInTheDocument();
expect(entryStatus).toHaveTextContent('alive');
expect(leagueEntryNumber).toHaveTextContent('Entry 2');
expect(leagueEntryPickButton).toHaveTextContent('Change Pick');
expect(leagueEntryPickLink).toHaveTextContent('Change Pick');
expect(leagueLink).toHaveAttribute('href', linkUrl);
expect(leagueEntryLogo).toBeInTheDocument();
expect(leagueEntryLogo).toHaveAttribute(
Expand Down
41 changes: 29 additions & 12 deletions components/LeagueEntries/LeagueEntries.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright (c) Gridiron Survivor.
// Licensed under the MIT License.

import { Button } from '../Button/Button';
import { cn } from '@/utils/utils';
import { EntryStatus } from '../EntryStatus/EntryStatus';
import { ILeagueEntriesProps } from './LeagueEntries.interface';
import React, { JSX } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import LinkCustom from '../LinkCustom/LinkCustom';
import React, { JSX } from 'react';
import useLockout from '@/hooks/useIsUserLockedOut';

/**
* A card that contains information on the user's entry for this league. Contains the entry number, their entry status (alive or eliminated), team logo once a pick is set, and a button to make a pick or change their pick
Expand All @@ -18,16 +18,25 @@ import Image from 'next/image';
* @param props.isPickSet - if true, the team logo of the picked team shows up on the LeagueEntries card and the button changes from "make a pick" to "chagne pick"
* @param props.userPickHistory - the user's pick history for this entry
* @param props.selectedTeamLogo - the team logo
* @param props.lockout - if true, the user is locked out from making a pick
* @returns {React.JSX.Element} - A div element that contains the user's entry information
*/

/**
* Display all entries for a league.
* @param {string} leagueId - The league id.
* @returns {JSX.Element} The rendered entries component.
*/
const LeagueEntries = ({
entryName,
linkUrl,
isEliminated = false,
isPickSet = false,
linkUrl,
userPickHistory,
selectedTeamLogo = '',
}: ILeagueEntriesProps): JSX.Element => {
const lockedOut = useLockout();

return (
<div
data-testid="league-entry-container-card"
Expand Down Expand Up @@ -107,14 +116,22 @@ const LeagueEntries = ({
data-testid="league-entry-pick-button-container"
>
{!isEliminated && (
<Link href={linkUrl} data-testid="league-entry-pick-button-link">
<Button
className="league-entry-pick-button"
data-testid="league-entry-pick-button"
label={isPickSet ? 'Change Pick' : 'Make Pick'}
variant={isPickSet ? 'secondary' : 'default'}
/>
</Link>
<LinkCustom

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 3 conditions in this component. This is best, it's having conditional statements for 2 separate components. It's easier to read and maintain.

aria-disabled={lockedOut ? 'true' : 'false'}
className={cn(
'league-entry-pick-link',
lockedOut ? 'opacity-50 cursor-not-allowed' : '',
)}
dataTestidProp="league-entry-pick-link"
href={linkUrl}
onClick={(e: { preventDefault: () => unknown }) =>
lockedOut && e.preventDefault()
}
size={'defaultButton'}
variant={isPickSet ? 'secondaryButton' : 'primaryButton'}
Comment thread
choir241 marked this conversation as resolved.
>
{isPickSet ? 'Change Pick' : 'Make Pick'}
</LinkCustom>
)}
</div>
</section>
Expand Down
2 changes: 1 addition & 1 deletion components/LinkCustom/LinkCustom.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import LinkCustom from './LinkCustom';
describe('LinkCustom Component', () => {
it('renders with default props', () => {
render(
<LinkCustom children="Test link" href="https://example.com"></LinkCustom>,
<LinkCustom children="Test link" dataTestidProp="linkCustom" href="https://example.com"></LinkCustom>,
);
const link = screen.getByTestId('linkCustom');
expect(link).toBeInTheDocument();
Expand Down
59 changes: 47 additions & 12 deletions components/LinkCustom/LinkCustom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,70 @@

import Link from 'next/link';
import React, { JSX } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/utils/utils';

interface ILinkCustomProps {
children: React.ReactNode;
const linkCustomVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed',
{
variants: {
variant: {
default: 'underline underline-offset-4 hover:text-primary-muted transition-colors',
primaryButton: 'bg-primary text-primary-foreground hover:bg-primary-muted text-sm font-medium',
disabledPrimaryButton: 'bg-primary text-primary-foreground hover:bg-primary-muted text-sm font-medium opacity-50 cursor-not-allowed',
secondaryButton: 'bg-secondary text-secondary-foreground hover:bg-secondary-muted text-sm font-medium',
disabledSecondaryButton: 'bg-secondary text-secondary-foreground hover:bg-secondary-muted text-sm font-medium opacity-50 cursor-not-allowed',
},
size: {
default: 'h-fit w-fit',
defaultButton: 'h-10 px-4 py-2',
smButton: 'h-9 rounded-md px-3',
lgButton: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
}
},
defaultVariants: {
size: 'default',
variant: 'default',
},
}
);

interface ILinkCustomProps extends VariantProps<typeof linkCustomVariants> {
children?: React.ReactNode;
className?: string;
dataTestidProp?: string;
href: string;
onClick?: ({}: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void;
}

/**
* Custom link component
* @param props - The props
* @param props.children - any additional items you want inside the link. This could include things like the link text, icons, etc.
* @param props.href - this is the URL you want the link to point to
* @param props.className - any additional classes you want to add to that instance of the LinkCustom component.
* @returns The custom link component
* @param props - the props for LinkCustom
* @param props.children - the children of the link
* @param props.className - the class name of the link
* @param props.dataTestidProp - the data-testid of the link
* @param props.href - the url of the link
* @param props.onClick - the click event of the link
* @param props.size - the size of the link
* @param props.variant - the variant of the link
* @returns {React.JSX.Element} - A link element
*/
const LinkCustom = ({
children,
className,
dataTestidProp,
href,
onClick,
size,
variant,
}: ILinkCustomProps): JSX.Element => {
return (
<Link
className={cn(
'underline underline-offset-4 hover:text-primary-muted transition-colors',
className,
)}
data-testid="linkCustom"
className={cn(linkCustomVariants({ size, variant }), className)}
data-testid={dataTestidProp}
href={href}
onClick={onClick}
passHref
>
{children}
Expand Down
35 changes: 35 additions & 0 deletions hooks/useIsUserLockedOut.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { renderHook } from '@testing-library/react';
import useIsUserLockedOut from './useIsUserLockedOut';

describe('useLockout hook', () => {
let getUTCDaySpy: jest.SpyInstance;
let getUTCHoursSpy: jest.SpyInstance;

beforeEach(() => {
getUTCDaySpy = jest.spyOn(Date.prototype, 'getUTCDay');
getUTCHoursSpy = jest.spyOn(Date.prototype, 'getUTCHours');
});

afterEach(() => {
getUTCDaySpy.mockRestore();
getUTCHoursSpy.mockRestore();
});

it('should lock out on Saturday at 12am UTC', () => {
getUTCDaySpy.mockReturnValue(6); //Mocking Saturday
getUTCHoursSpy.mockReturnValue(0); //Mocking 12am UTC

const { result } = renderHook(() => useIsUserLockedOut());

expect(result.current).toBe(true);
});

it('should not be locked out on Wednesday at any time', () => {
getUTCDaySpy.mockReturnValue(3); //Mocking Wednesday
getUTCHoursSpy.mockReturnValue(12); //Mocking 12pm UTC

const { result } = renderHook(() => useIsUserLockedOut());

expect(result.current).toBe(false);
});
});
Loading