126 lines
4.8 KiB
TypeScript
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>
|
|
);
|
|
}
|