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
112 changes: 69 additions & 43 deletions src/pages/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import './profile.css';
import '../modals/NotificationModal.css';
import axios from "axios";
import NotiService from '../services/NotiService';
import SockJS from "sockjs-client";
import Stomp from "stompjs";
import webSocketNotificationService from '../services/WebSocketNotificationService';

const Home = () => {
Expand All @@ -34,6 +36,7 @@ const Home = () => {
});
const [connected, setConnected] = useState(false); // 웹소켓 연결 상태
const navigate = useNavigate();
const [client, setClient] = useState(null); // WebSocket 클라이언트 상태


// 사용자 정보 불러오기
Expand All @@ -54,7 +57,8 @@ const Home = () => {
}, []);

const toggleModal = async () => {
setIsModalOpen(!isModalOpen);
// 읽은 알림들을 필터링하여 제거
setNotifications(prev => prev.filter(noti => !noti.read));
if (!isModalOpen) { // 모달이 열릴 때 (false -> true)
try {
// 읽지 않은 알림만 필터링
Expand All @@ -69,12 +73,14 @@ const Home = () => {
? { ...noti, read: true }
: noti
));

setHasNewNotification(false); // 모든 알림을 읽음으로 표시했으므로 빨간 점 제거
}
} catch (error) {
console.error('Failed to mark notifications as read:', error);
}
}
setIsModalOpen(!isModalOpen);
};

// 메시지 목록을 로드하는 함수
Expand All @@ -98,53 +104,73 @@ const Home = () => {
};

useEffect(() => {
const setupWebSocket = async () => {
try {
await webSocketNotificationService.connect();

// 연결 상태 변경 이벤트 리스너 등록
const unsubscribeFromConnection = webSocketNotificationService.onConnectionChange((isConnected) => {
console.log('WebSocket connection status:', isConnected);
setConnected(isConnected);
// 연결되면 바로 구독 시도
if (isConnected) {
const success = webSocketNotificationService.subscribeToNotificationChannel();
console.log('Notification subscription success:', success);
}
});
// userInfo.username이 없으면 WebSocket 연결 안 함
if (!userInfo.username) {
console.log("Username is not available yet, waiting...");
return;
}

// 초기 알림 데이터 로드
loadNotis();

// 기존 WebSocket 연결이 있으면 끊기
if (client) {
client.disconnect(() => {
console.log("Previous WebSocket disconnected.");
});
}

// 새로운 알림 메시지 핸들러 등록
const unsubscribeFromMessages = webSocketNotificationService.onMessage((newNotification) => {
console.log('New notification received:', newNotification);

// 새로운 알림을 상태에 추가
setNotifications(prev => [{
...newNotification,
read: false
}, ...prev]);

// 새 알림 표시
setHasNewNotification(true);
// 새로운 WebSocket 연결
const socket = new SockJS(`${API_BACKEND_URL}/ws`);
const newClient = Stomp.over(socket);

newClient.connect({}, () => {
console.log("WebSocket connected!");

// 커밋 수 업데이트 메시지 수신
newClient.subscribe(`/topic/notifications/${userInfo.username}`, (message) => {
const notifications = JSON.parse(message.body);

// 배열인 경우 처리
if (Array.isArray(notifications)) {
// 각 알림을 상태에 추가
notifications.forEach(notification => {
setNotifications(prev => [{
id: notification.id,
message: notification.message,
createdAt: notification.formattedCreatedAt,
read: false
}, ...prev]);
});
} else {
// 단일 알림인 경우
setNotifications(prev => [{
id: notifications.id,
message: notifications.message,
createdAt: notifications.formattedCreatedAt,
read: false
}, ...prev]);
}

// 새 알림 표시
setHasNewNotification(true);
});
}, (error) => {
console.error("WebSocket error:", error);
});

// WebSocket 클라이언트 저장
setClient(newClient);

// 초기 알림 데이터 로드
await loadNotis();

// 컴포넌트 언마운트 시 정리
return () => {
unsubscribeFromConnection();
unsubscribeFromMessages();
webSocketNotificationService.disconnect();
};
} catch (error) {
console.error('Error setting up WebSocket:', error);
return () => {
if (newClient) {
newClient.disconnect(() => {
console.log("WebSocket disconnected.");
});
}
};
};
}, [userInfo.username]); // username이 변경될 때마다 WebSocket 연결

setupWebSocket();
}, []);

useEffect(() => {
const fetchCommitData = async () => {
try {
Expand Down
53 changes: 26 additions & 27 deletions src/pages/Profile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const Profile = ({ userInfo }) => {
const refreshTimerRef = useRef(null);

const maxExp = userInfo.petGrow === "EGG" ? 150 : userInfo.petGrow === "HATCH" ? 300 : 300;

// 티어 아이콘 매핑
const tierEmojis = {
SEED: "🫘",
Expand All @@ -38,13 +38,13 @@ const Profile = ({ userInfo }) => {
setIsRefreshing(true);
try {
// 실제로는 여기서 API 호출을 통해 최신 데이터를 가져옵니다
const response = await fetch("/api/user/info", {
const response = await fetch("/api/user/info", {
credentials: 'include',
headers: {
'Cache-Control': 'no-cache'
}
});

if (response.ok) {
const data = await response.json();
// 새로운 데이터로 상태 업데이트
Expand All @@ -63,7 +63,7 @@ const Profile = ({ userInfo }) => {
// 자동 새로고침 설정 (30초마다)
useEffect(() => {
refreshTimerRef.current = setInterval(refreshProfileData, 30000);

// 컴포넌트 언마운트 시 타이머 정리
return () => {
if (refreshTimerRef.current) {
Expand All @@ -73,23 +73,22 @@ const Profile = ({ userInfo }) => {
}, []);

// userInfo가 변경되면 값 업데이트
useEffect(() => {
setSeasonCommitCount(userInfo.seasonCommitCount || 0);
setPetExp(userInfo.petExp || 0);
setLastRefreshTime(new Date());
}, [userInfo]);
useEffect(() => {
setSeasonCommitCount((prev) => prev || userInfo.seasonCommitCount);
setPetExp((prev) => prev || userInfo.petExp);
}, [userInfo]);

// 날짜 포맷팅 함수
const formatDate = (dateString) => {
if (!dateString) return "없음";
const date = new Date(dateString);
return date.toLocaleDateString('ko-KR', {
year: 'numeric',
month: 'long',
day: 'numeric'
return date.toLocaleDateString('ko-KR', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};

// 새로고침 시간 포맷팅 함수
const formatRefreshTime = (date) => {
return date.toLocaleTimeString('ko-KR', {
Expand Down Expand Up @@ -152,10 +151,10 @@ const Profile = ({ userInfo }) => {
{/* 왼쪽: 펫 이미지 */}
<div className="pet-section">
<div className="pet-frame">
<img
<img
src={`/pets/${userInfo.petGrow}_${userInfo.petType}_128.png`}
alt="Pet"
className={`pet-image animated-pet ${isRefreshing ? 'refreshing' : ''}`}
alt="Pet"
className={`pet-image animated-pet ${isRefreshing ? 'refreshing' : ''}`}
/>
</div>
<div className="pet-stage">
Expand All @@ -171,7 +170,7 @@ const Profile = ({ userInfo }) => {
<div className="avatar-container">
<img src={userInfo.avatarUrl} alt="User Avatar" />
</div>
<span className="username">{userInfo.username || "사용자"}</span>
z<span className="username">{userInfo.username || "사용자"}</span>
</div>

{/* 유저 세부 정보 */}
Expand All @@ -182,14 +181,14 @@ const Profile = ({ userInfo }) => {
이번 시즌 커밋: <span className="detail-value">{seasonCommitCount}</span>
</span>
</div>

<div className="detail-item">
<span className="detail-icon">🔄</span>
<span className="detail-text">
업데이트 커밋: <span className="detail-value">{commitCount}</span>
</span>
</div>

<div className="detail-item">
<span className="detail-icon">🏆</span>
<span className="detail-text">
Expand All @@ -198,7 +197,7 @@ const Profile = ({ userInfo }) => {
</span>
</span>
</div>

<div className="detail-item">
<span className="detail-icon">📅</span>
<span className="detail-text">
Expand All @@ -212,23 +211,23 @@ const Profile = ({ userInfo }) => {
<div className="pet-title">
<span>🐾</span> 펫 정보
</div>

<div className="exp-bar-container">
<div className="exp-bar">
<div
className="exp-progress"
<div
className="exp-progress"
style={{ width: `${progress}%` }}
></div>
</div>
<span className="exp-text">
{petExp} / {maxExp}
</span>
</div>

{/* 새로고침 버튼 및 마지막 새로고침 시간 표시 */}
<div className="refresh-info">
<button
className="refresh-button"
<button
className="refresh-button"
onClick={refreshProfileData}
disabled={isRefreshing}
>
Expand Down