Files
chat-frontend/frontend/app/login/page.tsx

269 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
const API_BASE_URL =
process.env.NEXT_PUBLIC_API_BASE_URL || "http://127.0.0.1:18000";
export default function LoginPage() {
const [login, setLogin] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const router = useRouter();
const handleSubmit = async (e) => {
e.preventDefault();
setError("");
setIsSubmitting(true);
try {
const res = await fetch(`${API_BASE_URL}/api/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ login, password }),
});
if (!res.ok) {
let message = "Неверный логин или пароль";
try {
const data = await res.json();
if (data?.detail && typeof data.detail === "string") {
message = data.detail;
}
} catch {
// ignore malformed/non-json body
}
throw new Error(message);
}
router.push("/");
router.refresh();
} catch (err) {
setError(err?.message || "Ошибка входа");
} finally {
setIsSubmitting(false);
}
};
return (
<div
style={{
display: "flex",
flexDirection: "column",
minHeight: "100vh",
backgroundColor: "var(--bg-color)",
alignItems: "center",
justifyContent: "center",
position: "relative",
}}
>
<div
style={{
position: "absolute",
top: 24,
left: 32,
display: "flex",
alignItems: "center",
gap: 8,
fontSize: 20,
fontWeight: 600,
color: "var(--text-main)",
}}
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 2C6.477 2 2 6.477 2 12C2 17.523 6.477 22 12 22C17.523 22 22 17.523 22 12C22 6.477 17.523 2 12 2ZM12 4C16.418 4 20 7.582 20 12C20 16.418 16.418 20 12 20C7.582 20 4 16.418 4 12C4 7.582 7.582 4 12 4Z"
fill="var(--primary)"
/>
<path
d="M15 13H9C8.448 13 8 12.552 8 12C8 11.448 8.448 11 9 11H15C15.552 11 16 11.448 16 12C16 12.552 15.552 13 15 13Z"
fill="var(--primary)"
/>
</svg>
AI Chat MVP
</div>
<div
style={{
background: "#fff",
padding: "40px",
borderRadius: "16px",
boxShadow: "0 4px 20px rgba(0,0,0,0.05)",
width: "100%",
maxWidth: "420px",
textAlign: "center",
}}
>
<h1
style={{
fontSize: "24px",
fontWeight: 500,
marginBottom: "32px",
color: "var(--text-main)",
}}
>
Вход в систему
</h1>
<form
onSubmit={handleSubmit}
style={{
display: "flex",
flexDirection: "column",
gap: "20px",
textAlign: "left",
}}
>
<div>
<label
style={{
display: "block",
fontSize: "14px",
marginBottom: "8px",
color: "var(--text-main)",
}}
>
Логин
</label>
<input
value={login}
onChange={(e) => setLogin(e.target.value)}
type="text"
placeholder="admin"
required
style={{
width: "100%",
padding: "12px 16px",
borderRadius: "8px",
border: "1px solid var(--border-color)",
fontSize: "15px",
outline: "none",
transition: "border-color 0.2s",
}}
onFocus={(e) => (e.target.style.borderColor = "var(--primary)")}
onBlur={(e) =>
(e.target.style.borderColor = "var(--border-color)")
}
/>
</div>
<div>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: "8px",
}}
>
<label style={{ fontSize: "14px", color: "var(--text-main)" }}>
Пароль
</label>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
style={{
background: "none",
border: "none",
fontSize: "13px",
color: "var(--text-muted)",
display: "flex",
alignItems: "center",
gap: "4px",
padding: 0,
cursor: "pointer",
}}
>
{showPassword ? "Скрыть" : "Показать"}
</button>
</div>
<input
value={password}
onChange={(e) => setPassword(e.target.value)}
type={showPassword ? "text" : "password"}
placeholder="••••••••"
required
style={{
width: "100%",
padding: "12px 16px",
borderRadius: "8px",
border: "1px solid var(--border-color)",
fontSize: "15px",
outline: "none",
transition: "border-color 0.2s",
}}
onFocus={(e) => (e.target.style.borderColor = "var(--primary)")}
onBlur={(e) =>
(e.target.style.borderColor = "var(--border-color)")
}
/>
</div>
{error ? (
<div
style={{
color: "#d32f2f",
fontSize: "14px",
textAlign: "center",
}}
>
{error}
</div>
) : null}
<button
type="submit"
disabled={isSubmitting}
style={{
padding: "14px",
marginTop: "8px",
backgroundColor: isSubmitting
? "var(--border-color)"
: "var(--primary)",
color: "#fff",
border: "none",
borderRadius: "8px",
fontSize: "15px",
fontWeight: 500,
transition: "background-color 0.2s",
cursor: isSubmitting ? "not-allowed" : "pointer",
}}
onMouseOver={(e) => {
if (!isSubmitting) {
e.currentTarget.style.backgroundColor = "var(--primary-hover)";
}
}}
onMouseOut={(e) => {
if (!isSubmitting) {
e.currentTarget.style.backgroundColor = "var(--primary)";
}
}}
>
{isSubmitting ? "Вход..." : "Войти"}
</button>
</form>
<div
style={{
marginTop: "24px",
fontSize: "14px",
color: "var(--text-muted)",
}}
>
Вход доступен только для пользователей, созданных администратором.
</div>
</div>
</div>
);
}