Separate distributor banner and notifications

This commit is contained in:
abingyyds committed 2026-04-25 11:38:14 +08:00
1 parent 7a142e4845
commit b850a5d709
5 files changed
+89 -36

No files matched your search

+2
View File
@@ -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() {
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
<NotificationBell />
</Suspense>
);
}
+77
View File
@@ -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 (
<>
<button
type="button"
onClick={() => setOpen(true)}
className="fixed bottom-6 right-6 z-[80] flex h-14 w-14 items-center justify-center rounded-full border border-brand-500/30 bg-brand-500 text-white shadow-2xl shadow-brand-500/30 transition hover:scale-105 hover:bg-brand-600 focus:outline-none focus:ring-2 focus:ring-brand-400"
aria-label={t('notifications.open')}
>
<Bell size={22} />
<span className="absolute -right-1 -top-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-red-500 px-1 text-[11px] font-bold text-white ring-2 ring-[var(--page-bg)]">
{notifications.length > 9 ? '9+' : notifications.length}
</span>
</button>
{open && (
<div className="fixed inset-0 z-[90] flex items-end justify-center bg-black/55 px-4 py-6 backdrop-blur-sm sm:items-center" onClick={() => setOpen(false)}>
<div
className="w-full max-w-lg overflow-hidden rounded-3xl border border-page-card-border bg-[var(--page-bg)] shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between border-b border-page-card-border bg-page-surface px-5 py-4">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-2xl bg-brand-500/15 text-page-link">
<Bell size={20} />
</div>
<div>
<h2 className="text-base font-semibold text-page">{t('notifications.title')}</h2>
<p className="text-xs text-page-muted">{t('notifications.subtitle')}</p>
</div>
</div>
<button
type="button"
onClick={() => setOpen(false)}
className="rounded-full p-2 text-page-muted transition hover:bg-page-surface-hover hover:text-page"
aria-label={t('common.close')}
>
<X size={18} />
</button>
</div>
<div className="max-h-[65vh] space-y-3 overflow-y-auto p-5">
{notifications.map((item, index) => (
<div key={`${item.publish_date || 'notice'}-${index}`} className="rounded-2xl border border-page-card-border bg-page-surface p-4">
<div className="mb-2 inline-flex rounded-full bg-brand-500/10 px-3 py-1 text-xs font-medium text-page-link">
{item.publish_date || t('notifications.notice')}
</div>
<p className="whitespace-pre-wrap text-sm leading-6 text-page-secondary">
{item.content}
</p>
</div>
))}
</div>
</div>
</div>
)}
</>
);
}
+5 -3
View File
@@ -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",
+5 -3
View File
@@ -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": "套餐",
-30
View File
@@ -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() {
<p className="text-sm text-page-secondary">{t('dashboard.manageDesc')}</p>
</div>
{announcements.length > 0 && (
<div className="glass rounded-2xl p-5 mb-8 border border-brand-500/20">
<div className="flex items-center gap-2 mb-4">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-brand-500/15 text-page-link">
<Megaphone size={17} />
</div>
<div>
<h2 className="text-base font-semibold text-page">{t('dashboard.announcements')}</h2>
<p className="text-xs text-page-muted">{t('dashboard.announcementsDesc')}</p>
</div>
</div>
<div className="space-y-3">
{announcements.slice(0, 10).map((item, index) => (
<div key={`${item.publish_date || 'notice'}-${index}`} className="flex gap-3 rounded-xl bg-page-surface/60 p-3">
<div className="shrink-0 rounded-full border border-brand-500/20 bg-brand-500/10 px-3 py-1 text-xs font-medium text-page-link">
{item.publish_date || t('dashboard.notice')}
</div>
<p className="min-w-0 whitespace-pre-wrap text-sm leading-6 text-page-secondary">
{item.content}
</p>
</div>
))}
</div>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
<div className="glass rounded-2xl p-6">
<p className="text-sm text-page-secondary mb-2">{t('dashboard.balance')}</p>