Add quick enroll button to admin users table

Adds a "+ Доступ" button to each user row in the admin users table.
Clicking it opens a centered modal with a course dropdown, optional
expiry date, and a Server Action that upserts CourseEnrollment, logs
to AccessLog, and sends sendCourseAccessEmail on new enrollments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 14:27:57 +05:00
parent 4f5b5c535a
commit b2fa98051f
3 changed files with 329 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
"use server";
import { prisma } from "@/lib/prisma";
import { headers } from "next/headers";
import { auth } from "@/lib/auth";
import { sendCourseAccessEmail } from "@/lib/email";
import { revalidatePath } from "next/cache";
async function requireAdmin() {
const session = await auth.api.getSession({ headers: await headers() });
if (!session || session.user.role !== "admin") throw new Error("Forbidden");
return session;
}
export async function grantCourseAccess(
userId: string,
courseId: string,
expiresAt: Date | null
): Promise<{ ok: true } | { error: string }> {
try {
const session = await requireAdmin();
const [user, course] = await Promise.all([
prisma.user.findUnique({ where: { id: userId }, select: { email: true, name: true } }),
prisma.course.findUnique({ where: { id: courseId }, select: { title: true } }),
]);
if (!user) return { error: "Пользователь не найден" };
if (!course) return { error: "Курс не найден" };
const existing = await prisma.courseEnrollment.findUnique({
where: { userId_courseId: { userId, courseId } },
});
await prisma.courseEnrollment.upsert({
where: { userId_courseId: { userId, courseId } },
update: { expiresAt },
create: { userId, courseId, expiresAt },
});
await prisma.accessLog.create({
data: {
courseId,
userId,
action: "granted",
method: "quick",
grantedById: session.user.id,
},
});
// Send email only on new enrollment (not on update)
if (!existing) {
await sendCourseAccessEmail(user.email, user.name ?? user.email, course.title).catch(
(e) => console.error("[enroll-action] sendCourseAccessEmail:", e)
);
}
revalidatePath("/admin/users");
revalidatePath(`/admin/users/${userId}`);
return { ok: true };
} catch (e) {
console.error("[enroll-action] grantCourseAccess:", e);
return { error: "Произошла ошибка. Попробуйте ещё раз." };
}
}
export async function getPublishedCourses(): Promise<{ id: string; title: string }[]> {
await requireAdmin();
return prisma.course.findMany({
where: { published: true },
select: { id: true, title: true },
orderBy: { title: "asc" },
});
}