Skip to content

Commit

Permalink
Merge pull request #1345 from 42organization/Feat/파티-패널티시-방-참여-및-생성-제한-
Browse files Browse the repository at this point in the history
…#1341

Feat/파티 패널티시 방 참여 및 생성 제한 #1341
  • Loading branch information
izone00 authored Apr 4, 2024
2 parents accee71 + 7327089 commit 071b58e
Show file tree
Hide file tree
Showing 16 changed files with 334 additions and 114 deletions.
20 changes: 19 additions & 1 deletion components/Layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useEffect, useContext } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { BsMegaphone } from 'react-icons/bs';
import { FaArrowLeft } from 'react-icons/fa';
import { FiMenu } from 'react-icons/fi';
import { IoStorefrontOutline } from 'react-icons/io5';
import { Modal } from 'types/modalTypes';
Expand All @@ -18,6 +20,7 @@ import useAxiosGet from 'hooks/useAxiosGet';
import styles from 'styles/Layout/Header.module.scss';

export default function Header() {
const router = useRouter();
const [live, setLive] = useRecoilState(liveState);
const HeaderState = useContext<HeaderContextState | null>(HeaderContext);
const openMenuBarHandler = () => {
Expand Down Expand Up @@ -54,11 +57,26 @@ export default function Header() {
type: 'setError',
});

// 현재 경로가 뒤로 가기 버튼을 사용해야 하는 경로 패턴 중 하나와 일치하는지 확인
const isBackButtonRoute = () => {
const path = router.asPath.split('?')[0]; // 쿼리 스트링 제거
const patterns = [
/^\/party\/create$/, // '/party/create'
/^\/party\/[0-9]+$/, // '/party/[roomId]'
];

return patterns.some((pattern) => pattern.test(path));
};

return (
<div className={styles.headerContainer}>
<div className={styles.headerWrap}>
<div className={styles.headerLeft}>
<FiMenu className={styles.menuIcon} onClick={openMenuBarHandler} />
{isBackButtonRoute() ? (
<FaArrowLeft onClick={router.back} />
) : (
<FiMenu className={styles.menuIcon} onClick={openMenuBarHandler} />
)}
</div>
<Link className={styles.logoWrap} href={'/'}>
42GG
Expand Down
2 changes: 1 addition & 1 deletion components/admin/party/PartyCategory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { PartyCategory } from 'types/partyTypes';
import { tableFormat } from 'constants/admin/table';
import { AdminTableHead } from 'components/admin/common/AdminTable';
import usePartyCategory from 'hooks/party/usePartyCategory';
import styles from 'styles/admin/Party/AdminPartyCommon.module.scss';
import styles from 'styles/admin/party/AdminPartyCommon.module.scss';

const tableTitle: { [key: string]: string } = {
categoryId: '카테고리번호',
Expand Down
24 changes: 19 additions & 5 deletions components/modal/Party/PartyRoomEditModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,24 @@ export default function PartyRoomEditModal({ roomId }: { roomId: number }) {
const [room, setRoom] = useState<PartyRoomDetail>();

function handleStatus(e: ChangeEvent<HTMLSelectElement>) {
setRoom({
...(room as PartyRoomDetail),
status: e.target.value as PartyRoomStatus,
});
instanceInPartyManage
.patch(`/rooms/${room?.roomId}`, {
status: e.target.value,
})
.then(() => {
setRoom({
...(room as PartyRoomDetail),
status: e.target.value as PartyRoomStatus,
});
})
.catch(() => {
setSnackBar({
toastName: 'PATCH request',
message: '방 상태를 변경할 수 없습니다',
severity: 'error',
clicked: true,
});
});
}
function handleCommentHidden(comment: PartyComment) {
instanceInPartyManage
Expand Down Expand Up @@ -65,7 +79,7 @@ export default function PartyRoomEditModal({ roomId }: { roomId: number }) {
<span className={styles.categoryName}>#{room.categoryName}</span>
{room.title}
</h2>
<select onChange={handleStatus}>
<select onChange={handleStatus} defaultValue={room.status}>
{roomStatusOpts.map((opt) => (
<option key={opt} value={opt}>
{opt}
Expand Down
2 changes: 1 addition & 1 deletion components/modal/admin/AdminTemplateModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function TemplateModal({
const [formData, setFormData] = useState<PartyTemplateForm>(
template ?? {
gameName: '',
categoryId: 1,
categoryName: '기타',
maxGamePeople: 1,
minGamePeople: 1,
maxGameTime: 1,
Expand Down
2 changes: 1 addition & 1 deletion components/modal/modalType/AdminModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import DetailModal from 'components/modal/admin/DetailModal';
import AdminSeasonEdit from 'components/modal/admin/SeasonEdit';
import AdminEditTournamentBraket from '../admin/AdminEditTournamentBraket';
import AdminTournamentParticipantEditModal from '../admin/AdminTournamentParticipantEditModal/AdminTournamentParticipantEditModal';
import PartyRoomEditModal from '../Party/PartyRoomEditModal';
import PartyRoomEditModal from '../party/PartyRoomEditModal';

export default function AdminModal() {
const {
Expand Down
2 changes: 1 addition & 1 deletion components/modal/modalType/PartyModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useRecoilValue } from 'recoil';
import { modalState } from 'utils/recoil/modal';
import { PartyReportModal } from '../Party/PartyReportModal';
import { PartyReportModal } from '../party/PartyReportModal';

export default function PartyModal() {
const { modalName, partyReport } = useRecoilValue(modalState);
Expand Down
136 changes: 136 additions & 0 deletions components/party/PartyMain.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { FaSearch } from 'react-icons/fa';
import { PartyCategory, PartyRoom } from 'types/partyTypes';
import styles from 'styles/party/PartyMain.module.scss';
import PartyRoomListItem from './PartyRoomListItem';

// ================================================================================
// JoinedRooms : 참여한 방 리스트 및 방 생성 버튼
// ================================================================================

type JoinedRoomsProps = {
joinedPartyRooms: PartyRoom[];
penaltyPeroid: string | null;
};

function JoinedRooms({ joinedPartyRooms, penaltyPeroid }: JoinedRoomsProps) {
return (
<section className={styles.joinedRoomContainer}>
<header className={styles.joinedRoomHeader}>
<h2>참여중인 파티</h2>
{penaltyPeroid ? (
<div className={styles.penalty}>
패널티 <span className={styles.timer}>{penaltyPeroid}</span>
</div>
) : (
<Link href='/party/create' className={styles.createRoomButton}>
방 만들기
</Link>
)}
</header>
<ul>
{joinedPartyRooms.length > 0 ? (
joinedPartyRooms.map((room) => (
<PartyRoomListItem key={room.roomId} room={room} />
))
) : (
<>참여중인 방이 없습니다.</>
)}
</ul>
</section>
);
}

// ================================================================================
// SearchBar: 검색창
// ================================================================================

function SearchBar({ titleQuery = '' }: { titleQuery?: string }) {
const router = useRouter();
const [searchTitle, setSearchTitle] = useState(titleQuery);

return (
<section className={styles.searchBar}>
<form
onSubmit={(e) => {
e.preventDefault();
router.replace({
pathname: router.pathname,
query: { title: searchTitle },
});
}}
>
<FaSearch className={styles.searchIcon} />
<input
placeholder='방 검색하기'
defaultValue={searchTitle}
onChange={(e) => {
setSearchTitle(e.target.value);
}}
/>
</form>
</section>
);
}

// ================================================================================
// AllRooms: 모든 방 리스트
// ================================================================================

const noFilter: PartyCategory = {
categoryId: 0,
categoryName: '전체',
};

type AllRoomsProps = {
partyRooms: PartyRoom[];
isSearchedResult: boolean;
categories: PartyCategory[];
};

function AllRooms({ partyRooms, isSearchedResult, categories }: AllRoomsProps) {
const [categoryFilter, setCategoryFilter] = useState(noFilter.categoryName);
const filteredRooms = partyRooms.filter(
(room) =>
categoryFilter === noFilter.categoryName ||
categoryFilter === room.categoryName
);
const categoryNavItems = [noFilter, ...categories];

return (
<section className={styles.allRoomContanier}>
<nav className={styles.categoryNav}>
<ul>
{categoryNavItems.map((c) => (
<li
key={c.categoryName}
onClick={() => setCategoryFilter(c.categoryName)}
className={
categoryFilter === c.categoryName ? styles.selected : ''
}
>
{c.categoryName}
</li>
))}
</ul>
</nav>
<div className={styles.allRoomListWrap}>
{filteredRooms.length > 0 ? (
<ul>
{filteredRooms.map((room) => (
<PartyRoomListItem key={room.roomId} room={room} />
))}
</ul>
) : isSearchedResult ? (
<div className={styles.emptyRooms}>검색결과가 없습니다.</div>
) : (
<div className={styles.emptyRooms}>모집중인 방이 없습니다.</div>
)}
</div>
</section>
);
}

export const PartyMain = { JoinedRooms, SearchBar, AllRooms };
13 changes: 12 additions & 1 deletion components/party/roomDetail/PartyDetailButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { LuAlertTriangle } from 'react-icons/lu';
import { instance } from 'utils/axios';
import { modalState } from 'utils/recoil/modal';
import { toastState } from 'utils/recoil/toast';
import usePartyPenaltyTimer from 'hooks/party/usePartyPenaltyTimer';
import styles from 'styles/party/PartyDetailRoom.module.scss';

type ParytButtonProps = {
Expand Down Expand Up @@ -108,6 +109,8 @@ type RefreshProps = ParytButtonProps & {

function JoinRoom({ roomId, fetchRoomDetail }: RefreshProps) {
const setSnackbar = useSetRecoilState(toastState);
const { penaltyPeroid } = usePartyPenaltyTimer();

const handlerJoin = () => {
instance
.post(`/party/rooms/${roomId}`)
Expand All @@ -124,7 +127,15 @@ function JoinRoom({ roomId, fetchRoomDetail }: RefreshProps) {
});
};

return (
return penaltyPeroid ? (
<button
className={`${styles.joinBtn} ${styles.penalty}`}
onClick={handlerJoin}
>
패널티 부여 중<br />
{penaltyPeroid}
</button>
) : (
<button className={styles.joinBtn} onClick={handlerJoin}>
참여하기
</button>
Expand Down
8 changes: 0 additions & 8 deletions components/party/roomDetail/PartyDetailProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Image from 'next/image';
import { useRouter } from 'next/router';
import { FaCrown } from 'react-icons/fa';
import {
PartyRoomDetail,
Expand All @@ -21,18 +20,11 @@ export default function PartyDetailProfile({
}: PartyDetailProfileProps) {
const { currentPeople, minPeople, roomId, status, roomUsers, hostNickname } =
partyRoomDetail;
const router = useRouter();

return (
<div className={styles.profile}>
<div className={styles.line}>
<span>{`인원 : ${currentPeople}`}</span>
<button
className={styles.exitBtn}
onClick={() => router.push('/party')}
>
로비
</button>
</div>
<div className={styles.profileItem}>
<Profile
Expand Down
1 change: 1 addition & 0 deletions hooks/party/usePartyCategory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default function usePartyCategory() {
clicked: true,
});
},
staleTime: Infinity,
});

const createMutation = useMutation(
Expand Down
57 changes: 57 additions & 0 deletions hooks/party/usePartyPenaltyTimer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useEffect, useState } from 'react';
import { useQuery } from 'react-query';
import { instance } from 'utils/axios';
import { calculatePeriod } from 'utils/handleTime';

type PartyPenaltyRes = {
penaltyEndTime: string | null;
};

export default function usePartyPenaltyTimer() {
const { data, isLoading: isQueryLoading } = useQuery({
queryKey: 'partyPenalty',
queryFn: () =>
instance.get<PartyPenaltyRes>('/party/penalty').then(({ data }) => ({
...data,
penaltyEndTime:
data.penaltyEndTime && data.penaltyEndTime
? new Date(data.penaltyEndTime)
: null,
})),
staleTime: 5 * 60 * 1000, // 5분
});
const partyPenalty = data ?? { penaltyEndTime: null };
const [penaltyPeroid, setPenaltyPeroid] = useState(
partyPenalty.penaltyEndTime && calculatePeriod(partyPenalty.penaltyEndTime)
);
const [isPenaltyLoading, setIsPenaltyLoading] = useState(true);

useEffect(() => {
let intervalId: ReturnType<typeof setInterval>;

if (!isQueryLoading) {
if (
partyPenalty.penaltyEndTime &&
partyPenalty.penaltyEndTime.getTime() > Date.now()
) {
setIsPenaltyLoading(false);

setPenaltyPeroid(calculatePeriod(partyPenalty.penaltyEndTime));
intervalId = setInterval(() => {
if (partyPenalty.penaltyEndTime!.getTime() > Date.now())
setPenaltyPeroid(calculatePeriod(partyPenalty.penaltyEndTime!));
else {
setPenaltyPeroid(null);
clearInterval(intervalId);
}
}, 1000);
} else {
setIsPenaltyLoading(false);
}
}

return () => clearInterval(intervalId);
}, [isQueryLoading, partyPenalty.penaltyEndTime]);

return { penaltyPeroid, isPenaltyLoading };
}
Loading

0 comments on commit 071b58e

Please sign in to comment.