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

120
lib/dashboard-model.ts Normal file
View File

@@ -0,0 +1,120 @@
import {
Gauge,
MailCheck,
MapPinned,
ShieldCheck,
UsersRound,
type LucideIcon,
} from "lucide-react";
export { dashboardNavigation } from "./dashboard-navigation";
export type PipelineStage = {
title: string;
description: string;
count: number;
meta: string;
icon: LucideIcon;
};
export type DashboardKpi = {
label: string;
value: string;
detail: string;
};
export type ReviewQueueItem = {
title: string;
company: string;
detail: string;
};
export const pipelineStages: PipelineStage[] = [
{
title: "Kampagnen",
description: "Aktive Suchlaeufe nach Kategorie, PLZ und Radius.",
count: 3,
meta: "1 Lauf heute geplant",
icon: MapPinned,
},
{
title: "Lead-Recherche",
description: "Neue Places-Quellen, Kontaktluecken und Dubletten.",
count: 18,
meta: "5 Leads brauchen E-Mail-Quelle",
icon: UsersRound,
},
{
title: "Audit-Freigabe",
description: "Interne Audits warten auf manuelle Pruefung.",
count: 6,
meta: "2 Seiten bereit zur Veroeffentlichung",
icon: ShieldCheck,
},
{
title: "Outreach",
description: "Freigegebene E-Mails und Telefon-Skripte.",
count: 4,
meta: "Keine automatische Kontaktaufnahme",
icon: MailCheck,
},
];
export const dashboardKpis: DashboardKpi[] = [
{
label: "Neue Leads",
value: "18",
detail: "aus 3 aktiven Kampagnen",
},
{
label: "Audit-Entwuerfe",
value: "6",
detail: "manuelle Freigabe offen",
},
{
label: "Outreach bereit",
value: "4",
detail: "E-Mail und Telefon-Skript",
},
{
label: "Antworten",
value: "2",
detail: "manuell nachzutragen",
},
];
export const reviewQueue: ReviewQueueItem[] = [
{
title: "Audit-Freigabe pruefen",
company: "Malerbetrieb Klein",
detail: "Mobile Kontaktfuehrung und lokaler CTA fehlen.",
},
{
title: "Kontaktstrategie bestaetigen",
company: "Physio am Park",
detail: "Telefon zuerst, E-Mail nach persoenlicher Einordnung.",
},
{
title: "Follow-up vormerken",
company: "Tischlerei Weber",
detail: "Respektvolles Follow-up in 5 Tagen, kein Autoversand.",
},
];
export const pipelineHealth = [
{
label: "Recherche",
value: "hoch",
icon: Gauge,
},
{
label: "Freigabe",
value: "manuell",
icon: ShieldCheck,
},
{
label: "Versand",
value: "gesperrt bis Review",
icon: MailCheck,
},
];

View File

@@ -0,0 +1,28 @@
import {
BarChart3,
FileSearch,
LayoutDashboard,
MailCheck,
MapPinned,
OctagonMinus,
Settings,
UsersRound,
type LucideIcon,
} from "lucide-react";
export type DashboardNavigationItem = {
label: string;
href: string;
icon: LucideIcon;
};
export const dashboardNavigation: DashboardNavigationItem[] = [
{ label: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
{ label: "Campaigns", href: "/dashboard/campaigns", icon: MapPinned },
{ label: "Leads", href: "/dashboard/leads", icon: UsersRound },
{ label: "Audits", href: "/dashboard/audits", icon: FileSearch },
{ label: "Outreach", href: "/dashboard/outreach", icon: MailCheck },
{ label: "Analytics", href: "/dashboard/analytics", icon: BarChart3 },
{ label: "Blacklist", href: "/dashboard/blacklist", icon: OctagonMinus },
{ label: "Settings", href: "/dashboard/settings", icon: Settings },
];

47
lib/mock-auth.ts Normal file
View File

@@ -0,0 +1,47 @@
export const MOCK_SESSION_COOKIE_NAME = "webdev_pipeline_mock_session";
export const MOCK_SESSION_COOKIE_VALUE = "mock-admin";
export type MockCookieStore = {
get: (name: string) => { name: string; value: string } | undefined;
};
export type MockSession = {
name: string;
email: string;
};
export const MOCK_ADMIN_SESSION: MockSession = {
name: "Matthias Meister",
email: "matthias@webdev-pipeline.local",
};
export function hasMockSession(cookieStore: MockCookieStore) {
return (
cookieStore.get(MOCK_SESSION_COOKIE_NAME)?.value === MOCK_SESSION_COOKIE_VALUE
);
}
export function getMockSession(cookieStore: MockCookieStore) {
return hasMockSession(cookieStore) ? MOCK_ADMIN_SESSION : null;
}
export function createMockSessionCookie() {
return {
name: MOCK_SESSION_COOKIE_NAME,
value: MOCK_SESSION_COOKIE_VALUE,
httpOnly: true,
sameSite: "lax" as const,
secure: true,
path: "/",
maxAge: 60 * 60 * 24 * 7,
};
}
export function createClearedMockSessionCookie() {
return {
name: MOCK_SESSION_COOKIE_NAME,
value: "",
path: "/",
maxAge: 0,
};
}

7
lib/mock-session.ts Normal file
View File

@@ -0,0 +1,7 @@
import { cookies } from "next/headers";
import { getMockSession } from "@/lib/mock-auth";
export async function getCurrentMockSession() {
return getMockSession(await cookies());
}

11
lib/proxy-auth.ts Normal file
View File

@@ -0,0 +1,11 @@
import { MOCK_SESSION_COOKIE_VALUE } from "./mock-auth";
export function shouldRedirectDashboardRequest(
pathname: string,
sessionCookieValue: string | undefined,
) {
return (
pathname.startsWith("/dashboard") &&
sessionCookieValue !== MOCK_SESSION_COOKIE_VALUE
);
}

5
lib/route-guards.ts Normal file
View File

@@ -0,0 +1,5 @@
import type { MockSession } from "@/lib/mock-auth";
export function getDashboardRedirectPath(session: MockSession | null) {
return session ? null : "/";
}