Skip to content

Commit

Permalink
ui/ux: update week page (#370)
Browse files Browse the repository at this point in the history
Closes #353 
---

![Image](https://github.com/LetsGetTechnical/gridiron-survivor/assets/362527/95993d62-63d4-462a-8f86-828f8d8cc24f)

- [x] Create breadcrumb link
- [x] Update Page title to Week #
- [x] Add date and times to each game above the teams
- [x] Remove submit button in favor of user clicking any team to submit
their choice

---------

Co-authored-by: Chris Nowicki <[email protected]>
Co-authored-by: Shashi Lo <[email protected]>
  • Loading branch information
3 people authored Jul 10, 2024
1 parent 0522ae9 commit 1df7007
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 49 deletions.
3 changes: 3 additions & 0 deletions api/serverConfig.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (c) Gridiron Survivor.
// Licensed under the MIT License.

import * as sdk from 'node-appwrite';

const URL = process.env.NEXT_PUBLIC_APPWRITE_API_URL as string;
Expand Down
4 changes: 2 additions & 2 deletions app/league/[leagueId]/entry/[entryId]/week/Week.interface.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Gridiron Survivor.
// Licensed under the MIT License.

import { INFLTeam } from '@/api/apiFunctions.interface';
import { INFLTeam, ILeague } from '@/api/apiFunctions.interface';

export interface IWeekParams {
params: {
Expand All @@ -13,7 +13,7 @@ export interface IWeekParams {

export interface IWeekProps {
entry: string;
league: string;
league: ILeague['leagueId'];
NFLTeams: INFLTeam[];
week: string;
}
114 changes: 75 additions & 39 deletions app/league/[leagueId]/entry/[entryId]/week/Week.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
// Licensed under the MIT License.

'use client';
import React, { JSX, useEffect, useState } from 'react';
import { Button } from '@/components/Button/Button';
import React, { ChangeEvent, JSX, useEffect, useState } from 'react';
import {
FormField,
FormItem,
Expand All @@ -18,6 +17,10 @@ import { parseUserPick } from '@/utils/utils';
import { zodResolver } from '@hookform/resolvers/zod';
import { useDataStore } from '@/store/dataStore';
import { ISchedule } from './WeekTeams.interface';
import LinkCustom from '@/components/LinkCustom/LinkCustom';
import { ChevronLeft } from 'lucide-react';
import { getCurrentLeague } from '@/api/apiFunctions';
import { ILeague } from '@/api/apiFunctions.interface';
import WeekTeams from './WeekTeams';

/**
Expand All @@ -28,12 +31,22 @@ import WeekTeams from './WeekTeams';
// eslint-disable-next-line no-unused-vars
const Week = ({ entry, league, NFLTeams, week }: IWeekProps): JSX.Element => {
const [schedule, setSchedule] = useState<ISchedule[]>([]);
const [selectedLeague, setSelectedLeague] = useState<ILeague | undefined>();
const [isLoading, setIsLoading] = useState<boolean>(true);
const [userPick, setUserPick] = useState<string>('');

const { user, updateWeeklyPicks, weeklyPicks } = useDataStore(
(state) => state,
);

/**
* Fetches the selected league.
* @returns {Promise<void>}
*/
const getSelectedLeague = async (): Promise<void> => {
const res = await getCurrentLeague(league);
setSelectedLeague(res);
};

const NFLTeamsList = NFLTeams.map((team) => team.teamName) as [
string,
...string[]
Expand Down Expand Up @@ -73,20 +86,28 @@ const Week = ({ entry, league, NFLTeams, week }: IWeekProps): JSX.Element => {
* @param data - The form data.
* @returns {void}
*/
const onSubmit = async (data: z.infer<typeof FormSchema>): Promise<void> => {
const onWeeklyPickChange = async (
data: ChangeEvent<HTMLInputElement>,
): Promise<void> => {
try {
const teamSelect = data.type.toLowerCase();
const teamSelect = data.target.value;
const teamID = NFLTeams.find(
(team) => team.teamName.toLowerCase() === teamSelect,
)?.teamId;
(team) => team.teamName === teamSelect,
)?.teamName;

const currentUserPick = parseUserPick(user.id, entry, teamID || '');

// combines current picks and the user pick into one object.
// if the user pick exists then it overrides the pick of the user.
const updatedWeeklyPicks = {
...weeklyPicks.userResults,
...currentUserPick,
[user.id]: {
...weeklyPicks.userResults[user.id],
[entry]: {
...weeklyPicks.userResults[user.id]?.[entry],
...currentUserPick[user.id][entry],
},
},
};

// update weekly picks in the database
Expand All @@ -110,44 +131,59 @@ const Week = ({ entry, league, NFLTeams, week }: IWeekProps): JSX.Element => {
};

useEffect(() => {
if (!selectedLeague) {
getSelectedLeague();
return;
}
getSchedule(week);
}, [week]);
setIsLoading(false);
}, [week, selectedLeague]);

if (schedule.length === 0) {
if (schedule.length === 0 || isLoading) {
return <p>Loading...</p>;
}

return (
<section className="w-full pt-8" data-testid="weekly-picks">
<h1 className="pb-8 text-center text-[2rem] font-bold text-white">
Your pick sheet
</h1>

<FormProvider {...form}>
<form
className="mx-auto flex w-[90%] max-w-3xl flex-col items-center gap-8"
onSubmit={form.handleSubmit(onSubmit)}
<div className="league-entry-week">
<nav className="py-6 text-orange-500 hover:no-underline">
<LinkCustom
className="text-orange-500 flex gap-3 items-center font-semibold text-xl hover:no-underline"
href={`/league/${league}/entry/all`}
>
<FormField
control={form.control as Control<object>}
name="type"
render={({ field }) => (
<FormItem>
<FormControl>
<WeekTeams
schedule={schedule}
field={field}
userPick={userPick}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button label="Submit Button" type="submit" />
</form>
</FormProvider>
</section>
<span aria-hidden="true">
<ChevronLeft size={16} />
</span>
{selectedLeague?.leagueName as string}
</LinkCustom>
</nav>
<section className="w-full pt-8" data-testid="weekly-picks">
<h1 className="pb-8 text-center text-[2rem] font-bold text-white">
Week {week} pick
</h1>

<FormProvider {...form}>
<form className="mx-auto flex w-[90%] max-w-3xl flex-col items-center">
<FormField
control={form.control as Control<object>}
name="type"
render={({ field }) => (
<FormItem>
<FormControl>
<WeekTeams
schedule={schedule}
field={field}
userPick={userPick}
onWeeklyPickChange={onWeeklyPickChange}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</FormProvider>
</section>
</div>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Gridiron Survivor.
// Licensed under the MIT License.

import { ChangeEvent } from 'react';
import { ControllerRenderProps, FieldValues } from 'react-hook-form';

export interface ISchedule {
Expand All @@ -9,6 +10,7 @@ export interface ISchedule {
date: string;
name: string;
shortName: string;
startDate: string;
season: {
year: number;
type: number;
Expand All @@ -26,6 +28,8 @@ export interface IWeekTeamsProps {
field: ControllerRenderProps<FieldValues, string>;
schedule: ISchedule[];
userPick: string;
// eslint-disable-next-line no-unused-vars
onWeeklyPickChange: (data: ChangeEvent<HTMLInputElement>) => Promise<void>;
}

interface ICompetition {
Expand Down
38 changes: 35 additions & 3 deletions app/league/[leagueId]/entry/[entryId]/week/WeekTeams.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,66 @@
// Copyright (c) Gridiron Survivor.
// Licensed under the MIT License.

import React, { JSX } from 'react';
import React, { JSX, ChangeEvent } from 'react';
import { FormItem, FormControl } from '@/components/Form/Form';
import { RadioGroup } from '@radix-ui/react-radio-group';
import { IWeekTeamsProps } from './WeekTeams.interface';
import { WeeklyPickButton } from '@/components/WeeklyPickButton/WeeklyPickButton';

/**
* Formats the date to 'day, mon date' format and the time to either 12 or 24-hour format based on the user's locale.
* @param dateString The date string to format.
* @returns The formatted date and time string.
*/
const formatDateTime = (dateString: string): string => {
const date = new Date(dateString);

const dateOptions: Intl.DateTimeFormatOptions = {
weekday: 'short',
month: 'short',
day: 'numeric',
};
const timeOptions: Intl.DateTimeFormatOptions = {
hour: 'numeric',
minute: 'numeric',
hour12: true,
};

const formattedDate = date.toLocaleDateString('en-US', dateOptions);
const formattedTime = date.toLocaleTimeString('en-US', timeOptions);

return `${formattedDate}${formattedTime}`;
};

/**
* Renders the weekly picks page.
* @param props The parameters for the weekly picks page.
* @param props.field The form field.
* @param props.schedule The schedule for the week.
* @param props.userPick The user's pick.
* @param props.onWeeklyPickChange The function to call when the user's pick changes.
* @returns The rendered weekly picks page.
*/
const WeekTeams = ({
field,
schedule,
userPick,
onWeeklyPickChange,
}: IWeekTeamsProps): JSX.Element => (
<>
{schedule.map((scheduledGame) => (
<RadioGroup
onValueChange={field.onChange}
defaultValue={userPick}
key={scheduledGame.id}
className="grid w-full grid-cols-2 gap-4"
className="grid w-full grid-cols-2 gap-4 pb-8"
onChange={(event) =>
onWeeklyPickChange(event as unknown as ChangeEvent<HTMLInputElement>)
}
>
<div className="week-page-game-schedule col-span-2 text-center">
<p>{formatDateTime(scheduledGame.date)}</p>
</div>
{scheduledGame.competitions[0].competitors.map((competition) => (
<FormItem key={competition.id}>
<FormControl>
Expand All @@ -42,5 +75,4 @@ const WeekTeams = ({
))}
</>
);

export default WeekTeams;
2 changes: 1 addition & 1 deletion components/Form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const FormItem = React.forwardRef<
return (
// eslint-disable-next-line react/jsx-no-constructed-context-values
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn('mb-4 w-full', className)} {...props} />
<div ref={ref} className={cn('w-full', className)} {...props} />
</FormItemContext.Provider>
);
});
Expand Down
13 changes: 10 additions & 3 deletions components/LinkCustom/LinkCustom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

import Link from 'next/link';
import React, { JSX } from 'react';
import { cn } from '@/utils/utils';

interface ILinkCustomProps {
children: string;
children: React.ReactNode;
className?: string;
href: string;
}

Expand All @@ -14,12 +16,17 @@ interface ILinkCustomProps {
* @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
*/
const LinkCustom = ({ children, href }: ILinkCustomProps): JSX.Element => {
const LinkCustom = ({
children,
className,
href,
}: ILinkCustomProps): JSX.Element => {
return (
<Link
className={'hover:text-orange-600 hover:underline'}
className={cn('hover:text-orange-600 hover:underline', className)}
data-testid="linkCustom"
href={href}
passHref
Expand Down
2 changes: 1 addition & 1 deletion utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export const parseUserPick = (
/**
* Get the list of leagues the user is a part of
* @param leagues - The list of leagues
* @returns {ILeague | Error} - The list of leagues
* @returns {Promise<ILeague[]>} The list of leagues the user is a part of
*/
export const getUserLeagues = async (
leagues: IUser['leagues'],
Expand Down

0 comments on commit 1df7007

Please sign in to comment.