Skip to content

Commit

Permalink
Merge pull request #104 from hufs-sports-live/feat/rummikub
Browse files Browse the repository at this point in the history
[FEAT] 루미큐브 페이지 별도 추가
  • Loading branch information
seongminn authored Nov 28, 2023
2 parents 2854072 + cf4fbb4 commit d36514b
Show file tree
Hide file tree
Showing 13 changed files with 466 additions and 169 deletions.
92 changes: 0 additions & 92 deletions src/app/match/[id]/modify/page.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/app/match/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export default function Match({ params }: { params: { id: string } }) {
<MatchCommentFetcher matchId={params.id}>
{({ commentList, matchTeams, ...data }) => (
<div className="max-h-[450px] overflow-y-auto p-5">
<ul className="pb-8">
<ul className="pb-10">
<CommentList
commentList={commentList.pages.flat()}
scrollToBottom={scrollToBottom}
Expand Down
160 changes: 160 additions & 0 deletions src/app/rummikube/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
'use client';

import { useRef, useState } from 'react';

import AsyncBoundary from '@/components/common/AsyncBoundary';
import Loader from '@/components/common/Loader';
import CommentForm from '@/components/match/CommentForm';
import CommentList from '@/components/match/CommentList';
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 Cheer from '@/components/rummikub/Cheer';
import RummiKubMatchBanner from '@/components/rummikub/MatchBanner';
import useSocket from '@/hooks/useSocket';
import MatchByIdFetcher from '@/queries/useMatchById/Fetcher';
import MatchCheerByIdFetcher from '@/queries/useMatchCheerById/Fetcher';
import MatchCommentFetcher from '@/queries/useMatchCommentById/Fetcher';
import MatchLineupFetcher from '@/queries/useMatchLineupById/Fetcher';
import MatchTimelineFetcher from '@/queries/useMatchTimelineById/Fetcher';
import MatchVideoFetcher from '@/queries/useMatchVideoById/Fetcher';
import useSaveCommentMutation from '@/queries/useSaveCommentMutation/query';
import { MatchCommentType } from '@/types/match';

export default function Rummikute({ params }: { params: { id: string } }) {
const [comments, setComments] = useState<MatchCommentType[]>([]);

const handleSocketMessage = (comment: MatchCommentType) => {
if (comment) {
setComments(prev => [...prev, comment]);
}
};

const { connect } = useSocket({
url: 'wss://api.hufstreaming.site/ws',
destination: `/topic/games/${params.id}`,
callback: handleSocketMessage,
});

connect();

const { mutate } = useSaveCommentMutation();
const options = [
{ label: '라인업' },
{ label: '응원댓글' },
{ label: '경기영상' },
{ label: '타임라인' },
];

const scrollRef = useRef(null);
const scrollToBottom = () => {
if (!scrollRef.current) return;

(scrollRef.current as HTMLDivElement).scrollIntoView();
};

return (
<section>
<AsyncBoundary
errorFallback={props => (
<RummiKubMatchBanner.ErrorFallback {...props} />
)}
loadingFallback={<RummiKubMatchBanner.Skeleton />}
>
<MatchByIdFetcher matchId={params.id}>
{data => <RummiKubMatchBanner {...data} />}
</MatchByIdFetcher>
</AsyncBoundary>

<AsyncBoundary
errorFallback={props => <Cheer.ErrorFallback {...props} />}
loadingFallback={<Loader />}
>
<MatchCheerByIdFetcher matchId={params.id}>
{data => <Cheer cheers={data} />}
</MatchCheerByIdFetcher>
</AsyncBoundary>
<Panel options={options} defaultValue="라인업">
{({ selected }) => (
<>
{selected === '라인업' && (
<AsyncBoundary
errorFallback={props => <Lineup.ErrorFallback {...props} />}
loadingFallback={<Loader />}
>
<MatchLineupFetcher matchId={params.id}>
{([firstTeam, secondTeam]) => (
<div className="grid grid-cols-2 py-5 [&>*:first-child>ul]:before:absolute [&>*:first-child>ul]:before:right-0 [&>*:first-child>ul]:before:h-full [&>*:first-child>ul]:before:border-l-2 [&>*:first-child>ul]:before:bg-gray-2">
<Lineup {...firstTeam} />
<Lineup {...secondTeam} />
</div>
)}
</MatchLineupFetcher>
</AsyncBoundary>
)}
{selected === '타임라인' && (
<AsyncBoundary
errorFallback={props => <RecordList.ErrorFallback {...props} />}
loadingFallback={<Loader />}
>
<MatchTimelineFetcher matchId={params.id}>
{([firstHalf, secondHalf]) => (
<div className="overflow-y-auto p-5">
<RecordList {...firstHalf} />
<RecordList {...secondHalf} />
</div>
)}
</MatchTimelineFetcher>
</AsyncBoundary>
)}
{selected === '응원댓글' && (
<AsyncBoundary
errorFallback={props => (
<CommentList.ErrorFallback {...props} />
)}
loadingFallback={<Loader />}
>
<MatchCommentFetcher matchId={params.id}>
{({ commentList, matchTeams, ...data }) => (
<div className="max-h-[450px] overflow-y-auto p-5">
<ul className="pb-8">
<CommentList
commentList={commentList.pages.flat()}
scrollToBottom={scrollToBottom}
{...data}
/>
<CommentList.SocketList commentList={comments} />
<li ref={scrollRef}></li>
</ul>
<CommentForm
matchTeams={matchTeams}
matchId={params.id}
mutate={mutate}
scrollToBottom={scrollToBottom}
/>
</div>
)}
</MatchCommentFetcher>
</AsyncBoundary>
)}
{selected === '경기영상' && (
<AsyncBoundary
errorFallback={props => <Video.ErrorFallback {...props} />}
loadingFallback={<Loader />}
>
<MatchVideoFetcher matchId={params.id}>
{data => (
<div className="overflow-y-auto p-5">
<Video {...data} />
</div>
)}
</MatchVideoFetcher>
</AsyncBoundary>
)}
</>
)}
</Panel>
</section>
);
}
2 changes: 2 additions & 0 deletions src/components/common/Icon/iconMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Image } from './svg/Image';
import { PaperPlane } from './svg/PaperPlane';
import { Pencil } from './svg/Pencil';
import { PlusCircled } from './svg/PlusCircled';
import { Profile } from './svg/Profile';
import { Symbol } from './svg/Symbol';
import { ThumbsUp } from './svg/Thumbsup';
import { Trash } from './svg/Trash';
Expand All @@ -34,4 +35,5 @@ export const iconMap = {
thumbsUp: ThumbsUp,
trash: Trash,
write: Write,
profile: Profile,
};
18 changes: 18 additions & 0 deletions src/components/common/Icon/svg/Profile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ComponentProps } from 'react';

export const Profile = ({
viewBox = '0 0 20 20',
...props
}: ComponentProps<'svg'>) => {
return (
<svg
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox={viewBox}
{...props}
>
<path d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm0 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Zm0 13a8.949 8.949 0 0 1-4.951-1.488A3.987 3.987 0 0 1 9 13h2a3.987 3.987 0 0 1 3.951 3.512A8.949 8.949 0 0 1 10 18Z" />
</svg>
);
};
15 changes: 6 additions & 9 deletions src/components/common/MatchCard/pieces/Team.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Image from 'next/image';
import { useMatchCardContext } from '@/hooks/useMatchCardContext';
import { $ } from '@/utils/core';

import { Icon } from '../../Icon';

type TeamProps = {
teamIndex: number;
className?: string;
Expand All @@ -14,15 +16,10 @@ export default function Team({ teamIndex, className }: TeamProps) {
if (gameTeams.length === 0) {
return (
<div className={$(className)}>
<svg
className="me-4 h-16 w-16 text-gray-200 dark:text-gray-700"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm0 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Zm0 13a8.949 8.949 0 0 1-4.951-1.488A3.987 3.987 0 0 1 9 13h2a3.987 3.987 0 0 1 3.951 3.512A8.949 8.949 0 0 1 10 18Z" />
</svg>
<Icon
iconName="profile"
className="h-16 w-16 text-gray-200 dark:text-gray-700"
/>
</div>
);
}
Expand Down
34 changes: 15 additions & 19 deletions src/components/match/CommentForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export default function CommentForm({
}: CommentFormProps) {
const [inputValue, setInputValue] = useState('');
const [selectedTeamId, setSelectedTeamId] = useState<number>(1);
const [isOpen, toggleOpen] = useState(false);

const handleCommentSubmit = (
e: FormEvent<HTMLFormElement>,
Expand All @@ -37,30 +36,28 @@ export default function CommentForm({
return (
<>
<form
className="h-70px absolute -bottom-1 left-0 w-full"
className="h-70px absolute bottom-0 left-0 w-full translate-y-full"
onSubmit={e =>
handleCommentSubmit(e, {
gameTeamId: Number(matchId),
content: inputValue,
})
}
>
{isOpen && (
<fieldset className="absolute top-0 flex w-full -translate-y-full items-center justify-start gap-2 rounded-lg bg-white px-5 py-3 shadow-md">
{matchTeams.map(team => (
<label key={team.gameTeamId} className="flex items-center">
<input
type="radio"
checked={selectedTeamId === team.gameTeamId}
value={team.gameTeamId}
onChange={handleRadioClick}
className="dark:border-gray-6 dark:bg-gray-6 h-4 w-4 border-gray-3 bg-gray-1 text-primary focus:ring-2 focus:ring-primary dark:ring-offset-black dark:focus:ring-primary"
/>
{team.gameTeamName}
</label>
))}
</fieldset>
)}
<fieldset className="absolute top-0 flex w-full -translate-y-full items-center justify-start gap-4 rounded-lg bg-white px-5 py-3">
{matchTeams.map(team => (
<label key={team.gameTeamId} className="flex items-center">
<input
type="radio"
checked={selectedTeamId === team.gameTeamId}
value={team.gameTeamId}
onChange={handleRadioClick}
className="dark:border-gray-6 dark:bg-gray-6 mr-1 h-4 w-4 border-gray-3 bg-gray-1 text-primary focus:ring-2 focus:ring-primary dark:ring-offset-black dark:focus:ring-primary"
/>
{team.gameTeamName}
</label>
))}
</fieldset>

<div
className="z-10 grid items-center rounded-lg bg-gray-2"
Expand All @@ -71,7 +68,6 @@ export default function CommentForm({
value={inputValue}
onChange={e => setInputValue(e.target.value)}
placeholder="응원하는 팀에 댓글을 남겨보세요!"
onFocus={() => toggleOpen(true)}
/>
<button className="rounded-xl bg-primary px-5 py-3 text-white">
댓글
Expand Down
Loading

0 comments on commit d36514b

Please sign in to comment.