产品体验修复:P0 阻塞 + P1/P2 改善(产品审查 14 项中修复 10 项)

P0 阻塞:
- Pro 加载不再阻塞整个看板:只显示精简顶栏 + 加载状态,不再遮盖整个页面
- Scan 失败加重试按钮:所有用户(含免费)均可手动重试
- Detail Panel 同步超时后显示提示:"同步时间较长,当前展示的数据可能不完整"

P1 体验:
- 登录页增加"忘记密码"链接:调用 Supabase 密码重置流程
- 登录失败/邮箱未验证增加明确提示:"如刚注册,请先点击验证链接"
- 公告横幅增加 ✕ 关闭按钮:点击后永久隐藏

P2 改善:
- entitlement-required 页面重设计:品牌化、增加返回首页和登录按钮
- 订阅帮助页中英双语:所有 FAQ 和 UI 文案支持中英文切换
- 新增产品审查文档 docs/product-review-jun-2026.md

Tested: npx tsc --noEmit
This commit is contained in:
2569718930@qq.com
2026-05-10 16:13:34 +08:00
parent 9635387beb
commit de0f037bf4
8 changed files with 392 additions and 46 deletions
@@ -353,12 +353,25 @@ function ScanTerminalScreen() {
<div className={scanTerminalRootClassName}>
<div className={clsx("scan-terminal", themeMode === "light" && "light")}>
<main className="scan-data-grid">
<div className="scan-topbar">
<div className="scan-topbar-title">
<strong>{isEn ? "AI Weather Decision Terminal" : "AI 天气交易决策台"}</strong>
<span>
{isEn
? "Start from the map, then open city cards to verify weather evidence"
: "从地图选城市,再打开决策卡验证天气证据"}
</span>
</div>
<div className="scan-topbar-actions">
<span className="scan-topbar-time">{userLocalTime}</span>
</div>
</div>
<div className="scan-loading-state">
<LoadingSignal
title={isEn ? "Preparing decision workspace" : "正在准备决策工作台"}
description={
isEn
? "Checking access, city context and todays tradable weather windows."
? "Checking access, city context and today's tradable weather windows."
: "正在检查权限、城市上下文和今日可交易天气窗口。"
}
/>
@@ -461,6 +474,17 @@ function ScanTerminalScreen() {
<li>{isEn ? "HKO observatory AI read" : "香港天文台观测 AI 解读"}</li>
<li>{isEn ? "Smoother scrolling experience" : "滚动体验优化"}</li>
</ul>
<button
type="button"
className="scan-announcement-dismiss"
aria-label={isEn ? "Dismiss" : "关闭"}
onClick={() => {
localStorage.setItem("polyweather_v156_announcement_seen_at", String(Date.now() + 90 * 24 * 60 * 60 * 1000));
setShowAnnouncement(false);
}}
>
</button>
</section>
) : null}
@@ -534,6 +558,14 @@ function ScanTerminalScreen() {
{isEn ? "Scan failed" : "扫描失败"}
</div>
<div className="scan-empty-copy">{staleReason}</div>
<button
type="button"
className="scan-retry-button"
onClick={() => refreshScanTerminalManually()}
>
<RefreshCw size={13} />
{isEn ? "Retry" : "重试"}
</button>
</div>
) : (
renderMainView()