diff --git a/src/app/api/my-notifications/[notificationId]/route.ts b/src/app/api/my-notifications/[notificationId]/route.ts
new file mode 100644
index 0000000..71525da
--- /dev/null
+++ b/src/app/api/my-notifications/[notificationId]/route.ts
@@ -0,0 +1,43 @@
+import { ServerErrorResponse } from '@/types/apiErrorResponseType';
+import axios, { AxiosError } from 'axios';
+import { cookies } from 'next/headers';
+import { NextRequest, NextResponse } from 'next/server';
+
+export async function DELETE(req: NextRequest) {
+ const url = new URL(req.url);
+ const segments = url.pathname.split('/');
+ const id = Number(segments.pop());
+
+ if (isNaN(id)) {
+ return NextResponse.json(
+ { message: '유효하지 않은 알림 ID' },
+ { status: 400 },
+ );
+ }
+
+ const cookieStore = await cookies();
+ const accessToken = cookieStore.get('accessToken')?.value;
+
+ if (!accessToken) {
+ return NextResponse.json({ message: '액세스 토큰 없음' }, { status: 401 });
+ }
+
+ try {
+ const res = await axios.delete(
+ `${process.env.NEXT_PUBLIC_API_SERVER_URL}/my-notifications/${id}`,
+ { headers: { Authorization: `Bearer ${accessToken}` } },
+ );
+
+ if (res.status === 204) {
+ return new NextResponse(null, { status: 204 });
+ }
+
+ return NextResponse.json({ message: '삭제 실패' }, { status: res.status });
+ } catch (err) {
+ const error = err as AxiosError
;
+ const message = error.response?.data?.error || '알람 데이터 조회 실패';
+ const status = error.response?.status || 500;
+
+ return NextResponse.json({ error: message }, { status });
+ }
+}
diff --git a/src/app/api/my-notifications/route.ts b/src/app/api/my-notifications/route.ts
new file mode 100644
index 0000000..2ab1add
--- /dev/null
+++ b/src/app/api/my-notifications/route.ts
@@ -0,0 +1,71 @@
+import { ServerErrorResponse } from '@/types/apiErrorResponseType';
+import axios, { AxiosError } from 'axios';
+import { cookies } from 'next/headers';
+import { NextRequest, NextResponse } from 'next/server';
+
+/**
+ * [GET] /api/my-notifications
+ *
+ * 클라이언트로부터 전달받은 액세스 토큰을 기반으로
+ * 사용자 본인의 알림 목록을 백엔드에서 조회하는 API 라우트 핸들러입니다.
+ *
+ * @param {NextRequest} req - Next.js에서 제공하는 요청 객체.
+ * - `searchParams.cursorId` (선택): 커서 기반 페이지네이션을 위한 알림 ID
+ * - `searchParams.size` (선택): 한 번에 가져올 알림 개수 (기본값: 10)
+ *
+ * @returns {Promise} 응답 객체
+ * - 200 OK: 알림 목록(JSON) 반환
+ * - 401 Unauthorized: 액세스 토큰이 없을 경우
+ * - 500 또는 기타 상태: 백엔드 오류 또는 알 수 없는 오류 발생 시
+ *
+ * @example
+ * // 요청 예시
+ * GET /api/my-notifications?cursorId=30&size=10
+ *
+ * // 성공 응답 예시
+ * {
+ * "notifications": [{ id: 31, content: "새 알림", ... }],
+ * "cursorId": 41,
+ * "totalCount": 99
+ * }
+ *
+ * @description
+ * - 이 핸들러는 클라이언트 쿠키에서 accessToken을 추출하여,
+ * 백엔드 `/my-notifications` 엔드포인트에 요청을 보냅니다.
+ * - `cursorId`와 `size`는 쿼리 파라미터로 전달되며, 둘 다 선택입니다.
+ * - 오류 발생 시 적절한 상태 코드 및 메시지를 포함하여 응답합니다.
+ */
+export async function GET(req: NextRequest) {
+ const { searchParams } = new URL(req.url);
+ const rawCursor = searchParams.get('cursorId');
+ const cursorId = rawCursor !== null ? Number(rawCursor) : undefined;
+
+ const size = Number(searchParams.get('size')) || 10;
+
+ const cookieStore = await cookies();
+ const accessToken = cookieStore.get('accessToken')?.value;
+
+ if (!accessToken) {
+ return NextResponse.json({ message: '액세스 토큰 없음' }, { status: 401 });
+ }
+
+ try {
+ const res = await axios.get(
+ `${process.env.NEXT_PUBLIC_API_SERVER_URL}/my-notifications`,
+ {
+ params: cursorId !== undefined ? { cursorId, size } : { size },
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ },
+ );
+
+ return NextResponse.json(res.data);
+ } catch (err) {
+ const error = err as AxiosError;
+ const message = error.response?.data?.error || '알람 데이터 조회 실패';
+ const status = error.response?.status || 500;
+
+ return NextResponse.json({ error: message }, { status });
+ }
+}
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index 666defe..125b230 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -6,22 +6,35 @@ import IconBell from '@assets/svg/bell';
import useUserStore from '@/stores/authStore';
import { useRouter } from 'next/navigation';
import ProfileDropdown from '@/components/ProfileDropdown';
+import useLogout from '@/hooks/useLogout';
+import { toast } from 'sonner';
+import { useState } from 'react';
+import NotificationDropdown from './Notification/NotificationDropdown';
export default function Header() {
const router = useRouter();
const user = useUserStore((state) => state.user);
const setUser = useUserStore((state) => state.setUser);
const isLoggedIn = !!user;
+ const logout = useLogout();
+ const [isOpen, setIsOpen] = useState(false);
+
+ const toggleOpen = () => setIsOpen((prev) => !prev);
// 로그아웃 처리
- const handleLogout = () => {
- setUser(null);
- router.push('/');
+ const handleLogout = async () => {
+ try {
+ await logout();
+ setUser(null);
+ router.push('/');
+ } catch {
+ toast.error('로그아웃 실패');
+ }
};
return (
-
+
{/* 로고 */}
{/* 우측 메뉴 */}
-
+
{isLoggedIn ? (
<>
{/* 알림 아이콘 */}
-