diff --git a/src/components/notification/NotificationBell.tsx b/src/components/notification/NotificationBell.tsx
new file mode 100644
index 00000000..232f5bbb
--- /dev/null
+++ b/src/components/notification/NotificationBell.tsx
@@ -0,0 +1,56 @@
+import { useState } from "react";
+import { twMerge } from "tailwind-merge";
+
+import { useNotificationHistory } from "@/hooks/notification/useNotificationHistory";
+
+import NotificationList from "@/components/notification/NotificationList";
+import NotificationPanel from "@/components/notification/NotificationPanel";
+
+import BellIcon from "@/assets/icon/sidebar/notification.svg?react";
+
+export default function NotificationBell() {
+ const [isOpen, setIsOpen] = useState(false);
+ const { orgId, notifications, unreadCount, isLoading } =
+ useNotificationHistory();
+
+ // const openPanel = () => setIsOpen(true);
+ const closePanel = () => setIsOpen(false);
+ const togglePanel = () => setIsOpen((prev) => !prev);
+
+ const badgeLabel = unreadCount > 99 ? "99+" : String(unreadCount);
+ const badgeSizeClass =
+ unreadCount > 99 ? "h-4.5 w-6.5" : unreadCount < 10 ? "h-4 w-4" : "h-4 w-5";
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/notification/NotificationItem.tsx b/src/components/notification/NotificationItem.tsx
new file mode 100644
index 00000000..d2fc5a1c
--- /dev/null
+++ b/src/components/notification/NotificationItem.tsx
@@ -0,0 +1,87 @@
+import { twMerge } from "tailwind-merge";
+
+import type {
+ INotificationHistoryItem,
+ TNotificationType,
+} from "@/types/notification/notification";
+
+import TrendDownIcon from "@/assets/icon/chevron/trend-down.svg?react";
+import TrendUpIcon from "@/assets/icon/chevron/trend-up.svg?react";
+
+interface INotificationItemProps {
+ item: INotificationHistoryItem;
+}
+
+function formatNotificationTime(iso: string) {
+ return new Intl.DateTimeFormat("ko-KR", {
+ month: "numeric",
+ day: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ }).format(new Date(iso));
+}
+
+function getClickTrend(item: INotificationHistoryItem): "up" | "down" | null {
+ const text = `${item.title} ${item.message}`;
+
+ if (text.includes("급증") || text.includes("증가")) return "up";
+ if (text.includes("급감") || text.includes("감소")) return "down";
+ if (item.type !== "CLICKS") return null;
+
+ return null;
+}
+
+function getNotificationRowClass(
+ type: TNotificationType,
+ trend: "up" | "down" | null,
+): string {
+ if (type === "REPORT") return "bg-surface-200";
+ if (trend === "up") return "bg-info-red/[0.08]";
+ if (trend === "down") return "bg-info-blue/[0.08]";
+ return "";
+}
+
+export default function NotificationItem({ item }: INotificationItemProps) {
+ const trend = getClickTrend(item);
+ const rowClass = getNotificationRowClass(item.type, trend);
+ return (
+
+
+
+
+
+ {item.title}
+
+ {trend === "up" ? (
+
+ ) : null}
+ {trend === "down" ? (
+
+ ) : null}
+
+
+
{item.message}
+
+ {formatNotificationTime(item.createdAt)}
+
+
+
+ );
+}
diff --git a/src/components/notification/NotificationList.tsx b/src/components/notification/NotificationList.tsx
new file mode 100644
index 00000000..fbc157a6
--- /dev/null
+++ b/src/components/notification/NotificationList.tsx
@@ -0,0 +1,52 @@
+import type { INotificationHistoryItem } from "@/types/notification/notification";
+
+import NotificationItem from "@/components/notification/NotificationItem";
+import NotificationListSkeleton from "@/components/notification/NotificationListSkeleton";
+
+interface INotificationListProps {
+ orgId: number | null;
+ isLoading: boolean;
+ notifications: INotificationHistoryItem[];
+}
+
+export default function NotificationList({
+ orgId,
+ isLoading,
+ notifications,
+}: INotificationListProps) {
+ if (orgId === null) {
+ return (
+
+
+ 워크스페이스를 선택해주세요
+
+
+ 현재 워크스페이스 기준으로 알림을 보여줍니다
+
+
+ );
+ }
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (notifications.length === 0) {
+ return (
+
+
아직 알림이 없어요
+
+ 클릭수 변화나 주간 리포트가 오면
+
여기에 표시됩니다
+
+
+ );
+ }
+ return (
+
+ {notifications.map((item) => (
+
+ ))}
+
+ );
+}
diff --git a/src/components/notification/NotificationListSkeleton.tsx b/src/components/notification/NotificationListSkeleton.tsx
new file mode 100644
index 00000000..e88d93a5
--- /dev/null
+++ b/src/components/notification/NotificationListSkeleton.tsx
@@ -0,0 +1,15 @@
+import { Skeleton } from "../common/skeleton/Skeleton";
+
+export default function NotificationListSkeleton() {
+ return (
+
+ {Array.from({ length: 5 }, (_, index) => (
+ -
+
+
+
+
+ ))}
+
+ );
+}
diff --git a/src/components/notification/NotificationPanel.tsx b/src/components/notification/NotificationPanel.tsx
new file mode 100644
index 00000000..702c6a20
--- /dev/null
+++ b/src/components/notification/NotificationPanel.tsx
@@ -0,0 +1,32 @@
+import type { ReactNode } from "react";
+
+import Drawer from "@/components/common/drawer/Drawer";
+
+import BellIcon from "@/assets/icon/sidebar/notification.svg?react";
+
+interface INotificationPanelProps {
+ isOpen: boolean;
+ onClose: () => void;
+ children: ReactNode;
+}
+
+export default function NotificationPanel({
+ isOpen,
+ onClose,
+ children,
+}: INotificationPanelProps) {
+ return (
+
+ 알림
+
+ }
+ className="max-w-90 h-auto min-h-[min(72vh,560px)] my-4 rounded-l-3xl"
+ >
+ {children}
+
+ );
+}
diff --git a/src/hooks/notification/useNotificationHistory.ts b/src/hooks/notification/useNotificationHistory.ts
new file mode 100644
index 00000000..712d7938
--- /dev/null
+++ b/src/hooks/notification/useNotificationHistory.ts
@@ -0,0 +1,35 @@
+import type { INotificationHistoryData } from "@/types/notification/notification";
+import { MOCK_NOTIFICATION_HISTORY } from "@/types/notification/notification.mock";
+
+import { useCoreQuery } from "@/hooks/customQuery";
+
+import useWorkspaceStore from "@/store/useWorkspaceStore";
+
+const MOCK_LOADING_MS = 400;
+
+//API 연동전 mock데이터 활용을 위함. API함수추가시 삭제 예정
+async function getMockNotificationHistory(): Promise {
+ await new Promise((resolve) => setTimeout(resolve, MOCK_LOADING_MS));
+ return MOCK_NOTIFICATION_HISTORY;
+}
+
+export function useNotificationHistory() {
+ const orgId = useWorkspaceStore((s) => s.selectedOrgId);
+
+ const query = useCoreQuery(
+ ["notification-history", orgId],
+ () => getMockNotificationHistory(),
+ { enabled: orgId != null },
+ );
+
+ const notifications = query.data?.notifications ?? [];
+ const unreadCount = notifications.filter((item) => !item.isRead).length;
+
+ return {
+ orgId,
+ notifications,
+ unreadCount,
+ isLoading: query.isLoading,
+ isError: query.isError,
+ };
+}
diff --git a/src/layout/main/MainLayout.tsx b/src/layout/main/MainLayout.tsx
index 5b850984..618cf32c 100644
--- a/src/layout/main/MainLayout.tsx
+++ b/src/layout/main/MainLayout.tsx
@@ -20,6 +20,7 @@ import {
import { useCoreQuery } from "@/hooks/customQuery";
import OnboardingTour from "@/components/common/OnboardingTour";
+import NotificationBell from "@/components/notification/NotificationBell";
import Sidebar from "@/components/sidebar/Sidebar";
import { getMyInfo } from "@/api/auth/auth";
@@ -274,8 +275,9 @@ export default function MainLayout() {
)}
-
diff --git a/src/types/notification/notification.mock.ts b/src/types/notification/notification.mock.ts
new file mode 100644
index 00000000..0d3b1650
--- /dev/null
+++ b/src/types/notification/notification.mock.ts
@@ -0,0 +1,46 @@
+import type { INotificationHistoryData } from "@/types/notification/notification";
+
+export const MOCK_NOTIFICATION_HISTORY: INotificationHistoryData = {
+ hasNext: false,
+ nextCursor: null,
+ notifications: [
+ {
+ userNotificationId: 4,
+ title: "주간 리포트",
+ message: "이번 주 성과 리포트가 이메일로 발송되었습니다.",
+ createdAt: "2026-08-13T23:00:00.000Z",
+ type: "REPORT",
+ isRead: false,
+ },
+ {
+ userNotificationId: 3,
+ title: "클릭수 급감 알림",
+ message: "오늘 클릭수가 전일 대비 13% 감소했습니다",
+ createdAt: "2026-08-14T04:52:25.364Z",
+ type: "CLICKS",
+ isRead: false,
+ },
+ {
+ userNotificationId: 2,
+ title: "클릭수 급증 알림",
+ message: "오늘 클릭수가 전일 대비 49% 증가했습니다",
+ createdAt: "2026-08-13T11:20:00.000Z",
+ type: "CLICKS",
+ isRead: false,
+ },
+ {
+ userNotificationId: 1,
+ title: "클릭수 급감 알림",
+ message: "오늘 클릭수가 전일 대비 68% 감소했습니다",
+ createdAt: "2026-08-12T08:10:00.000Z",
+ type: "CLICKS",
+ isRead: true,
+ },
+ ],
+};
+
+export const MOCK_NOTIFICATION_HISTORY_EMPTY: INotificationHistoryData = {
+ hasNext: false,
+ nextCursor: null,
+ notifications: [],
+};
diff --git a/src/types/notification/notification.ts b/src/types/notification/notification.ts
new file mode 100644
index 00000000..b3ba8d77
--- /dev/null
+++ b/src/types/notification/notification.ts
@@ -0,0 +1,38 @@
+export interface IMyNotificationSettings {
+ isMasterEnabled: boolean;
+ isBrowserPushEnabled: boolean;
+ isEmailEnabled: boolean;
+ isSlackEnabled: boolean;
+ isSlackConnted: boolean;
+ isDiscordEnabled: boolean;
+ isDiscordConnected: boolean;
+ alertClicks: boolean;
+ alertReport: boolean;
+ orgAlertclicks: boolean;
+ orgAlertReport: boolean;
+}
+
+export interface INotificationMemberSetting {
+ membershipId: number;
+ name: string;
+ email: string;
+ role: string;
+ isReceive: boolean;
+}
+
+export type TNotificationType = "CLICKS" | "REPORT";
+
+export interface INotificationHistoryItem {
+ userNotificationId: number;
+ title: string;
+ message: string;
+ createdAt: string;
+ type: TNotificationType;
+ isRead: boolean;
+}
+
+export interface INotificationHistoryData {
+ hasNext: boolean;
+ nextCursor: string | null;
+ notifications: INotificationHistoryItem[];
+}