Add admin command menu, mobile drawer and animated course progress
- cmdk 1.1.1: Cmd/Ctrl+K command menu in the admin panel (students, courses, lessons, admin sections) plus a "Search" button in the sidebar. New read-only endpoint GET /api/admin/search with the same admin role check as /api/admin/export-users; query length 2-100 chars. - vaul 1.1.2: ResponsiveDialog wrapper (Base UI Dialog on desktop, bottom drawer below 768px) styled per DS-2. Used for the storefront checkout on the dashboard and for course navigation on phones (tablet and desktop unchanged). - @number-flow/react 0.6.2: animated "N of M lessons" and completion percent in course navigation; respects prefers-reduced-motion. No migrations. Not deployed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { headers } from "next/headers";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
// Read-only lookup for the admin command menu (Ctrl/⌘+K).
|
||||
const MIN_QUERY = 2;
|
||||
const MAX_QUERY = 100;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
if (!session || session.user.role !== "admin") {
|
||||
return NextResponse.json({ error: "Нет доступа" }, { status: 403 });
|
||||
}
|
||||
|
||||
const q = (request.nextUrl.searchParams.get("q") ?? "").trim().slice(0, MAX_QUERY);
|
||||
if (q.length < MIN_QUERY) {
|
||||
return NextResponse.json({ users: [], courses: [], lessons: [] });
|
||||
}
|
||||
|
||||
const contains = { contains: q, mode: "insensitive" as const };
|
||||
|
||||
const [users, courses, lessons] = await Promise.all([
|
||||
prisma.user.findMany({
|
||||
where: { OR: [{ name: contains }, { email: contains }] },
|
||||
select: { id: true, name: true, email: true, role: true },
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 8,
|
||||
}),
|
||||
prisma.course.findMany({
|
||||
where: { OR: [{ title: contains }, { slug: contains }] },
|
||||
select: { id: true, title: true, slug: true, published: true },
|
||||
orderBy: { order: "asc" },
|
||||
take: 6,
|
||||
}),
|
||||
prisma.lesson.findMany({
|
||||
where: { title: contains },
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
published: true,
|
||||
module: { select: { id: true, courseId: true, course: { select: { title: true } } } },
|
||||
},
|
||||
orderBy: { updatedAt: "desc" },
|
||||
take: 8,
|
||||
}),
|
||||
]);
|
||||
|
||||
return NextResponse.json({
|
||||
users,
|
||||
courses,
|
||||
lessons: lessons.map((l) => ({
|
||||
id: l.id,
|
||||
title: l.title,
|
||||
published: l.published,
|
||||
moduleId: l.module.id,
|
||||
courseId: l.module.courseId,
|
||||
courseTitle: l.module.course.title,
|
||||
})),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user