docs: bring back products section

This commit is contained in:
Bereket Engida
2026-03-01 21:49:09 -08:00
parent fec020f111
commit 07b9d6a3af
7 changed files with 147 additions and 161 deletions

View File

@@ -66,7 +66,7 @@ function EnterpriseHero() {
{/* CTA */}
<div className="flex items-center gap-3 pt-1">
<Link
href="/infrastructure"
href="/products?tab=infrastructure"
className="inline-flex items-center gap-1.5 text-[12px] text-foreground/60 hover:text-foreground/80 font-mono uppercase tracking-wider transition-colors"
>
View Products

View File

@@ -1,49 +0,0 @@
"use client";
import { motion } from "framer-motion";
import { HalftoneBackground } from "@/components/landing/halftone-bg";
import { FrameworkContent, FrameworkHero } from "../products/products-client";
export default function FrameworkPage() {
return (
<div className="relative h-full overflow-x-hidden">
<div className="relative text-foreground h-full">
<div className="flex flex-col lg:flex-row h-full">
<div className="hidden lg:block relative w-full lg:w-[30%] border-b lg:border-b-0 lg:border-r border-foreground/[0.06] overflow-hidden px-5 sm:px-6 lg:px-10">
<div className="hidden lg:block">
<HalftoneBackground />
</div>
<motion.div
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.25 }}
className="h-full"
>
<FrameworkHero />
</motion.div>
</div>
<div className="relative w-full lg:w-[70%] overflow-y-auto overflow-x-hidden no-scrollbar">
<div className="px-5 sm:px-6 lg:px-8 pt-16 lg:pt-16 pb-4">
<motion.h2
initial={{ opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.2 }}
className="text-base text-foreground/90 tracking-tight"
>
Better Auth Framework
</motion.h2>
</div>
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25 }}
>
<FrameworkContent />
</motion.div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,52 +0,0 @@
"use client";
import { motion } from "framer-motion";
import { HalftoneBackground } from "@/components/landing/halftone-bg";
import {
InfrastructureContent,
InfrastructureHero,
} from "../products/products-client";
export default function InfrastructurePage() {
return (
<div className="relative h-full overflow-x-hidden">
<div className="relative text-foreground h-full">
<div className="flex flex-col lg:flex-row h-full">
<div className="hidden lg:block relative w-full lg:w-[30%] border-b lg:border-b-0 lg:border-r border-foreground/[0.06] overflow-hidden px-5 sm:px-6 lg:px-10">
<div className="hidden lg:block">
<HalftoneBackground />
</div>
<motion.div
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.25 }}
className="h-full"
>
<InfrastructureHero />
</motion.div>
</div>
<div className="relative w-full lg:w-[70%] overflow-y-auto overflow-x-hidden no-scrollbar">
<div className="px-5 sm:px-6 lg:px-8 pt-16 lg:pt-16 pb-4">
<motion.h2
initial={{ opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.2 }}
className="text-base text-foreground/90 tracking-tight"
>
Infrastructure
</motion.h2>
</div>
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25 }}
>
<InfrastructureContent />
</motion.div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -260,7 +260,7 @@ function ComparisonCell({ value }: { value: string | boolean }) {
);
}
export function FrameworkHero() {
function FrameworkHero() {
const highlights = [{ label: "License", value: "MIT" }];
return (
@@ -336,12 +336,13 @@ export function FrameworkHero() {
);
}
export function InfrastructureHero() {
function InfrastructureHero() {
const principles = [
{ label: "Framework", value: "Open source" },
{ label: "Users", value: "Unlimited" },
{ label: "Audit logs", value: "From 10k/mo" },
{ label: "Security", value: "$0.0001/event overage" },
{ label: "Self-service SSO", value: "Business+" },
{ label: "SSO", value: "Business+" },
];
const tiers = [
@@ -424,10 +425,10 @@ export function InfrastructureHero() {
Get Started
</Link>
<Link
href="/docs/infrastructure/introduction"
href="/enterprise"
className="inline-flex items-center gap-1.5 text-[12px] text-foreground/70 dark:text-foreground/50 hover:text-foreground/80 font-mono uppercase tracking-wider transition-colors"
>
Read Docs
Contact Sales
<svg
className="h-2.5 w-2.5 opacity-50"
viewBox="0 0 10 10"
@@ -540,7 +541,7 @@ function PricingCard({
);
}
export function FrameworkContent() {
function FrameworkContent() {
const frameworkFeatures = [
{
category: "Authentication",
@@ -692,7 +693,7 @@ export function FrameworkContent() {
);
}
export function InfrastructureContent() {
function InfrastructureContent() {
const tierKeys = ["starter", "pro", "business", "enterprise"] as const;
const tierLabels = ["Starter", "Pro", "Business", "Enterprise"];

View File

@@ -1615,7 +1615,7 @@ export function HeroReadMe({
headline: "Security & observability.",
desc: "Bot detection, real-time behavior analysis, IP blocking, email validation, and more.",
security: true,
href: "/infrastructure",
href: "/products?tab=infrastructure",
managed: true,
},
{
@@ -1623,7 +1623,7 @@ export function HeroReadMe({
headline: "User management.",
desc: "Manage users, sessions, and organizations. Track sign-ups, active users, and growth.",
dashboard: true,
href: "/infrastructure",
href: "/products?tab=infrastructure",
managed: true,
},
].map((feature, i) => (
@@ -2493,7 +2493,7 @@ export function HeroReadMe({
</p>
</div>
<Link
href="/infrastructure"
href="/products?tab=infrastructure"
className="inline-flex items-center gap-1.5 shrink-0 ml-4 px-4 py-2 border border-dashed border-foreground/[0.14] text-foreground dark:text-foreground/80 hover:text-foreground hover:border-foreground/25 hover:bg-foreground/[0.02] transition-all"
>
<span className="font-mono text-[11px] uppercase tracking-widest">

View File

@@ -23,7 +23,11 @@ interface NavFileItem {
const navFiles: NavFileItem[] = [
{ name: "readme", href: "/" },
{ name: "docs", href: "/docs" },
{ name: "infrastructure", href: "/infrastructure" },
];
const productFiles: NavFileItem[] = [
{ name: "framework", href: "/products?tab=framework" },
{ name: "infrastructure", href: "/products?tab=infrastructure" },
];
const resourceFiles: NavFileItem[] = [
@@ -92,12 +96,21 @@ const logoAssets = {
export function StaggeredNavFiles() {
const pathname = usePathname() || "/";
const [productsOpen, setProductsOpen] = useState(false);
const [resourcesOpen, setResourcesOpen] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [mobileView, setMobileView] = useState<"docs" | "nav">("docs");
const [mobileDocSection, setMobileDocSection] = useState(-1);
const productsTimeout = useRef<NodeJS.Timeout>(undefined);
const resourcesTimeout = useRef<NodeJS.Timeout>(undefined);
const openProducts = () => {
clearTimeout(productsTimeout.current);
setProductsOpen(true);
};
const closeProducts = () => {
productsTimeout.current = setTimeout(() => setProductsOpen(false), 150);
};
const openResources = () => {
clearTimeout(resourcesTimeout.current);
setResourcesOpen(true);
@@ -107,8 +120,8 @@ export function StaggeredNavFiles() {
};
const isActive = useCallback((href: string) => pathname === href, [pathname]);
const isDocs = pathname.startsWith("/docs");
const isFrameworkPage = pathname === "/framework";
const isInfrastructurePage = pathname === "/infrastructure";
const isProductPage =
pathname === "/products" || pathname.startsWith("/products/");
const isResourcePage = resourceFiles.some((r) => {
const matchPath = r.path || r.href;
return pathname === matchPath || pathname.startsWith(`${matchPath}/`);
@@ -116,7 +129,7 @@ export function StaggeredNavFiles() {
const isNarrowLeft = isDocs;
const leftPaneWidthClass = isNarrowLeft
? "w-[22vw] max-w-[300px]"
: isFrameworkPage || isInfrastructurePage || isResourcePage
: isProductPage || isResourcePage
? "w-[30%]"
: "w-[40%]";
const navBottomBorderClass = isNarrowLeft ? "border-foreground/5" : "";
@@ -275,6 +288,80 @@ export function StaggeredNavFiles() {
);
})}
{/* Products folder tab */}
<motion.div
initial={{ opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.2, delay: 0.14, ease: "easeOut" }}
className="relative flex-1"
onMouseEnter={openProducts}
onMouseLeave={closeProducts}
>
<div
className={`group/tab flex items-center justify-center gap-1.5 px-2 xl:px-4 py-3 h-full border-r ${tabDividerClass} cursor-pointer transition-colors duration-150 ${
isProductPage
? `bg-background border-b-2 ${activeTabBorderClass}`
: productsOpen
? "bg-foreground/[0.04]"
: "hover:bg-foreground/[0.03]"
}`}
>
<span
className={`font-mono text-xs uppercase tracking-wider transition-colors duration-150 whitespace-nowrap ${
isProductPage
? "text-foreground"
: productsOpen
? "text-foreground/80"
: "text-foreground/65 dark:text-foreground/50 group-hover/tab:text-foreground/75"
}`}
>
products
</span>
<svg
className={`h-2 w-2 text-foreground/55 dark:text-foreground/40 transition-transform duration-200 ${
productsOpen ? "rotate-180" : ""
}`}
viewBox="0 0 10 6"
fill="none"
>
<path
d="M1 1L5 5L9 1"
stroke="currentColor"
strokeWidth="1.2"
/>
</svg>
</div>
<AnimatePresence>
{productsOpen && (
<motion.div
initial={{ opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -4 }}
transition={{ duration: 0.12, ease: "easeOut" }}
className={`absolute top-full left-0 z-50 w-full border ${dropdownBorderClass} bg-background shadow-2xl shadow-black/20 dark:shadow-black/60 py-1`}
>
{productFiles.map((item, i) => (
<motion.div
key={item.name}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.1, delay: i * 0.02 }}
>
<Link
href={item.href}
onClick={() => setProductsOpen(false)}
className="block"
>
<DropdownItem item={item} />
</Link>
</motion.div>
))}
</motion.div>
)}
</AnimatePresence>
</motion.div>
{/* Enterprise tab */}
<motion.div
initial={{ opacity: 0, y: -4 }}
@@ -701,57 +788,61 @@ export function StaggeredNavFiles() {
)}
{/* Default nav items */}
{[...navFiles, ...resourceFiles].map((item, i) => (
<motion.div
key={item.name}
initial={{ opacity: 0, x: -8 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.15, delay: i * 0.03 }}
>
<Link
href={item.href}
target={item.external ? "_blank" : undefined}
rel={item.external ? "noreferrer" : undefined}
onClick={() => setMobileMenuOpen(false)}
className={`flex items-center gap-2.5 px-5 py-3.5 border-b border-foreground/[0.06] transition-colors ${
isActive(item.path || item.href) ||
(item.href === "/docs" && isDocs)
? "bg-foreground/[0.04]"
: "hover:bg-foreground/[0.03]"
}`}
{[...navFiles, ...productFiles, ...resourceFiles].map(
(item, i) => (
<motion.div
key={item.name}
initial={{ opacity: 0, x: -8 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.15, delay: i * 0.03 }}
>
<span
className={`font-mono text-sm uppercase tracking-wider ${
<Link
href={item.href}
target={item.external ? "_blank" : undefined}
rel={item.external ? "noreferrer" : undefined}
onClick={() => setMobileMenuOpen(false)}
className={`flex items-center gap-2.5 px-5 py-3.5 border-b border-foreground/[0.06] transition-colors ${
isActive(item.path || item.href) ||
(item.href === "/docs" && isDocs)
? "text-foreground"
: "text-foreground/75 dark:text-foreground/60"
? "bg-foreground/[0.04]"
: "hover:bg-foreground/[0.03]"
}`}
>
{item.name}
</span>
{item.external && (
<svg
className="h-2.5 w-2.5 text-foreground/45 dark:text-foreground/30 ml-auto"
viewBox="0 0 10 10"
fill="none"
<span
className={`font-mono text-sm uppercase tracking-wider ${
isActive(item.path || item.href) ||
(item.href === "/docs" && isDocs)
? "text-foreground"
: "text-foreground/75 dark:text-foreground/60"
}`}
>
<path
d="M1 9L9 1M9 1H3M9 1V7"
stroke="currentColor"
strokeWidth="1.2"
/>
</svg>
)}
</Link>
</motion.div>
))}
{item.name}
</span>
{item.external && (
<svg
className="h-2.5 w-2.5 text-foreground/45 dark:text-foreground/30 ml-auto"
viewBox="0 0 10 10"
fill="none"
>
<path
d="M1 9L9 1M9 1H3M9 1V7"
stroke="currentColor"
strokeWidth="1.2"
/>
</svg>
)}
</Link>
</motion.div>
),
)}
<motion.div
initial={{ opacity: 0, x: -8 }}
animate={{ opacity: 1, x: 0 }}
transition={{
duration: 0.15,
delay: [...navFiles, ...resourceFiles].length * 0.03,
delay:
[...navFiles, ...productFiles, ...resourceFiles]
.length * 0.03,
}}
className="px-5 pt-4"
>

View File

@@ -33,11 +33,6 @@ const nextConfig = {
destination: "/docs/introduction",
permanent: false,
},
{
source: "/products",
destination: "/framework",
permanent: false,
},
];
},
async rewrites() {