Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: Deploy Release to Vercel

on:
release:
types: [published]
workflow_run:
workflows: ["Create release & tag"]
types: [completed]

jobs:
deploy:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# fandom-K

| fandom-K는 팬덤 기반 콘텐츠를 시각화하고 관리하기 위한 React 기반 프론트엔드 프로젝트입니다.
> fandom-K는 팬덤 기반 콘텐츠를 시각화하고 관리하기 위한 React 기반 프론트엔드 프로젝트입니다.

## 기술 스택

Expand Down
27 changes: 18 additions & 9 deletions src/pages/ListPage/components/chart/ChartSection.jsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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);

Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -76,6 +76,10 @@ const ChartSection = () => {
setVisibleCount((v) => Math.min(v + 5, data.length));
};

const handleTrigger = () => {
setTrigger((pre) => !pre);
};

let gridContent;
if (isSingleCol) {
gridContent = (
Expand Down Expand Up @@ -109,7 +113,10 @@ const ChartSection = () => {
<S.RightArea>
<HighlightButton
type="button"
onClick={onOpen}
onClick={() => {
onOpen();
setModalContent({ list: list, gender: gender });
}}
$customStyle={css`
display: flex;
width: 128px;
Expand Down Expand Up @@ -154,7 +161,9 @@ const ChartSection = () => {
</S.MoreBtn>
</S.MoreArea>

{isOpen && <VoteModal onClose={onClose} initialGender={gender} />}
{isOpen && (
<VoteModal modalContent={modalContent} onClose={onClose} handleTrigger={handleTrigger} />
)}
</S.Wrap>
);
};
Expand Down
66 changes: 18 additions & 48 deletions src/pages/ListPage/components/chart/VoteModal.jsx
Original file line number Diff line number Diff line change
@@ -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);
};

Expand All @@ -68,17 +34,21 @@ const VoteModal = ({ onClose, initialGender = "female" }) => {
<S.Modal onClick={(e) => e.stopPropagation()}>
<S.MobileHeader>
<S.BackBtn onClick={onClose} aria-label="뒤로" />
<S.Title>{gender === "female" ? "이달의 여자 아이돌" : "이달의 남자 아이돌"}</S.Title>
<S.Title>
{modalContent.gender === "female" ? "이달의 여자 아이돌" : "이달의 남자 아이돌"}
</S.Title>
<S.Rbox />
</S.MobileHeader>

<S.Header>
<S.Title>{gender === "female" ? "이달의 여자 아이돌" : "이달의 남자 아이돌"}</S.Title>
<S.Title>
{modalContent.gender === "female" ? "이달의 여자 아이돌" : "이달의 남자 아이돌"}
</S.Title>
<S.CloseBtn onClick={onClose} />
</S.Header>

<S.List>
{list.map((c) => (
{modalContent.list.map((c) => (
<ListItem
key={c.id}
{...c}
Expand Down Expand Up @@ -108,7 +78,7 @@ const VoteModal = ({ onClose, initialGender = "female" }) => {
투표하기
</HighlightButton>
<S.VoteNotice>
투표하는 데 <S.Credit>1000</S.Credit> 크레딧이 소모됩니다.
투표하는 데 <S.Credit>{NEED_CREDIT}</S.Credit> 크레딧이 소모됩니다.
</S.VoteNotice>
</S.Vote>
</S.Modal>
Expand Down
26 changes: 14 additions & 12 deletions src/pages/ListPage/components/chart/VoteModal.style.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
`;

Expand All @@ -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}) {
Expand Down
3 changes: 3 additions & 0 deletions vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"rewrites": [{ "source": "/(.*)", "destination": "/" }]
}