mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-25 16:36:34 -05:00
docs: bring back products section
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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"];
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -33,11 +33,6 @@ const nextConfig = {
|
||||
destination: "/docs/introduction",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/products",
|
||||
destination: "/framework",
|
||||
permanent: false,
|
||||
},
|
||||
];
|
||||
},
|
||||
async rewrites() {
|
||||
|
||||
Reference in New Issue
Block a user