Skip to content

Commit 65e508b

Browse files
committed
feat: 게임 진행 시 문제 타이머 구현, 마이페이지 구현, 전체 랭킹 구현
1 parent 4d19a7e commit 65e508b

4 files changed

Lines changed: 127 additions & 102 deletions

File tree

src/pages/game/GamePlay.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ function GamePlay() {
104104
{questions && currentQuestion && `${currentQuestion.round} / ${questions.length}`}
105105
</span>
106106
</div>
107-
<QuizTimer duration={currentQuestion ? currentQuestion.timeLimit : 0} onTimeUp={handleTimeUp} />
107+
{visibleQuestion && <QuizTimer duration={currentQuestion.timeLimit} onTimeUp={handleTimeUp}/>}
108108
</div>
109109

110110
{/* Question Content */}

src/pages/mypage/MyPage.js

Lines changed: 70 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,69 @@
1-
import { useState } from 'react';
1+
import {useEffect, useState} from 'react';
22
import useConfirm from '../../hooks/useConfirm';
3-
import { useNavigate } from 'react-router-dom';
4-
import { Trophy, BarChart3 } from 'lucide-react';
3+
import {useNavigate} from 'react-router-dom';
4+
import {BarChart3, Trophy} from 'lucide-react';
55
import styles from './mypage.module.scss'
6+
import axios from "axios";
7+
import NicknameForm from "../login/NicknameForm";
8+
import {useApiQuery} from "../../hooks/useApiQuery";
9+
import {useApiMutation} from "../../hooks/useApiMutation";
610

7-
const userStats = {
8-
nickname: '빵야빵야',
9-
totalGames: 50,
10-
wins: 30,
11-
losses: 20,
12-
score: 70,
13-
currentRank: 3,
11+
const userRequest = async () => {
12+
const response = await axios.get(`/user/me`);
13+
return response.data;
14+
};
15+
16+
const userDeleteRequest = async () => {
17+
await axios.delete(`/user/me`);
18+
};
19+
20+
const userEditRequest = async (nickname) => {
21+
const params = {
22+
nickname
23+
};
24+
await axios.put('/user/me', params);
1425
};
1526

1627
const MyPage = () => {
1728
const [nickname, setNickname] = useState('빵야빵야');
29+
const [error, setError] = useState({
30+
nickname: "",
31+
});
32+
const [inputStatus, setInputStatus] = useState({
33+
nickname: false,
34+
});
1835
const { openConfirm } = useConfirm();
1936
const navigate = useNavigate();
37+
const { data } = useApiQuery(
38+
['/user/me'],
39+
() => userRequest()
40+
);
41+
const { mutate: userDeleteMutate } = useApiMutation(userDeleteRequest, {
42+
onSuccess: () => {
43+
// 회원가입 완료 후 방 목록으로 이동
44+
navigate("/login");
45+
},
46+
});
2047

21-
const handleDuplicateCheck = () => {
22-
openConfirm({
23-
title: '중복 확인',
24-
html: <div>사용 가능한 닉네임입니다.</div>,
25-
confirmButtonText: '확인',
26-
});
27-
};
48+
const { mutate: userEditMutate } = useApiMutation(userEditRequest, {
49+
onSuccess: () => {
50+
openConfirm({
51+
title: '프로필 수정',
52+
html: <div>저장되었습니다.</div>,
53+
confirmButtonText: '확인',
54+
});
55+
},
56+
});
2857

29-
const handleSaveClick = () => {
30-
openConfirm({
31-
title: '프로필 수정',
32-
html: <div>저장되었습니다.</div>,
33-
confirmButtonText: '확인',
34-
});
58+
useEffect(() => {
59+
if (data) {
60+
setNickname(data.nickname)
61+
}
62+
}, [data])
63+
64+
const handleSaveClick = (e) => {
65+
e.preventDefault();
66+
userEditMutate(nickname);
3567
};
3668

3769
const handleExitClick = () => {
@@ -45,7 +77,7 @@ const MyPage = () => {
4577
</div>
4678
),
4779
confirmButtonText: '탈퇴',
48-
callback: () => navigate('/login'),
80+
callback: () => userDeleteMutate(),
4981
});
5082
};
5183

@@ -59,34 +91,14 @@ const MyPage = () => {
5991
<span className={styles.headerText}>프로필 정보</span>
6092
</div>
6193
<div className={styles.form}>
62-
<form>
63-
{/* 닉네임 섹션 */}
64-
<div className={styles.inputRow}>
65-
<label className={styles.label}>닉네임</label>
66-
<input
67-
type='text'
68-
value={nickname}
69-
onChange={(e) => setNickname(e.target.value)}
70-
className={styles.input}
71-
onFocus={(e) => (e.target.style.borderColor = '#dc2626')}
72-
onBlur={(e) =>
73-
(e.target.style.borderColor = 'rgba(220, 38, 38, 0.5)')
74-
}
75-
/>
76-
<button
77-
type='button'
78-
onClick={handleDuplicateCheck}
79-
className={styles.button}
80-
onMouseOver={(e) =>
81-
(e.target.style.backgroundColor = '#b91c1c')
82-
}
83-
onMouseOut={(e) =>
84-
(e.target.style.backgroundColor = '#dc2626')
85-
}
86-
>
87-
중복 체크
88-
</button>
89-
</div>
94+
<form className={styles.nicknameForm} onSubmit={handleSaveClick}>
95+
<NicknameForm
96+
nickname={nickname}
97+
setNickname={setNickname}
98+
error={error}
99+
setError={setError}
100+
setInputStatus={setInputStatus}
101+
/>
90102

91103
{/* 게임 통계 섹션 */}
92104
<div className={styles.statsCard}>
@@ -98,18 +110,18 @@ const MyPage = () => {
98110
<div className={styles.statsRow}>
99111
<label className={styles.label}>전적</label>
100112
<span style={{ color: '#374151', fontWeight: '600' }}>
101-
{userStats.totalGames}{userStats.wins}{' '}
102-
{userStats.losses}
113+
{data?.totalGames}{data?.winningGames}{' '}
114+
{data ? (data.totalGames - data.winningGames) : '' }
103115
</span>
104116
</div>
105117
<div className={styles.statsRow}>
106118
<label className={styles.label}>점수</label>
107-
<span className={styles.score}>{userStats.score}</span>
119+
<span className={styles.score}>{data?.score}</span>
108120
</div>
109121
<div className={styles.statsRow}>
110122
<label className={styles.label}>현재 랭킹</label>
111123
<span className={styles.rankBadge}>
112-
{userStats.currentRank}
124+
{data?.rank}
113125
</span>
114126
</div>
115127
</div>
@@ -118,17 +130,9 @@ const MyPage = () => {
118130
{/* 버튼 영역 */}
119131
<div className={styles.buttonGroup}>
120132
<button
121-
type='button'
122-
onClick={handleSaveClick}
133+
type='submit'
123134
className={styles.saveButton}
124-
onMouseOver={(e) => {
125-
e.target.style.backgroundColor = '#dc2626';
126-
e.target.style.color = 'white';
127-
}}
128-
onMouseOut={(e) => {
129-
e.target.style.backgroundColor = 'transparent';
130-
e.target.style.color = '#dc2626';
131-
}}
135+
disabled={!inputStatus.nickname}
132136
>
133137
변경사항 저장
134138
</button>

src/pages/mypage/mypage.module.scss

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,17 @@
155155
border-radius: 0.25rem;
156156
cursor: pointer;
157157
transition: all 0.2s;
158+
//&:hover:not(:disabled) {
159+
// background-color: #dc2626;
160+
// color: white;
161+
//}
162+
163+
&:disabled {
164+
background-color: #f1f1f1;
165+
color: #aaa;
166+
border-color: #ccc;
167+
cursor: not-allowed;
168+
}
158169
}
159170

160171
.deleteButton {
@@ -182,4 +193,9 @@
182193
font-size: 1.25rem;
183194
}
184195

196+
.nicknameForm {
197+
display: flex;
198+
flex-direction: column;
199+
gap: 30px;
200+
}
185201

src/pages/rank/Rank.js

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
// Rank.jsx
2-
import { Button, Form, Stack } from "react-bootstrap";
3-
import { useState } from "react";
2+
import {Button, Form} from "react-bootstrap";
3+
import {useEffect, useState} from "react";
44
import styles from './rank.module.scss';
55
import TableBackGroundCard from "../../shared/TableBackGroundCard";
66
import FlexibleTable from "../../shared/table/FlexibleTable";
77
import PaginationNavigator from '../../layout/PaginationNavigator.js';
8+
import axios from "axios";
9+
import {useApiQuery} from "../../hooks/useApiQuery";
10+
import {useQueryParam} from "../../hooks/QueryParam";
811

912
const initColumns = [
1013
{ accessorKey: "rank", header: "순위" },
@@ -13,29 +16,43 @@ const initColumns = [
1316
{ accessorKey: "score", header: "점수" },
1417
];
1518

16-
const sampleData = [
17-
{ rank: 1, nickname: "세희", winLoss: "50전 30승 20패", score: "4931점" },
18-
{ rank: 2, nickname: "경찬", winLoss: "48전 28승 20패", score: "4825점" },
19-
{ rank: 3, nickname: "강현", winLoss: "45전 27승 18패", score: "4720점" },
20-
{ rank: 4, nickname: "민수", winLoss: "42전 25승 17패", score: "4615점" },
21-
{ rank: 5, nickname: "지영", winLoss: "40전 24승 16패", score: "4510점" },
22-
{ rank: 6, nickname: "현우", winLoss: "38전 22승 16패", score: "4405점" },
23-
{ rank: 7, nickname: "수진", winLoss: "35전 20승 15패", score: "4300점" },
24-
{ rank: 8, nickname: "태현", winLoss: "33전 19승 14패", score: "4195점" },
25-
{ rank: 9, nickname: "은지", winLoss: "30전 17승 13패", score: "4090점" },
26-
{ rank: 10, nickname: "준호", winLoss: "28전 15승 13패", score: "3985점" },
27-
];
19+
const rankRequest = async (params) => {
20+
const response = await axios.get(`/stats/rankings`, {params});
21+
return response.data;
22+
};
2823

2924
const Rank = () => {
3025
const [keyword, setKeyword] = useState("");
31-
const [currentPage, setCurrentPage] = useState(1);
32-
const itemsPerPage = 10;
33-
const totalPages = Math.ceil(sampleData.length / itemsPerPage);
26+
const [tableRows, setTableRows] = useState([]);
27+
const [params, setParams] = useQueryParam();
28+
const { data } = useApiQuery(
29+
['/stats/rankings', params], // queryKey에 params 포함
30+
() => rankRequest(params)
31+
);
32+
console.log(data);
33+
34+
useEffect(() => {
35+
if (data) {
36+
const processedRows = data.ranks.map((item) => {
37+
return {
38+
rank: item.rank,
39+
nickname: item.nickname,
40+
winLoss: `${item.totalGames}${item.winningGames}${item.totalGames - item.winningGames}패`,
41+
score: `${item.score}점`
42+
}
43+
})
44+
setTableRows(processedRows);
45+
}
46+
}, [data])
3447

3548
const handleSearchClick = (e) => {
3649
e.preventDefault();
3750
e.stopPropagation();
3851
console.log("🔍 검색 버튼 클릭됨");
52+
setParams({
53+
page: 1,
54+
nickname: keyword.trim()
55+
})
3956
};
4057

4158
const handleKeyDown = (e) => {
@@ -45,17 +62,6 @@ const Rank = () => {
4562
}
4663
};
4764

48-
const handlePageChange = (page) => {
49-
setCurrentPage(page);
50-
console.log(`페이지 ${page}로 이동`);
51-
};
52-
53-
// 현재 페이지 데이터
54-
const currentData = sampleData.slice(
55-
(currentPage - 1) * itemsPerPage,
56-
currentPage * itemsPerPage
57-
);
58-
5965
return (
6066
<div className={styles.container}>
6167

@@ -97,16 +103,15 @@ const Rank = () => {
97103

98104
<div className={styles.tableContainer}>
99105
<TableBackGroundCard className={styles.rankingCard}>
100-
<FlexibleTable initColumns={initColumns} data={currentData} />
106+
<FlexibleTable initColumns={initColumns} data={tableRows} />
101107
</TableBackGroundCard>
102108

103109
{/* Pagination */}
104-
105-
<PaginationNavigator
106-
currentPage={currentPage}
107-
totalPages={5}
108-
onPageChange={setCurrentPage}
109-
/>
110+
<PaginationNavigator currentPage={data?.currentPage} totalPages={data?.totalPages}
111+
onPageChange={(page) => setParams({
112+
...params
113+
,page: page,
114+
})}/>
110115
</div>
111116
</div>
112117

0 commit comments

Comments
 (0)