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

82
components/auth-entry.tsx Normal file
View File

@@ -0,0 +1,82 @@
import { ArrowRight, LockKeyhole, UserPlus } from "lucide-react";
import { signInMock, signUpMock } from "@/app/actions/auth";
import { Button } from "@/components/ui/button";
export function AuthEntry() {
return (
<main className="flex min-h-dvh items-center justify-center bg-background px-6 py-10">
<section className="grid w-full max-w-5xl overflow-hidden rounded-lg border bg-card text-card-foreground shadow-sm md:grid-cols-[1.05fr_0.95fr]">
<div className="flex min-h-[520px] flex-col justify-between border-b p-6 md:border-b-0 md:border-r lg:p-8">
<div>
<div className="mb-8 inline-flex size-10 items-center justify-center rounded-lg border bg-background">
<LockKeyhole className="size-5" />
</div>
<p className="text-sm font-medium text-muted-foreground">
WebDev Pipeline MVP
</p>
<h1 className="mt-4 max-w-xl text-3xl font-semibold tracking-normal sm:text-4xl">
Lokale Webdesign-Leads recherchieren, auditieren und freigeben.
</h1>
<p className="mt-4 max-w-lg text-sm leading-6 text-muted-foreground sm:text-base">
Melde dich an, um Kampagnen, Lead-Qualitaet, Audit-Freigaben und
Outreach-Schritte in einem Arbeitsbereich zu steuern.
</p>
</div>
<dl className="grid gap-4 pt-8 sm:grid-cols-3">
{[
["Recherche", "Google Places Quellen und Kontaktluecken."],
["Audit", "Website-Potenzial und Review-Status."],
["Outreach", "Manuelle Freigabe vor Versand."],
].map(([label, value]) => (
<div key={label}>
<dt className="text-sm font-medium">{label}</dt>
<dd className="mt-1 text-sm leading-5 text-muted-foreground">
{value}
</dd>
</div>
))}
</dl>
</div>
<div className="flex flex-col justify-center p-6 lg:p-8">
<div className="mx-auto w-full max-w-sm">
<h2 className="text-2xl font-semibold tracking-normal">
Sign in oder sign up
</h2>
<p className="mt-2 text-sm leading-6 text-muted-foreground">
Die Authentifizierung ist in TASK-1 noch simuliert. Beide
Aktionen setzen eine lokale Mock-Session und leiten ins Dashboard.
</p>
<div className="mt-8 grid gap-3">
<form action={signInMock}>
<Button className="w-full justify-between" size="lg">
<span className="inline-flex items-center gap-2">
<LockKeyhole />
Sign in
</span>
<ArrowRight />
</Button>
</form>
<form action={signUpMock}>
<Button
className="w-full justify-between"
size="lg"
variant="outline"
>
<span className="inline-flex items-center gap-2">
<UserPlus />
Sign up
</span>
<ArrowRight />
</Button>
</form>
</div>
</div>
</div>
</section>
</main>
);
}

View File

@@ -0,0 +1,21 @@
export function DashboardPlaceholderPage({
title,
description,
}: {
title: string;
description: string;
}) {
return (
<main className="px-4 py-5 sm:px-6 lg:px-8">
<section className="mx-auto max-w-7xl rounded-lg border bg-card p-5 text-card-foreground">
<p className="text-sm font-medium text-muted-foreground">
WebDev Pipeline
</p>
<h1 className="mt-2 text-2xl font-semibold tracking-normal">{title}</h1>
<p className="mt-3 max-w-2xl text-sm leading-6 text-muted-foreground">
{description}
</p>
</section>
</main>
);
}

View File

@@ -0,0 +1,76 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { LogOut } from "lucide-react";
import { signOutMock } from "@/app/actions/auth";
import { Button } from "@/components/ui/button";
import { dashboardNavigation } from "@/lib/dashboard-navigation";
import type { MockSession } from "@/lib/mock-auth";
import { cn } from "@/lib/utils";
export function DashboardSidebar({ session }: { session: MockSession }) {
const pathname = usePathname();
return (
<aside className="flex w-full shrink-0 flex-col border-b bg-sidebar text-sidebar-foreground md:sticky md:top-0 md:min-h-dvh md:w-72 md:border-b-0 md:border-r">
<div className="flex h-16 items-center gap-3 border-b px-4">
<div className="flex size-9 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
W
</div>
<div className="min-w-0">
<p className="truncate text-sm font-semibold">WebDev Pipeline</p>
<p className="truncate text-xs text-muted-foreground">
Akquise Workspace
</p>
</div>
</div>
<nav
className="flex gap-1 overflow-x-auto p-3 md:grid md:overflow-visible"
aria-label="Dashboard navigation"
>
{dashboardNavigation.map((item) => {
const Icon = item.icon;
const isActive =
item.href === "/dashboard"
? pathname === item.href
: pathname.startsWith(item.href);
return (
<Link
aria-current={isActive ? "page" : undefined}
className={cn(
"flex h-9 shrink-0 items-center gap-2 rounded-lg px-3 text-sm font-medium transition-colors",
isActive
? "bg-sidebar-primary text-sidebar-primary-foreground"
: "text-muted-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
)}
href={item.href}
key={item.href}
>
<Icon className="size-4" />
<span>{item.label}</span>
</Link>
);
})}
</nav>
<div className="border-t p-3 md:mt-auto">
<div className="mb-3 rounded-lg border bg-background p-3 md:block">
<p className="truncate text-sm font-medium">{session.name}</p>
<p className="truncate text-xs text-muted-foreground">
{session.email}
</p>
</div>
<form action={signOutMock}>
<Button className="w-full justify-start" variant="outline">
<LogOut />
Sign out
</Button>
</form>
</div>
</aside>
);
}