feat: complete MVP foundation auth and dashboard

This commit is contained in:
2026-06-04 09:05:40 +02:00
parent 20615e12a1
commit df7a955736
32 changed files with 880 additions and 139 deletions

View File

@@ -1,63 +1,130 @@
import { FileSearch, MailCheck, MapPinned, ShieldCheck } from "lucide-react";
const pipelineSteps = [
{
title: "Kampagnen",
description: "Kategorie, PLZ, Radius und Lauf-Limits vorbereiten.",
icon: MapPinned,
},
{
title: "Lead-Audit",
description: "Website-Potenzial, Screenshots und Quellen buendeln.",
icon: FileSearch,
},
{
title: "Freigabe",
description: "Audit-Seite, E-Mail und Telefon-Skript manuell pruefen.",
icon: ShieldCheck,
},
{
title: "Outreach",
description: "Freigegebene Kontakte senden und Antworten nachhalten.",
icon: MailCheck,
},
];
import {
dashboardKpis,
pipelineHealth,
pipelineStages,
reviewQueue,
} from "@/lib/dashboard-model";
export default function DashboardPage() {
return (
<main className="min-h-dvh bg-background px-6 py-8">
<div className="mx-auto flex w-full max-w-6xl flex-col gap-8">
<header className="flex flex-col gap-2 border-b pb-6">
<main className="px-4 py-5 sm:px-6 lg:px-8">
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6">
<header className="flex flex-col gap-3 border-b pb-5 lg:flex-row lg:items-end lg:justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">
Interner Arbeitsbereich
</p>
<h1 className="mt-2 text-3xl font-semibold tracking-normal">
Pipeline-Uebersicht
</h1>
<p className="mt-2 max-w-2xl text-sm leading-6 text-muted-foreground">
Recherche, Audit-Freigabe und Outreach bleiben eng gekoppelt:
wenige gute Leads, manuelle Pruefung, kein automatischer Versand.
</p>
</div>
<p className="text-sm font-medium text-muted-foreground">
Interner Arbeitsbereich
</p>
<h1 className="text-3xl font-semibold tracking-normal">
Dashboard-Platzhalter
</h1>
<p className="max-w-2xl text-sm leading-6 text-muted-foreground">
Hier entsteht der scanbare Funnel fuer Recherche, Audit, Review,
Versand und Follow-up.
Mock-Session aktiv
</p>
</header>
<section className="grid gap-3 md:grid-cols-4">
{pipelineSteps.map((step) => {
const Icon = step.icon;
<section className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
{dashboardKpis.map((kpi) => (
<article
className="rounded-lg border bg-card p-4 text-card-foreground"
key={kpi.label}
>
<p className="text-sm text-muted-foreground">{kpi.label}</p>
<p className="mt-3 text-3xl font-semibold tracking-normal">
{kpi.value}
</p>
<p className="mt-2 text-sm leading-5 text-muted-foreground">
{kpi.detail}
</p>
</article>
))}
</section>
<section className="grid gap-3 xl:grid-cols-4">
{pipelineStages.map((stage) => {
const Icon = stage.icon;
return (
<article
className="rounded-lg border bg-card p-4 text-card-foreground"
key={step.title}
key={stage.title}
>
<Icon className="mb-4 size-5 text-muted-foreground" />
<h2 className="text-sm font-medium">{step.title}</h2>
<div className="flex items-center justify-between gap-4">
<Icon className="size-5 text-muted-foreground" />
<span className="text-2xl font-semibold">{stage.count}</span>
</div>
<h2 className="mt-4 text-sm font-medium">{stage.title}</h2>
<p className="mt-2 text-sm leading-6 text-muted-foreground">
{step.description}
{stage.description}
</p>
<p className="mt-4 rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground">
{stage.meta}
</p>
</article>
);
})}
</section>
<section className="grid gap-3 lg:grid-cols-[1.45fr_0.55fr]">
<div className="rounded-lg border bg-card text-card-foreground">
<div className="border-b p-4">
<h2 className="text-base font-semibold tracking-normal">
Naechste Review-Schritte
</h2>
<p className="mt-1 text-sm leading-6 text-muted-foreground">
Alles bleibt an manuelle Freigabe gekoppelt.
</p>
</div>
<div className="divide-y">
{reviewQueue.map((item) => (
<article
className="grid gap-2 p-4 sm:grid-cols-[1fr_auto]"
key={`${item.title}-${item.company}`}
>
<div>
<h3 className="text-sm font-medium">{item.title}</h3>
<p className="mt-1 text-sm text-muted-foreground">
{item.company}
</p>
</div>
<p className="max-w-sm text-sm leading-6 text-muted-foreground sm:text-right">
{item.detail}
</p>
</article>
))}
</div>
</div>
<div className="rounded-lg border bg-card p-4 text-card-foreground">
<h2 className="text-base font-semibold tracking-normal">
Betriebsmodus
</h2>
<div className="mt-4 grid gap-3">
{pipelineHealth.map((item) => {
const Icon = item.icon;
return (
<div
className="flex items-center justify-between gap-3 rounded-lg border bg-background p-3"
key={item.label}
>
<span className="inline-flex items-center gap-2 text-sm font-medium">
<Icon className="size-4 text-muted-foreground" />
{item.label}
</span>
<span className="text-right text-sm text-muted-foreground">
{item.value}
</span>
</div>
);
})}
</div>
</div>
</section>
</div>
</main>
);