Files
PolyWeather/frontend/components/landing/LandingAuthActions.tsx
2026-05-30 23:43:07 +08:00

192 lines
5.8 KiB
TypeScript

"use client";
import Link from "next/link";
import { useEffect, useState } from "react";
import { LandingLocaleToggle } from "@/components/landing/LandingLocaleToggle";
import type { LandingLocale } from "@/components/landing/landingLocale";
let authStatePromise: Promise<boolean> | null = null;
function getLandingAuthState() {
if (!authStatePromise) {
authStatePromise = Promise.resolve()
.then(async () => {
const { getSupabaseBrowserClient, hasSupabasePublicEnv } = await import(
"@/lib/supabase/client"
);
if (!hasSupabasePublicEnv()) return false;
const supabase = getSupabaseBrowserClient();
const {
data: { session },
} = await supabase.auth.getSession();
return !!session?.user;
})
.catch(() => false);
}
return authStatePromise;
}
function useLandingAuthState() {
const [state, setState] = useState({ checked: false, authenticated: false });
useEffect(() => {
let active = true;
getLandingAuthState().then((authenticated) => {
if (!active) return;
setState({ checked: true, authenticated });
});
return () => {
active = false;
};
}, []);
return state;
}
function ArrowIcon() {
return (
<svg aria-hidden="true" viewBox="0 0 20 20" className="h-3.5 w-3.5" fill="none">
<path
d="M4 10h11m-4.5-4.5L15 10l-4.5 4.5"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
);
}
function UserIcon() {
return (
<svg aria-hidden="true" viewBox="0 0 20 20" className="h-4 w-4" fill="none">
<path
d="M10 10a3.2 3.2 0 1 0 0-6.4A3.2 3.2 0 0 0 10 10Zm-5.5 6.4c.7-2.5 2.7-4 5.5-4s4.8 1.5 5.5 4"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.8"
/>
</svg>
);
}
function trackLandingEvent(
eventType: "enter_terminal" | "login_start",
payload: Record<string, unknown>,
) {
void import("@/lib/app-analytics")
.then(({ trackAppEvent }) => {
trackAppEvent(eventType, payload);
})
.catch(() => {});
}
function trackLoginStart(mode: "login" | "signup") {
trackLandingEvent("login_start", {
entry: "landing",
mode,
next: "/terminal",
});
}
function trackEnterTerminal(entry: string, authenticated: boolean) {
trackLandingEvent("enter_terminal", {
entry,
authenticated,
});
}
function trackTerminalAuthStart(entry: string, mode: "login" | "signup") {
trackLandingEvent("enter_terminal", {
entry,
authenticated: false,
});
trackLoginStart(mode);
}
export function LandingHeaderActions({ locale }: { locale: LandingLocale }) {
const isEn = locale === "en-US";
const { authenticated, checked } = useLandingAuthState();
return (
<div className="flex items-center gap-2">
<LandingLocaleToggle locale={locale} />
{!checked ? (
<div className="h-9 w-24 animate-pulse rounded-md bg-slate-200" />
) : authenticated ? (
<div className="flex items-center gap-2">
<Link
href="/terminal"
onClick={() => trackEnterTerminal("landing_header", true)}
className="inline-flex h-9 items-center gap-2 rounded-md bg-slate-950 px-3 text-sm font-semibold text-white hover:bg-slate-800"
>
{isEn ? "Open" : "进入"}
<ArrowIcon />
</Link>
<Link
href="/account"
className="grid h-9 w-9 place-items-center rounded-md border border-slate-200 bg-white text-slate-600 shadow-sm hover:border-slate-300 hover:text-slate-950"
title={isEn ? "Account" : "账户"}
aria-label={isEn ? "Account" : "账户"}
>
<UserIcon />
</Link>
</div>
) : (
<>
<Link
href="/auth/login?next=%2Fterminal"
onClick={() => trackTerminalAuthStart("landing_header_login", "login")}
className="hidden h-9 items-center rounded-md px-3 text-sm font-semibold text-slate-600 hover:text-slate-950 sm:inline-flex"
>
{isEn ? "Log in" : "登录"}
</Link>
<Link
href="/auth/login?next=%2Fterminal&mode=signup"
onClick={() => trackTerminalAuthStart("landing_header_signup", "signup")}
className="inline-flex h-9 items-center gap-2 rounded-md bg-slate-950 px-3 text-sm font-semibold text-white hover:bg-slate-800"
>
{isEn ? "Start" : "开始使用"}
<ArrowIcon />
</Link>
</>
)}
</div>
);
}
export function LandingHeroActions({ locale }: { locale: LandingLocale }) {
const isEn = locale === "en-US";
const { authenticated, checked } = useLandingAuthState();
const href = checked && !authenticated ? "/auth/login?next=%2Fterminal" : "/terminal";
const handleOpenProduct = () => {
if (checked && !authenticated) {
trackTerminalAuthStart("landing_hero_login", "login");
return;
}
trackEnterTerminal("landing_hero", authenticated);
};
return (
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
<Link
href={href}
onClick={handleOpenProduct}
className="inline-flex h-11 items-center justify-center gap-2 rounded-md bg-slate-950 px-5 text-sm font-bold text-white shadow-sm hover:bg-slate-800"
>
{isEn ? "Open product" : "进入产品"}
<ArrowIcon />
</Link>
<Link
href="/account"
className="inline-flex h-11 items-center justify-center gap-2 rounded-md border border-slate-200 bg-white px-5 text-sm font-bold text-slate-700 shadow-sm hover:border-slate-300 hover:text-slate-950"
>
{isEn ? "Subscribe / account" : "订阅 / 账户"}
</Link>
</div>
);
}