Move business logic out of App Router into module packages, add boundary validation scripts, and keep all routes as thin re-exports without changing URLs or API behavior. Co-authored-by: Cursor <cursoragent@cursor.com>
98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
"use client";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
export function Section({
|
|
children,
|
|
className,
|
|
title,
|
|
description,
|
|
}: {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
title?: string;
|
|
description?: string;
|
|
}) {
|
|
return (
|
|
<section className={cn("space-y-4", className)}>
|
|
{title ? (
|
|
<div>
|
|
<h2 className="font-semibold text-secondary">{title}</h2>
|
|
{description ? <p className="mt-1 text-sm text-[var(--muted)]">{description}</p> : null}
|
|
</div>
|
|
) : null}
|
|
{children}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export function Hero({
|
|
badge,
|
|
title,
|
|
description,
|
|
actions,
|
|
}: {
|
|
badge?: string;
|
|
title: string;
|
|
description: string;
|
|
actions?: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<section className="relative overflow-hidden border-b border-[var(--border)] bg-gradient-to-b from-[var(--beauty-accent-soft)] to-[var(--canvas)] px-4 py-20 sm:px-6">
|
|
<div className="mx-auto max-w-4xl text-center">
|
|
{badge ? (
|
|
<span className="mb-4 inline-block rounded-full bg-[var(--beauty-accent-soft)] px-4 py-1 text-sm font-medium text-[var(--beauty-accent)]">
|
|
{badge}
|
|
</span>
|
|
) : null}
|
|
<h1 className="text-4xl font-bold tracking-tight text-secondary sm:text-5xl">{title}</h1>
|
|
<p className="mx-auto mt-4 max-w-2xl text-lg text-[var(--muted)]">{description}</p>
|
|
{actions ? <div className="mt-8 flex flex-wrap items-center justify-center gap-3">{actions}</div> : null}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export function StatWidget({
|
|
label,
|
|
value,
|
|
hint,
|
|
icon: Icon,
|
|
}: {
|
|
label: string;
|
|
value: string | number;
|
|
hint?: string;
|
|
icon?: React.ComponentType<{ className?: string }>;
|
|
}) {
|
|
return (
|
|
<div className="rounded-2xl border border-[var(--border)] bg-[var(--surface)] p-5 shadow-[var(--shadow-sm)] transition-shadow hover:shadow-[var(--shadow-md)]">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div>
|
|
<p className="text-sm text-[var(--muted)]">{label}</p>
|
|
<p className="mt-2 text-3xl font-bold tracking-tight text-secondary">{value}</p>
|
|
{hint ? <p className="mt-1 text-xs text-[var(--muted)]">{hint}</p> : null}
|
|
</div>
|
|
{Icon ? (
|
|
<div className="rounded-xl bg-[var(--beauty-accent-soft)] p-2.5 text-[var(--beauty-accent)]">
|
|
<Icon className="h-5 w-5" />
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function PageSection({
|
|
children,
|
|
className,
|
|
}: {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}) {
|
|
return (
|
|
<div className={cn("mx-auto max-w-6xl px-4 py-8 sm:px-6 lg:py-10", className)}>{children}</div>
|
|
);
|
|
}
|
|
|
|
export { SearchResultRow, TimelineItem } from "./BeautyCards";
|