feat: Implement user account center with authentication, subscription management, and cryptocurrency payment integration.

This commit is contained in:
2569718930@qq.com
2026-03-16 20:30:46 +08:00
parent 6a229a592b
commit 609fc8914a
6 changed files with 448 additions and 80 deletions
+59 -20
View File
@@ -17,6 +17,7 @@ import {
getSupabaseBrowserClient,
hasSupabasePublicEnv,
} from "@/lib/supabase/client";
import { useI18n } from "@/hooks/useI18n";
type Mode = "login" | "signup";
@@ -26,6 +27,7 @@ type LoginClientProps = {
export function LoginClient({ nextPath }: LoginClientProps) {
const router = useRouter();
const { locale } = useI18n();
const [mode, setMode] = useState<Mode>("login");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
@@ -34,6 +36,42 @@ export function LoginClient({ nextPath }: LoginClientProps) {
const [infoText, setInfoText] = useState("");
const supabaseReady = hasSupabasePublicEnv();
const isEn = locale === "en-US";
const copy = {
backHome: isEn ? "Back to Home" : "返回首页",
subtitle: isEn
? "Explore weather details from every corner of the world"
: "探索世界每一个角落的气象细节",
googleOneClick: isEn
? "Continue with Google"
: "使用 Google 账号一键登录",
orEmail: isEn ? "Or continue with email" : "或使用邮箱",
login: isEn ? "Sign In" : "登录",
signup: isEn ? "Sign Up" : "注册",
passwordLoginPlaceholder: isEn ? "Enter password" : "输入密码",
passwordSignupPlaceholder: isEn
? "Set at least 6 characters"
: "设置至少 6 位密码",
loginSubmit: isEn ? "Start your weather journey" : "开启天气之旅",
signupSubmit: isEn ? "Create account now" : "立即创建账号",
loginHint: isEn
? "After signing in, your homepage will be personalized."
: "登录后将为您个性化定制首页数据",
signupHint: isEn
? "By signing up, you agree to our Terms of Service."
: "注册即代表同意我们的服务条款",
realtime: isEn ? "Realtime data" : "实时数据",
highPrecision: isEn ? "High-precision forecast" : "高精度预测",
supabaseMissing: isEn
? "Supabase is not configured. Sign-in is unavailable."
: "Supabase 未配置,无法使用登录",
needEmailPassword: isEn
? "Please enter email and password."
: "请输入邮箱和密码",
signupCheckEmail: isEn
? "Sign-up successful. Please verify your email before signing in."
: "注册成功,请检查邮箱并完成验证后登录。",
} as const;
useEffect(() => {
if (!supabaseReady) return;
@@ -53,7 +91,7 @@ export function LoginClient({ nextPath }: LoginClientProps) {
setErrorText("");
setInfoText("");
if (!supabaseReady) {
setErrorText("Supabase 未配置,无法使用登录");
setErrorText(copy.supabaseMissing);
return;
}
@@ -82,11 +120,11 @@ export function LoginClient({ nextPath }: LoginClientProps) {
setErrorText("");
setInfoText("");
if (!supabaseReady) {
setErrorText("Supabase 未配置,无法使用登录");
setErrorText(copy.supabaseMissing);
return;
}
if (!email.trim() || !password.trim()) {
setErrorText("请输入邮箱和密码");
setErrorText(copy.needEmailPassword);
return;
}
@@ -124,7 +162,7 @@ export function LoginClient({ nextPath }: LoginClientProps) {
router.replace(nextPath);
return;
}
setInfoText("注册成功,请检查邮箱并完成验证后登录。");
setInfoText(copy.signupCheckEmail);
} finally {
setLoading(false);
}
@@ -141,18 +179,17 @@ export function LoginClient({ nextPath }: LoginClientProps) {
<Link
href="/"
className="group absolute left-6 top-6 rounded-full border border-white/10 bg-white/5 p-2 text-slate-400 transition-all hover:bg-white/10 hover:text-white active:scale-90"
title="返回首页"
aria-label="返回首页"
title={copy.backHome}
aria-label={copy.backHome}
>
<ChevronLeft className="h-5 w-5 transition-transform group-hover:-translate-x-0.5" />
</Link>
<div className="mb-8 flex flex-col items-center">
<div className="mb-4 flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-tr from-blue-500 to-indigo-400 shadow-lg shadow-blue-500/20">
<Cloud className="h-10 w-10 text-white" />
</div>
<h1 className="text-3xl font-bold tracking-tight text-white">PolyWeather</h1>
<p className="mt-2 text-sm text-slate-400"></p>
<p className="mt-2 text-sm text-slate-400">{copy.subtitle}</p>
</div>
<button
@@ -162,13 +199,13 @@ export function LoginClient({ nextPath }: LoginClientProps) {
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"
>
<Chrome className="mr-3 h-5 w-5" />
使 Google
{copy.googleOneClick}
</button>
<div className="my-6 flex items-center">
<div className="h-[1px] flex-grow bg-white/10" />
<span className="px-4 text-xs font-medium uppercase tracking-widest text-slate-500">
使
{copy.orEmail}
</span>
<div className="h-[1px] flex-grow bg-white/10" />
</div>
@@ -183,7 +220,7 @@ export function LoginClient({ nextPath }: LoginClientProps) {
: "text-slate-400 hover:text-slate-200"
}`}
>
{copy.login}
</button>
<button
type="button"
@@ -194,7 +231,7 @@ export function LoginClient({ nextPath }: LoginClientProps) {
: "text-slate-400 hover:text-slate-200"
}`}
>
{copy.signup}
</button>
</div>
@@ -218,7 +255,11 @@ export function LoginClient({ nextPath }: LoginClientProps) {
minLength={6}
value={password}
onChange={(event) => setPassword(event.target.value)}
placeholder={isLogin ? "输入密码" : "设置至少 6 位密码"}
placeholder={
isLogin
? copy.passwordLoginPlaceholder
: copy.passwordSignupPlaceholder
}
className="w-full rounded-xl border border-white/10 bg-white/5 py-3.5 pl-12 pr-4 text-white placeholder:text-slate-600 transition-all focus:border-blue-500/50 focus:outline-none focus:ring-2 focus:ring-blue-500/50"
/>
</div>
@@ -228,7 +269,7 @@ export function LoginClient({ nextPath }: LoginClientProps) {
disabled={loading}
className="group mt-8 flex w-full items-center justify-center rounded-xl bg-gradient-to-r from-blue-600 to-indigo-600 py-3.5 font-bold text-white shadow-xl shadow-blue-600/20 transition-all hover:from-blue-500 hover:to-indigo-500 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-70"
>
{isLogin ? "开启天气之旅" : "立即创建账号"}
{isLogin ? copy.loginSubmit : copy.signupSubmit}
<ArrowRight className="ml-2 h-5 w-5 transition-transform group-hover:translate-x-1" />
</button>
</form>
@@ -238,23 +279,21 @@ export function LoginClient({ nextPath }: LoginClientProps) {
<div className="mt-8 text-center">
<p className="text-xs text-slate-500">
{isLogin ? "登录后将为您个性化定制首页数据" : "注册即代表同意我们的服务条款"}
{isLogin ? copy.loginHint : copy.signupHint}
</p>
</div>
{!supabaseReady ? (
<p className="mt-3 text-center text-sm text-rose-300">
Supabase 使
</p>
<p className="mt-3 text-center text-sm text-rose-300">{copy.supabaseMissing}</p>
) : null}
</div>
<div className="absolute bottom-8 flex items-center gap-4 text-sm text-slate-600">
<span className="flex items-center">
<Sun className="mr-1 h-4 w-4" />
<Sun className="mr-1 h-4 w-4" /> {copy.realtime}
</span>
<span className="flex items-center">
<CloudRain className="mr-1 h-4 w-4" />
<CloudRain className="mr-1 h-4 w-4" /> {copy.highPrecision}
</span>
</div>
</div>