diff --git a/src/api/league.ts b/src/api/league.ts index b9bf7db..5c62d53 100644 --- a/src/api/league.ts +++ b/src/api/league.ts @@ -1,7 +1,7 @@ import * as Sentry from '@sentry/nextjs'; import { AxiosError } from 'axios'; -import instance from './instance'; +import instance from '.'; export type LeagueType = { name: string; diff --git a/src/app/match/[id]/page.tsx b/src/app/match/[id]/page.tsx index c7c440b..2814061 100644 --- a/src/app/match/[id]/page.tsx +++ b/src/app/match/[id]/page.tsx @@ -1,30 +1,71 @@ 'use client'; -// import Link from 'next/link'; - import { Suspense } from 'react'; import MatchBanner from '@/components/match/Banner'; import Cheer from '@/components/match/Cheer'; -// import CommentList from '@/components/detail/CommentList'; -// import GameInfo from '@/components/detail/GameInfo'; -// import GameTimeline from '@/components/detail/GameTimeline'; +import Lineup from '@/components/match/LineupList'; +import Panel from '@/components/match/Panel'; +import RecordList from '@/components/match/RecordList'; +import Video from '@/components/match/Video'; import MatchByIdFetcher from '@/queries/useMatchById/Fetcher'; import MatchCheerByIdFetcher from '@/queries/useMatchCheerById/Fetcher'; +import MatchLineupFetcher from '@/queries/useMatchLineupById/Fetcher'; +import MatchTimelineFetcher from '@/queries/useMatchTimelineById/Fetcher'; export default function Match({ params }: { params: { id: string } }) { + const options = [ + { label: '라인업' }, + { label: '응원댓글' }, + { label: '경기영상' }, + { label: '타임라인' }, + ]; + return (
- 배너 로딩 중...}> + 배너 로딩중...}> {data => } - 응원 로딩 중...}> + 응원 로딩중...}> {data => } + + + {({ selected }) => ( + 로딩중...}> + {selected === '라인업' && ( + + {([firstTeam, secondTeam]) => ( +
+ + +
+ )} +
+ )} + {selected === '타임라인' && ( + + {([firstHalf, secondHalf]) => ( +
+ + +
+ )} +
+ )} + {selected === '경기영상' && ( +
+ {/* // TODO VideoId API 업데이트 시 ID를 받아와서 주입하는 형태로 수정 */} +
+ )} +
+ )} +
); } diff --git a/src/components/common/Dropdown/index.tsx b/src/components/common/Dropdown/index.tsx new file mode 100644 index 0000000..38e05e1 --- /dev/null +++ b/src/components/common/Dropdown/index.tsx @@ -0,0 +1,8 @@ +import Item from './units/Item'; +import Menu from './units/Menu'; +import DropdownWrapper from './units/Wrapper'; + +export const Dropdown = Object.assign(DropdownWrapper, { + Menu, + Item, +}); diff --git a/src/components/common/Dropdown/units/Item.tsx b/src/components/common/Dropdown/units/Item.tsx new file mode 100644 index 0000000..03c9250 --- /dev/null +++ b/src/components/common/Dropdown/units/Item.tsx @@ -0,0 +1,11 @@ +'use client'; + +import { ComponentProps, MouseEvent } from 'react'; + +interface DropdownItemProps extends ComponentProps<'button'> { + onClick: (e: MouseEvent) => void; +} + +export default function Item({ children, ...props }: DropdownItemProps) { + return ; +} diff --git a/src/components/common/Dropdown/units/Menu.tsx b/src/components/common/Dropdown/units/Menu.tsx new file mode 100644 index 0000000..2ff7001 --- /dev/null +++ b/src/components/common/Dropdown/units/Menu.tsx @@ -0,0 +1,15 @@ +import { ComponentProps } from 'react'; + +import { $ } from '@/utils/core'; + +export default function Menu({ + className, + children, + ...props +}: ComponentProps<'div'>) { + return ( +
+ {children} +
+ ); +} diff --git a/src/components/common/Dropdown/units/Wrapper.tsx b/src/components/common/Dropdown/units/Wrapper.tsx new file mode 100644 index 0000000..da9ac94 --- /dev/null +++ b/src/components/common/Dropdown/units/Wrapper.tsx @@ -0,0 +1,23 @@ +import { ComponentProps, createContext } from 'react'; + +interface DropdownProps extends DropdownContextType, ComponentProps<'div'> {} + +type DropdownContextType = { + label?: string; +}; + +export const DropdownContext = createContext({ + label: '', +}); + +export default function DropdownWrapper({ + className, + children, + ...props +}: DropdownProps) { + return ( + +
{children}
+
+ ); +} diff --git a/src/components/match/LineupItem/index.tsx b/src/components/match/LineupItem/index.tsx new file mode 100644 index 0000000..a3cd865 --- /dev/null +++ b/src/components/match/LineupItem/index.tsx @@ -0,0 +1,18 @@ +import { MatchPlayerType } from '@/types/match'; + +export default function LineupItem({ + playerName, + description, +}: MatchPlayerType) { + return ( +
  • + + {description} + + {playerName} +
  • + ); +} diff --git a/src/components/match/LineupList/index.tsx b/src/components/match/LineupList/index.tsx new file mode 100644 index 0000000..f14ceb4 --- /dev/null +++ b/src/components/match/LineupList/index.tsx @@ -0,0 +1,16 @@ +import { MatchLineupType } from '@/types/match'; + +import LineupItem from '../LineupItem'; + +export default function Lineup({ teamName, gameTeamPlayers }: MatchLineupType) { + return ( +
    +
    {teamName}
    +
      + {gameTeamPlayers.map((player, idx) => ( + + ))} +
    +
    + ); +} diff --git a/src/components/match/Panel/index.tsx b/src/components/match/Panel/index.tsx new file mode 100644 index 0000000..7640037 --- /dev/null +++ b/src/components/match/Panel/index.tsx @@ -0,0 +1,45 @@ +import { MouseEvent, ReactNode, useState } from 'react'; + +import { Dropdown } from '@/components/common/Dropdown'; +import { $ } from '@/utils/core'; + +type PanelProps = { + options: Array<{ label: string }>; + children: ({ selected }: { selected: string }) => ReactNode; + defaultValue: string; +}; + +export default function Panel({ defaultValue, options, children }: PanelProps) { + const [selected, setSelected] = useState(defaultValue); + + const handleClickItem = (e: MouseEvent) => { + const selectedValue = (e.target as Element).textContent; + + if (!selectedValue) return; + if (selected === selectedValue) return; + + setSelected(selectedValue); + }; + + return ( +
    + + + {options.map(option => ( + + {option.label} + + ))} + + {children({ selected })} + +
    + ); +} diff --git a/src/components/match/RecordItem/index.tsx b/src/components/match/RecordItem/index.tsx new file mode 100644 index 0000000..682d7df --- /dev/null +++ b/src/components/match/RecordItem/index.tsx @@ -0,0 +1,18 @@ +import { MatchRecordsType } from '@/types/match'; + +export default function RecordItem({ + playerName, + score, + scoredAt, + teamName, +}: MatchRecordsType) { + return ( +
  • + + [ {teamName} ] + + {playerName} 선수 {score}점 득점 🎉 + +
  • + ); +} diff --git a/src/components/match/RecordList/index.tsx b/src/components/match/RecordList/index.tsx new file mode 100644 index 0000000..e78d5b7 --- /dev/null +++ b/src/components/match/RecordList/index.tsx @@ -0,0 +1,19 @@ +import { MatchTimelineType } from '@/types/match'; + +import RecordItem from '../RecordItem'; + +export default function RecordList({ + gameQuarter, + records, +}: MatchTimelineType) { + return ( + <> +
    {gameQuarter}
    +
      + {records.map(record => ( + + ))} +
    + + ); +} diff --git a/src/components/match/Video/index.tsx b/src/components/match/Video/index.tsx new file mode 100644 index 0000000..b937cc2 --- /dev/null +++ b/src/components/match/Video/index.tsx @@ -0,0 +1,11 @@ +export default function Video() { + return ( + + ); +} diff --git a/src/queries/useMatchLineupById/Fetcher.tsx b/src/queries/useMatchLineupById/Fetcher.tsx new file mode 100644 index 0000000..5c9961d --- /dev/null +++ b/src/queries/useMatchLineupById/Fetcher.tsx @@ -0,0 +1,21 @@ +import { ReactNode } from 'react'; + +import { MatchLineupType } from '@/types/match'; + +import { useMatchLineupById } from './query'; + +type MatchLineupFetcherProps = { + matchId: string; + children: (data: MatchLineupType[]) => ReactNode; +}; + +export default function MatchLineupFetcher({ + matchId, + children, +}: MatchLineupFetcherProps) { + const { lineup, error } = useMatchLineupById(matchId); + + if (error) throw error; + + return children(lineup); +} diff --git a/src/queries/useMatchLineupById/query.ts b/src/queries/useMatchLineupById/query.ts new file mode 100644 index 0000000..a74ec70 --- /dev/null +++ b/src/queries/useMatchLineupById/query.ts @@ -0,0 +1,15 @@ +import { useSuspenseQuery } from '@tanstack/react-query'; + +import { getMatchLineupById } from '@/api/match'; + +export const useMatchLineupById = (matchId: string) => { + const { data, error } = useSuspenseQuery({ + queryKey: ['match-lineup', matchId], + queryFn: () => getMatchLineupById(matchId), + }); + + return { + lineup: data, + error, + }; +}; diff --git a/src/queries/useMatchTimelineById/Fetcher.tsx b/src/queries/useMatchTimelineById/Fetcher.tsx new file mode 100644 index 0000000..a16483f --- /dev/null +++ b/src/queries/useMatchTimelineById/Fetcher.tsx @@ -0,0 +1,31 @@ +import { ReactNode } from 'react'; + +import { useMatchTimelineById } from './query'; + +type MatchRecordsType = { + scoredAt: number; + playerName: string; + teamName: string; + score: number; +}; + +type MatchTimelineType = { + gameQuarter: string; + records: MatchRecordsType[]; +}; + +type MatchTimelineFetcherProps = { + matchId: string; + children: (data: MatchTimelineType[]) => ReactNode; +}; + +export default function MatchTimelineFetcher({ + matchId, + children, +}: MatchTimelineFetcherProps) { + const { timeline, error } = useMatchTimelineById(matchId); + + if (error) throw error; + + return children(timeline); +} diff --git a/src/queries/useMatchTimelineById/query.ts b/src/queries/useMatchTimelineById/query.ts new file mode 100644 index 0000000..d804cf5 --- /dev/null +++ b/src/queries/useMatchTimelineById/query.ts @@ -0,0 +1,17 @@ +import { useSuspenseQuery } from '@tanstack/react-query'; + +import { getMatchTimelineById } from '@/api/match'; + +export const useMatchTimelineById = (matchId: string) => { + const { data, error } = useSuspenseQuery({ + queryKey: ['match-timeline', matchId], + queryFn: () => getMatchTimelineById(matchId), + }); + + if (error) throw error; + + return { + timeline: data, + error, + }; +}; diff --git a/src/types/match.ts b/src/types/match.ts index 4c1ac02..62ce89e 100644 --- a/src/types/match.ts +++ b/src/types/match.ts @@ -39,7 +39,7 @@ export type MatchRecordsType = { }; export type MatchTimelineType = { - gameQuater: MatchQuarterType; + gameQuarter: MatchQuarterType; records: MatchRecordsType[]; };