diff --git a/src/Root.jsx b/src/Root.jsx
index e0068f38..a3e2f9a8 100644
--- a/src/Root.jsx
+++ b/src/Root.jsx
@@ -18,7 +18,6 @@ function Root() {
return (
<>
-
{isLoading ? (
) : (
@@ -34,7 +33,6 @@ function Root() {
{!isLanding && }
>
)}
-
>
);
}
diff --git a/src/components/button/index.js b/src/components/button/index.js
index 4d0a97b8..70bfbf9c 100644
--- a/src/components/button/index.js
+++ b/src/components/button/index.js
@@ -2,3 +2,4 @@ export { default as AvatarButton } from './avatarButton';
export { default as CustomButton } from './customButton';
export { default as RadioButton } from './radioButton';
export { default as ArrowButton } from './arrowButton';
+export { default as LoadMoreButton } from './loadMoreButton';
diff --git a/src/components/chart/LoadMoreButton.jsx b/src/components/button/loadMoreButton/LoadMoreButton.jsx
similarity index 96%
rename from src/components/chart/LoadMoreButton.jsx
rename to src/components/button/loadMoreButton/LoadMoreButton.jsx
index cf8c7925..b0aa13d7 100644
--- a/src/components/chart/LoadMoreButton.jsx
+++ b/src/components/button/loadMoreButton/LoadMoreButton.jsx
@@ -1,4 +1,4 @@
-import * as S from './chart.styles';
+import * as S from './loadMoreButton.styles';
/**
* '더 보기' 버튼 컴포넌트. 더 많은 데이터를 로드할 수 있을 때 버튼을 표시하고, 로딩 중에는 비활성화 상태로 표시합니다.
diff --git a/src/components/button/loadMoreButton/index.js b/src/components/button/loadMoreButton/index.js
new file mode 100644
index 00000000..67ed6f08
--- /dev/null
+++ b/src/components/button/loadMoreButton/index.js
@@ -0,0 +1 @@
+export { default } from './LoadMoreButton';
diff --git a/src/components/button/loadMoreButton/loadMoreButton.styles.js b/src/components/button/loadMoreButton/loadMoreButton.styles.js
new file mode 100644
index 00000000..24364a25
--- /dev/null
+++ b/src/components/button/loadMoreButton/loadMoreButton.styles.js
@@ -0,0 +1,52 @@
+import { css, keyframes } from '@emotion/react';
+import media from '@/styles/responsive';
+
+export const voteButtonFlow = keyframes`
+ 0% {
+ background-position: 0% 50%;
+ }
+ 50% {
+ background-position: 100% 50%;
+ }
+ 100% {
+ background-position: 0% 50%;
+ }
+`;
+
+export const flexCenter = css`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+`;
+
+export const moreButton = css`
+ ${flexCenter};
+ margin-inline: auto;
+ margin-block: 3rem;
+ border-radius: 8px;
+ background: linear-gradient(45deg, var(--black) 0%, var(--white-alpha-20) 51%, var(--white-alpha-10) 100%);
+ background-position: left center; /* 기본 시작 위치 */
+ background-size: 400%; /* 배경 크기 확장 */
+ transition: all 0.1s ease;
+
+ ${media({
+ fontSize: ['1.1rem', '1.2rem', '1.4rem', '1.6rem'],
+ width: ['10rem', '15rem', '20rem', '25rem'],
+ height: ['2.3rem', '2.7rem', '3.5rem', '4rem'],
+ })}
+
+ &:hover {
+ background-position: right center; /* hover 시 배경 이동 */
+ animation: ${voteButtonFlow} 4s ease infinite; /* 애니메이션 흐름 */
+ opacity: 0.9;
+ }
+
+ &:active {
+ transform: scale(0.97);
+ }
+
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 0.6;
+ }
+`;
diff --git a/src/components/chart/Chart.jsx b/src/components/chart/Chart.jsx
index b7675cea..ba76cd56 100644
--- a/src/components/chart/Chart.jsx
+++ b/src/components/chart/Chart.jsx
@@ -3,11 +3,11 @@ import { CustomButton } from '@/components/button';
import { LoadingSpinner } from '@/components/loadingStatus';
import chartImg from '@/assets/images/chart.png';
import { fetchData } from './fetchData';
-import { useScreenSize } from './useScreenSize';
-import LoadMoreButton from './LoadMoreButton';
+import { useScreenSize } from '@/utils/responsive';
+import { LoadMoreButton } from '@/components/button';
import * as S from './chart.styles';
-const Chart = ({ setModalType, selectedTab, setSelectedTab, updateCredit }) => {
+const Chart = ({ setModalType, selectedTab, setSelectedTab, voteSuccessTrigger }) => {
const [chartData, setChartData] = useState([]);
const [cursor, setCursor] = useState(0);
const [hasMore, setHasMore] = useState(true);
@@ -52,7 +52,7 @@ const Chart = ({ setModalType, selectedTab, setSelectedTab, updateCredit }) => {
return () => {
controller.abort();
};
- }, [selectedTab, screenSize, updateCredit]);
+ }, [selectedTab, screenSize, voteSuccessTrigger]);
const loadMore = () => {
fetchData(
diff --git a/src/components/chart/chart.styles.js b/src/components/chart/chart.styles.js
index f060c792..0a404fcc 100644
--- a/src/components/chart/chart.styles.js
+++ b/src/components/chart/chart.styles.js
@@ -140,7 +140,7 @@ export const idolList = css`
}
img {
- border: 2px solid var(--black);
+ border: 2px solid var(--black-deep);
border-radius: 50%;
box-shadow: 0 0 0 0.1rem var(--orange);
object-fit: cover;
@@ -151,6 +151,10 @@ export const idolList = css`
marginInline: ['0.1rem', '0.2rem', '0.5rem', '0.5rem'],
})}
}
+
+ &:active {
+ transform: scale(1.015);
+ }
}
`;
@@ -177,35 +181,3 @@ export const nameStyle = css`
font-weight: 700;
color: var(--white);
`;
-
-export const moreButton = css`
- ${flexCenter};
- margin-inline: auto;
- margin-block: 3rem;
- border-radius: 8px;
- background: linear-gradient(45deg, var(--black) 0%, var(--white-alpha-20) 51%, var(--white-alpha-10) 100%);
- background-position: left center; /* 기본 시작 위치 */
- background-size: 400%; /* 배경 크기 확장 */
- transition: all 0.1s ease;
-
- ${media({
- fontSize: ['1.1rem', '1.2rem', '1.4rem', '1.6rem'],
- width: ['10rem', '15rem', '20rem', '25rem'],
- height: ['2.3rem', '2.7rem', '3.5rem', '4rem'],
- })}
-
- &:hover {
- background-position: right center; /* hover 시 배경 이동 */
- animation: ${voteButtonFlow} 4s ease infinite; /* 애니메이션 흐름 */
- opacity: 0.9;
- }
-
- &:active {
- transform: scale(0.97);
- }
-
- &:disabled {
- cursor: not-allowed;
- opacity: 0.6;
- }
-`;
diff --git a/src/components/list/ListModal.jsx b/src/components/list/ListModal.jsx
index 2b65576a..3b40aad7 100644
--- a/src/components/list/ListModal.jsx
+++ b/src/components/list/ListModal.jsx
@@ -6,7 +6,15 @@ import {
VoteModal,
} from '@/components/modals';
-const ListModal = ({ modalType, setModalType, credit, updateCredit, gender, donations }) => {
+const ListModal = ({
+ modalType,
+ setModalType,
+ credit,
+ updateCredit,
+ setVoteSuccessTrigger,
+ gender,
+ donations,
+}) => {
const onCloseModal = () => setModalType(null);
const renderModalContent = () => {
@@ -33,7 +41,12 @@ const ListModal = ({ modalType, setModalType, credit, updateCredit, gender, dona
);
case 'vote':
return (
-
+
);
default:
return null;
diff --git a/src/components/modals/creditChargeModal/CreditChargeModal.jsx b/src/components/modals/creditChargeModal/CreditChargeModal.jsx
index 56140deb..7ffe9baa 100644
--- a/src/components/modals/creditChargeModal/CreditChargeModal.jsx
+++ b/src/components/modals/creditChargeModal/CreditChargeModal.jsx
@@ -1,8 +1,11 @@
import { useState } from 'react';
import { CustomButton, RadioButton } from '@/components/button';
import { Prices } from '@/constants/creditPrice';
+import { addCommas } from '@/utils/format';
+import { showAlert } from '@/utils/alert';
import starTwoImg from '@/assets/images/2-star.png';
import starThreeImg from '@/assets/images/3-star.png';
+import logoImg from '@/assets/images/logo.png';
import starImg from '@/assets/images/star.png';
import * as S from './creditChargeModal.styles';
@@ -21,7 +24,7 @@ const CreditChargeModal = ({ credit, updateCredit, onClose }) => {
const handleCharge = () => {
if (!selectedValue) {
- alert('충전할 크레딧을 선택해주세요!');
+ showAlert('충전할 크레딧을 선택해주세요!', 'warning');
return;
}
@@ -29,15 +32,17 @@ const CreditChargeModal = ({ credit, updateCredit, onClose }) => {
const newTotal = prev + Number(selectedValue);
localStorage.setItem('selectedCredit', newTotal);
updateCredit(newTotal); // 업데이트된 credit을 ListPage에 전달
- alert(`크레딧 ${selectedValue} 충전 완료! 총 보유: ${newTotal}`);
-
+ showAlert(`${selectedValue} 크레딧 충전 완료!`, 'success');
// 모달 닫기 로직
onClose();
};
return (
-
크레딧 충전하기
+
+
+ 크레딧 충전하기
+
{Prices.map((price) => {
const imgSrc = imageMap[price.id] || starImg;
@@ -50,8 +55,8 @@ const CreditChargeModal = ({ credit, updateCredit, onClose }) => {
handleSelect={() => handleRadioSelect(price.value)}
>
-

-
{price.value}
+

+
{addCommas(Number(price.value))}
);
diff --git a/src/components/modals/creditChargeModal/creditChargeModal.styles.js b/src/components/modals/creditChargeModal/creditChargeModal.styles.js
index caf0b049..e58c704d 100644
--- a/src/components/modals/creditChargeModal/creditChargeModal.styles.js
+++ b/src/components/modals/creditChargeModal/creditChargeModal.styles.js
@@ -1,5 +1,4 @@
import { css, keyframes } from '@emotion/react';
-import media from '@/styles/responsive';
export const voteButtonFlow = keyframes`
0% {
@@ -16,17 +15,26 @@ export const voteButtonFlow = keyframes`
export const modalContent = css`
display: flex;
flex-direction: column;
- justify-content: center;
- align-items: baseline;
-
- ${media({
- margin: ['2.4rem 1.6rem', '2.4rem 1.6rem', '3rem 3rem', '3rem 3rem', '3rem 3rem'],
- gap: ['1rem', '1rem', '2rem', '2rem', '2rem'],
- })}
+ gap: 1.2rem;
+ width: 32.7rem;
+ height: 38rem;
+ padding: 2.4rem 1.6rem;
+`;
+
+export const modalTitle = css`
+ display: flex;
+ align-items: center;
+ margin-bottom: 0.7rem;
+
+ img {
+ width: 2.5rem;
+ height: 2.5rem;
+ margin-right: 0.5rem;
+ margin-bottom: 0.1rem;
+ }
h2 {
- margin-block: 0.2rem 1rem;
- font-size: 1.8rem;
+ font-size: 2rem;
}
`;
@@ -43,10 +51,8 @@ export const radioButtons = css`
`;
export const buttonStyle = css`
- ${media({
- width: ['28rem', '29rem', '29rem', '32rem', '32rem'],
- height: ['7rem', '7rem', '7rem', '8rem', '8rem'],
- })}
+ width: 29rem;
+ height: 6.8rem;
border: 1px solid var(--white);
border-radius: 8px;
transition: border-color 0.3s ease;
@@ -58,11 +64,14 @@ export const buttonStyle = css`
&:has(input[type="radio"]:checked) {
border-color: var(--orange);
color: var(--gray);
+ transform: scale(1.015);
}
-
- img {
- width: 2.4rem;
- }
+`;
+
+export const creditImg = (id) => css`
+ margin-inline: ${id === 1 ? '0.8rem' : id === 2 ? '0.2rem' : '0.1rem'};
+ width: ${id === 1 ? '2.5rem' : id === 2 ? '3.7rem' : '4rem'};
+ height: ${id === 1 ? '2.45rem' : id === 2 ? '2.6rem' : '3rem'};
`;
export const customButton = css`
@@ -73,11 +82,6 @@ export const customButton = css`
transition: all 0.1s ease;
animation: ${voteButtonFlow} 3s ease infinite; /* 애니메이션 흐름 */
- ${media({
- width: ['28.7rem', '29.8rem', '29.8rem', '32.8rem', '32.8rem'],
- height: ['3.8rem', '4rem', '4rem', '5rem', '5rem'],
- })}
-
p {
font-weight: 700;
}
@@ -103,7 +107,6 @@ export const radioButtonContent = css`
padding: 0.5rem;
span {
- margin: 0.2rem 0 0 0.3rem;
font-size: 2rem;
font-weight: 700;
}
diff --git a/src/components/modals/voteModal/VoteModal.jsx b/src/components/modals/voteModal/VoteModal.jsx
index c071b153..6fd399ad 100644
--- a/src/components/modals/voteModal/VoteModal.jsx
+++ b/src/components/modals/voteModal/VoteModal.jsx
@@ -7,7 +7,7 @@ import { showAlert } from '@/utils/alert';
import { requestPost } from '@/utils/api';
import { addCommas } from '@/utils/format';
import * as S from './voteModal.styles';
-const VoteModal = ({ gender, updateCredit, setModalType }) => {
+const VoteModal = ({ gender, updateCredit, setVoteSuccessTrigger, setModalType }) => {
const [idols, setIdols] = useState([]);
const [checkedItem, setCheckedItem] = useState();
@@ -28,6 +28,7 @@ const VoteModal = ({ gender, updateCredit, setModalType }) => {
if (response) {
localStorage.setItem('selectedCredit', Number(getCredit) - 1000);
updateCredit(Number(getCredit) - 1000);
+ setVoteSuccessTrigger((prev) => !prev); // 차트에 투표 수 반영하는 상태변수
showAlert('투표에 성공했습니다', 'success');
} else {
showAlert('투표에 실패했습니다', 'warning');
diff --git a/src/pages/list/ListPage.jsx b/src/pages/list/ListPage.jsx
index e781d561..7f0bed09 100644
--- a/src/pages/list/ListPage.jsx
+++ b/src/pages/list/ListPage.jsx
@@ -9,9 +9,10 @@ import * as S from './listPage.styles';
const ListPage = () => {
const [modalType, setModalType] = useState(null); // 모달 타입 상태 관리
const [credit, setCredit] = useState(0); // 크레딧 상태 관리
- const { idols, donations } = useLoaderData(); // 여기서 데이터 받음
const [selectedTab, setSelectedTab] = useState('females');
const [selectedIndex, setSelectedIndex] = useState(null); // 후원의 인덱스를 받아옴
+ const [voteSuccessTrigger, setVoteSuccessTrigger] = useState(false);
+ const { idols, donations } = useLoaderData(); // 여기서 데이터 받음
useEffect(() => {
const storedCredit = localStorage.getItem('selectedCredit');
@@ -36,13 +37,14 @@ const ListPage = () => {
setModalType={setModalType}
selectedTab={selectedTab}
setSelectedTab={setSelectedTab}
- updateCredit={updateCredit}
+ voteSuccessTrigger={voteSuccessTrigger}
/>
diff --git a/src/utils/responsive/index.js b/src/utils/responsive/index.js
new file mode 100644
index 00000000..715995df
--- /dev/null
+++ b/src/utils/responsive/index.js
@@ -0,0 +1 @@
+export { default as useScreenSize } from './useScreenSize';
diff --git a/src/components/chart/useScreenSize.js b/src/utils/responsive/useScreenSize.js
similarity index 94%
rename from src/components/chart/useScreenSize.js
rename to src/utils/responsive/useScreenSize.js
index eb134d61..1a5e8913 100644
--- a/src/components/chart/useScreenSize.js
+++ b/src/utils/responsive/useScreenSize.js
@@ -18,7 +18,7 @@ const getScreenSize = () => {
* const screenSize = useScreenSize();
* console.log(screenSize); // 'mobile', 'tablet', 또는 'desktop'
*/
-export const useScreenSize = () => {
+const useScreenSize = () => {
const [screenSize, setScreenSize] = useState(getScreenSize());
useEffect(() => {
@@ -36,3 +36,5 @@ export const useScreenSize = () => {
return screenSize;
};
+
+export default useScreenSize;