Skip to content

Commit e4dca2e

Browse files
committed
feat: 레벨업 팝업 컴포넌트 추가 및 관련 이미지 파일 추가
1 parent f016c62 commit e4dca2e

File tree

6 files changed

+115
-1
lines changed

6 files changed

+115
-1
lines changed

src/assets/images/ic_highlight.png

298 KB
Loading
27.8 KB
Loading
443 KB
Loading

src/assets/images/text_levelup.png

47.8 KB
Loading

src/routes/session/$sessionId/index.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Timer } from "@/components/timer/Timer";
1313
import { getUserSession } from "@/generated/user-session/user-session";
1414
import { getHeaderToken } from "@/utils/cookie";
1515
import { getLevelInfo } from "@/utils/level";
16+
import { LevelUpPopup } from "../-components/level-up-popup";
1617
import { SessionLevelProgress } from "../-components/session-level-progress";
1718

1819
function formatDateToKorean(dateString?: string): string {
@@ -67,6 +68,10 @@ function RouteComponent() {
6768
prevLevelInfo.levelExp
6869
);
6970

71+
// 레벨업 팝업 상태 관리
72+
const [isLevelUpPopupOpen, setIsLevelUpPopupOpen] = useState(false);
73+
const [newLevelForPopup, setNewLevelForPopup] = useState(1);
74+
7075
// 세션 정보가 바뀌면 초기 표시 상태를 이전 점수 기준으로 재설정
7176
useEffect(() => {
7277
setDisplayLevel(prevLevelInfo.displayLevel);
@@ -87,6 +92,17 @@ function RouteComponent() {
8792
}, 500);
8893
};
8994

95+
// 레벨업 발생 시 팝업 표시
96+
const handleLevelUp = () => {
97+
setNewLevelForPopup(nextLevelInfo.displayLevel);
98+
setIsLevelUpPopupOpen(true);
99+
};
100+
101+
// 레벨업 팝업 닫기
102+
const handleCloseLevelUpPopup = () => {
103+
setIsLevelUpPopupOpen(false);
104+
};
105+
90106
// 로딩 상태 처리
91107
if (isLoading) {
92108
return (
@@ -194,7 +210,7 @@ function RouteComponent() {
194210
currentExp={displayCurrentExp}
195211
levelExp={displayLevelExp}
196212
progressWrapperClassName="w-full"
197-
onLevelUp={() => {}}
213+
onLevelUp={handleLevelUp}
198214
/>
199215
</div>
200216
</div>
@@ -229,6 +245,13 @@ function RouteComponent() {
229245
<Link to="/my">완등 영상 보기</Link>
230246
</Button>
231247
</div>
248+
249+
{/* 레벨업 팝업 */}
250+
<LevelUpPopup
251+
isOpen={isLevelUpPopupOpen}
252+
onClose={handleCloseLevelUpPopup}
253+
newLevel={newLevelForPopup}
254+
/>
232255
</div>
233256
);
234257
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { motion } from "motion/react";
2+
import { Dialog } from "radix-ui";
3+
import { useEffect } from "react";
4+
import assetHighlight from "@/assets/images/ic_highlight.png";
5+
import assetBadge from "@/assets/images/ic_levelup_badge.png";
6+
import assetGradient from "@/assets/images/ic_levelup_gr.png";
7+
import assetTextLevelup from "@/assets/images/text_levelup.png";
8+
9+
interface LevelUpPopupProps {
10+
isOpen: boolean;
11+
onClose: () => void;
12+
newLevel: number;
13+
}
14+
15+
export const LevelUpPopup = ({ isOpen, onClose }: LevelUpPopupProps) => {
16+
// 3초 후 자동으로 닫기
17+
useEffect(() => {
18+
if (isOpen) {
19+
const timer = setTimeout(() => {
20+
onClose();
21+
}, 3000);
22+
23+
return () => clearTimeout(timer);
24+
}
25+
}, [isOpen, onClose]);
26+
27+
return (
28+
<Dialog.Root open={isOpen} onOpenChange={onClose}>
29+
<Dialog.Portal>
30+
<Dialog.Content className="fixed inset-0 z-50 bg-neutral-800">
31+
{/* 레벨업 텍스트와 뱃지 */}
32+
{/* 배경 그라데이션 */}
33+
<div className="absolute inset-0 flex flex-center">
34+
<motion.img
35+
className="absolute object-cover w-full h-full top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
36+
src={assetGradient}
37+
alt="백그라운드 그라데이션"
38+
/>
39+
{/* 하이라이트 - 360도 회전 */}
40+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] h-[500px]">
41+
<motion.img
42+
className="absolute object-cover will-change-transform w-full h-full"
43+
src={assetHighlight}
44+
alt="하이라이트"
45+
animate={{
46+
rotate: 360,
47+
}}
48+
transition={{
49+
duration: 8,
50+
repeat: Infinity,
51+
ease: "linear",
52+
}}
53+
/>
54+
</div>
55+
</div>
56+
57+
<div className="absolute top-[calc(50%-66px)] left-1/2 -translate-x-1/2 -translate-y-[calc(50%-33px)] flex flex-col gap-4 items-center justify-center">
58+
{/* 레벨업 텍스트 - 위로 바운스 */}
59+
<motion.img
60+
src={assetTextLevelup}
61+
alt="레벨업"
62+
initial={{ y: 50, opacity: 0 }}
63+
animate={{ y: 0, opacity: 1 }}
64+
transition={{
65+
duration: 0.6,
66+
type: "spring",
67+
bounce: 0.6,
68+
delay: 0.3,
69+
}}
70+
/>
71+
72+
{/* 뱃지 */}
73+
<motion.img
74+
className="w-[130px] h-[140px] z-10"
75+
src={assetBadge}
76+
alt="뱃지"
77+
initial={{ scale: 0, opacity: 0 }}
78+
animate={{ scale: 1, opacity: 1 }}
79+
transition={{
80+
duration: 0.5,
81+
type: "spring",
82+
bounce: 0.4,
83+
delay: 0.6,
84+
}}
85+
/>
86+
</div>
87+
</Dialog.Content>
88+
</Dialog.Portal>
89+
</Dialog.Root>
90+
);
91+
};

0 commit comments

Comments
 (0)