diff --git a/src/app/(student)/profile/actions.ts b/src/app/(student)/profile/actions.ts new file mode 100644 index 0000000..7e5c937 --- /dev/null +++ b/src/app/(student)/profile/actions.ts @@ -0,0 +1,35 @@ +"use server"; + +import { headers } from "next/headers"; +import { auth } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; +import bcrypt from "bcryptjs"; + +export async function changePasswordAction(_prevState: unknown, formData: FormData) { + const current = (formData.get("currentPassword") as string) ?? ""; + const next = (formData.get("newPassword") as string) ?? ""; + const confirm = (formData.get("confirmPassword") as string) ?? ""; + + if (!current || !next || !confirm) return { error: "Заполните все поля" }; + if (next !== confirm) return { error: "Пароли не совпадают" }; + if (next.length < 8) return { error: "Новый пароль должен быть не короче 8 символов" }; + + const session = await auth.api.getSession({ headers: await headers() }); + if (!session) return { error: "Сессия истекла, войдите заново" }; + + const account = await prisma.account.findFirst({ + where: { userId: session.user.id, providerId: "credential" }, + }); + if (!account?.password) return { error: "Аккаунт не найден" }; + + const valid = await bcrypt.compare(current, account.password); + if (!valid) return { error: "Неверный текущий пароль" }; + + const hash = await bcrypt.hash(next, 10); + await prisma.account.update({ + where: { id: account.id }, + data: { password: hash }, + }); + + return { success: true }; +} diff --git a/src/app/(student)/profile/change-password-form.tsx b/src/app/(student)/profile/change-password-form.tsx index 21d1af9..205b030 100644 --- a/src/app/(student)/profile/change-password-form.tsx +++ b/src/app/(student)/profile/change-password-form.tsx @@ -1,6 +1,7 @@ "use client"; -import { useState } from "react"; +import { useActionState } from "react"; +import { changePasswordAction } from "./actions"; const inputStyle: React.CSSProperties = { border: "2px solid var(--border)", @@ -10,56 +11,17 @@ const inputStyle: React.CSSProperties = { }; export function ChangePasswordForm() { - const [current, setCurrent] = useState(""); - const [next, setNext] = useState(""); - const [confirm, setConfirm] = useState(""); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(""); - const [success, setSuccess] = useState(false); - - async function handleSubmit(e: React.FormEvent) { - e.preventDefault(); - setError(""); - setSuccess(false); - - if (next !== confirm) { - setError("Пароли не совпадают"); - return; - } - if (next.length < 8) { - setError("Новый пароль должен быть не короче 8 символов"); - return; - } - - setLoading(true); - const res = await fetch("/api/auth/change-password", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ currentPassword: current, newPassword: next, revokeOtherSessions: false }), - }); - setLoading(false); - - if (!res.ok) { - setError("Неверный текущий пароль"); - return; - } - - setCurrent(""); - setNext(""); - setConfirm(""); - setSuccess(true); - } + const [state, formAction, isPending] = useActionState(changePasswordAction, null); return ( -
+
setCurrent(e.target.value)} + name="currentPassword" required className="w-full px-3 py-2 text-sm bg-transparent" style={inputStyle} @@ -74,8 +36,7 @@ export function ChangePasswordForm() { setNext(e.target.value)} + name="newPassword" required minLength={8} className="w-full px-3 py-2 text-sm bg-transparent" @@ -91,8 +52,7 @@ export function ChangePasswordForm() { setConfirm(e.target.value)} + name="confirmPassword" required className="w-full px-3 py-2 text-sm bg-transparent" style={inputStyle} @@ -101,19 +61,19 @@ export function ChangePasswordForm() { placeholder="••••••••" />
- {error && ( -

{error}

+ {state?.error && ( +

{state.error}

)} - {success && ( + {state?.success && (

Пароль успешно изменён.

)}
);