diff --git a/frontend/src/app/me/page.tsx b/frontend/src/app/me/page.tsx index f61e1dd..07199a6 100644 --- a/frontend/src/app/me/page.tsx +++ b/frontend/src/app/me/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useMemo, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { ArrowRightLeft, Calendar, @@ -105,8 +105,29 @@ export default function MePage() { const [loading, setLoading] = useState(false); const [redeemLoading, setRedeemLoading] = useState(false); - const [error, setError] = useState(""); - const [msg, setMsg] = useState(""); + + // Toast notification system + const [toast, setToast] = useState<{ type: "success" | "error"; text: string } | null>(null); + const [toastVisible, setToastVisible] = useState(false); + const toastTimer = useRef>(undefined); + + const showToast = useCallback((type: "success" | "error", text: string) => { + if (toastTimer.current) clearTimeout(toastTimer.current); + // Clean up common error prefixes + let cleaned = text.replace(/^Error:\s*/i, ""); + if (type === "error") { + cleaned = cleaned + .replace(/rating not enough/i, isZh ? "💎 绿宝石不足,继续冒险积攒吧!" : "💎 Not enough Emeralds! Keep adventuring!") + .replace(/please sign in/i, isZh ? "🔒 请先登录" : "🔒 Please sign in first") + .replace(/select trade item/i, isZh ? "📦 请选择交易物品" : "📦 Select a trade item"); + } + setToast({ type, text: cleaned }); + setToastVisible(true); + toastTimer.current = setTimeout(() => { + setToastVisible(false); + setTimeout(() => setToast(null), 300); + }, type === "error" ? 5000 : 4000); + }, [isZh]); const selectedItem = useMemo( () => items.find((item) => item.id === selectedItemId) ?? null, @@ -175,8 +196,6 @@ export default function MePage() { const loadAll = async () => { setLoading(true); - setError(""); - setMsg(""); try { const tk = readToken(); setToken(tk); @@ -202,7 +221,7 @@ export default function MePage() { setSelectedItemId((prev) => prev || redeemItems[0].id); } } catch (e: unknown) { - setError(String(e)); + showToast("error", String(e)); } finally { setLoading(false); } @@ -215,8 +234,6 @@ export default function MePage() { const redeem = async () => { setRedeemLoading(true); - setError(""); - setMsg(""); try { if (!token) throw new Error(tx("请先登录", "Please sign in first")); if (!selectedItemId) throw new Error(tx("请选择交易物品", "Select trade item")); @@ -238,15 +255,15 @@ export default function MePage() { token ); - setMsg( + showToast("success", isZh - ? `交易成功:${created.item_name} × ${created.quantity},花费 ${created.total_cost} 绿宝石。` - : `Trade successful: ${itemName(created.item_name)} × ${created.quantity}, cost ${created.total_cost} Emeralds.` + ? `✅ 交易成功:${created.item_name} × ${created.quantity},花费 ${created.total_cost} 绿宝石` + : `✅ Trade successful: ${itemName(created.item_name)} × ${created.quantity}, cost ${created.total_cost} Emeralds` ); setNote(""); await loadAll(); } catch (e: unknown) { - setError(String(e)); + showToast("error", String(e)); } finally { setRedeemLoading(false); } @@ -260,8 +277,27 @@ export default function MePage() { {tx("冒险者档案 & 交易站", "Character Sheet & Trading Post")} {loading &&

{tx("读取存档中...", "Loading Save...")}

} - {error &&

{error}

} - {msg &&

{msg}

} + + {/* Toast notification */} + {toast && ( +
+
+ {toast.text} + +
+
+ )} {profile && (