diff --git a/src/api/member/getGameRegistrationStatus.ts b/src/api/member/getGameRegistrationStatus.ts index 453ca3e2..126fe7e9 100644 --- a/src/api/member/getGameRegistrationStatus.ts +++ b/src/api/member/getGameRegistrationStatus.ts @@ -2,14 +2,14 @@ import { axiosInstance } from '@api/axiosInstance'; import { GetGameRegistrationStatusRequest, - GetRegistrationStatusResponse, + GetGameRegistrationStatusResponse, } from '@type/api/member'; export const getGameRegistrationStatus = async ({ memberId, gameId, }: GetGameRegistrationStatusRequest) => { - const { data } = await axiosInstance.get<GetRegistrationStatusResponse>( + const { data } = await axiosInstance.get<GetGameRegistrationStatusResponse>( `/members/${memberId}/games/${gameId}/registration-status` ); diff --git a/src/hooks/mutations/useMannerScoreReviewPatchMutation.ts b/src/hooks/mutations/useMannerScoreReviewPatchMutation.ts index 72553a24..dc1d9d59 100644 --- a/src/hooks/mutations/useMannerScoreReviewPatchMutation.ts +++ b/src/hooks/mutations/useMannerScoreReviewPatchMutation.ts @@ -1,7 +1,9 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { patchMannerScoreReview } from '@api/games/patchMannerScoreReview'; +import { useLoginInfoStore } from '@stores/loginInfo.store'; + import { PatchGameMannerScoreReviewRequest } from '@type/api/games'; import { Game } from '@type/models'; @@ -12,8 +14,23 @@ export const useMannerScoreReviewPatchMutation = ({ payload: PatchGameMannerScoreReviewRequest; gameId: Game['id']; }) => { + const queryClient = useQueryClient(); + const loginInfo = useLoginInfoStore((state) => state.loginInfo); + return useMutation({ mutationKey: ['patch-manner-score-review', gameId, JSON.stringify(payload)], mutationFn: () => patchMannerScoreReview({ payload, gameId }), + onSuccess: () => { + console.log(['game-registration', loginInfo?.id, gameId]); + queryClient.invalidateQueries({ + queryKey: ['created-games', loginInfo?.id], + }); + queryClient.invalidateQueries({ + queryKey: ['confirmed-games', loginInfo?.id], + }); + queryClient.invalidateQueries({ + queryKey: ['game-registration', loginInfo?.id, gameId], + }); + }, }); }; diff --git a/src/mocks/data/member.ts b/src/mocks/data/member.ts index c3fe80ea..8f5c3bcf 100644 --- a/src/mocks/data/member.ts +++ b/src/mocks/data/member.ts @@ -4,7 +4,12 @@ import { PostRefreshAccessTokenResponse, PostRegistrationResponse, } from '@type/api/member'; -import { Authenticated, CrewProfile, Game, Registration } from '@type/models'; +import { + Authenticated, + CrewProfile, + MemberGame, + Registration, +} from '@type/models'; export const AUTH_LOGIN_MEMBER: Authenticated = { accessToken: @@ -129,7 +134,7 @@ export const MEMBERS_MEMBERID: GetMemberProfileResponse = { ], }; -export const MEMBERS_MEMBERID_CONFIRMED_GAMES: Game[] = [ +export const MEMBERS_MEMBERID_CONFIRMED_GAMES: MemberGame[] = [ { id: 1, content: '같이 즐거운 게임 해요~', @@ -146,6 +151,7 @@ export const MEMBERS_MEMBERID_CONFIRMED_GAMES: Game[] = [ cost: 0, memberCount: 3, maxMemberCount: 5, + isReviewDone: false, host: { id: 1, email: 'james123@pickple.kr', @@ -190,7 +196,7 @@ export const MEMBERS_MEMBERID_CONFIRMED_GAMES: Game[] = [ }, ]; -export const MEMBERS_MEMBERID_CREATED_GAMES: Game[] = [ +export const MEMBERS_MEMBERID_CREATED_GAMES: MemberGame[] = [ { id: 1, content: '같이 즐거운 게임 해요~', @@ -207,6 +213,7 @@ export const MEMBERS_MEMBERID_CREATED_GAMES: Game[] = [ cost: 0, memberCount: 3, maxMemberCount: 5, + isReviewDone: false, host: { id: 1, email: 'james123@pickple.kr', diff --git a/src/pages/GamesDetailPage/GamesDetailPage.tsx b/src/pages/GamesDetailPage/GamesDetailPage.tsx index c2ad7475..af00a475 100644 --- a/src/pages/GamesDetailPage/GamesDetailPage.tsx +++ b/src/pages/GamesDetailPage/GamesDetailPage.tsx @@ -19,7 +19,12 @@ import { useLoginInfoStore } from '@stores/loginInfo.store'; import { PATH_NAME } from '@consts/pathName'; import { WEEKDAY } from '@consts/weekday'; -import { getGameStartDate, isGameEnded, isGameStarted } from '@utils/domain'; +import { + getGameStartDate, + isGameEnded, + isGameStarted, + isReviewPeriod, +} from '@utils/domain'; import Ball from '@assets/ball.svg'; import GameMember from '@assets/gameMember.svg'; @@ -65,6 +70,11 @@ export const GamesDetailPage = () => { (member) => member.id === loginInfo?.id ); const vacancy = match.maxMemberCount - match.memberCount > 0; + const canReview = isReviewPeriod( + match.playDate, + match.playStartTime, + match.playTimeMinutes + ); const [year, month, day] = match.playDate.split('-'); const [hour, min] = match.playStartTime.split(':'); @@ -260,9 +270,11 @@ export const GamesDetailPage = () => { <ErrorBoundary fallback={<></>}> {loginInfo?.id && isMyMatch && ( <HostButton + loginId={loginInfo.id} gameId={gameId} isStarted={isStarted} isEnded={isEnded} + isReviewPeriod={canReview} /> )} {loginInfo?.id && !isMyMatch && ( @@ -272,6 +284,7 @@ export const GamesDetailPage = () => { isStarted={isStarted} isEnded={isEnded} vacancy={vacancy} + isReviewPeriod={canReview} /> )} </ErrorBoundary> diff --git a/src/pages/GamesDetailPage/components/GuestButton.tsx b/src/pages/GamesDetailPage/components/GuestButton.tsx index 9de9b1c2..d0774ff5 100644 --- a/src/pages/GamesDetailPage/components/GuestButton.tsx +++ b/src/pages/GamesDetailPage/components/GuestButton.tsx @@ -18,6 +18,7 @@ type GuestButtonProps = { isStarted: boolean; isEnded: boolean; vacancy: boolean; + isReviewPeriod: boolean; }; export const GuestButton = ({ @@ -26,12 +27,13 @@ export const GuestButton = ({ isStarted, isEnded, vacancy, + isReviewPeriod, }: GuestButtonProps) => { const isContinue = isStarted && !isEnded; const navigate = useNavigate(); const { - data: { memberRegistrationStatus }, + data: { memberRegistrationStatus, isReviewDone }, } = useGameRegistrationStatusQuery({ memberId: loginId, gameId }); const { mutate: participateMutate } = useGameParticipateCreateMutation(); @@ -53,7 +55,11 @@ export const GuestButton = ({ } if (isEnded) { - if (memberRegistrationStatus === '확정') { + if ( + isReviewPeriod && + memberRegistrationStatus === '확정' && + !isReviewDone + ) { return ( <BottomButton onClick={navigateReviewPage}>리뷰 남기기</BottomButton> ); diff --git a/src/pages/GamesDetailPage/components/HostButton.tsx b/src/pages/GamesDetailPage/components/HostButton.tsx index dc3d2b78..bf5be661 100644 --- a/src/pages/GamesDetailPage/components/HostButton.tsx +++ b/src/pages/GamesDetailPage/components/HostButton.tsx @@ -1,17 +1,30 @@ import { useNavigate } from 'react-router-dom'; +import { useGameRegistrationStatusQuery } from '@hooks/queries/useGameRegistrationStatusQuery'; + import { PATH_NAME } from '@consts/pathName'; import { BottomButton } from './BottomButton'; type HostButtonProps = { + loginId: number; gameId: number; isStarted: boolean; isEnded: boolean; + isReviewPeriod: boolean; }; -export const HostButton = ({ gameId, isStarted, isEnded }: HostButtonProps) => { +export const HostButton = ({ + loginId, + gameId, + isStarted, + isEnded, + isReviewPeriod, +}: HostButtonProps) => { const navigate = useNavigate(); + const { + data: { isReviewDone }, + } = useGameRegistrationStatusQuery({ memberId: loginId, gameId }); const navigateManagePage = () => navigate(PATH_NAME.GET_GAMES_MANAGE_PATH(String(gameId))); @@ -23,7 +36,7 @@ export const HostButton = ({ gameId, isStarted, isEnded }: HostButtonProps) => { return <BottomButton onClick={navigateManagePage}>매치 관리</BottomButton>; } - if (isEnded) { + if (isReviewPeriod && isEnded && !isReviewDone) { return ( <BottomButton onClick={navigateReviewPage}>리뷰 남기기</BottomButton> ); diff --git a/src/pages/GamesHostPage/GamesHostPage.tsx b/src/pages/GamesHostPage/GamesHostPage.tsx index 9ec6391a..4c3ec8c5 100644 --- a/src/pages/GamesHostPage/GamesHostPage.tsx +++ b/src/pages/GamesHostPage/GamesHostPage.tsx @@ -13,7 +13,12 @@ import { useLoginInfoStore } from '@stores/loginInfo.store'; import { PATH_NAME } from '@consts/pathName'; -import { getGameStartDate, isGameEnded, isGameStarted } from '@utils/domain'; +import { + getGameStartDate, + isGameEnded, + isGameStarted, + isReviewPeriod, +} from '@utils/domain'; import { PageContent, PageWrapper } from './GamesHostPage.styles'; @@ -44,6 +49,14 @@ export const GamesHostPage = () => { (member) => member.profileImageUrl ); const startTime = getGameStartDate(game.playDate, game.playStartTime); + const canReview = + isReviewPeriod( + game.playDate, + game.playStartTime, + game.playTimeMinutes + ) && + isGameEnded(startTime, game.playTimeMinutes) && + !game.isReviewDone; return ( <MatchItem @@ -65,7 +78,7 @@ export const GamesHostPage = () => { 매치 관리 </MatchItem.BottomButton> )} - {isGameEnded(startTime, game.playTimeMinutes) && ( + {canReview && ( <MatchItem.BottomButton onClick={() => navigate(PATH_NAME.GET_GAMES_REVIEW_PATH(String(game.id))) diff --git a/src/pages/GamesParticipatePage/GamesParticipatePage.tsx b/src/pages/GamesParticipatePage/GamesParticipatePage.tsx index 8a2ad196..0fb768b9 100644 --- a/src/pages/GamesParticipatePage/GamesParticipatePage.tsx +++ b/src/pages/GamesParticipatePage/GamesParticipatePage.tsx @@ -13,7 +13,7 @@ import { useLoginInfoStore } from '@stores/loginInfo.store'; import { PATH_NAME } from '@consts/pathName'; -import { getGameStartDate } from '@utils/domain'; +import { getGameStartDate, isReviewPeriod } from '@utils/domain'; import { PageContent, PageWrapper } from './GamesParticipatePage.styles'; @@ -47,6 +47,14 @@ export const GamesParticipatePage = () => { const endTimeNumber = startTime.getTime() + game.playTimeMinutes * 60000; const isMatchEnd = endTimeNumber <= new Date().getTime(); + const canReview = + isReviewPeriod( + game.playDate, + game.playStartTime, + game.playTimeMinutes + ) && + isMatchEnd && + !game.isReviewDone; return ( <MatchItem @@ -59,7 +67,7 @@ export const GamesParticipatePage = () => { maxMemberCount={game.maxMemberCount} membersProfileImageUrls={membersProfileImageUrls} > - {isMatchEnd && ( + {canReview && ( <MatchItem.BottomButton onClick={() => navigate(PATH_NAME.GET_GAMES_REVIEW_PATH(String(game.id))) diff --git a/src/pages/MannerScoreReviewPage/MannerScoreReviewPage.tsx b/src/pages/MannerScoreReviewPage/MannerScoreReviewPage.tsx index 26f1954b..779d5412 100644 --- a/src/pages/MannerScoreReviewPage/MannerScoreReviewPage.tsx +++ b/src/pages/MannerScoreReviewPage/MannerScoreReviewPage.tsx @@ -4,6 +4,8 @@ import { useNavigate } from 'react-router-dom'; import { ProfileSkeleton } from '@pages/ProfilePage'; import { Profile } from '@pages/ProfilePage'; +import { LoginRequireError } from '@routes/LoginRequireBoundary'; + import { Avatar } from '@components/Avatar'; import { Header } from '@components/Header'; import { Modal } from '@components/Modal'; @@ -13,6 +15,7 @@ import { Text } from '@components/shared/Text'; import { useMannerScoreReviewPatchMutation } from '@hooks/mutations/useMannerScoreReviewPatchMutation'; import { useGameDetailQuery } from '@hooks/queries/useGameDetailQuery'; +import { useGameRegistrationStatusQuery } from '@hooks/queries/useGameRegistrationStatusQuery'; import { theme } from '@styles/theme'; @@ -20,6 +23,8 @@ import { useLoginInfoStore } from '@stores/loginInfo.store'; import { PATH_NAME } from '@consts/pathName'; +import { isReviewPeriod } from '@utils/domain'; + import leftArrowIcon from '@assets/leftArrow.svg'; import rightArrowIcon from '@assets/rightArrow.svg'; @@ -37,16 +42,31 @@ import { ToggleButton } from './ToggleButton'; export const MannerScoreReviewPage = () => { const navigate = useNavigate(); const gameId = Number(location.pathname.split('/')[2]); - const { data: gameData } = useGameDetailQuery(gameId); const loginInfo = useLoginInfoStore((state) => state.loginInfo); + if (!loginInfo?.id) { + throw new LoginRequireError(); + } + + const { data: gameData } = useGameDetailQuery(gameId); + const { + data: { isReviewDone }, + } = useGameRegistrationStatusQuery({ memberId: loginInfo.id, gameId }); const teammateListInfo = gameData.members.filter(({ id }) => { return loginInfo?.id !== id; }); + const nowDate = new Date(); const gameDate = new Date(`${gameData.playDate}T${gameData.playEndTime}`); - + const canReview = isReviewPeriod( + gameData.playDate, + gameData.playStartTime, + gameData.playTimeMinutes + ); const exitCode = - nowDate <= gameDate || !loginInfo || teammateListInfo.length === 0; + !canReview || + isReviewDone || + nowDate <= gameDate || + teammateListInfo.length === 0; const [currentSelectedMemberIndex, setCurrentSelectedMemberIndex] = useState(0); diff --git a/src/type/api/member.ts b/src/type/api/member.ts index fa3e82cf..97f726bd 100644 --- a/src/type/api/member.ts +++ b/src/type/api/member.ts @@ -3,6 +3,7 @@ import { CrewProfile, Game, Member, + MemberGame, MemberProfile, Registration, } from '@type/models'; @@ -37,12 +38,12 @@ export type GetConfirmedGamesRequest = { memberId: Member['id']; }; -export type GetConfirmedGamesResponse = Game[]; +export type GetConfirmedGamesResponse = MemberGame[]; export type GetCreatedGamesRequest = { memberId: Member['id']; }; -export type GetCreatedGamesResponse = Game[]; +export type GetCreatedGamesResponse = MemberGame[]; export type GetJoinedCrewsResponse = CrewProfile[]; @@ -63,3 +64,6 @@ export type GetGameRegistrationStatusRequest = { export type GetRegistrationStatusResponse = { memberRegistrationStatus: '없음' | '대기' | '확정'; }; + +export type GetGameRegistrationStatusResponse = + GetRegistrationStatusResponse & { isReviewDone: boolean }; diff --git a/src/type/models/Game.ts b/src/type/models/Game.ts index 131e2992..98e58df2 100644 --- a/src/type/models/Game.ts +++ b/src/type/models/Game.ts @@ -23,3 +23,5 @@ export type Game = { positions: Position[]; members: Member[]; }; + +export type MemberGame = Game & { isReviewDone: boolean }; diff --git a/src/utils/domain.ts b/src/utils/domain.ts index b8b59438..0af5a2ab 100644 --- a/src/utils/domain.ts +++ b/src/utils/domain.ts @@ -20,3 +20,16 @@ export const isGameEnded = (startDate: Date, playTimeMinutes: number) => { const endTimeNumber = startDate.getTime() + playTimeMinutes * 60000; return endTimeNumber <= new Date().getTime(); }; + +export const isReviewPeriod = ( + gamePlayDate: string, + gamePlayStartTime: string, + playTimeMinutes: number +) => { + const gameStartDate = getGameStartDate(gamePlayDate, gamePlayStartTime); + const gameEndTimeNumber = gameStartDate.getTime() + playTimeMinutes * 60000; + const oneWeekMs = 1000 * 60 * 60 * 24 * 7; + const now = new Date(); + + return now.getTime() - gameEndTimeNumber < oneWeekMs; +};