diff --git a/src/app/admin/users/actions.ts b/src/app/admin/users/actions.ts
new file mode 100644
index 0000000..70de4bb
--- /dev/null
+++ b/src/app/admin/users/actions.ts
@@ -0,0 +1,54 @@
+"use server";
+
+import { headers } from "next/headers";
+import { auth } from "@/lib/auth";
+import { prisma } from "@/lib/prisma";
+import bcrypt from "bcryptjs";
+import { sendWelcomeEmail } from "@/lib/email";
+
+export async function createUser(data: {
+ name: string;
+ email: string;
+ password: string;
+ role: string;
+ emailVerified: boolean;
+ sendWelcome: boolean;
+}): Promise<{ success: true; userId: string } | { success: false; error: string }> {
+ const session = await auth.api.getSession({ headers: await headers() });
+ if (!session || session.user.role !== "admin") {
+ return { success: false, error: "Нет доступа" };
+ }
+
+ const { name, email, password, role, emailVerified, sendWelcome } = data;
+
+ if (!name.trim() || !email.trim() || !password.trim()) {
+ return { success: false, error: "Заполните все обязательные поля" };
+ }
+
+ const existing = await prisma.user.findUnique({ where: { email } });
+ if (existing) {
+ return { success: false, error: "Пользователь с таким email уже существует" };
+ }
+
+ const hashedPassword = await bcrypt.hash(password, 10);
+
+ const user = await prisma.user.create({
+ data: { name: name.trim(), email: email.trim().toLowerCase(), role, emailVerified },
+ });
+
+ // Create credential account (Better Auth's internal structure)
+ await prisma.account.create({
+ data: {
+ userId: user.id,
+ accountId: user.id,
+ providerId: "credential",
+ password: hashedPassword,
+ },
+ });
+
+ if (sendWelcome) {
+ await sendWelcomeEmail(user.email, user.name).catch(() => {});
+ }
+
+ return { success: true, userId: user.id };
+}
diff --git a/src/app/admin/users/new/page.tsx b/src/app/admin/users/new/page.tsx
new file mode 100644
index 0000000..8052405
--- /dev/null
+++ b/src/app/admin/users/new/page.tsx
@@ -0,0 +1,29 @@
+import Link from "next/link";
+import { CreateUserForm } from "@/components/admin/create-user-form";
+
+export const metadata = { title: "Новый пользователь" };
+
+export default function NewUserPage() {
+ return (
+
+
+
+
+
+ Создание пользователя
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx
index 84e94f9..e1cf675 100644
--- a/src/app/admin/users/page.tsx
+++ b/src/app/admin/users/page.tsx
@@ -1,6 +1,7 @@
import { prisma } from "@/lib/prisma";
import { Badge } from "@/components/ui/badge";
import Link from "next/link";
+import { UserPlus } from "lucide-react";
const roleLabel: Record = {
admin: "Администратор",
@@ -22,9 +23,18 @@ export default async function UsersPage() {
return (
-
-
Пользователи
-
{users.length} пользователей
+
+
+
Пользователи
+
{users.length} пользователей
+
+
+
+ Добавить пользователя
+
diff --git a/src/components/admin/create-user-form.tsx b/src/components/admin/create-user-form.tsx
new file mode 100644
index 0000000..e5156f4
--- /dev/null
+++ b/src/components/admin/create-user-form.tsx
@@ -0,0 +1,226 @@
+"use client";
+
+import { useState, useTransition } from "react";
+import { useRouter } from "next/navigation";
+import { Eye, EyeOff, RefreshCw } from "lucide-react";
+import { createUser } from "@/app/admin/users/actions";
+
+const ROLES = [
+ { value: "student", label: "Ученик" },
+ { value: "curator", label: "Куратор" },
+ { value: "admin", label: "Администратор" },
+];
+
+function generatePassword() {
+ const chars = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789!@#$";
+ return Array.from({ length: 12 }, () => chars[Math.floor(Math.random() * chars.length)]).join("");
+}
+
+const inputStyle: React.CSSProperties = {
+ border: "2px solid var(--border)",
+ background: "var(--background)",
+ outline: "none",
+ width: "100%",
+ padding: "0.5rem 0.75rem",
+ fontSize: "0.875rem",
+ fontFamily: "inherit",
+};
+
+const focusHandlers = {
+ onFocus: (e: React.FocusEvent
) =>
+ (e.currentTarget.style.borderColor = "var(--foreground)"),
+ onBlur: (e: React.FocusEvent) =>
+ (e.currentTarget.style.borderColor = "var(--border)"),
+};
+
+function Field({ label, required, children }: { label: string; required?: boolean; children: React.ReactNode }) {
+ return (
+
+
+ {children}
+
+ );
+}
+
+function Toggle({ label, hint, checked, onChange }: { label: string; hint?: string; checked: boolean; onChange: (v: boolean) => void }) {
+ return (
+
+
+
+
{label}
+ {hint &&
{hint}
}
+
+
+ );
+}
+
+export function CreateUserForm() {
+ const router = useRouter();
+ const [pending, startTransition] = useTransition();
+ const [error, setError] = useState(null);
+ const [showPassword, setShowPassword] = useState(false);
+
+ const [name, setName] = useState("");
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [role, setRole] = useState("student");
+ const [emailVerified, setEmailVerified] = useState(true);
+ const [sendWelcome, setSendWelcome] = useState(true);
+
+ function handleGenerate() {
+ setPassword(generatePassword());
+ setShowPassword(true);
+ }
+
+ function handleSubmit(e: React.FormEvent) {
+ e.preventDefault();
+ setError(null);
+ startTransition(async () => {
+ const result = await createUser({ name, email, password, role, emailVerified, sendWelcome });
+ if (!result.success) {
+ setError(result.error);
+ return;
+ }
+ router.push(`/admin/users/${result.userId}`);
+ router.refresh();
+ });
+ }
+
+ return (
+
+ );
+}