Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui/ux: update week page #370

Merged
merged 11 commits into from
Jul 10, 2024
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
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
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);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: We'll need to look into Suspense in the near future.

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]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

By adding selectedLeague, it's going to cause side effects. Every time selectedLeague() is called, it will run this useEffect.


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
Loading