diff --git a/src/components/home/Home.jsx b/src/components/home/Home.jsx index ecf1a1e..fbe04f5 100644 --- a/src/components/home/Home.jsx +++ b/src/components/home/Home.jsx @@ -12,19 +12,8 @@ import { getUserInfoAPI } from "services/user/user"; import "styles/components/home/Home.scss"; import { getAuthToken } from "utils/token"; import { getUserInfo } from "utils/user"; -import { RankingPreview } from "./RankingPreview"; -const rankingData = [ - { username: "Player1", score: 1000 }, - { username: "Player2", score: 900 }, - { username: "Player3", score: 800 }, - { username: "Player4", score: 700 }, - { username: "Player5", score: 600 }, - { username: "Player6", score: 500 }, - { username: "Player7", score: 400 }, - { username: "Player8", score: 300 }, - { username: "Player9", score: 200 }, - { username: "Player10", score: 100 }, -]; +import { RankingPreview } from "components/home/RankingPreview.jsx"; + const Home = () => { const navigate = useNavigate(); const { isSignIn } = useOutletContext(); @@ -91,7 +80,7 @@ const Home = () => {
- +
diff --git a/src/components/home/RankingPreview.jsx b/src/components/home/RankingPreview.jsx index b894487..c7fcfd0 100644 --- a/src/components/home/RankingPreview.jsx +++ b/src/components/home/RankingPreview.jsx @@ -1,5 +1,33 @@ +import { useEffect, useState } from "react"; +import { getMyRankingAPI, getRankingListAPI } from "services/home/ranking"; import "styles/components/home/RankingPreview.scss"; -export const RankingPreview = ({ rankingData }) => { +export const RankingPreview = () => { + const [rankingData, setRankingData] = useState([]); + const [myScore, setMyScore] = useState(null); + + useEffect(() => { + const fetchRanking = async () => { + try { + // ๐Ÿ”น ๋‚ด ๋žญํ‚น ๋จผ์ € ๊ฐ€์ ธ์˜ค๊ธฐ + const myRes = await getMyRankingAPI(); + const me = myRes?.data || myRes; + + if (me) { + setMyScore(me.exp); + } + + // ๐Ÿ”น ์ „์ฒด ๋žญํ‚น ๊ฐ€์ ธ์˜ค๊ธฐ + const listRes = await getRankingListAPI(); + const list = listRes?.data || listRes || []; + setRankingData(list); + } catch (e) { + console.error("๋žญํ‚น ํ”„๋ฆฌ๋ทฐ ์กฐํšŒ ์‹คํŒจ:", e); + } + }; + + fetchRanking(); + }, []); + console.log(rankingData); return (

๋žญํ‚น

@@ -8,10 +36,10 @@ export const RankingPreview = ({ rankingData }) => { rankingData.slice(0, 4).map((item, index) => (
  • - {index + 1}. {item.username} + {item.rank}. {item.nickname}
    - {item.score} ์  + {item.exp} ์ 
  • )) ) : ( @@ -24,7 +52,7 @@ export const RankingPreview = ({ rankingData }) => {
    - 9ssssss ์  + {myScore ?? 0} ์ 
    diff --git a/src/components/home/SideRankingList.jsx b/src/components/home/SideRankingList.jsx new file mode 100644 index 0000000..bfb9435 --- /dev/null +++ b/src/components/home/SideRankingList.jsx @@ -0,0 +1,27 @@ +import "styles/components/home/SideRankingList.scss"; + +const SideRankingList = ({ title, list, highlightRank }) => { + return ( +
    +

    {title}

    + +
    + {list.map((item) => ( +
    + {item.rank}. + {item.nickname} + + {item.exp}์  +
    + ))} +
    +
    + ); +}; + +export default SideRankingList; diff --git a/src/constants/api.js b/src/constants/api.js index 7d65ff2..56be9cc 100644 --- a/src/constants/api.js +++ b/src/constants/api.js @@ -75,4 +75,7 @@ export const SERVICE_LIST_API = process.env.REACT_APP_SERVICE_LIST_API; export const SERVICE_MY_LIST_API = process.env.REACT_APP_SERVICE_MY_LIST_API; export const SERVICE_QNA_API = process.env.REACT_APP_SERVICE_QNA; +export const RANKING_API = process.env.REACT_APP_RANKING_API; +export const RANkING_MY_API = process.env.REACT_APP_MY_RANKING_API; + export const AI_SERVER_IP = process.env.REACT_APP_AI_SERVER_IP; \ No newline at end of file diff --git a/src/pages/home/RankingPage.jsx b/src/pages/home/RankingPage.jsx new file mode 100644 index 0000000..81b4aa8 --- /dev/null +++ b/src/pages/home/RankingPage.jsx @@ -0,0 +1,89 @@ +import SideRankingList from "components/home/SideRankingList"; +import { useEffect, useState } from "react"; +import { getMyRankingAPI, getRankingListAPI } from "services/home/ranking"; +import "styles/pages/home/RankingPage.scss"; + +const RankingPage = () => { + const [rankingList, setRankingList] = useState([]); + const [myRank, setMyRank] = useState(null); + const [myScore, setMyScore] = useState(null); + const [topList, setTopList] = useState([]); + const [nearList, setNearList] = useState([]); + + useEffect(() => { + const fetchRanking = async () => { + try { + // โ‘ก ๋‚ด ๋žญํ‚น ์กฐํšŒ + const myRes = await getMyRankingAPI(); + const me = myRes?.data || myRes; + + if (!me) return; + + // ์ „์ฒด ๋žญํ‚น ์กฐํšŒ (7๋ช…๊นŒ์ง€) + const fullRes = await getRankingListAPI(); + const list = fullRes?.data || fullRes || []; + setRankingList(list); + setTopList(list.slice(0, 7)); + + setMyRank(me.rank); + setMyScore(me.exp); + + // ๋‚ด ๊ทผ์ฒ˜ ๋žญํ‚น ๊ณ„์‚ฐ (์œ„ ์•„๋ž˜ 3๋ช…) + const myIndex = list.findIndex((item) => item.rank === me.rank); + const start = Math.max(myIndex - 3, 0); + const end = Math.min(myIndex + 4, list.length); + setNearList(list.slice(start, end)); + } catch (err) { + console.error("๋žญํ‚น ์กฐํšŒ ์‹คํŒจ:", err); + } + }; + + fetchRanking(); + }, []); + + // ๋ฐ”๋กœ ์œ„ ๋“ฑ์ˆ˜์™€์˜ ์ ์ˆ˜ ์ฐจ์ด + const diffScore = + myRank > 1 && rankingList.length >= myRank - 1 + ? rankingList[myRank - 2].exp - myScore + : 0; + + return ( +
    +
    + + +
    +
    ๋‚ด ๋“ฑ์ˆ˜
    +
    + {myRank ?? "-"} + ๋“ฑ +
    + +
    + ๋‚ด ์ ์ˆ˜ + + {myScore ?? 0} ์  +
    +
    + + +
    +
    + {myRank === 1 ? ( +

    ํ˜„์žฌ 1๋“ฑ์ž…๋‹ˆ๋‹ค!

    + ) : ( +

    + {diffScore}์ ๋งŒ ๋” ๋†’์œผ๋ฉด + ๋“ฑ์ˆ˜๋ฅผ ์•ž์ง€๋ฅผ ์ˆ˜ ์žˆ์–ด์š”! +

    + )} +
    +
    + ); +}; + +export default RankingPage; diff --git a/src/routes/home.js b/src/routes/home.js index af61342..fd8f379 100644 --- a/src/routes/home.js +++ b/src/routes/home.js @@ -9,6 +9,7 @@ import ServiceFAQPage from "pages/home/ServiceFAQPage"; import ServiceCenterDetailPage from "pages/home/ServiceCenterDetailPage"; import ServicePostPage from "pages/home/ServicePostPage"; import FAQDetailPage from "pages/home/FAQDetailPage"; +import RankingPage from "pages/home/RankingPage"; const home = [ { path: "/", @@ -19,7 +20,10 @@ const home = [ index: true, element: , }, - + { + path: "ranking", + element: , + }, { path: "character-intro", element: , @@ -55,7 +59,7 @@ const home = [ { path: "service-center/post", element: - } + }, ], }, ]; diff --git a/src/services/home/ranking.js b/src/services/home/ranking.js new file mode 100644 index 0000000..3f695ce --- /dev/null +++ b/src/services/home/ranking.js @@ -0,0 +1,10 @@ +import { RANKING_API, RANkING_MY_API } from "constants/api"; +import { apiInterface } from "services/axiosForm"; + +export const getRankingListAPI = async (params = {}) => { + return await apiInterface("get", RANKING_API, {}, params, false); +}; + +export const getMyRankingAPI = async () => { + return await apiInterface("get", RANkING_MY_API, {}, {}, true); +}; \ No newline at end of file diff --git a/src/styles/components/home/SideRankingList.scss b/src/styles/components/home/SideRankingList.scss new file mode 100644 index 0000000..eac852e --- /dev/null +++ b/src/styles/components/home/SideRankingList.scss @@ -0,0 +1,52 @@ +.side-ranking { + width: 302px; + + .side-title { + font-size: 20px; + font-weight: 400; + margin-bottom: 35px; + } + + .side-list { + display: flex; + flex-direction: column; + gap: 16.47px; + font-family: SUIT-Regular; + + .side-item { + display: flex; + font-size: 21.08px; + padding-left: 5px; + padding-right: 5px; + align-items: center; + + line-height: 29.65px; + + &.highlight { + background: #FFDA37; + } + + .rank { + padding-right: 10px; + } + + .name { + padding-right: 5px; + + } + + .dashed-line { + flex: 1; + border-bottom: 2px dashed #D7CCCC; + } + + .score { + width: 60px; + font-size: 16px; + text-align: right; + } + } + } + + +} \ No newline at end of file diff --git a/src/styles/pages/home/RankingPage.scss b/src/styles/pages/home/RankingPage.scss new file mode 100644 index 0000000..1ced9e8 --- /dev/null +++ b/src/styles/pages/home/RankingPage.scss @@ -0,0 +1,117 @@ +.ranking-page-wrapper { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + min-height: 100vh; + + .ranking-page { + width: 1187px; + display: flex; + justify-content: space-between; + padding-top: 222px; + font-family: YoonChild; + + .ranking-center { + position: relative; + margin-top: 90px; + width: 476px; + + .rank-label { + font-size: 20px; + } + + .my-rank { + display: flex; + align-items: flex-end; + justify-content: flex-end; + gap: 6px; + + .rank-number { + font-family: SUIT-Regular; + font-size: 128px; + font-weight: 600; + } + + .rank-unit { + font-family: SUIT-Regular; + font-size: 32px; + margin-bottom: 20px; + margin-left: 21px; + } + } + + /* ์ ์ˆ˜ ๋ผ์ธ */ + .score-row { + margin-top: 60px; + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + + .score-label { + font-size: 20px; + } + + .dashed-line { + flex: 1; + border-bottom: 3px dashed #F6AA30; + } + + .my-score { + position: relative; + display: flex; + align-items: center; + justify-content: center; + + width: 152px; + height: 40px; + + font-family: SUIT-Regular; + font-size: 32px; + font-weight: 700; + + background: #FFDA37; + + &::before { + content: ""; + position: absolute; + left: 0; + top: 0; + width: 9px; + height: 100%; + background: #FF9F03 + } + } + } + } + + } + + + .bottom-banner { + margin-top: 50px; + width: 100%; + height: 66px; + font-family: YoonChild-ManSeh; + + background: #E9FF9F; + box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.15); + + display: flex; + align-items: center; + justify-content: center; + + p { + font-size: 16px; + display: flex; + align-items: center; + justify-content: center; + } + + .banner-score { + font-size: 32px; + font-weight: 500px; + } + } +} \ No newline at end of file