From de0f037bf467485fa72c0413f62e4786a670edd7 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sun, 10 May 2026 16:13:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=A7=E5=93=81=E4=BD=93=E9=AA=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=EF=BC=9AP0=20=E9=98=BB=E5=A1=9E=20+=20P1/P2=20?= =?UTF-8?q?=E6=94=B9=E5=96=84=EF=BC=88=E4=BA=A7=E5=93=81=E5=AE=A1=E6=9F=A5?= =?UTF-8?q?=2014=20=E9=A1=B9=E4=B8=AD=E4=BF=AE=E5=A4=8D=2010=20=E9=A1=B9?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0 阻塞: - Pro 加载不再阻塞整个看板:只显示精简顶栏 + 加载状态,不再遮盖整个页面 - Scan 失败加重试按钮:所有用户(含免费)均可手动重试 - Detail Panel 同步超时后显示提示:"同步时间较长,当前展示的数据可能不完整" P1 体验: - 登录页增加"忘记密码"链接:调用 Supabase 密码重置流程 - 登录失败/邮箱未验证增加明确提示:"如刚注册,请先点击验证链接" - 公告横幅增加 ✕ 关闭按钮:点击后永久隐藏 P2 改善: - entitlement-required 页面重设计:品牌化、增加返回首页和登录按钮 - 订阅帮助页中英双语:所有 FAQ 和 UI 文案支持中英文切换 - 新增产品审查文档 docs/product-review-jun-2026.md Tested: npx tsc --noEmit --- docs/product-review-jun-2026.md | 130 ++++++++++++++++++ frontend/app/entitlement-required/page.tsx | 79 ++++++++--- frontend/app/subscription-help/page.tsx | 82 +++++++---- frontend/components/auth/LoginClient.tsx | 57 ++++++++ frontend/components/dashboard/DetailPanel.tsx | 9 ++ .../dashboard/DetailPanelChrome.module.css | 21 +++ .../dashboard/ScanTerminalDashboard.tsx | 34 ++++- .../dashboard/ScanTerminalShell.module.css | 26 ++++ 8 files changed, 392 insertions(+), 46 deletions(-) create mode 100644 docs/product-review-jun-2026.md diff --git a/docs/product-review-jun-2026.md b/docs/product-review-jun-2026.md new file mode 100644 index 00000000..1ecfb0cc --- /dev/null +++ b/docs/product-review-jun-2026.md @@ -0,0 +1,130 @@ +# PolyWeather 前端产品审查报告 + +> 审查日期:2026-06 | 视角:产品经理 | 范围:`frontend/` 全部页面、组件、用户流程 + +## 一、产品概览 + +PolyWeather 是一个面向天气衍生品交易者的气象情报平台。核心价值主张:**结合多模型气象预报 + AI 机场报文解读 + Polymarket 市场价格,为交易决策提供一站式证据链。** + +### 产品分层 + +| 层级 | 功能 | 门槛 | +|------|------|------| +| 免费 | 交互式全球天气地图 + 城市简报 | 无需登录 | +| Pro 试用 | 3 天全功能 | 注册后自动获得 | +| Pro 订阅 | 城市决策卡(AI 机场报文 + 模型证据 + 市场层)、日内分析、历史对账、未来预报 | 5 USDC/月(积分抵扣最多 3 USDC) | + +### 页面结构(9 个路由) + +| 路由 | 功能 | 是否必需登录 | +|------|------|-------------| +| `/` | 主看板 — AI 天气决策台 | 否 | +| `/account` | 账户中心 — 身份/订阅/钱包/积分/Bot 绑定 | 是 | +| `/auth/login` | 登录页(Google OAuth + 邮箱密码) | 否 | +| `/docs/[...slug]` | 产品文档中心(8 篇双语文档) | 否 | +| `/subscription-help` | 订阅 FAQ(静态) | 否 | +| `/entitlement-required` | 访问被拒页面 | 否 | +| `/ops` | 运营管理后台 | 是(管理员) | +| `/ops/truth-history` | 真值历史查看器 | 是(管理员) | + +--- + +## 二、用户流程分析 + +### 主看板的两个视图 + +``` +┌─ 分布视图(地图) ──────────────────────────────┐ +│ Leaflet 交互式地图,城市彩色气泡 │ +│ 点击城市 → 自动添加到决策卡工作区 + 切换到卡片视图 │ +│ 免费用户和 Pro 用户均可使用 │ +├─ 决策卡(分析) ──────────────────────────────┤ +│ 钉选的城市卡片:AI 机场解读 + 模型集群 + 市场层 + 图表 │ +│ 需要 Pro 订阅 │ +└──────────────────────────────────────────────┘ + └── 右侧栏:城市简报面板(始终可见,免费可用) +``` + +### 关键用户路径 + +1. **新用户落地** → 看到地图 + 城市列表 → 点击城市 → 看到城市简报 → 想深入分析 → 遇到 Pro 付费墙 +2. **Pro 用户打开** → Pro 加载中…… → 等待 → 看到决策卡工作区 +3. **Pro 用户工作流** → 地图选城市 → 自动钉选到决策卡 → 展开卡片 → 阅读 AI 报文解读 → 查看市场层 → 判断交易方向 + +--- + +## 三、发现的问题(按严重程度排序) + +### 🔴 P0 — 阻塞性问题 + +| # | 问题 | 影响 | +|---|------|------| +| 1 | **Pro 加载阻塞整个看板** — 检查 Pro 状态时(`proAccess.loading`),整个页面显示加载动画,免费用户的地图也被遮住 | 所有用户在加载期间无法使用任何功能 | +| 2 | **Scan 失败无重试** — 免费用户如果 scan terminal 数据拉取失败,只能看到 "Scan failed" 文字,没有重试按钮(重试按钮仅 Pro 用户可见) | 免费用户永久卡在失败状态 | +| 3 | **Detail Panel 同步超时无反馈** — 12 秒后停止显示 "Syncing..." 但不显示错误状态,用户看到空白内容 | 用户以为功能正常但数据缺失 | + +### 🟡 P1 — 体验断裂 + +| # | 问题 | 影响 | +|---|------|------| +| 4 | **无新用户引导** — 没有任何欢迎弹窗、引导提示、或步骤教程。唯一的指引是顶栏内的一行副标题文字 | 新用户不知道从哪开始,可能直接流失 | +| 5 | **付费墙没有预览** — 历史对账和日内分析被硬阻断,非 Pro 用户完全看不到任何预览或示例 | 用户不知道付费后能得到什么,降低付费转化 | +| 6 | **密码重置缺失** — 邮箱注册的用户如果忘记密码,没有任何重置流程 | 用户被永久锁在账户外 | +| 7 | **邮箱验证状态不明** — 注册后提示"请检查邮箱验证",但未验证的用户反复登录失败,没有明确的下一步提示 | 用户反复尝试登录但不知道为什么失败 | + +### 🟢 P2 — 改善体验 + +| # | 问题 | 影响 | +|---|------|------| +| 8 | **公告横幅不可手动关闭** — v1.5.6 升级公告只能通过 localStorage 倒计时 3 天后自动消失,没有 ✕ 关闭按钮 | 占用屏幕空间,用户无法跳过 | +| 9 | **无应用内反馈渠道** — 没有"反馈"/"帮助"/"报 Bug"按钮,用户只能主动去 Telegram 群或发邮件 | 用户反馈成本高,问题发现延迟 | +| 10 | **entitlement-required 页面过于简陋** — 没有品牌标识、没有导航、没有返回首页的链接 | 用户仿佛掉进了一个黑洞页面 | +| 11 | **账户中心缺少个人设置** — 用户无法编辑显示名称/头像、管理活跃会话、删除账户、调整通知偏好 | 产品感觉"半成品" | +| 12 | **Docs 无搜索** — 8 篇文档没有搜索功能,用户必须逐篇浏览找到需要的内容 | 查找效率低 | +| 13 | **订阅帮助仅中文** — `/subscription-help` 页面没有英文版 | 英文用户无法自助解决计费问题 | +| 14 | **Ops 面板无审计日志** — 管理员补发积分等操作没有审计记录 | 运营风险 | + +--- + +## 四、做得好的地方 + +1. **地图 → 决策卡的自动流转设计** — 点击地图城市自动钉选到分析工作区并切换视图,这是很聪明的"零步骤发现"设计 +2. **双语覆盖完整** — 所有 UI 文案、文档、AI 解读都有中英文对照,覆盖率接近 100% +3. **数据新鲜度可视化** — DataFreshnessBar 让用户一眼看到 METAR/模型/市场数据的新鲜度(fresh/stale/loading/unavailable),降低误判风险 +4. **AI 解读的产品化程度高** — 不只是显示原始 AI 输出,而是分层展示:快速判断 → 完整解读 → 证据链 → 风险提示,每层都可独立查看 +5. **免费层有实际价值** — 地图 + 城市简报不是"空壳",用户可以看到真实的气象数据,建立信任后再转化 Pro +6. **支付链路完整** — 从钱包绑定到链上签约到支付恢复,整个流程处理了多种异常情况 +7. **空状态有引导文字** — "Click a city on the map" 这样的空状态文案能告诉用户下一步做什么 +8. **浅色/深色主题都有** — 两个主题都经过完整设计,不是简单的颜色反转 +9. **Ops 面板功能齐全** — 运营人员可以在一个页面看到系统健康、转化漏斗、缓存状态、支付异常、用户管理 + +--- + +## 五、建议的优先级路线图 + +### Phase 1 — 修复阻塞(本周) + +1. **Pro 加载不阻塞免费层** — `proAccess.loading` 时先显示地图,决策卡区单独显示加载状态 +2. **Scan 失败加重试** — 免费用户也能看到重试按钮 +3. **Detail Panel 同步超时后显示错误提示** — "数据加载超时,请刷新" + +### Phase 2 — 体验堵漏(本月) + +4. **新用户引导** — 简单的 3 步 overlay:①点地图选城市 → ②看右侧城市简报 → ③Pro 解锁决策卡分析 +5. **付费墙加预览** — 历史对账/日内分析展示一个模糊的示例截图 + "升级 Pro 查看完整分析" +6. **密码重置** — 集成 Supabase 的密码重置 API +7. **公告横幅加关闭按钮** — "知道了"按钮,点击后永久隐藏 + +### Phase 3 — 产品完善(下月) + +8. **应用内反馈入口** — 底部固定"反馈"按钮 → 简单的表单/Telegram 跳转 +9. **账户设置补充** — 显示名称编辑、会话管理 +10. **Docs 搜索** — 简单的客户端全文搜索 +11. **entitlement-required 页面美化** — 加品牌、导航、返回首页链接 + +### Phase 4 — 增长优化 + +12. 注册后邮件验证引导流程 +13. 订阅帮助页中英双语 +14. Ops 操作审计日志 +15. 通知偏好设置(Email/Telegram 推送开关) diff --git a/frontend/app/entitlement-required/page.tsx b/frontend/app/entitlement-required/page.tsx index 082ba87e..b856ec44 100644 --- a/frontend/app/entitlement-required/page.tsx +++ b/frontend/app/entitlement-required/page.tsx @@ -1,3 +1,5 @@ +import Link from "next/link"; + type Props = { searchParams?: Promise<{ next?: string }>; }; @@ -21,37 +23,82 @@ export default async function EntitlementRequiredPage({ searchParams }: Props) {
-

- Entitlement Required +

+ 需要登录方可访问此页面

- This dashboard is protected. If Supabase auth is enabled, please go to{" "} - + Sign in required to access this page. +

+
+ - /auth/login - {" "} - to sign in first. -

-

- Legacy mode still supports ?access_token=<your-token>. -

-

- Requested path: {nextPath} + 去登录 / Sign in + + + 返回首页 / Back to Home + +

+

+ 传统令牌模式仍支持 ?access_token=<your-token> +
+ + 请求路径 / Requested path: {nextPath} +

diff --git a/frontend/app/subscription-help/page.tsx b/frontend/app/subscription-help/page.tsx index 73448f05..695542a9 100644 --- a/frontend/app/subscription-help/page.tsx +++ b/frontend/app/subscription-help/page.tsx @@ -1,5 +1,7 @@ -import type { Metadata } from "next"; +"use client"; + import Link from "next/link"; +import { useMemo } from "react"; import { ArrowLeft, CheckCircle2, @@ -8,11 +10,7 @@ import { MessageSquare, ShieldCheck, } from "lucide-react"; - -export const metadata: Metadata = { - title: "PolyWeather | 订阅说明", - description: "PolyWeather Pro 订阅、积分抵扣与支付方式说明。", -}; +import { useI18n } from "@/hooks/useI18n"; const TELEGRAM_GROUP_URL = String( process.env.NEXT_PUBLIC_TELEGRAM_GROUP_URL || @@ -21,24 +19,50 @@ const TELEGRAM_GROUP_URL = String( const FAQ_ITEMS = [ { - q: "Pro 包含哪些功能?", - a: "开通后可解锁:今日日内机场报文规则分析(含高温时段)、历史对账 + 未来日期分析、全平台智能气象推送。", + q_zh: "Pro 包含哪些功能?", + q_en: "What features does Pro include?", + a_zh: "开通后可解锁:今日日内机场报文规则分析(含高温时段)、历史对账 + 未来日期分析、全平台智能气象推送。", + a_en: "Unlocks: intraday METAR rule analysis (including peak window), historical reconciliation + future-date analysis, and cross-platform smart weather push.", }, { - q: "当前订阅价格是多少?", - a: "目前仅提供月付:5 USDC / 30 天。", + q_zh: "当前订阅价格是多少?", + q_en: "What is the current subscription price?", + a_zh: "目前仅提供月付:5 USDC / 30 天。", + a_en: "Monthly plan only: 5 USDC / 30 days.", }, { - q: "积分如何抵扣?", - a: "满 500 积分起兑,每 500 积分抵 1U,单次最多抵 3U。", + q_zh: "积分如何抵扣?", + q_en: "How do points work for discounts?", + a_zh: "满 500 积分起兑,每 500 积分抵 1U,单次最多抵 3U。", + a_en: "500 points minimum — every 500 points = 1 USDC off, up to 3 USDC discount per payment.", }, { - q: "支持哪些钱包和支付方式?", - a: "支持 EVM 浏览器钱包(MetaMask / OKX / Rabby / Bitget 等)及 WalletConnect 扫码钱包(Trust Wallet / Binance Web3 Wallet / TokenPocket 等)。", + q_zh: "支持哪些钱包和支付方式?", + q_en: "Which wallets and payment methods are supported?", + a_zh: "支持 EVM 浏览器钱包(MetaMask / OKX / Rabby / Bitget 等)及 WalletConnect 扫码钱包(Trust Wallet / Binance Web3 Wallet / TokenPocket 等)。", + a_en: "EVM browser wallets (MetaMask, OKX, Rabby, Bitget, etc.) and WalletConnect-compatible wallets (Trust Wallet, Binance Web3 Wallet, TokenPocket, etc.).", }, ]; export default function SubscriptionHelpPage() { + const { locale } = useI18n(); + const isEn = locale === "en-US"; + + const copy = useMemo(() => ({ + back: isEn ? "Back to Account" : "返回账户中心", + title: isEn ? "PolyWeather Pro Subscription Guide" : "PolyWeather Pro 订阅说明", + description: isEn + ? "Complete subscription rules and payment guide." + : "这里是完整的订阅规则和支付说明。你可以先在页面内绑定钱包,再直接开通 Pro。", + priceLabel: isEn ? "Price" : "订阅价格", + priceText: "5 USDC / 30 " + (isEn ? "Days" : "天"), + discountLabel: isEn ? "Points Discount" : "积分抵扣", + discountText: isEn ? "Up to 3 USDC off" : "最多抵 3U", + communityLabel: isEn ? "Community Points" : "社群积分", + communityLink: isEn ? "Join community to earn" : "加入社群即可赚取积分", + faqTitle: isEn ? "FAQ" : "常见问题", + }), [isEn]); + return (
@@ -47,62 +71,62 @@ export default function SubscriptionHelpPage() { className="mb-5 inline-flex items-center gap-2 rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-sm text-slate-300 transition hover:bg-white/10" > - 返回账户中心 + {copy.back}
-

PolyWeather Pro 订阅说明

+

{copy.title}

-

- 这里是完整的订阅规则和支付说明。你可以先在页面内绑定钱包,再直接开通 Pro。 -

+

{copy.description}

- 订阅价格 + {copy.priceLabel}
-

5 USDC / 30 天

+

{copy.priceText}

- 积分抵扣 + {copy.discountLabel}
-

最多抵 3U

+

{copy.discountText}

- 社群积分 + {copy.communityLabel}
- 加入社群即可赚取积分 + {copy.communityLink}
-

常见问题

+

{copy.faqTitle}

{FAQ_ITEMS.map((item) => (

- {item.q} + {isEn ? item.q_en : item.q_zh}

-

{item.a}

+

+ {isEn ? item.a_en : item.a_zh} +

))}
diff --git a/frontend/components/auth/LoginClient.tsx b/frontend/components/auth/LoginClient.tsx index eac5586b..60c3fb78 100644 --- a/frontend/components/auth/LoginClient.tsx +++ b/frontend/components/auth/LoginClient.tsx @@ -34,6 +34,7 @@ export function LoginClient({ nextPath }: LoginClientProps) { const [loading, setLoading] = useState(false); const [errorText, setErrorText] = useState(""); const [infoText, setInfoText] = useState(""); + const [resetSent, setResetSent] = useState(false); const supabaseReady = hasSupabasePublicEnv(); const isEn = locale === "en-US"; @@ -74,8 +75,45 @@ export function LoginClient({ nextPath }: LoginClientProps) { trialPromo: isEn ? "New users unlock a free 3-day Pro trial after sign-up." : "新用户注册后可免费体验 3 天 Pro。", + reset: isEn ? "Forgot password?" : "忘记密码?", + resetSent: isEn + ? "Reset link sent. Check your inbox." + : "重置链接已发送,请检查收件箱。", + resetPlaceholder: isEn ? "Enter your email to reset" : "输入邮箱以重置密码", + resendVerify: isEn + ? "Didn't receive the verification email? Sign up again with the same email to resend." + : "没收到验证邮件?用同一邮箱重新注册即可重发。", + loginFailedHint: isEn + ? "If you just signed up, please verify your email first. Check your inbox or spam folder." + : "如果刚注册,请先点击邮箱中的验证链接。检查收件箱或垃圾邮件。", } as const; + const onResetPassword = async () => { + setErrorText(""); + setInfoText(""); + if (!email.trim()) { + setErrorText(copy.resetPlaceholder); + return; + } + if (!supabaseReady) { + setErrorText(copy.supabaseMissing); + return; + } + setLoading(true); + try { + const supabase = getSupabaseBrowserClient(); + const { error } = await supabase.auth.resetPasswordForEmail(email.trim()); + if (error) { + setErrorText(error.message); + return; + } + setResetSent(true); + setInfoText(copy.resetSent); + } finally { + setLoading(false); + } + }; + useEffect(() => { if (!supabaseReady) return; const run = async () => { @@ -270,6 +308,19 @@ export function LoginClient({ nextPath }: LoginClientProps) { />
+ {isLogin && !resetSent ? ( +
+ +
+ ) : null} + ) : null} @@ -534,6 +558,14 @@ function ScanTerminalScreen() { {isEn ? "Scan failed" : "扫描失败"}
{staleReason}
+ ) : ( renderMainView() diff --git a/frontend/components/dashboard/ScanTerminalShell.module.css b/frontend/components/dashboard/ScanTerminalShell.module.css index 00d2c557..84d78fde 100644 --- a/frontend/components/dashboard/ScanTerminalShell.module.css +++ b/frontend/components/dashboard/ScanTerminalShell.module.css @@ -148,6 +148,32 @@ padding: 7px 10px; } +.root :global(.scan-announcement-dismiss) { + position: absolute; + top: 10px; + right: 12px; + width: 28px; + height: 28px; + display: grid; + place-items: center; + border: none; + border-radius: 8px; + background: rgba(148, 163, 184, 0.1); + color: #94a3b8; + font-size: 14px; + cursor: pointer; + transition: background 0.15s, color 0.15s; +} + +.root :global(.scan-announcement-dismiss:hover) { + background: rgba(148, 163, 184, 0.22); + color: #e2e8f0; +} + +.root :global(.scan-upgrade-announcement) { + position: relative; +} + .root :global(.scan-ghost-button), .root :global(.scan-cta-ghost), .root :global(.scan-theme-button),