diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 52cff2b..9f1d536 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -1,8 +1,9 @@ name: Deploy Release to Vercel on: - release: - types: [published] + workflow_run: + workflows: ["Create release & tag"] + types: [completed] jobs: deploy: diff --git a/README.md b/README.md index 8e7c861..1f244e8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # fandom-K -| fandom-K는 팬덤 기반 콘텐츠를 시각화하고 관리하기 위한 React 기반 프론트엔드 프로젝트입니다. +> fandom-K는 팬덤 기반 콘텐츠를 시각화하고 관리하기 위한 React 기반 프론트엔드 프로젝트입니다. ## 기술 스택 diff --git a/src/pages/ListPage/components/chart/ChartSection.jsx b/src/pages/ListPage/components/chart/ChartSection.jsx index a0c279c..b950fb7 100644 --- a/src/pages/ListPage/components/chart/ChartSection.jsx +++ b/src/pages/ListPage/components/chart/ChartSection.jsx @@ -1,4 +1,4 @@ -import client from "@/api/client"; +import { getChartList } from "@/api/chartClient"; import HighlightButton from "@/components/common/HighlightButton"; import useModal from "@/hooks/useModal"; import { useEffect, useState } from "react"; @@ -15,8 +15,8 @@ const ChartSection = () => { const getInitWidth = () => (typeof window !== "undefined" ? window.innerWidth : BREAKPOINT); const [gender, setGender] = useState("female"); - const { isOpen, onOpen, onClose } = useModal(); - + const { isOpen, onOpen, onClose, modalContent, setModalContent } = useModal(); + const [trigger, setTrigger] = useState(false); const [windowWidth, setWindowWidth] = useState(getInitWidth()); const [visibleCount, setVisibleCount] = useState(getInitWidth() >= BREAKPOINT ? 10 : 5); @@ -25,9 +25,8 @@ const ChartSection = () => { useEffect(() => { const loadIdols = async () => { try { - const res = await client.get("/idols", { params: { pageSize: 100 } }); - const idols = res.data.list; - + const res = await getChartList({ pageSize: 100, gender: gender }); + const idols = res.idols; const filtered = idols.filter((i) => i.gender === gender); const sorted = filtered @@ -41,12 +40,13 @@ const ChartSection = () => { })); setList(sorted); + setModalContent({ list: sorted, gender: gender }); } catch (e) { showBoundary(e); } }; loadIdols(); - }, [gender, showBoundary]); + }, [gender, showBoundary, setModalContent, trigger]); useEffect(() => { const onResize = () => setWindowWidth(window.innerWidth); @@ -76,6 +76,10 @@ const ChartSection = () => { setVisibleCount((v) => Math.min(v + 5, data.length)); }; + const handleTrigger = () => { + setTrigger((pre) => !pre); + }; + let gridContent; if (isSingleCol) { gridContent = ( @@ -109,7 +113,10 @@ const ChartSection = () => { { + onOpen(); + setModalContent({ list: list, gender: gender }); + }} $customStyle={css` display: flex; width: 128px; @@ -154,7 +161,9 @@ const ChartSection = () => { - {isOpen && } + {isOpen && ( + + )} ); }; diff --git a/src/pages/ListPage/components/chart/VoteModal.jsx b/src/pages/ListPage/components/chart/VoteModal.jsx index 9d05196..437f07b 100644 --- a/src/pages/ListPage/components/chart/VoteModal.jsx +++ b/src/pages/ListPage/components/chart/VoteModal.jsx @@ -1,64 +1,30 @@ import client from "@/api/client"; +import useCreditContext from "@/app/contexts/CreditContext"; import HighlightButton from "@/components/common/HighlightButton"; -import { creditStorage } from "@/storage/credit.storage"; import { media } from "@/styles/media"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { css } from "styled-components"; import CreditLimitModal from "./CreditLimitModal"; import ListItem from "./ListItem"; import * as S from "./VoteModal.style"; -const VoteModal = ({ onClose, initialGender = "female" }) => { - const [gender] = useState(initialGender); - const [list, setList] = useState([]); - const [selectedId, setSelectedId] = useState(null); +const NEED_CREDIT = 1000; +const VoteModal = ({ onClose, modalContent, handleTrigger }) => { + const [selectedId, setSelectedId] = useState(null); + const [_, { isEnoughCredit, subtractCredit }] = useCreditContext(); const [isCreditModalOpen, setIsCreditModalOpen] = useState(false); - useEffect(() => { - const fetchIdols = async () => { - const res = await client.get("/idols"); - const idols = res.data.list; - - const filtered = idols.filter((i) => i.gender === gender); - - const sorted = filtered - .sort((a, b) => b.totalVotes - a.totalVotes) - .map((i, idx) => ({ - id: i.id, - name: i.name, - img: i.profilePicture, - votes: i.totalVotes, - rank: idx + 1, - })); - - setList(sorted); - }; - - fetchIdols(); - }, [gender]); - const submit = async () => { if (!selectedId) return; - - const currentCredit = creditStorage.get() || 0; - - if (currentCredit < 1000) { + if (!isEnoughCredit(NEED_CREDIT)) { setIsCreditModalOpen(true); return; } - const res = await client.post("/votes", { idolId: selectedId }); - const updated = res.data.idol; - - creditStorage.set(currentCredit - 1000); - - const newList = list - .map((i) => (i.id === updated.id ? { ...i, votes: updated.totalVotes } : i)) - .sort((a, b) => b.votes - a.votes) - .map((i, idx) => ({ ...i, rank: idx + 1 })); - - setList(newList); + await client.post("/votes", { idolId: selectedId }); + subtractCredit(NEED_CREDIT); + handleTrigger(); setSelectedId(null); }; @@ -68,17 +34,21 @@ const VoteModal = ({ onClose, initialGender = "female" }) => { e.stopPropagation()}> - {gender === "female" ? "이달의 여자 아이돌" : "이달의 남자 아이돌"} + + {modalContent.gender === "female" ? "이달의 여자 아이돌" : "이달의 남자 아이돌"} + - {gender === "female" ? "이달의 여자 아이돌" : "이달의 남자 아이돌"} + + {modalContent.gender === "female" ? "이달의 여자 아이돌" : "이달의 남자 아이돌"} + - {list.map((c) => ( + {modalContent.list.map((c) => ( { 투표하기 - 투표하는 데 1000 크레딧이 소모됩니다. + 투표하는 데 {NEED_CREDIT} 크레딧이 소모됩니다. diff --git a/src/pages/ListPage/components/chart/VoteModal.style.js b/src/pages/ListPage/components/chart/VoteModal.style.js index 1642772..0ec7751 100644 --- a/src/pages/ListPage/components/chart/VoteModal.style.js +++ b/src/pages/ListPage/components/chart/VoteModal.style.js @@ -28,7 +28,7 @@ export const Modal = styled.div` max-width: none; border-radius: 0; padding: 16px; - background: var(--color-bg-base); + background: ${hexToRgba(COLOR_VAR_MAP["--color-bg-base"])}; @media (${media.tablet}) { position: static; @@ -115,18 +115,20 @@ export const CloseBtn = styled.button` export const List = styled.div` height: 100%; - @media (${media.tablet}) { - flex: 1; - min-height: 0; - overflow-y: auto; + overflow-y: scroll; + + /* 스크롤바 숨김(필요 시 제거 가능) */ + scrollbar-width: none; + -ms-overflow-style: none; + + &:-webkit-scrollbar { + display: none; + } - /* 스크롤바 숨김(필요 시 제거 가능) */ - scrollbar-width: none; - -ms-overflow-style: none; + padding-bottom: 140px; - &:-webkit-scrollbar { - display: none; - } + @media (${media.tablet}) { + padding-bottom: 0; } `; @@ -137,7 +139,7 @@ export const Vote = styled.div` bottom: 0; z-index: 10; height: 106px; - background: transparent; + background: ${hexToRgba(COLOR_VAR_MAP["--color-bg-base"])}; width: 100%; @media (${media.tablet}) { diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..3a48e56 --- /dev/null +++ b/vercel.json @@ -0,0 +1,3 @@ +{ + "rewrites": [{ "source": "/(.*)", "destination": "/" }] +}