From afbec94db4d030b0a309cd4ea964c451e2c3edb2 Mon Sep 17 00:00:00 2001 From: baeksunghyun Date: Fri, 7 Mar 2025 11:52:42 +0900 Subject: [PATCH] =?UTF-8?q?Fix:=20Pet=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Profile.jsx | 199 +++++++++++++++++++++----- src/pages/profile.css | 316 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 433 insertions(+), 82 deletions(-) diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx index a4561f3..ca113fa 100644 --- a/src/pages/Profile.jsx +++ b/src/pages/Profile.jsx @@ -1,14 +1,19 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import SockJS from "sockjs-client"; import Stomp from "stompjs"; const Profile = ({ userInfo }) => { - const [seasonCommitCount, setSeasonCommitCount] = useState(userInfo.seasonCommitCount); - const [petExp, setPetExp] = useState(userInfo.petExp); + const [seasonCommitCount, setSeasonCommitCount] = useState(userInfo.seasonCommitCount || 0); + const [petExp, setPetExp] = useState(userInfo.petExp || 0); const [commitCount, setCommitCount] = useState(0); const [client, setClient] = useState(null); // WebSocket 클라이언트 상태 + const [lastRefreshTime, setLastRefreshTime] = useState(new Date()); + const [isRefreshing, setIsRefreshing] = useState(false); + const refreshTimerRef = useRef(null); const maxExp = userInfo.petGrow === "EGG" ? 150 : userInfo.petGrow === "HATCH" ? 300 : 300; + + // 티어 아이콘 매핑 const tierEmojis = { SEED: "🌱", SPROUT: "🌿", @@ -17,15 +22,83 @@ const Profile = ({ userInfo }) => { TREE: "🌳", }; + // 펫 성장 단계 한글화 + const petGrowthStages = { + EGG: "알", + HATCH: "부화", + BABY: "아기", + ADULT: "성체", + }; + // 경험치 바 계산 const progress = (petExp / maxExp) * 100; + // 프로필 정보 자동 새로고침 함수 + const refreshProfileData = async () => { + setIsRefreshing(true); + try { + // 실제로는 여기서 API 호출을 통해 최신 데이터를 가져옵니다 + const response = await fetch("/api/user/info", { + credentials: 'include', + headers: { + 'Cache-Control': 'no-cache' + } + }); + + if (response.ok) { + const data = await response.json(); + // 새로운 데이터로 상태 업데이트 + setSeasonCommitCount(data.data.seasonCommitCount || 0); + setPetExp(data.data.petExp || 0); + // 마지막 갱신 시간 업데이트 + setLastRefreshTime(new Date()); + } + } catch (error) { + console.error("프로필 데이터 새로고침 오류:", error); + } finally { + setIsRefreshing(false); + } + }; + + // 자동 새로고침 설정 (30초마다) + useEffect(() => { + refreshTimerRef.current = setInterval(refreshProfileData, 30000); + + // 컴포넌트 언마운트 시 타이머 정리 + return () => { + if (refreshTimerRef.current) { + clearInterval(refreshTimerRef.current); + } + }; + }, []); + // userInfo가 변경되면 값 업데이트 useEffect(() => { - setSeasonCommitCount(userInfo.seasonCommitCount); - setPetExp(userInfo.petExp); + setSeasonCommitCount(userInfo.seasonCommitCount || 0); + setPetExp(userInfo.petExp || 0); + setLastRefreshTime(new Date()); }, [userInfo]); + // 날짜 포맷팅 함수 + const formatDate = (dateString) => { + if (!dateString) return "없음"; + const date = new Date(dateString); + return date.toLocaleDateString('ko-KR', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + }; + + // 새로고침 시간 포맷팅 함수 + const formatRefreshTime = (date) => { + return date.toLocaleTimeString('ko-KR', { + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }); + }; + useEffect(() => { // userInfo.username이 없으면 WebSocket 연결 안 함 if (!userInfo.username) { @@ -75,46 +148,100 @@ const Profile = ({ userInfo }) => { }, [userInfo.username]); // username이 변경될 때마다 WebSocket 연결 return ( -
-
- {/* 왼쪽: 펫 이미지 */} -
- Pet +
+ {/* 왼쪽: 펫 이미지 */} +
+
+ Pet +
+
+ {petGrowthStages[userInfo.petGrow] || userInfo.petGrow} +
+ {isRefreshing &&
} +
+ + {/* 오른쪽: 사용자 정보 및 펫 정보 */} +
+ {/* 유저 이름과 아바타 */} +
+
+ User Avatar +
+ {userInfo.username || "사용자"}
- {/* 오른쪽: 사용자 정보 및 펫 정보 */} -
-
- User Avatar {userInfo.username} + {/* 유저 세부 정보 */} +
+
+ 📊 + + 이번 시즌 커밋: {seasonCommitCount} + +
+ +
+ 🔄 + + 업데이트 커밋: {commitCount} +
-
이번 시즌 커밋 수: {seasonCommitCount}
-
업데이트 커밋 수: {commitCount}
-
티어: {tierEmojis[userInfo.tier] || userInfo.tier} / 마지막 커밋 날짜: {new Date(userInfo.lastCommitted).toLocaleDateString()}
- - {/* 펫 정보 */} -
🐾 펫 정보
-
-
-
-
-
+ +
+ 🏆 + + 티어: + {tierEmojis[userInfo.tier] || ""} {userInfo.tier || "없음"} + + +
+ +
+ 📅 + + 마지막 커밋: {formatDate(userInfo.lastCommitted)} + +
+
+ + {/* 펫 정보 */} +
+
+ 🐾 펫 정보 +
+ +
+
+
-
+ {petExp} / {maxExp} -
+ +
+ + {/* 새로고침 버튼 및 마지막 새로고침 시간 표시 */} +
+ + + {isRefreshing ? '새로고침 중...' : `마지막 업데이트: ${formatRefreshTime(lastRefreshTime)}`} +
-
성장 단계: {userInfo.petGrow}
); }; -export default Profile; +export default Profile; \ No newline at end of file diff --git a/src/pages/profile.css b/src/pages/profile.css index 6ef84a6..30251bb 100644 --- a/src/pages/profile.css +++ b/src/pages/profile.css @@ -1,74 +1,298 @@ -@keyframes breathe { - 0%, 100% { - transform: scaleY(1); /* 원래 크기 */ - } - 50% { - transform: scaleY(0.8); /* 0.8배로 작아짐 */ - } -} - -h2 { - color: #222222; +/* Enhanced Profile Styles */ +.profile-container { + width: 100%; + background-color: white; + border-radius: 12px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + padding: 24px; + margin-bottom: 32px; + box-sizing: border-box; + display: flex; + align-items: center; + gap: 24px; + transition: all 0.3s ease; } - -.animated-pet { - animation: breathe 1.5s infinite ease-in-out; +.profile-container:hover { + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); + transform: translateY(-2px); } -.flex-box{ - width:100%; - display: flex; - justify-content: center; +/* Pet section styling */ +.pet-section { + position: relative; + flex-shrink: 0; } -.profile-container { +.pet-frame { + width: 140px; + height: 140px; + background: linear-gradient(135deg, #f0f9ff 0%, #e1f5fe 100%); + border-radius: 70px; + padding: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); display: flex; + justify-content: center; align-items: center; - gap: 20px; - padding: 15px; - width: 750px; - height: 140px; - background-color: #fefefe; - color: #010101; - border: 1px solid #fff; - border-radius: 10px; - line-height: 1.4; + position: relative; + overflow: hidden; } -.pet-box { - width: 128px; - height: 128px; - border: 1px solid #ff8a9e; - background-image: url('/pets/BACK.png'); +.pet-frame::before { + content: ''; + position: absolute; + width: 100%; + height: 100%; + background: linear-gradient(135deg, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0) 50%); + border-radius: 70px; + z-index: 1; } -.pet-box img { + +.pet-image { width: 128px; height: 128px; + object-fit: contain; + z-index: 2; +} + +.pet-stage { + position: absolute; + bottom: -5px; + right: -5px; + background-color: #ff6b6b; + color: white; + font-weight: bold; + font-size: 14px; + padding: 6px 10px; + border-radius: 20px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + z-index: 3; } -.info-box { +/* User info section styling */ +.user-info { flex: 1; + display: flex; + flex-direction: column; + gap: 12px; } -.info-box img { - width: 32px; - height: 32px; +.user-header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 8px; } -.avatar { - width: 100px; + +.avatar-container { + width: 50px; + height: 50px; border-radius: 50%; + overflow: hidden; + border: 3px solid #f0f0f0; + box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1); +} + +.avatar-container img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.username { + font-size: 20px; + font-weight: 700; + color: #1f2937; +} + +.user-details { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; +} + +.detail-item { + display: flex; + align-items: center; + gap: 8px; +} + +.detail-icon { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + color: #6b7280; +} + +.detail-text { + font-size: 14px; + color: #4b5563; +} + +.detail-value { + font-weight: 600; + color: #111827; +} + +/* Pet info section */ +.pet-info { + margin-top: 8px; + padding-top: 12px; + border-top: 1px solid #f3f4f6; +} + +.pet-title { + display: flex; + align-items: center; + gap: 6px; + font-size: 16px; + font-weight: 600; + color: #4b5563; + margin-bottom: 8px; +} + +.exp-bar-container { + display: flex; + align-items: center; + gap: 12px; + margin-top: 8px; } .exp-bar { + flex: 1; + height: 8px; + background-color: #f3f4f6; + border-radius: 4px; + overflow: hidden; + position: relative; +} + +.exp-progress { + height: 100%; + background: linear-gradient(to right, #3fb27f, #4ade80); + border-radius: 4px; + transition: width 0.5s ease; +} + +.exp-text { + font-size: 14px; + font-weight: 600; + color: #6b7280; + min-width: 70px; + text-align: right; +} + +/* Tier badge */ +.tier-badge { + display: inline-flex; + align-items: center; + background-color: #f3f4f6; + padding: 4px 10px; + border-radius: 16px; + font-size: 14px; + font-weight: 600; +} + +/* Animation for pet */ +@keyframes float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-5px); + } +} + +@keyframes rotate { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@keyframes pulse { + 0% { + transform: scale(1); + opacity: 0.7; + } + 50% { + transform: scale(1.05); + opacity: 1; + } + 100% { + transform: scale(1); + opacity: 0.7; + } +} + +.animated-pet { + animation: float 3s ease-in-out infinite; +} + +.animated-pet.refreshing { + animation: pulse 1s ease-in-out infinite; +} + +/* 새로고침 관련 스타일 */ +.refresh-info { + display: flex; + align-items: center; + gap: 8px; + margin-top: 8px; + font-size: 12px; + color: #9ca3af; +} + +.refresh-button { + background: none; + border: none; + cursor: pointer; + padding: 4px; + border-radius: 50%; display: flex; align-items: center; - gap: 10px; + justify-content: center; + transition: all 0.2s ease; +} + +.refresh-button:hover { + background-color: #f3f4f6; +} + +.refresh-button:disabled { + cursor: not-allowed; + opacity: 0.7; } -.bar { - width: 200px; - display: flex; - align-items: center; - height: 30px; +.refresh-icon { + font-size: 14px; + display: inline-block; } + +.refresh-icon.rotating { + animation: rotate 1s linear infinite; +} + +.last-refresh-time { + font-size: 11px; + color: #9ca3af; +} + +/* 새로고침 진행 중 표시 */ +.refreshing-indicator { + position: absolute; + top: -5px; + right: -5px; + width: 14px; + height: 14px; + border-radius: 50%; + background-color: #3b82f6; + box-shadow: 0 0 0 2px white; + z-index: 10; + animation: pulse 1s infinite ease-in-out; +} \ No newline at end of file