diff --git a/src/app/admin/users/[userId]/actions.ts b/src/app/admin/users/[userId]/actions.ts index b462179..78f5af6 100644 --- a/src/app/admin/users/[userId]/actions.ts +++ b/src/app/admin/users/[userId]/actions.ts @@ -4,6 +4,7 @@ import { prisma } from "@/lib/prisma"; import { headers } from "next/headers"; import { auth } from "@/lib/auth"; import { revalidatePath } from "next/cache"; +import bcrypt from "bcryptjs"; async function requireAdmin() { const session = await auth.api.getSession({ headers: await headers() }); @@ -78,6 +79,26 @@ export async function deleteBalanceTransaction(userId: string, txId: string) { revalidatePath(`/admin/users/${userId}`); } +export async function resetUserPassword(userId: string): Promise<{ tempPassword: string }> { + await requireAdmin(); + + const account = await prisma.account.findFirst({ + where: { userId, providerId: "credential" }, + }); + if (!account) throw new Error("Аккаунт с паролем не найден"); + + const chars = "ABCDEFGHJKMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789"; + const tempPassword = Array.from({ length: 10 }, () => chars[Math.floor(Math.random() * chars.length)]).join(""); + + const hash = await bcrypt.hash(tempPassword, 10); + await prisma.account.update({ + where: { id: account.id }, + data: { password: hash }, + }); + + return { tempPassword }; +} + export async function revokeUserAccess(userId: string, courseId: string) { const session = await requireAdmin(); await prisma.courseEnrollment.delete({ diff --git a/src/app/admin/users/[userId]/page.tsx b/src/app/admin/users/[userId]/page.tsx index f4ed166..c5f3d07 100644 --- a/src/app/admin/users/[userId]/page.tsx +++ b/src/app/admin/users/[userId]/page.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import { UserEnrollmentManager } from "@/components/admin/user-enrollment-manager"; import { UserContactEditor } from "@/components/admin/user-contact-editor"; import { UserBalanceBlock } from "@/components/admin/user-balance-block"; +import { ResetPasswordButton } from "@/components/admin/reset-password-button"; interface Props { params: Promise<{ userId: string }>; @@ -77,6 +78,14 @@ export default async function UserPage({ params }: Props) { + {/* Reset password */} +
+

+ Пароль +

+ +
+ {/* Balance */}

diff --git a/src/components/admin/reset-password-button.tsx b/src/components/admin/reset-password-button.tsx new file mode 100644 index 0000000..5343a1b --- /dev/null +++ b/src/components/admin/reset-password-button.tsx @@ -0,0 +1,65 @@ +"use client"; + +import { useState } from "react"; +import { resetUserPassword } from "@/app/admin/users/[userId]/actions"; + +export function ResetPasswordButton({ userId }: { userId: string }) { + const [tempPassword, setTempPassword] = useState(null); + const [isPending, setIsPending] = useState(false); + const [copied, setCopied] = useState(false); + + async function handleReset() { + if (!confirm("Сгенерировать новый временный пароль для пользователя?")) return; + setIsPending(true); + setTempPassword(null); + try { + const { tempPassword: pw } = await resetUserPassword(userId); + setTempPassword(pw); + } catch (e) { + alert(e instanceof Error ? e.message : "Ошибка"); + } finally { + setIsPending(false); + } + } + + async function handleCopy() { + if (!tempPassword) return; + await navigator.clipboard.writeText(tempPassword); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + + return ( +

+ + + {tempPassword && ( +
+

+ Временный пароль — передай пользователю +

+
+ {tempPassword} + +
+

+ Пользователь сможет сменить пароль в профиле после входа. +

+
+ )} +
+ ); +}