Skip to content

Commit 06cbef7

Browse files
authored
Merge pull request #144 from yoonc01/feat/#111/MonthlyChartVote-List
feat: #111/차트 투표하기 버튼 이벤트 구현
2 parents 728eccc + 88d4e67 commit 06cbef7

7 files changed

Lines changed: 124 additions & 22 deletions

File tree

src/apis/monthlyChartApi.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ export async function getLists(gender, cursor = 0, pageSize) {
1818
}
1919
}
2020

21-
export async function postVotes() {
21+
export async function postVotes(idolId) {
2222
try {
23-
const res = await instance.post('/votes');
23+
const loadData = { idolId };
24+
const res = await instance.post('/votes', loadData, {
25+
headers: {
26+
'Content-Type': 'application/json',
27+
},
28+
});
2429
return res.data;
2530
} catch (error) {
2631
console.error(error);

src/components/Modal.jsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,37 @@
11
import closeButton from '@/assets/icons/closeButton.svg';
22
import leftTopGradient from '@/assets/images/leftTopGradient.png';
33
import exitArrow from '@/assets/icons/exitArrow.svg';
4-
import { useEffect } from 'react';
4+
import { useEffect, useState } from 'react';
55

66
function Modal({ title, onClose, children }) {
7+
const [isMobile, setIsMobile] = useState(false);
8+
79
useEffect(() => {
810
document.body.style.overflow = 'hidden'; // 모달창 열려 있으면 뒤의 배경 스크롤 막기
911
return () => {
1012
document.body.style.overflow = 'auto'; // 모달 닫힐 때 스크롤 허용
1113
};
1214
}, []);
1315

14-
if (title.includes('아이돌') && window.innerWidth <= 375) {
16+
useEffect(() => {
17+
const handleResize = () => {
18+
setIsMobile(window.innerWidth < 768);
19+
};
20+
21+
window.addEventListener('resize', handleResize);
22+
handleResize();
23+
return () => window.removeEventListener('resize', handleResize);
24+
}, []);
25+
26+
if (title.includes('아이돌') && isMobile) {
1527
return (
16-
<div className="fixed top-0 left-0 size-full bg-midnightBlack">
28+
<div className="fixed flex flex-col top-0 left-0 size-full bg-midnightBlack">
1729
<img
1830
src={leftTopGradient}
1931
alt="leftTopGradient"
2032
className="absolute w-[200px] h-[272px] opacity-70 z-10 pointer-events-none"
2133
/>
22-
<div className="fixed top-2 left-0 w-full h-screen font-pretendard mx-[24px]">
34+
<div className="fixed top-2 left-0 w-full h-screen font-pretendard px-[24px]">
2335
<div className="w-full h-[44px] flex justify-start items-center">
2436
<img
2537
src={exitArrow}
@@ -40,7 +52,7 @@ function Modal({ title, onClose, children }) {
4052
}
4153

4254
return (
43-
<div className="fixed top-0 left-0 w-[100%] h-[100%] flex justify-center items-center bg-black/80 font-pretendard">
55+
<div className="fixed top-0 left-0 w-[100%] h-[100%] flex justify-center items-center bg-midnightBlack/80 font-pretendard">
4456
<div className="relative bg-deepCharcoal p-[20px] rounded-[8px] py-[24px] px-[16px]">
4557
<img
4658
src={closeButton}

src/components/modalContent/MonthlyChartVoteList.jsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
1+
import { useEffect, useState } from 'react';
12
import MonthlyChartItem from '@/pages/listPage/monthlyChart/MonthlyChartItem';
23

3-
const MonthlyChartVoteList = ({ idols, selectedIdol, setSelectedIdol }) => {
4+
const MonthlyChartVoteList = ({
5+
idols,
6+
selectedIdol,
7+
setSelectedIdol,
8+
children,
9+
}) => {
10+
const [isMobile, setIsMobile] = useState(false);
11+
12+
useEffect(() => {
13+
const handleResize = () => {
14+
setIsMobile(window.innerWidth < 768);
15+
};
16+
17+
window.addEventListener('resize', handleResize);
18+
handleResize();
19+
return () => window.removeEventListener('resize', handleResize);
20+
}, []);
21+
422
return (
5-
<div className="flex flex-col mt-[24px] mb-[40px]">
23+
<div
24+
style={{ height: isMobile ? 'calc(100vh - 156px)' : '693px' }}
25+
className={`flex flex-col overflow-y-scroll scrollbar-hidden w-full mt-[24px] ${isMobile ? 'mb-[40px]' : 'mb-[400px]'}`}
26+
>
627
{idols.map((idol, idx) => (
728
<div key={idol.id} onClick={() => setSelectedIdol(idol.id)}>
829
<MonthlyChartItem idol={idol} rank={idx + 1} layout="vote">
@@ -19,6 +40,7 @@ const MonthlyChartVoteList = ({ idols, selectedIdol, setSelectedIdol }) => {
1940
<div className="w-full h-[1px] bg-white bg-opacity-10 my-[4px]"></div>
2041
</div>
2142
))}
43+
{children}
2244
</div>
2345
);
2446
};

src/components/modalContent/MonthlyChartVoteModal.jsx

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import { useState, useEffect } from 'react';
1+
import { useState, useEffect, useRef } from 'react';
22
import MonthlyChartVoteList from '@/components/modalContent/MonthlyChartVoteList';
3-
import { getLists } from '@/apis/monthlyChartApi';
3+
import { getLists, postVotes } from '@/apis/monthlyChartApi';
44
import PrimaryButton from '@/components/PrimaryButton';
5+
import { spendCredits } from '../../utils/creditStorage';
56

6-
const MonthlyChartVoteModal = ({ gender }) => {
7+
const MonthlyChartVoteModal = ({ gender, onClickVoteCredit, closeModal }) => {
78
const [cursor, setCursor] = useState(0);
89
const [idolData, setIdolData] = useState([]);
910
const [loading, setLoading] = useState(false);
1011
const [selectedIdol, setSelectedIdol] = useState(0);
12+
const [isMobile, setIsMobile] = useState(false);
13+
const observerRef = useRef();
1114

1215
const loadIdolData = async () => {
1316
setLoading(true);
@@ -26,11 +29,21 @@ const MonthlyChartVoteModal = ({ gender }) => {
2629
}
2730
};
2831

29-
const loadMoreData = () => {
30-
if (cursor === null) {
31-
alert('불러올 데이터가 없습니다.');
32+
const handleVoteClick = async () => {
33+
const result = spendCredits(1000);
34+
if (result === 'NOT-ENOUGH') {
35+
alert('앗! 투표하기 위한 크레딧이 부족해요');
3236
} else {
33-
loadIdolData();
37+
try {
38+
const voteResult = await postVotes(selectedIdol);
39+
alert('투표 완료!');
40+
41+
if (onClickVoteCredit) onClickVoteCredit();
42+
closeModal();
43+
} catch (error) {
44+
console.error(error);
45+
alert('투표에 실패했습니다. 다시 시도해 주세요.');
46+
}
3447
}
3548
};
3649

@@ -40,22 +53,58 @@ const MonthlyChartVoteModal = ({ gender }) => {
4053
loadIdolData();
4154
}, [gender]);
4255

56+
useEffect(() => {
57+
const handleResize = () => {
58+
setIsMobile(window.innerWidth < 768);
59+
};
60+
61+
window.addEventListener('resize', handleResize);
62+
handleResize();
63+
return () => window.removeEventListener('resize', handleResize);
64+
}, []);
65+
66+
useEffect(() => {
67+
if (cursor === null) return;
68+
69+
const observer = new IntersectionObserver(
70+
(entries) => {
71+
if (entries[0].isIntersecting) loadIdolData();
72+
},
73+
{ threshold: 0.2 }
74+
);
75+
if (observerRef.current) observer.observe(observerRef.current);
76+
return () => observer.disconnect();
77+
}, [cursor]);
78+
4379
return (
44-
<div className="w-[calc(100%-48px)] h-[693px] tablet:w-[525px] tablet:h-[693px] pc:w-[525px] pc:h-[693px] overflow-y-auto">
80+
<div
81+
className={`relative overflow-hidden ${isMobile ? 'w-[calc(100%-24px)] h-full' : 'w-[525px] h-[693px]'}`}
82+
>
4583
{loading ? (
4684
<div className="text-center text-white">로딩 중입니다...</div>
4785
) : (
4886
<MonthlyChartVoteList
4987
idols={idolData}
5088
selectedIdol={selectedIdol}
5189
setSelectedIdol={setSelectedIdol}
52-
/>
90+
>
91+
<div
92+
className="w-full h-[40px]"
93+
ref={cursor !== null ? observerRef : null}
94+
></div>
95+
</MonthlyChartVoteList>
5396
)}
54-
<div className="fixed bottom-0 w-[calc(100%-48px)] h-[106px] text-white leading-[26px] bg-midnightBlack/80 tablet:w-[525px] pc:w-[525px] tablet:bg-transparent pc:bg-transparent">
55-
<PrimaryButton className="w-full h-[42px] font-bold text-[14px] font-pretendard">
97+
<div
98+
style={{ width: isMobile ? 'calc(100vw - 68px)' : '525px' }}
99+
className={`absolute text-white leading-[26px] ${isMobile ? 'bottom-[64px] h-[112px] bg-midnightBlack' : 'bottom-0 h-[72px] bg-deepCharcoal'}`}
100+
>
101+
<PrimaryButton
102+
onClickFunc={handleVoteClick}
103+
className="w-full h-[42px] font-bold text-[14px] font-pretendard"
104+
>
56105
투표하기
57106
</PrimaryButton>
58-
<p className="font-medium text-[12px] text-center">
107+
<p className="font-medium text-[12px] text-center mt-[8px] tablet:mt-[12px] pc:mt-[12px]">
59108
투표하는 데<span className=" text-coralRed"> 1000 크레딧</span>
60109
소모됩니다.
61110
</p>

src/index.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,14 @@ html,
66
body {
77
@apply bg-midnightBlack;
88
}
9+
10+
@layer utilities {
11+
.scrollbar-hidden {
12+
scrollbar-width: none; /* Firefox */
13+
-ms-overflow-style: none; /* IE 10+ */
14+
}
15+
16+
.scrollbar-hidden::-webkit-scrollbar {
17+
display: none; /* Chrome, Safari, Edge */
18+
}
19+
}

src/pages/listPage/ListPage.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ function ListPage() {
111111
{modalStep === 'donationSuccess' && (
112112
<DonationSuccess onConfirm={closeModal} />
113113
)}
114-
{modalStep === 'vote' && <MonthlyChartVoteModal gender={gender} />}
114+
{modalStep === 'vote' && (
115+
<MonthlyChartVoteModal closeModal={closeModal} gender={gender} />
116+
)}
115117
</Modal>
116118
)}
117119
</div>

src/utils/creditStorage.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ export const spendCredits = (amount) => {
3333

3434
const newCredits = Math.max(currentCredits - amount, 0);
3535
localStorage.setItem('credits', newCredits);
36+
return 'SUCCESS';
3637
}
3738
};

0 commit comments

Comments
 (0)