Update routing logic to redirect users to the dashboard after login and evaluation actions. Refactor middleware to handle public routes and adjust navigation links across the application for improved user experience.
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
This commit is contained in:
@@ -24,7 +24,7 @@ export default function LoginPage() {
|
||||
setError("Email ou mot de passe incorrect");
|
||||
return;
|
||||
}
|
||||
window.location.href = "/";
|
||||
window.location.href = "/dashboard";
|
||||
} catch {
|
||||
setError("Erreur de connexion");
|
||||
} finally {
|
||||
|
||||
167
src/app/dashboard/page.tsx
Normal file
167
src/app/dashboard/page.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { format } from "date-fns";
|
||||
import { ConfirmModal } from "@/components/ConfirmModal";
|
||||
import { RadarChart } from "@/components/RadarChart";
|
||||
|
||||
interface Dimension {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface EvalRow {
|
||||
id: string;
|
||||
candidateName: string;
|
||||
candidateRole: string;
|
||||
candidateTeam?: string | null;
|
||||
evaluatorName: string;
|
||||
evaluationDate: string;
|
||||
template?: { name: string; dimensions?: Dimension[] };
|
||||
status: string;
|
||||
dimensionScores?: { dimensionId: string; score: number | null; dimension?: { title: string } }[];
|
||||
}
|
||||
|
||||
function buildRadarData(e: EvalRow) {
|
||||
const dimensions = e.template?.dimensions ?? [];
|
||||
const scoreMap = new Map(
|
||||
(e.dimensionScores ?? []).map((ds) => [ds.dimensionId, ds])
|
||||
);
|
||||
return dimensions
|
||||
.filter((dim) => !(dim.title ?? "").startsWith("[Optionnel]"))
|
||||
.map((dim) => {
|
||||
const ds = scoreMap.get(dim.id);
|
||||
const score = ds?.score;
|
||||
if (score == null) return null;
|
||||
const s = Number(score);
|
||||
if (Number.isNaN(s) || s < 0 || s > 5) return null;
|
||||
const title = dim.title ?? "";
|
||||
return {
|
||||
dimension: title.length > 12 ? title.slice(0, 12) + "…" : title,
|
||||
score: s,
|
||||
fullMark: 5,
|
||||
};
|
||||
})
|
||||
.filter((d): d is { dimension: string; score: number; fullMark: number } => d != null);
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const [evaluations, setEvaluations] = useState<EvalRow[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [deleteTarget, setDeleteTarget] = useState<EvalRow | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/evaluations")
|
||||
.then((r) => r.json())
|
||||
.then(setEvaluations)
|
||||
.catch(() => [])
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<h1 className="font-mono text-lg font-medium text-zinc-800 dark:text-zinc-100">Évaluations</h1>
|
||||
<Link
|
||||
href="/evaluations/new"
|
||||
className="rounded border border-cyan-500/50 bg-cyan-500/10 px-3 py-1.5 font-mono text-xs text-cyan-600 dark:text-cyan-400 hover:bg-cyan-500/20 transition-colors"
|
||||
>
|
||||
+ nouvelle
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="py-12 text-center font-mono text-xs text-zinc-600 dark:text-zinc-500">loading...</div>
|
||||
) : evaluations.length === 0 ? (
|
||||
<div className="py-12 text-center text-zinc-600 dark:text-zinc-500">
|
||||
Aucune évaluation.{" "}
|
||||
<Link href="/evaluations/new" className="text-cyan-600 dark:text-cyan-400 hover:underline">
|
||||
Créer
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{evaluations.map((e) => {
|
||||
const radarData = buildRadarData(e);
|
||||
return (
|
||||
<Link
|
||||
key={e.id}
|
||||
href={`/evaluations/${e.id}`}
|
||||
className="group flex flex-col overflow-hidden rounded-lg border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800 shadow-sm dark:shadow-none hover:border-cyan-500/50 dark:hover:border-cyan-500/30 transition-colors"
|
||||
>
|
||||
<div className="flex flex-1 flex-col p-4">
|
||||
<div className="mb-3 flex items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<h3 className="truncate font-medium text-zinc-800 dark:text-zinc-100">{e.candidateName}</h3>
|
||||
<p className="truncate text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{e.candidateRole}
|
||||
{e.candidateTeam && ` · ${e.candidateTeam}`}
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
className={`shrink-0 font-mono text-xs px-1.5 py-0.5 rounded ${
|
||||
e.status === "submitted" ? "bg-emerald-500/20 text-emerald-600 dark:text-emerald-400" : "bg-amber-500/20 text-amber-600 dark:text-amber-400"
|
||||
}`}
|
||||
>
|
||||
{e.status === "submitted" ? "ok" : "draft"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mb-3 flex flex-wrap gap-x-3 gap-y-0.5 font-mono text-xs text-zinc-500 dark:text-zinc-400">
|
||||
<span>{e.evaluatorName}</span>
|
||||
<span>{format(new Date(e.evaluationDate), "yyyy-MM-dd")}</span>
|
||||
<span>{e.template?.name ?? ""}</span>
|
||||
</div>
|
||||
<div className="mt-auto min-h-[7rem]">
|
||||
{radarData.length > 0 ? (
|
||||
<RadarChart data={radarData} compact />
|
||||
) : (
|
||||
<div className="flex h-28 items-center justify-center rounded bg-zinc-50 dark:bg-zinc-700/30 font-mono text-xs text-zinc-400 dark:text-zinc-500">
|
||||
pas de scores
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex border-t border-zinc-200 dark:border-zinc-600 bg-zinc-50 dark:bg-zinc-700/30 px-4 py-2">
|
||||
<span className="font-mono text-xs text-cyan-600 dark:text-cyan-400 group-hover:underline">→ ouvrir</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
setDeleteTarget(e);
|
||||
}}
|
||||
className="ml-auto font-mono text-xs text-red-500 hover:text-red-400"
|
||||
title="Supprimer"
|
||||
>
|
||||
supprimer
|
||||
</button>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={!!deleteTarget}
|
||||
title="Supprimer l'évaluation"
|
||||
message={
|
||||
deleteTarget
|
||||
? `Supprimer l'évaluation de ${deleteTarget.candidateName} ? Cette action est irréversible.`
|
||||
: ""
|
||||
}
|
||||
confirmLabel="Supprimer"
|
||||
cancelLabel="Annuler"
|
||||
variant="danger"
|
||||
onConfirm={async () => {
|
||||
if (!deleteTarget) return;
|
||||
const res = await fetch(`/api/evaluations/${deleteTarget.id}`, { method: "DELETE" });
|
||||
if (res.ok) setEvaluations((prev) => prev.filter((x) => x.id !== deleteTarget.id));
|
||||
else alert("Erreur lors de la suppression");
|
||||
}}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -212,7 +212,7 @@ export default function EvaluationDetailPage() {
|
||||
return (
|
||||
<div className="py-12 text-center font-mono text-xs text-zinc-600 dark:text-zinc-500">
|
||||
Évaluation introuvable.{" "}
|
||||
<Link href="/" className="text-cyan-600 dark:text-cyan-400 hover:underline">
|
||||
<Link href="/dashboard" className="text-cyan-600 dark:text-cyan-400 hover:underline">
|
||||
← dashboard
|
||||
</Link>
|
||||
</div>
|
||||
@@ -391,7 +391,7 @@ export default function EvaluationDetailPage() {
|
||||
>
|
||||
soumettre
|
||||
</button>
|
||||
<button onClick={() => router.push("/")} className="rounded border border-zinc-300 dark:border-zinc-600 px-3 py-1.5 font-mono text-xs text-zinc-600 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-700">
|
||||
<button onClick={() => router.push("/dashboard")} className="rounded border border-zinc-300 dark:border-zinc-600 px-3 py-1.5 font-mono text-xs text-zinc-600 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-700">
|
||||
← dashboard
|
||||
</button>
|
||||
<button
|
||||
@@ -428,7 +428,7 @@ export default function EvaluationDetailPage() {
|
||||
variant="danger"
|
||||
onConfirm={async () => {
|
||||
const res = await fetch(`/api/evaluations/${id}`, { method: "DELETE" });
|
||||
if (res.ok) router.push("/");
|
||||
if (res.ok) router.push("/dashboard");
|
||||
else alert("Erreur lors de la suppression");
|
||||
}}
|
||||
onCancel={() => setDeleteConfirmOpen(false)}
|
||||
|
||||
218
src/app/page.tsx
218
src/app/page.tsx
@@ -1,167 +1,69 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { format } from "date-fns";
|
||||
import { ConfirmModal } from "@/components/ConfirmModal";
|
||||
import { RadarChart } from "@/components/RadarChart";
|
||||
|
||||
interface Dimension {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface EvalRow {
|
||||
id: string;
|
||||
candidateName: string;
|
||||
candidateRole: string;
|
||||
candidateTeam?: string | null;
|
||||
evaluatorName: string;
|
||||
evaluationDate: string;
|
||||
template?: { name: string; dimensions?: Dimension[] };
|
||||
status: string;
|
||||
dimensionScores?: { dimensionId: string; score: number | null; dimension?: { title: string } }[];
|
||||
}
|
||||
|
||||
function buildRadarData(e: EvalRow) {
|
||||
const dimensions = e.template?.dimensions ?? [];
|
||||
const scoreMap = new Map(
|
||||
(e.dimensionScores ?? []).map((ds) => [ds.dimensionId, ds])
|
||||
);
|
||||
return dimensions
|
||||
.filter((dim) => !(dim.title ?? "").startsWith("[Optionnel]"))
|
||||
.map((dim) => {
|
||||
const ds = scoreMap.get(dim.id);
|
||||
const score = ds?.score;
|
||||
if (score == null) return null;
|
||||
const s = Number(score);
|
||||
if (Number.isNaN(s) || s < 0 || s > 5) return null;
|
||||
const title = dim.title ?? "";
|
||||
return {
|
||||
dimension: title.length > 12 ? title.slice(0, 12) + "…" : title,
|
||||
score: s,
|
||||
fullMark: 5,
|
||||
};
|
||||
})
|
||||
.filter((d): d is { dimension: string; score: number; fullMark: number } => d != null);
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const [evaluations, setEvaluations] = useState<EvalRow[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [deleteTarget, setDeleteTarget] = useState<EvalRow | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/evaluations")
|
||||
.then((r) => r.json())
|
||||
.then(setEvaluations)
|
||||
.catch(() => [])
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
export default function LandingPage() {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<h1 className="font-mono text-lg font-medium text-zinc-800 dark:text-zinc-100">Évaluations</h1>
|
||||
<Link
|
||||
href="/evaluations/new"
|
||||
className="rounded border border-cyan-500/50 bg-cyan-500/10 px-3 py-1.5 font-mono text-xs text-cyan-600 dark:text-cyan-400 hover:bg-cyan-500/20 transition-colors"
|
||||
>
|
||||
+ nouvelle
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="py-12 text-center font-mono text-xs text-zinc-600 dark:text-zinc-500">loading...</div>
|
||||
) : evaluations.length === 0 ? (
|
||||
<div className="py-12 text-center text-zinc-600 dark:text-zinc-500">
|
||||
Aucune évaluation.{" "}
|
||||
<Link href="/evaluations/new" className="text-cyan-600 dark:text-cyan-400 hover:underline">
|
||||
Créer
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<div className="py-16 md:py-24">
|
||||
<h1 className="font-mono text-3xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-50 md:text-4xl">
|
||||
Évaluez la maturité IA/GenAI de vos candidats
|
||||
</h1>
|
||||
<p className="mt-6 font-mono text-base leading-relaxed text-zinc-600 dark:text-zinc-400">
|
||||
Grilles structurées, guide d'entretien, rubriques 1→5, questions de relance.
|
||||
Un outil pensé pour standardiser vos évaluations et générer synthèses et recommandations.
|
||||
</p>
|
||||
<div className="mt-10 flex flex-wrap gap-4">
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="rounded-lg border border-cyan-500 bg-cyan-500 px-5 py-2.5 font-mono text-sm font-medium text-white hover:bg-cyan-600 dark:border-cyan-400 dark:bg-cyan-500/90 dark:text-zinc-900 dark:hover:bg-cyan-400 transition-colors"
|
||||
>
|
||||
Accéder à l'app
|
||||
</Link>
|
||||
<Link
|
||||
href="/auth/signup"
|
||||
className="rounded-lg border border-zinc-300 dark:border-zinc-600 px-5 py-2.5 font-mono text-sm text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors"
|
||||
>
|
||||
Créer un compte
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{evaluations.map((e) => {
|
||||
const radarData = buildRadarData(e);
|
||||
return (
|
||||
<Link
|
||||
key={e.id}
|
||||
href={`/evaluations/${e.id}`}
|
||||
className="group flex flex-col overflow-hidden rounded-lg border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800 shadow-sm dark:shadow-none hover:border-cyan-500/50 dark:hover:border-cyan-500/30 transition-colors"
|
||||
>
|
||||
<div className="flex flex-1 flex-col p-4">
|
||||
<div className="mb-3 flex items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<h3 className="truncate font-medium text-zinc-800 dark:text-zinc-100">{e.candidateName}</h3>
|
||||
<p className="truncate text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{e.candidateRole}
|
||||
{e.candidateTeam && ` · ${e.candidateTeam}`}
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
className={`shrink-0 font-mono text-xs px-1.5 py-0.5 rounded ${
|
||||
e.status === "submitted" ? "bg-emerald-500/20 text-emerald-600 dark:text-emerald-400" : "bg-amber-500/20 text-amber-600 dark:text-amber-400"
|
||||
}`}
|
||||
>
|
||||
{e.status === "submitted" ? "ok" : "draft"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mb-3 flex flex-wrap gap-x-3 gap-y-0.5 font-mono text-xs text-zinc-500 dark:text-zinc-400">
|
||||
<span>{e.evaluatorName}</span>
|
||||
<span>{format(new Date(e.evaluationDate), "yyyy-MM-dd")}</span>
|
||||
<span>{e.template?.name ?? ""}</span>
|
||||
</div>
|
||||
<div className="mt-auto min-h-[7rem]">
|
||||
{radarData.length > 0 ? (
|
||||
<RadarChart data={radarData} compact />
|
||||
) : (
|
||||
<div className="flex h-28 items-center justify-center rounded bg-zinc-50 dark:bg-zinc-700/30 font-mono text-xs text-zinc-400 dark:text-zinc-500">
|
||||
pas de scores
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex border-t border-zinc-200 dark:border-zinc-600 bg-zinc-50 dark:bg-zinc-700/30 px-4 py-2">
|
||||
<span className="font-mono text-xs text-cyan-600 dark:text-cyan-400 group-hover:underline">→ ouvrir</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
setDeleteTarget(e);
|
||||
}}
|
||||
className="ml-auto font-mono text-xs text-red-500 hover:text-red-400"
|
||||
title="Supprimer"
|
||||
>
|
||||
supprimer
|
||||
</button>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={!!deleteTarget}
|
||||
title="Supprimer l'évaluation"
|
||||
message={
|
||||
deleteTarget
|
||||
? `Supprimer l'évaluation de ${deleteTarget.candidateName} ? Cette action est irréversible.`
|
||||
: ""
|
||||
}
|
||||
confirmLabel="Supprimer"
|
||||
cancelLabel="Annuler"
|
||||
variant="danger"
|
||||
onConfirm={async () => {
|
||||
if (!deleteTarget) return;
|
||||
const res = await fetch(`/api/evaluations/${deleteTarget.id}`, { method: "DELETE" });
|
||||
if (res.ok) setEvaluations((prev) => prev.filter((x) => x.id !== deleteTarget.id));
|
||||
else alert("Erreur lors de la suppression");
|
||||
}}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
/>
|
||||
<section className="border-t border-zinc-200 dark:border-zinc-700 py-12">
|
||||
<h2 className="font-mono text-lg font-medium text-zinc-800 dark:text-zinc-200">
|
||||
Fonctionnalités
|
||||
</h2>
|
||||
<ul className="mt-6 grid gap-6 sm:grid-cols-2">
|
||||
<li className="rounded-lg border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800/50 p-4">
|
||||
<span className="font-mono text-sm font-medium text-cyan-600 dark:text-cyan-400">Templates</span>
|
||||
<p className="mt-1 font-mono text-xs text-zinc-600 dark:text-zinc-400">
|
||||
Grilles multi-dimensions (Full 15, Short 8) avec rubriques et signaux par niveau.
|
||||
</p>
|
||||
</li>
|
||||
<li className="rounded-lg border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800/50 p-4">
|
||||
<span className="font-mono text-sm font-medium text-cyan-600 dark:text-cyan-400">Guide d'entretien</span>
|
||||
<p className="mt-1 font-mono text-xs text-zinc-600 dark:text-zinc-400">
|
||||
Questions suggérées, relances IA, notation 1→5 avec justification et exemples.
|
||||
</p>
|
||||
</li>
|
||||
<li className="rounded-lg border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800/50 p-4">
|
||||
<span className="font-mono text-sm font-medium text-cyan-600 dark:text-cyan-400">Radar & synthèse</span>
|
||||
<p className="mt-1 font-mono text-xs text-zinc-600 dark:text-zinc-400">
|
||||
Visualisation radar, findings et recommandations générés automatiquement.
|
||||
</p>
|
||||
</li>
|
||||
<li className="rounded-lg border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800/50 p-4">
|
||||
<span className="font-mono text-sm font-medium text-cyan-600 dark:text-cyan-400">Export</span>
|
||||
<p className="mt-1 font-mono text-xs text-zinc-600 dark:text-zinc-400">
|
||||
Export PDF et CSV pour partage et archivage.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div className="border-t border-zinc-200 dark:border-zinc-700 py-8 text-center">
|
||||
<p className="font-mono text-xs text-zinc-500 dark:text-zinc-400">
|
||||
IA Gen Maturity Evaluator · Équipe Cars Front
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ export function Header() {
|
||||
iag-eval
|
||||
</Link>
|
||||
<nav className="flex items-center gap-6 font-mono text-xs">
|
||||
{status === "authenticated" && (
|
||||
{status === "authenticated" ? (
|
||||
<>
|
||||
<Link href="/" className="text-zinc-500 hover:text-cyan-600 dark:text-zinc-400 dark:hover:text-cyan-400 transition-colors">
|
||||
<Link href="/dashboard" className="text-zinc-500 hover:text-cyan-600 dark:text-zinc-400 dark:hover:text-cyan-400 transition-colors">
|
||||
/dashboard
|
||||
</Link>
|
||||
<Link href="/evaluations/new" className="text-zinc-500 hover:text-cyan-600 dark:text-zinc-400 dark:hover:text-cyan-400 transition-colors">
|
||||
@@ -39,6 +39,10 @@ export function Header() {
|
||||
déconnexion
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<Link href="/auth/login" className="text-zinc-500 hover:text-cyan-600 dark:text-zinc-400 dark:hover:text-cyan-400 transition-colors">
|
||||
Se connecter
|
||||
</Link>
|
||||
)}
|
||||
<ThemeToggle />
|
||||
</nav>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { NextResponse } from "next/server";
|
||||
|
||||
export default auth((req) => {
|
||||
const isLoggedIn = !!req.auth;
|
||||
const isPublicRoute = req.nextUrl.pathname === "/";
|
||||
const isAuthRoute =
|
||||
req.nextUrl.pathname.startsWith("/auth/login") ||
|
||||
req.nextUrl.pathname.startsWith("/auth/signup");
|
||||
@@ -10,8 +11,9 @@ export default auth((req) => {
|
||||
const isAdminRoute = req.nextUrl.pathname.startsWith("/admin");
|
||||
|
||||
if (isApiAuth) return NextResponse.next();
|
||||
if (isPublicRoute) return NextResponse.next();
|
||||
if (isAuthRoute && isLoggedIn) {
|
||||
return NextResponse.redirect(new URL("/", req.nextUrl));
|
||||
return NextResponse.redirect(new URL("/dashboard", req.nextUrl));
|
||||
}
|
||||
if (!isLoggedIn && !isAuthRoute) {
|
||||
return NextResponse.redirect(new URL("/auth/login", req.nextUrl));
|
||||
|
||||
Reference in New Issue
Block a user