"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import type { User } from "@supabase/supabase-js"; import type { LucideIcon } from "lucide-react"; import { Bot, CheckCircle2, ChevronLeft, Clock, Copy, Crown, Fingerprint, Hash, Loader2, LogIn, LogOut, Mail, RefreshCw, Shield, User as UserIcon, UserCheck, } from "lucide-react"; import { getSupabaseBrowserClient, hasSupabasePublicEnv, } from "@/lib/supabase/client"; type AuthMeResponse = { authenticated?: boolean; user_id?: string | null; email?: string | null; entitlement_mode?: string | null; auth_required?: boolean; subscription_required?: boolean; subscription_active?: boolean | null; }; type InfoItemProps = { icon: LucideIcon; label: string; value: string; status?: "default" | "primary"; }; function formatTime(value: string | undefined | null, locale: string) { if (!value) return "--"; try { const dt = new Date(value); if (Number.isNaN(dt.getTime())) return "--"; return new Intl.DateTimeFormat(locale, { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }).format(dt); } catch { return "--"; } } function normalizeProvider(user: User | null) { const provider = String(user?.app_metadata?.provider || "").trim().toLowerCase(); if (provider) return provider; const providers = user?.app_metadata?.providers; if (Array.isArray(providers) && providers.length) { return String(providers[0] || "").trim().toLowerCase(); } return ""; } function InfoItem({ icon: Icon, label, value, status = "default" }: InfoItemProps) { return (
{label}
{value}
); } export function AccountCenter() { const router = useRouter(); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [errorText, setErrorText] = useState(""); const [copied, setCopied] = useState(false); const [updatedAt, setUpdatedAt] = useState(""); const [user, setUser] = useState(null); const [backend, setBackend] = useState(null); const supabaseReady = hasSupabasePublicEnv(); const loadSnapshot = useCallback(async () => { setErrorText(""); try { const userPromise = supabaseReady ? getSupabaseBrowserClient().auth.getUser() : Promise.resolve({ data: { user: null as User | null } }); const backendPromise = fetch("/api/auth/me", { cache: "no-store" }); const [userResult, backendResult] = await Promise.all([ userPromise, backendPromise, ]); setUser(userResult.data?.user ?? null); if (!backendResult.ok) { const raw = (await backendResult.text()).slice(0, 260); throw new Error(`HTTP ${backendResult.status} ${raw}`.trim()); } const backendJson = (await backendResult.json()) as AuthMeResponse; setBackend(backendJson); setUpdatedAt(new Date().toISOString()); } catch (error) { setErrorText(String(error)); } }, [supabaseReady]); useEffect(() => { let cancelled = false; const run = async () => { setLoading(true); await loadSnapshot(); if (!cancelled) setLoading(false); }; void run(); return () => { cancelled = true; }; }, [loadSnapshot]); const onRefresh = async () => { setRefreshing(true); await loadSnapshot(); setRefreshing(false); }; const onSignOut = async () => { if (supabaseReady) { try { await getSupabaseBrowserClient().auth.signOut(); } catch {} } router.replace("/"); }; const userId = backend?.user_id || user?.id || ""; const isAuthenticated = Boolean(userId); const email = backend?.email || user?.email || ""; const displayName = String(user?.user_metadata?.full_name || "").trim() || (email ? String(email).split("@")[0] : "") || "PolyWeather 用户"; const initials = (displayName.slice(0, 2) || "PW").toUpperCase(); const providerRaw = normalizeProvider(user); const provider = providerRaw ? providerRaw.toUpperCase() : "--"; const lastSignIn = formatTime( user?.last_sign_in_at, typeof navigator !== "undefined" ? navigator.language : "zh-CN", ); const updatedAtLabel = formatTime( updatedAt, typeof navigator !== "undefined" ? navigator.language : "zh-CN", ); const modeLabel = useMemo(() => { const mode = String(backend?.entitlement_mode || "").trim().toLowerCase(); if (mode === "supabase_required") return "Supabase 强制登录"; if (mode === "supabase_optional") return "Supabase 可选登录"; if (mode === "legacy_token") return "Legacy Token 鉴权"; if (mode === "disabled") return "未启用鉴权"; return "未知模式"; }, [backend?.entitlement_mode]); const backendStatus = useMemo(() => { if (backend?.authenticated) return "通过"; if (backend?.auth_required) return "未登录"; return "游客模式"; }, [backend?.authenticated, backend?.auth_required]); const subscriptionRequirement = backend?.subscription_required ? "已启用订阅校验" : "当前未强制订阅"; const subscriptionResult = !backend?.subscription_required ? "当前未强制订阅" : backend?.subscription_active ? "有效订阅" : "无有效订阅"; const bindCommand = userId ? `/bind ${userId}${email ? ` ${email}` : ""}` : "/bind "; const copyBindCommand = async () => { try { await navigator.clipboard.writeText(bindCommand); setCopied(true); window.setTimeout(() => setCopied(false), 2000); } catch {} }; return (

账户中心

管理您的身份、权限与 Bot 绑定

返回看板 {isAuthenticated ? ( ) : ( 登录 / 注册 )}
{errorText ? (
加载失败: {errorText}
) : null} {!supabaseReady ? (
NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY 未配置。
) : null}
最近同步: {updatedAtLabel}
{initials}

{displayName}

{isAuthenticated ? "已登录" : "游客模式"}

{email || "--"}

当前角色

Free Tier

会员与权限

身份信息

Bot 绑定

将下面命令发送至 Telegram Bot,即可把网页账户与机器人权限绑定,实现全平台气象推送。

{bindCommand}
); }