app-template/app/web/src/components/pages/HomeBody.tsx

126 lines
4.8 KiB
TypeScript

/**
* 首页主体:薄 SSR — Astro 壳负责 head/导航,本组件在浏览器渲染。
*/
import { t, type Locale } from "@app/i18n";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useState } from "react";
import { go } from "~/lib/nav";
import { createTRPCReactClient, trpc } from "~/lib/trpc";
export interface HomeBodyProps {
locale: Locale;
}
function ContinueStrip(props: { locale: Locale }) {
const { locale } = props;
const list = trpc.media.playbackContinue.useQuery({ limit: 4 }, { retry: false });
const items = list.data ?? [];
if (items.length === 0) return null;
return (
<section className="u-card u-reveal p-6">
<p className="u-label">{t(locale, "library.continueTitle")}</p>
<div className="mt-3 flex flex-wrap gap-2">
{items.map((item) => (
<button
key={item.mediaItemId}
type="button"
className="u-btn u-btn--ghost"
onClick={() => go(`/${locale}/watch?id=${item.mediaItemId}`)}
>
{item.title}
</button>
))}
</div>
</section>
);
}
function HomeInner({ locale }: HomeBodyProps) {
const tr = (key: Parameters<typeof t>[1]) => t(locale, key);
const libraryHref = locale === "zh-CN" ? "/zh-CN/library" : "/en/library";
const loginHref = locale === "zh-CN" ? "/zh-CN/auth/login" : "/en/auth/login";
const me = trpc.auth.me.useQuery(undefined, { retry: false });
const loggedIn = me.data?.user != null;
const steps = [
{ key: "nav.mounts", href: `/${locale}/mounts` },
{ key: "nav.browse", href: `/${locale}/browse` },
{ key: "nav.library", href: `/${locale}/library` },
{ key: "nav.danmaku", href: `/${locale}/danmaku` },
] as const;
return (
<section className="space-y-10 py-10">
<div className="u-reveal">
<p className="u-label u-label-edge">{tr("meta.tagline")}</p>
<h1 className="mt-3 text-[40px] font-black tracking-tight leading-[1.05]">
{tr("home.heroLine1")}
<br />
{tr("home.heroLine2")}
</h1>
<span className="u-edge-bar mt-5" />
<p
className="mt-5 max-w-xl text-[14px] leading-relaxed"
style={{ color: "var(--ink-2);" }}
>
{tr("home.subtitle")}
</p>
<div className="mt-8 flex flex-wrap gap-3">
<a href={libraryHref} className="u-btn u-btn--edge">
{tr("home.ctaDashboard")}
</a>
{!loggedIn && (
<a href={loginHref} className="u-btn u-btn--ghost">
{tr("home.ctaLogin")}
</a>
)}
</div>
</div>
<ContinueStrip locale={locale} />
<div className="grid gap-6 md:grid-cols-2">
<section className="u-card flex flex-col p-6 u-reveal">
<p className="u-label">{tr("home.stackTitle")}</p>
<p
className="mt-3 text-[14px] leading-relaxed"
style={{ color: "var(--ink-2);" }}
>
{tr("home.stackBody")}
</p>
</section>
<section className="u-card flex flex-col p-6 u-reveal">
<p className="u-label">{tr("home.layerTitle")}</p>
<p
className="mt-3 text-[14px] leading-relaxed"
style={{ color: "var(--ink-2);" }}
>
{tr("home.layerBody")}
</p>
<div className="mt-auto flex flex-wrap gap-2 pt-4">
{steps.map((s, i) => (
<a key={s.key} href={s.href} className="u-btn u-btn--ghost">
<span className="u-mono">{String(i + 1).padStart(2, "0")}</span>
{tr(s.key)}
</a>
))}
</div>
</section>
</div>
</section>
);
}
const trpcClient = createTRPCReactClient();
export default function HomeBody({ locale }: HomeBodyProps) {
const [qc] = useState(() => new QueryClient());
return (
<QueryClientProvider client={qc}>
<trpc.Provider client={trpcClient} queryClient={qc}>
<HomeInner locale={locale} />
</trpc.Provider>
</QueryClientProvider>
);
}