refactor: update environment configuration and enhance notification handling

- Refactored ecosystem configuration to utilize .env for sensitive variables, improving security and flexibility.
- Replaced direct notification button implementation with a dedicated PlayerNotificationBell component for better code organization.
- Updated various screens to integrate the new notification component and adjusted prefetch settings for links to optimize performance.
- Added new translations for notifications and wallet logs to enhance user experience across multiple languages.
This commit is contained in:
2026-06-01 09:29:02 +08:00
parent 9f43d07778
commit 10bee1b857
16 changed files with 241 additions and 59 deletions

View File

@@ -0,0 +1,107 @@
"use client";
import { Bell } from "lucide-react";
import Link from "next/link";
import { useState, type ReactElement } from "react";
import { useTranslation } from "react-i18next";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { logTypeLabel } from "@/features/wallet/wallet-logs-block";
import { usePendingWalletReconcile } from "@/hooks/use-pending-wallet-reconcile";
import { playerHeaderControl } from "@/lib/player-spacing";
import { formatMinorAsCurrency } from "@/lib/money";
import { cn } from "@/lib/utils";
type PlayerNotificationBellProps = {
className?: string;
};
/** 顶栏铃铛待对账划转等提醒Popover 右上角展开) */
export function PlayerNotificationBell({ className }: PlayerNotificationBellProps): ReactElement {
const { t } = useTranslation("common");
const { t: tp } = useTranslation("player");
const [open, setOpen] = useState(false);
const { pending, hasPending, loading, refresh } = usePendingWalletReconcile();
return (
<Popover
open={open}
onOpenChange={(next) => {
setOpen(next);
if (next) void refresh();
}}
>
<PopoverTrigger
render={
<button
type="button"
className={cn(
playerHeaderControl,
"relative size-8 rounded-full text-[#1d57b7] hover:bg-[#f4f7fb]",
className,
)}
aria-label={t("navigation.notifications")}
>
<Bell className="size-4" aria-hidden />
{hasPending ? (
<span className="absolute right-1.5 top-1.5 size-1.5 rounded-full bg-[#ff143d]" />
) : null}
</button>
}
/>
<PopoverContent
align="end"
sideOffset={8}
className="w-[min(20rem,calc(100vw-1.5rem))] border-[#dce7f7] p-0 shadow-[0_16px_40px_rgba(15,23,42,0.14)]"
>
<div className="border-b border-[#edf2f9] px-3 py-2.5">
<p className="text-sm font-black text-[#0b3f96]">{t("navigation.notifications")}</p>
</div>
{loading && pending.length === 0 ? (
<p className="px-3 py-6 text-center text-xs text-slate-500">{tp("actions.loading")}</p>
) : null}
{!loading && pending.length === 0 ? (
<p className="px-3 py-6 text-center text-xs text-slate-500">
{tp("notifications.empty")}
</p>
) : null}
{pending.length > 0 ? (
<div className="max-h-[min(50vh,320px)] overflow-y-auto p-2">
<div className="rounded-xl border border-amber-200 bg-amber-50 px-3 py-3">
<p className="text-sm font-black text-amber-700">{tp("wallet.pendingTitle")}</p>
<p className="mt-1 text-xs leading-relaxed text-amber-800/90">
{tp("wallet.pendingDescription")}
</p>
<ul className="mt-2 space-y-2">
{pending.map((p) => (
<li
key={p.transfer_no}
className="flex flex-wrap items-baseline justify-between gap-1 border-b border-dashed border-amber-200/80 py-2 last:border-0"
>
<span className="text-sm text-amber-950">
{logTypeLabel(p.type, tp)}{" "}
{formatMinorAsCurrency(p.amount, p.currency_code)}
</span>
<span className="text-xs font-semibold text-amber-700">
{tp("wallet.pendingStatus")}
</span>
</li>
))}
</ul>
</div>
<Link
href="/wallet/logs"
className="mt-2 block rounded-lg px-2 py-2 text-center text-xs font-bold text-[#0b56b7] hover:bg-[#f8fbff]"
onClick={() => setOpen(false)}
>
{tp("wallet.logs", { defaultValue: "查看流水" })}
</Link>
</div>
) : null}
</PopoverContent>
</Popover>
);
}

View File

@@ -3,10 +3,11 @@
import Link from "next/link";
import type { ReactNode } from "react";
import { Bell, ChevronLeft } from "lucide-react";
import { ChevronLeft } from "lucide-react";
import { useTranslation } from "react-i18next";
import { LanguageSwitcher } from "@/components/language-switcher";
import { PlayerNotificationBell } from "@/components/layout/player-notification-bell";
import {
playerHeaderControl,
playerPageHeader,
@@ -79,17 +80,7 @@ export function PlayerPanel({
"rounded-full border border-[#e4eaf4] bg-[#f8fafc] [&_button]:h-8 [&_button]:gap-1 [&_button]:px-2 [&_button]:py-0 [&_button]:text-xs",
)}
/>
<button
type="button"
className={cn(
playerHeaderControl,
"relative size-8 rounded-full text-[#1d57b7] hover:bg-[#f4f7fb]",
)}
aria-label={t("navigation.notifications")}
>
<Bell className="size-4" aria-hidden />
<span className="absolute right-1.5 top-1.5 size-1.5 rounded-full bg-[#ff143d]" />
</button>
<PlayerNotificationBell />
</div>
</header>
{children}