diff --git a/frontend/components/account/AccountCenter.tsx b/frontend/components/account/AccountCenter.tsx
index 4007aae5..354cd818 100644
--- a/frontend/components/account/AccountCenter.tsx
+++ b/frontend/components/account/AccountCenter.tsx
@@ -4,25 +4,29 @@ 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 {
- ArrowLeft,
Bot,
CheckCircle2,
+ ChevronLeft,
+ Clock,
Copy,
- KeyRound,
+ Crown,
+ Fingerprint,
+ Hash,
Loader2,
LogIn,
LogOut,
+ Mail,
RefreshCw,
- ShieldCheck,
- UserCircle2,
+ Shield,
+ User as UserIcon,
+ UserCheck,
} from "lucide-react";
-import { useI18n } from "@/hooks/useI18n";
import {
getSupabaseBrowserClient,
hasSupabasePublicEnv,
} from "@/lib/supabase/client";
-import styles from "./AccountCenter.module.css";
type AuthMeResponse = {
authenticated?: boolean;
@@ -34,11 +38,18 @@ type AuthMeResponse = {
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 "";
+ if (!value) return "--";
try {
const dt = new Date(value);
- if (Number.isNaN(dt.getTime())) return "";
+ if (Number.isNaN(dt.getTime())) return "--";
return new Intl.DateTimeFormat(locale, {
year: "numeric",
month: "2-digit",
@@ -47,7 +58,7 @@ function formatTime(value: string | undefined | null, locale: string) {
minute: "2-digit",
}).format(dt);
} catch {
- return "";
+ return "--";
}
}
@@ -61,8 +72,27 @@ function normalizeProvider(user: User | null) {
return "";
}
+function InfoItem({ icon: Icon, label, value, status = "default" }: InfoItemProps) {
+ return (
+
+ );
+}
+
export function AccountCenter() {
- const { locale, t } = useI18n();
const router = useRouter();
const [loading, setLoading] = useState(true);
@@ -91,9 +121,10 @@ export function AccountCenter() {
setUser(userResult.data?.user ?? null);
if (!backendResult.ok) {
- const raw = (await backendResult.text()).slice(0, 240);
+ 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());
@@ -124,42 +155,54 @@ export function AccountCenter() {
const onSignOut = async () => {
if (supabaseReady) {
try {
- const supabase = getSupabaseBrowserClient();
- await supabase.auth.signOut();
+ await getSupabaseBrowserClient().auth.signOut();
} catch {}
}
- router.replace("/auth/login");
+ router.replace("/");
};
const userId = backend?.user_id || user?.id || "";
const isAuthenticated = Boolean(userId);
const email = backend?.email || user?.email || "";
- const providerRaw = normalizeProvider(user);
- const provider = providerRaw ? providerRaw.toUpperCase() : t("account.na");
- const lastSignIn = formatTime(user?.last_sign_in_at, locale) || t("account.na");
- const updatedAtLabel = formatTime(updatedAt, locale) || t("account.na");
const displayName =
String(user?.user_metadata?.full_name || "").trim() ||
(email ? String(email).split("@")[0] : "") ||
- t("account.guestName");
- const initials = displayName.slice(0, 2).toUpperCase();
+ "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 t("account.mode.supabaseRequired");
- if (mode === "supabase_optional") return t("account.mode.supabaseOptional");
- if (mode === "supabase") return t("account.mode.supabase");
- if (mode === "legacy_token") return t("account.mode.legacy");
- if (mode === "disabled") return t("account.mode.disabled");
- return t("account.mode.unknown");
- }, [backend?.entitlement_mode, t]);
+ 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 subscriptionLabel = useMemo(() => {
- if (!backend?.subscription_required) return t("account.subscription.notRequired");
- if (backend.subscription_active === true) return t("account.subscription.active");
- if (backend.subscription_active === false) return t("account.subscription.inactive");
- return t("account.subscription.unknown");
- }, [backend?.subscription_active, backend?.subscription_required, t]);
+ 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}` : ""}`
@@ -169,185 +212,187 @@ export function AccountCenter() {
try {
await navigator.clipboard.writeText(bindCommand);
setCopied(true);
- window.setTimeout(() => setCopied(false), 1300);
+ window.setTimeout(() => setCopied(false), 2000);
} catch {}
};
return (
-
-
-
-
-
-
-
{t("account.title")}
-
{t("account.subtitle")}
+
+
+
+
+
+
+
+
+ 账户中心
+
+
+ 管理您的身份、权限与 Bot 绑定
+
-
-
-
- {t("account.backDashboard")}
+
+
+ 返回看板
{isAuthenticated ? (
-
-
- {initials || "PW"}
-
-
{displayName}
-
{email || t("account.na")}
-
- {isAuthenticated ? (
- <>
-
-
- {t("account.authenticated")}
-
- {backend?.subscription_active ? (
-
-
- {t("account.subscriptionActive")}
-
- ) : backend?.subscription_required ? (
-
-
- {t("account.subscriptionRequired")}
-
- ) : (
-
-
- {t("account.subscriptionUnknown")}
-
- )}
- >
- ) : (
-
-
- {t("account.guest")}
-
- )}
-
-
- {t("account.updatedAt", { time: updatedAtLabel })}
-
-
- {loading ? (
-
-
- {t("account.loading")}
-
- ) : null}
-
{errorText ? (
-
- {t("account.error", { message: errorText })}
-
+
+ 加载失败: {errorText}
+
) : null}
-
-
-
-
- {t("account.card.membership")}
-
-
-
-
- {t("account.field.mode")}
- - {modeLabel}
-
-
-
- {t("account.field.backendStatus")}
- -
- {backend?.authenticated
- ? t("account.backend.ok")
- : backend?.auth_required
- ? t("account.backend.fail")
- : t("account.guest")}
-
-
-
-
- {t("account.field.requirement")}
- -
- {backend?.subscription_required
- ? t("account.subscriptionRequired")
- : t("account.subscription.notRequired")}
-
-
-
-
- {t("account.field.subscription")}
- - {subscriptionLabel}
-
-
-
-
-
-
-
- {t("account.card.identity")}
-
-
-
-
- {t("account.field.email")}
- - {email || t("account.na")}
-
-
-
- {t("account.field.userId")}
- - {userId || t("account.na")}
-
-
-
- {t("account.field.provider")}
- - {provider}
-
-
-
- {t("account.field.lastSignIn")}
- - {lastSignIn}
-
-
-
-
-
-
-
- {t("account.card.bot")}
-
- {t("account.field.bindHint")}
-
- {bindCommand}
- void copyBindCommand()}>
-
- {copied ? t("account.copied") : t("account.copy")}
-
-
-
-
-
{!supabaseReady ? (
-
-
- NEXT_PUBLIC_SUPABASE_URL / ANON_KEY is not configured.
-
+
+ NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY 未配置。
+
) : null}
+
+
+
+
+
+ 最近同步: {updatedAtLabel}
+
+
+
+
+
+
+
+
+
{displayName}
+
+
+ {isAuthenticated ? "已登录" : "游客模式"}
+
+
+
{email || "--"}
+
+
+
+
+
+ 当前角色
+
+
+ Free Tier
+
+
+
+
+
+
+
+
+
+ 会员与权限
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Bot 绑定
+
+
+ 将下面命令发送至 Telegram Bot,即可把网页账户与机器人权限绑定,实现全平台气象推送。
+
+
+
+
+ {bindCommand}
+
+
void copyBindCommand()}
+ className={`flex items-center justify-center gap-2 rounded-xl px-8 py-4 font-bold transition-all active:scale-95 ${
+ copied
+ ? "bg-green-500 text-white"
+ : "bg-blue-600 text-white shadow-lg shadow-blue-600/20 hover:bg-blue-500"
+ }`}
+ >
+ {copied ? : }
+ {copied ? "已复制" : "复制命令"}
+
+
+
+
+
+
+
+
-
+
);
}
diff --git a/frontend/components/auth/LoginClient.tsx b/frontend/components/auth/LoginClient.tsx
index c8dba5ce..e8c13233 100644
--- a/frontend/components/auth/LoginClient.tsx
+++ b/frontend/components/auth/LoginClient.tsx
@@ -2,6 +2,15 @@
import { FormEvent, useEffect, useState } from "react";
import { useRouter } from "next/navigation";
+import {
+ ArrowRight,
+ Chrome,
+ Cloud,
+ CloudRain,
+ Lock,
+ Mail,
+ Sun,
+} from "lucide-react";
import {
getSupabaseBrowserClient,
hasSupabasePublicEnv,
@@ -119,154 +128,128 @@ export function LoginClient({ nextPath }: LoginClientProps) {
}
};
+ const isLogin = mode === "login";
+
return (
-
-
- PolyWeather 登录
-
- 优先推荐 Google 一键登录,邮箱注册/登录可并行使用。
-
+
+
+
+
+
+
+
+
+
+
PolyWeather
+
探索世界每一个角落的气象细节
+
void onGoogleSignIn()}
disabled={loading}
- style={{
- width: "100%",
- marginTop: 12,
- padding: "12px 14px",
- borderRadius: 10,
- border: "1px solid rgba(132, 169, 237, 0.5)",
- background: "linear-gradient(135deg, #1a4c95 0%, #2a6ed2 100%)",
- color: "#f3f7ff",
- fontWeight: 700,
- cursor: "pointer",
- }}
+ className="mb-6 flex w-full items-center justify-center rounded-xl bg-white px-4 py-3.5 font-semibold text-slate-900 shadow-lg transition-all duration-200 hover:bg-slate-100 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-70"
>
- 使用 Google 一键登录
+
+ 使用 Google 账号一键登录
-
- 或使用邮箱 {mode === "login" ? "登录" : "注册"}
+
-
+
setMode("login")}
- style={{
- flex: 1,
- padding: "10px 12px",
- borderRadius: 8,
- border: "1px solid rgba(116, 148, 206, 0.45)",
- background: mode === "login" ? "#1c4a90" : "transparent",
- color: "#d8e6ff",
- cursor: "pointer",
- }}
+ className={`flex-1 rounded-lg py-2 text-sm font-medium transition-all ${
+ isLogin
+ ? "bg-blue-600 text-white shadow-md"
+ : "text-slate-400 hover:text-slate-200"
+ }`}
>
- 邮箱登录
+ 登录
setMode("signup")}
- style={{
- flex: 1,
- padding: "10px 12px",
- borderRadius: 8,
- border: "1px solid rgba(116, 148, 206, 0.45)",
- background: mode === "signup" ? "#1c4a90" : "transparent",
- color: "#d8e6ff",
- cursor: "pointer",
- }}
+ className={`flex-1 rounded-lg py-2 text-sm font-medium transition-all ${
+ !isLogin
+ ? "bg-blue-600 text-white shadow-md"
+ : "text-slate-400 hover:text-slate-200"
+ }`}
>
- 邮箱注册
+ 注册
-
- {errorText ? (
-
{errorText}
- ) : null}
- {infoText ? (
-
{infoText}
- ) : null}
+ {errorText ?
{errorText}
: null}
+ {infoText ?
{infoText}
: null}
-
- 登录后将跳转到: {nextPath}
+
+
+ {isLogin ? "登录后将为您个性化定制首页数据" : "注册即代表同意我们的服务条款"}
+
+
+
+
+ 登录后跳转到: {nextPath}
-
-
+
+ {!supabaseReady ? (
+
+ Supabase 未配置,无法使用登录
+
+ ) : null}
+
+
+
+
+ 实时数据
+
+
+ 高精度预测
+
+
+
);
}
-