feat: AI Directory Platform 底座升级 + 五套主题站点
- 数据模型: vote/review/submission/claim/use_case/audience/collection/event (+tool 冗余统计列) - 社区: 投票(限流+原子toggle+API) / 评论(评分+优缺点+Helpful+聚合回写) / 提交(重复检测+审批发布) / 认领(Owner白名单编辑) - Ranking Engine: trending/popular/most-voted/most-saved/highest-rated/new/editors-choice (纯函数可测) - 发现体系: Use Case + Audience + Collection 三维发现 / /ai-tools-for / /best SEO 页 - Programmatic SEO: 页面生成器 / sitemap 270条 / AggregateRating/FAQPage/Breadcrumb/ItemList schema / alternatives + vs 对比页 - Analytics: event 事件流 + 外链跳转埋点 + /admin/analytics CTR 与搜索词分析 - Admin: submissions/claims/reviews/discovery/vote-audit/analytics 六个新后台 + 仪表盘扩展 - 主题系统: themes registry + 五套站点 (classic/product/editorial/niche/minimal) 构建期注入 - 认证: GitHub OAuth (env门控) + 登录回跳 redirect - i18n: 新增 7 个命名空间中英文案 (546 键一致) - 测试: bun test 32 用例 (ranking/slug/去重/SEO生成器) + Playwright e2e 脚本 - 种子: 14分类/16用例/9受众/40工具/6用户/投票/评论/6合集/7718事件
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
import { Code2, Terminal, Workflow, Layers } from "lucide-react";
|
||||
import { ToolCard } from "@/components/tool-card";
|
||||
import { LocaleLink } from "@/components/locale-link";
|
||||
import { SectionHeader } from "../shared/section";
|
||||
import { getHomeDataSafe } from "../shared/home-data";
|
||||
import type { ThemeModule } from "../types";
|
||||
|
||||
/** Niche:垂直行业目录(AI for Developers) */
|
||||
const brand = {
|
||||
name: "DevAI.tools",
|
||||
tagline: "AI tools for developers — curated, tested, ranked by the community.",
|
||||
headline: "AI tools for developers",
|
||||
subheadline:
|
||||
"Code assistants, DevOps automation, testing copilots and everything else you ship with.",
|
||||
};
|
||||
|
||||
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 NicheHome({ 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" />
|
||||
{brand.headline}
|
||||
</p>
|
||||
<p className="text-xs leading-relaxed text-muted-foreground">
|
||||
{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 const nicheTheme: ThemeModule = {
|
||||
id: "niche",
|
||||
brand,
|
||||
HomePage: NicheHome,
|
||||
};
|
||||
|
||||
export { navItems as nicheNavItems, footerGroups as nicheFooterGroups };
|
||||
Reference in New Issue
Block a user