Add Better Auth admin authentication
This commit is contained in:
@@ -1,9 +1,41 @@
|
||||
import { ArrowRight, LockKeyhole, UserPlus } from "lucide-react";
|
||||
"use client"
|
||||
import { type FormEvent, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ArrowRight, LockKeyhole } from "lucide-react";
|
||||
|
||||
import { signInMock, signUpMock } from "@/app/actions/auth";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export function AuthEntry() {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [pending, setPending] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
setError(null);
|
||||
setPending(true);
|
||||
|
||||
const formData = new FormData(event.currentTarget);
|
||||
const email = String(formData.get("email") ?? "");
|
||||
const password = String(formData.get("password") ?? "");
|
||||
|
||||
const result = await authClient.signIn.email({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
setPending(false);
|
||||
|
||||
if (result.error) {
|
||||
setError(result.error.message ?? "Authentifizierung fehlgeschlagen.");
|
||||
return;
|
||||
}
|
||||
|
||||
router.replace("/dashboard");
|
||||
router.refresh();
|
||||
}
|
||||
|
||||
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]">
|
||||
@@ -19,8 +51,9 @@ export function AuthEntry() {
|
||||
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.
|
||||
Melde dich mit dem Admin-Konto an, um Kampagnen, Lead-Qualitaet,
|
||||
Audit-Freigaben und Outreach-Schritte in einem Arbeitsbereich zu
|
||||
steuern.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -43,37 +76,56 @@ export function AuthEntry() {
|
||||
<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
|
||||
Admin Login
|
||||
</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.
|
||||
Melde dich mit E-Mail und Passwort an.
|
||||
</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"
|
||||
<form className="mt-8 grid gap-3" onSubmit={handleSubmit}>
|
||||
<label className="block space-y-2 text-sm font-medium">
|
||||
<span>E-Mail</span>
|
||||
<input
|
||||
name="email"
|
||||
type="email"
|
||||
className="h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring/30"
|
||||
autoComplete="email"
|
||||
required
|
||||
placeholder="admin@firma.de"
|
||||
/>
|
||||
</label>
|
||||
<label className="block space-y-2 text-sm font-medium">
|
||||
<span>Passwort</span>
|
||||
<input
|
||||
name="password"
|
||||
type="password"
|
||||
className="h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring/30"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
minLength={8}
|
||||
placeholder="mindestens 8 Zeichen"
|
||||
/>
|
||||
</label>
|
||||
{error ? (
|
||||
<p
|
||||
className="text-sm leading-6 text-destructive"
|
||||
role="alert"
|
||||
>
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<UserPlus />
|
||||
Sign up
|
||||
</span>
|
||||
<ArrowRight />
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<Button
|
||||
className="w-full justify-between"
|
||||
size="lg"
|
||||
disabled={pending}
|
||||
>
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<LockKeyhole />
|
||||
Anmelden
|
||||
</span>
|
||||
{pending ? "..." : <ArrowRight />}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { ConvexProvider, ConvexReactClient } from "convex/react";
|
||||
import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
|
||||
const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL;
|
||||
|
||||
if (!convexUrl) {
|
||||
@@ -11,6 +14,20 @@ if (!convexUrl) {
|
||||
|
||||
const convex = new ConvexReactClient(convexUrl);
|
||||
|
||||
export function ConvexClientProvider({ children }: { children: ReactNode }) {
|
||||
return <ConvexProvider client={convex}>{children}</ConvexProvider>;
|
||||
export function ConvexClientProvider({
|
||||
children,
|
||||
initialToken,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
initialToken?: string | null;
|
||||
}) {
|
||||
return (
|
||||
<ConvexBetterAuthProvider
|
||||
client={convex}
|
||||
authClient={authClient}
|
||||
initialToken={initialToken}
|
||||
>
|
||||
<ConvexProvider client={convex}>{children}</ConvexProvider>
|
||||
</ConvexBetterAuthProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,14 +4,18 @@ import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { LogOut } from "lucide-react";
|
||||
|
||||
import { signOutMock } from "@/app/actions/auth";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { dashboardNavigation } from "@/lib/dashboard-navigation";
|
||||
import type { MockSession } from "@/lib/mock-auth";
|
||||
import { useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export function DashboardSidebar({ session }: { session: MockSession }) {
|
||||
export function DashboardSidebar() {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const [isSigningOut, setIsSigningOut] = useState(false);
|
||||
const { data: session, isPending } = authClient.useSession();
|
||||
|
||||
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">
|
||||
@@ -59,17 +63,27 @@ export function DashboardSidebar({ session }: { session: MockSession }) {
|
||||
|
||||
<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-sm font-medium">
|
||||
{isPending ? "Lade..." : session?.user?.name ?? "Admin"}
|
||||
</p>
|
||||
<p className="truncate text-xs text-muted-foreground">
|
||||
{session.email}
|
||||
{session?.user?.email ?? "admin@local"}
|
||||
</p>
|
||||
</div>
|
||||
<form action={signOutMock}>
|
||||
<Button className="w-full justify-start" variant="outline">
|
||||
<LogOut />
|
||||
Sign out
|
||||
</Button>
|
||||
</form>
|
||||
<Button
|
||||
className="w-full justify-start"
|
||||
variant="outline"
|
||||
onClick={async () => {
|
||||
setIsSigningOut(true);
|
||||
await authClient.signOut();
|
||||
router.replace("/login");
|
||||
router.refresh();
|
||||
}}
|
||||
disabled={isSigningOut}
|
||||
>
|
||||
<LogOut />
|
||||
{isSigningOut ? "Abmeldung..." : "Abmelden"}
|
||||
</Button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user