diff --git a/src/App.jsx b/src/App.jsx
index 64d032c..0f7d142 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,6 +1,7 @@
import React, { lazy, Suspense } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import AuthGuard from './components/AuthGuard';
+import NotificationBell from './components/NotificationBell';
import { ThemeProvider, useTheme } from './context/ThemeContext';
const Login = lazy(() => import('./pages/Login'));
@@ -46,6 +47,7 @@ function ThemedRoutes() {
} />
+
);
}
diff --git a/src/components/NotificationBell.jsx b/src/components/NotificationBell.jsx
new file mode 100644
index 0000000..665197f
--- /dev/null
+++ b/src/components/NotificationBell.jsx
@@ -0,0 +1,77 @@
+import React, { useMemo, useState } from 'react';
+import { Bell, X } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
+import { useSite } from '../context/SiteContext';
+
+export default function NotificationBell() {
+ const { t } = useTranslation();
+ const { site } = useSite();
+ const [open, setOpen] = useState(false);
+
+ const notifications = useMemo(() => {
+ if (!Array.isArray(site?.notifications)) return [];
+ return site.notifications
+ .filter((item) => item?.content)
+ .slice(0, 20);
+ }, [site?.notifications]);
+
+ if (notifications.length === 0) return null;
+
+ return (
+ <>
+
+
+ {open && (
+
setOpen(false)}>
+
e.stopPropagation()}
+ >
+
+
+
+
+
+
+
{t('notifications.title')}
+
{t('notifications.subtitle')}
+
+
+
+
+
+
+ {notifications.map((item, index) => (
+
+
+ {item.publish_date || t('notifications.notice')}
+
+
+ {item.content}
+
+
+ ))}
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index cdac093..d209f9d 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -9,6 +9,7 @@
"nav.signUp": "Sign Up",
"nav.logout": "Logout",
"nav.contact": "Contact",
+ "common.close": "Close",
"home.heroSubtitle": "Access the world's most powerful AI models through a single API endpoint. Simple, affordable, reliable.",
"home.getStarted": "Get Started Free",
@@ -84,9 +85,10 @@
"dashboard.redeem": "Redeem",
"dashboard.redeemSuccess": "Code redeemed successfully!",
"dashboard.quickLinks": "Quick Links",
- "dashboard.announcements": "Announcements",
- "dashboard.announcementsDesc": "Latest updates from the site owner",
- "dashboard.notice": "Notice",
+ "notifications.open": "Open announcements",
+ "notifications.title": "Announcements",
+ "notifications.subtitle": "Latest updates from the site owner",
+ "notifications.notice": "Notice",
"dashboard.apiKeys": "API Keys",
"dashboard.manageKeys": "Manage your keys",
"dashboard.packages": "Packages",
diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json
index e527f86..e90617d 100644
--- a/src/i18n/locales/zh.json
+++ b/src/i18n/locales/zh.json
@@ -9,6 +9,7 @@
"nav.signUp": "注册",
"nav.logout": "退出",
"nav.contact": "联系我们",
+ "common.close": "关闭",
"home.heroSubtitle": "通过单一 API 端点访问全球最强大的 AI 模型。简单、实惠、可靠。",
"home.getStarted": "免费开始",
@@ -84,9 +85,10 @@
"dashboard.redeem": "兑换",
"dashboard.redeemSuccess": "兑换码兑换成功!",
"dashboard.quickLinks": "快捷入口",
- "dashboard.announcements": "公告通知",
- "dashboard.announcementsDesc": "站点管理员发布的最新消息",
- "dashboard.notice": "通知",
+ "notifications.open": "打开公告通知",
+ "notifications.title": "公告通知",
+ "notifications.subtitle": "站点管理员发布的最新消息",
+ "notifications.notice": "通知",
"dashboard.apiKeys": "API 密钥",
"dashboard.manageKeys": "管理您的密钥",
"dashboard.packages": "套餐",
diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx
index affe88c..154d679 100644
--- a/src/pages/Dashboard.jsx
+++ b/src/pages/Dashboard.jsx
@@ -1,7 +1,6 @@
import React, { useState, useEffect, useCallback } from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
-import { Megaphone } from 'lucide-react';
import { useAuth } from '../context/AuthContext';
import {
getUserUsage,
@@ -175,9 +174,6 @@ export default function Dashboard() {
);
const hasCustomCommissionRate =
currentCommissionRate > defaultCommissionRate + 1e-8;
- const announcements = Array.isArray(site?.announcements)
- ? site.announcements.filter((item) => item?.content)
- : (site?.announcement ? [{ content: site.announcement, publish_date: '' }] : []);
const handleWithdraw = async () => {
const amount = Number.parseFloat(withdrawAmount);
@@ -315,32 +311,6 @@ export default function Dashboard() {
{t('dashboard.manageDesc')}
- {announcements.length > 0 && (
-
-
-
-
-
-
-
{t('dashboard.announcements')}
-
{t('dashboard.announcementsDesc')}
-
-
-
- {announcements.slice(0, 10).map((item, index) => (
-
-
- {item.publish_date || t('dashboard.notice')}
-
-
- {item.content}
-
-
- ))}
-
-
- )}
-