refactor: 主题系统 → monorepo 五个独立网站

- packages/core: @aidir/core 底座库(src/pages 页面组件体 1:1 镜像路由树 / kit 站点外壳工厂 / content/blog / scripts)
- sites/: 5 个独立 Next.js 网站(ai-stack / launchai / the-ai-ledger / devai-tools / findai)
  - 每站独立 package.json / next.config / tailwind / messages / .env / 构建
  - src/app 约 56 个薄路由文件(re-export 底座页面 + 内联路由段配置 revalidate/dynamic)
  - src/site/brand.tsx 站点配置 + src/site/home.tsx 站点首页(createMarketingLayout / createHomePage 工厂)
- generate-site.mjs: 站点生成器(新增网站 = SITES 加条目,零 Core 修改)
- start-all-sites.sh: 一键启动 3001-3005
- next-config.mjs: 底座统一 Next 配置工厂(next-intl + MDX + standalone)
- 五站共享同一套 PostgreSQL / Better Auth / Admin(设计文档 §42)
- 验证: 5 站 build OK / typecheck OK / 55 路由冒烟 200 / 品牌-内容-截图 差异确认 / 登录-投票-重复检测 e2e PASS
This commit is contained in:
AI Directory Platform
2026-09-08 19:19:33 +00:00
parent e46b4b52a3
commit 4a1a034386
699 changed files with 19829 additions and 2029 deletions
+192
View File
@@ -0,0 +1,192 @@
// 自动生成:站点首页组件(源自底座 niche 形态,品牌来自 @/site/brand)
import { siteConfig } from "@/site/brand";
import { Code2, Terminal, Workflow, Layers } from "lucide-react";
import { ToolCard } from "@/components/tool-card";
import { LocaleLink } from "@/components/locale-link";
import { SectionHeader } from "@/kit/section";
import { getHomeDataSafe } from "@/kit/home-data";
/** Niche:垂直行业目录(AI for Developers) */
const navItems = [
{ label: "Tools", href: "/tools" },
{ label: "Workflows", href: "/best" },
{ label: "Use Cases", href: "/ai-tools-for" },
{ label: "Rankings", href: "/rankings" },
{ label: "Submit", href: "/submit" },
];
const footerGroups = [
{
title: "Directory",
links: [
{ label: "All Tools", href: "/tools" },
{ label: "Rankings", href: "/rankings" },
{ label: "Use Cases", href: "/ai-tools-for" },
],
},
{
title: "Build",
links: [
{ label: "Workflows", href: "/best" },
{ label: "Submit a Tool", href: "/submit" },
{ label: "Blog", href: "/blog" },
],
},
{
title: "Legal",
links: [
{ label: "Terms", href: "/terms" },
{ label: "Privacy", href: "/privacy" },
],
},
];
async function SiteHome({ locale }: { locale: string }) {
const data = await getHomeDataSafe(6);
return (
<div className="min-h-screen bg-background">
{/* Terminal 风格 Hero */}
<section className="border-b border-border bg-[hsl(var(--secondary))]/40">
<div className="mx-auto max-w-5xl px-4 py-14 sm:px-6 md:py-20">
<div className="mx-auto max-w-2xl overflow-hidden rounded-xl border border-border bg-background shadow-sm">
<div className="flex items-center gap-1.5 border-b border-border bg-secondary/50 px-4 py-2.5">
<span className="h-2.5 w-2.5 rounded-full bg-red-400" />
<span className="h-2.5 w-2.5 rounded-full bg-amber-400" />
<span className="h-2.5 w-2.5 rounded-full bg-emerald-400" />
<span className="ml-2 text-xs text-muted-foreground">devai — zsh</span>
</div>
<div className="space-y-1.5 p-5 font-mono text-sm">
<p className="text-muted-foreground">
<span className="text-emerald-600 dark:text-emerald-400">$</span>{" "}
find-ai-tools --for developer
</p>
<p className="flex items-center gap-2 text-foreground">
<Terminal className="h-3.5 w-3.5 text-emerald-600 dark:text-emerald-400" />
{siteConfig.brand.headline}
</p>
<p className="text-xs leading-relaxed text-muted-foreground">
{siteConfig.brand.subheadline}
</p>
<p className="pt-2">
<LocaleLink
href="/tools"
className="inline-block rounded-md bg-primary px-4 py-1.5 font-sans text-xs font-semibold text-primary-foreground hover:opacity-90"
>
Browse tools
</LocaleLink>
</p>
</div>
</div>
</div>
</section>
<div className="mx-auto max-w-5xl space-y-12 px-4 py-12 sm:px-6">
{/* Use Cases as workflows */}
{data.useCases.length > 0 && (
<section>
<SectionHeader
title="Ship faster with AI"
subtitle="Pick your workflow — we ranked the tools for it"
viewAllHref="/ai-tools-for"
viewAllLabel="All use cases"
/>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{data.useCases.slice(0, 6).map((uc) => (
<LocaleLink
key={uc.id}
href={`/ai-tools-for/${uc.slug}`}
className="group rounded-xl border border-border p-4 transition-all hover:border-primary/40 hover:bg-primary/5"
>
<div className="flex items-center justify-between">
<Workflow className="h-4 w-4 text-primary" />
<span className="text-xs text-muted-foreground tabular-nums">
{uc.toolCount} tools
</span>
</div>
<h3 className="mt-2 text-sm font-semibold text-foreground group-hover:text-primary">
{locale === "zh" && uc.nameZh ? uc.nameZh : uc.nameEn}
</h3>
</LocaleLink>
))}
</div>
</section>
)}
{/* Trending tools */}
{data.trending.length > 0 && (
<section>
<SectionHeader
title="Most upvoted this week"
viewAllHref="/rankings?board=trending"
/>
<div className="grid gap-3 md:grid-cols-2">
{data.trending.slice(0, 6).map((tool, i) => (
<ToolCard
key={tool.id}
tool={tool}
locale={locale}
rank={i + 1}
/>
))}
</div>
</section>
)}
{/* Workflows(collections) */}
{data.collections.length > 0 && (
<section>
<SectionHeader
title="Workflows & stacks"
subtitle="Batteries-included tool stacks for common dev tasks"
viewAllHref="/best"
viewAllLabel="All workflows"
/>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{data.collections.slice(0, 3).map((col) => (
<LocaleLink
key={col.id}
href={`/best/${col.slug}`}
className="group rounded-xl border border-border p-4 transition-all hover:border-primary/40"
>
<Layers className="h-4 w-4 text-primary" />
<h3 className="mt-2 text-sm font-semibold text-foreground group-hover:text-primary">
{locale === "zh" && col.nameZh ? col.nameZh : col.nameEn}
</h3>
<p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
{locale === "zh" && col.introZh ? col.introZh : col.introEn}
</p>
</LocaleLink>
))}
</div>
</section>
)}
{/* Categories */}
{data.categories.length > 0 && (
<section>
<SectionHeader title="Browse by category" viewAllHref="/tools" />
<div className="flex flex-wrap gap-2">
{data.categories.slice(0, 14).map((cat) => (
<LocaleLink
key={cat.id}
href={`/category/${cat.slug}`}
className="rounded-lg border border-border px-3 py-1.5 font-mono text-xs text-foreground transition-colors hover:border-primary/40 hover:bg-primary/5"
>
{locale === "zh" && cat.nameZh ? cat.nameZh : cat.nameEn}
<span className="ml-1.5 text-muted-foreground">
{cat.toolCount}
</span>
</LocaleLink>
))}
</div>
</section>
)}
</div>
</div>
);
}
export { SiteHome };