Implement login screen

This commit is contained in:
2025-08-30 10:32:14 +03:00
Unverified
parent 283ed18bc9
commit 6f9c9a6b73
4 changed files with 97 additions and 74 deletions
+2 -58
View File
@@ -5,12 +5,9 @@
* @version 1.0.0 * @version 1.0.0
*/ */
import { initializeProfile } from "../userPanel/profile/profile"; import type { ErrorResponse, RegisterRequest } from "../core/types";
import type { ErrorResponse, LoginResponse, LoginRequest, RegisterRequest } from "../core/types";
import { API_BASE_URL } from "../core/config"; import { API_BASE_URL } from "../core/config";
import { showChat, showLogin, showRegister } from "../navigation"; import { showLogin } from "../navigation";
import { setUser } from "./api";
import { ensureKeysOnLogin } from "./crypto";
import { id } from "../utils/utils"; import { id } from "../utils/utils";
/** /**
@@ -36,58 +33,6 @@ export function showAlert(containerId: string, message: string, type: "success"
container.appendChild(alertDiv); container.appendChild(alertDiv);
} }
/**
* Handles login form submission
* @param {Event} e - Form submission event
*/
async function handleLogin(e: Event): Promise<void> {
e.preventDefault();
const usernameElement = id<HTMLInputElement>('login-username');
const passwordElement = id<HTMLInputElement>('login-password');
const username = usernameElement.value.trim();
const password = passwordElement.value.trim();
if (!username || !password) {
showAlert('login-alerts', 'Пожалуйста, заполните все поля', 'danger');
return;
}
try {
const request: LoginRequest = {
username: username,
password: password
}
const response = await fetch(`${API_BASE_URL}/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(request)
});
if (response.ok) {
const data: LoginResponse = await response.json();
// Store the JWT token
setUser(data.token, data.user)
try {
await ensureKeysOnLogin(password);
} catch (e) {
console.error("Key setup failed:", e);
}
showChat();
initializeProfile(); // Initialize profile after login
} else {
const data: ErrorResponse = await response.json();
showAlert('login-alerts', data.message || 'Неверное имя пользователя или пароль', 'danger');
}
} catch (error) {
showAlert('login-alerts', 'Ошибка соединения с сервером', 'danger');
}
}
/** /**
* Handles registration form submission * Handles registration form submission
* @param {Event} e - Form submission event * @param {Event} e - Form submission event
@@ -159,7 +104,6 @@ async function handleRegister(e: Event): Promise<void> {
* @private * @private
*/ */
function init(): void { function init(): void {
id('login-form-element').addEventListener('submit', handleLogin);
id('register-form-element').addEventListener('submit', handleRegister); id('register-form-element').addEventListener('submit', handleRegister);
} }
+16
View File
@@ -0,0 +1,16 @@
export type AlertType = "success" | "danger"
export interface Alert {
type: AlertType;
message: string;
}
export function AlertsContainer({ alerts }: { alerts: Alert[]}) {
return (
<div>
{alerts.map(alert => {
return <div className={`alert alert-${alert.type}`}>{alert.message}</div>
})}
</div>
)
}
+68 -6
View File
@@ -1,7 +1,24 @@
import { showLogin, showRegister } from "../../navigation"; import { useImmer } from "use-immer";
import { showChat, showRegister } from "../../navigation";
import { AlertsContainer, type Alert, type AlertType } from "../components/Alerts";
import { AuthContainer } from "../components/Auth"; import { AuthContainer } from "../components/Auth";
import type { ErrorResponse, LoginRequest, LoginResponse } from "../../core/types";
import { setUser } from "../../auth/api";
import { ensureKeysOnLogin } from "../../auth/crypto";
import { API_BASE_URL } from "../../core/config";
import { initializeProfile } from "../../userPanel/profile/profile";
import { useRef } from "react";
export default function LoginScreen() { export default function LoginScreen() {
const [alerts, updateAlerts] = useImmer<Alert[]>([]);
function showAlert(type: AlertType, message: string) {
updateAlerts((alerts) => alerts.push({type: type, message: message}));
}
const usernameElement = useRef<HTMLInputElement>(null);
const passwordElement = useRef<HTMLInputElement>(null);
return ( return (
<AuthContainer> <AuthContainer>
<div className="auth-header"> <div className="auth-header">
@@ -12,9 +29,53 @@ export default function LoginScreen() {
<p>Войдите в свой аккаунт</p> <p>Войдите в свой аккаунт</p>
</div> </div>
<div className="auth-body"> <div className="auth-body">
<div id="login-alerts"></div> <AlertsContainer alerts={alerts} />
<form id="login-form-element"> <form
onSubmit={async (e) => {
e.preventDefault();
const username = usernameElement.current!.value.trim();
const password = passwordElement.current!.value.trim();
if (!username || !password) {
showAlert("danger", "Пожалуйста, заполните все поля");
return;
}
try {
const request: LoginRequest = {
username: username,
password: password
}
const response = await fetch(`${API_BASE_URL}/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(request)
});
if (response.ok) {
const data: LoginResponse = await response.json();
// Store the JWT token
setUser(data.token, data.user)
try {
await ensureKeysOnLogin(password);
} catch (e) {
console.error("Key setup failed:", e);
}
showChat();
initializeProfile(); // Initialize profile after login
} else {
const data: ErrorResponse = await response.json();
showAlert("danger", data.message || "Неверное имя пользователя или пароль");
}
} catch (error) {
showAlert("danger", "Ошибка соединения с сервером");
}
}}>
<mdui-text-field <mdui-text-field
label="Имя пользователя" label="Имя пользователя"
id="login-username" id="login-username"
@@ -22,7 +83,8 @@ export default function LoginScreen() {
variant="outlined" variant="outlined"
icon="person--filled" icon="person--filled"
autocomplete="username" autocomplete="username"
required> required
ref={usernameElement}>
</mdui-text-field> </mdui-text-field>
<mdui-text-field <mdui-text-field
label="Пароль" label="Пароль"
@@ -33,7 +95,8 @@ export default function LoginScreen() {
toggle-password toggle-password
icon="password--filled" icon="password--filled"
autocomplete="current-password" autocomplete="current-password"
required> required
ref={passwordElement}>
</mdui-text-field> </mdui-text-field>
<mdui-button type="submit">Войти</mdui-button> <mdui-button type="submit">Войти</mdui-button>
@@ -44,7 +107,6 @@ export default function LoginScreen() {
Ещё нет аккаунта? Ещё нет аккаунта?
<a <a
href="#" href="#"
id="register-link"
className="link" className="link"
onClick={showRegister}> onClick={showRegister}>
Зарегистрируйтесь Зарегистрируйтесь
+1
View File
@@ -51,6 +51,7 @@
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"use-immer": "^0.11.0",
"zustand": "^5.0.8" "zustand": "^5.0.8"
} }
} }