feat(WEB): 헤더 알림 드롭다운 TodayIssue API 연동

- TodayIssue 타입 정의 파일 생성 (src/types/today-issue.ts)
- TodayIssue API 서비스 함수 생성 (src/lib/api/today-issue.ts)
  - getUnreadTodayIssues: 읽지 않은 알림 목록 조회
  - markTodayIssueAsRead: 개별 읽음 처리
  - markAllTodayIssuesAsRead: 전체 읽음 처리
- AuthenticatedLayout 알림 드롭다운 API 연동
  - MOCK_NOTIFICATIONS 제거, 실제 API 연동
  - 30초 폴링으로 알림 데이터 갱신
  - 알림 클릭 시 읽음 처리 + 페이지 이동
  - 모두 읽음 버튼 기능 구현
  - 벨 애니메이션 (읽지 않은 알림 있을 때만)
This commit is contained in:
2026-01-21 21:14:56 +09:00
parent f2b87ddf0a
commit 81a4d6baf1
3 changed files with 380 additions and 85 deletions

View File

@@ -0,0 +1,94 @@
'use server';
/**
* TodayIssue API 서비스
* 헤더 알림 기능을 위한 API 호출 함수
*/
import { apiClient } from './index';
import type {
ApiResponse,
TodayIssueUnreadResponse,
TodayIssueUnreadCountResponse,
TodayIssueMarkAllReadResponse,
} from '@/types/today-issue';
/**
* 읽지 않은 이슈 목록 조회 (헤더 알림용)
* @param limit 조회할 최대 항목 수 (기본 10)
*/
export async function getUnreadTodayIssues(limit: number = 10): Promise<ApiResponse<TodayIssueUnreadResponse>> {
try {
const response = await apiClient.get<ApiResponse<TodayIssueUnreadResponse>>(
'/today-issues/unread',
{ params: { limit: String(limit) } }
);
return response;
} catch (error) {
console.error('[TodayIssue] getUnreadTodayIssues error:', error);
// 에러 시 빈 응답 반환 (UI에서 처리)
return {
success: false,
message: '알림을 불러오는데 실패했습니다.',
data: { items: [], total: 0 },
};
}
}
/**
* 읽지 않은 이슈 개수 조회 (헤더 뱃지용)
*/
export async function getUnreadTodayIssueCount(): Promise<ApiResponse<TodayIssueUnreadCountResponse>> {
try {
const response = await apiClient.get<ApiResponse<TodayIssueUnreadCountResponse>>(
'/today-issues/unread/count'
);
return response;
} catch (error) {
console.error('[TodayIssue] getUnreadTodayIssueCount error:', error);
return {
success: false,
message: '알림 개수를 불러오는데 실패했습니다.',
data: { count: 0 },
};
}
}
/**
* 이슈 읽음 처리
* @param id 이슈 ID
*/
export async function markTodayIssueAsRead(id: number): Promise<ApiResponse<null>> {
try {
const response = await apiClient.post<ApiResponse<null>>(
`/today-issues/${id}/read`
);
return response;
} catch (error) {
console.error('[TodayIssue] markTodayIssueAsRead error:', error);
return {
success: false,
message: '읽음 처리에 실패했습니다.',
data: null,
};
}
}
/**
* 모든 이슈 읽음 처리
*/
export async function markAllTodayIssuesAsRead(): Promise<ApiResponse<TodayIssueMarkAllReadResponse>> {
try {
const response = await apiClient.post<ApiResponse<TodayIssueMarkAllReadResponse>>(
'/today-issues/read-all'
);
return response;
} catch (error) {
console.error('[TodayIssue] markAllTodayIssuesAsRead error:', error);
return {
success: false,
message: '모두 읽음 처리에 실패했습니다.',
data: { count: 0 },
};
}
}