141 lines
4.4 KiB
TypeScript
141 lines
4.4 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||
import ky from "ky";
|
||
import useAuthStore from "./stores/useAuthStore";
|
||
import { FormEvent, useRef, useState } from "react";
|
||
|
||
type User = {
|
||
id: string;
|
||
username: string;
|
||
};
|
||
|
||
interface IResult {
|
||
error?: number;
|
||
accessToken?: string;
|
||
user?: User;
|
||
}
|
||
|
||
function PersonalAreaLoginPage() {
|
||
const [setAccessToken, setUser] = useAuthStore((state) => [
|
||
state.setAccessToken,
|
||
state.setUser,
|
||
]);
|
||
|
||
const [username, setUsername] = useState<string>("");
|
||
const [password, setPassword] = useState<string>("");
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||
const usernameRef = useRef<HTMLInputElement>(null);
|
||
const passwordRef = useRef<HTMLInputElement>(null);
|
||
|
||
async function auth(e: FormEvent<HTMLFormElement>) {
|
||
e.preventDefault();
|
||
|
||
setIsLoading(true);
|
||
|
||
try {
|
||
const result: IResult = await ky
|
||
.post(import.meta.env.VITE_COORD_URL + "/login", {
|
||
json: { username, password },
|
||
})
|
||
.json();
|
||
|
||
setIsLoading(false);
|
||
|
||
if (result.error) {
|
||
passwordRef.current?.focus();
|
||
|
||
setPassword("");
|
||
setError("Неверное имя пользователя или пароль");
|
||
return;
|
||
}
|
||
|
||
if (!result.accessToken || !result.user) {
|
||
setError("Не удалось получить данные");
|
||
return;
|
||
}
|
||
|
||
setAccessToken(result.accessToken);
|
||
setUser(result.user);
|
||
} catch (error) {
|
||
setIsLoading(false);
|
||
|
||
if (error instanceof Error) {
|
||
if (error.message === "Failed to fetch") {
|
||
setError("Нет соединения с сервером, попробуйте позже");
|
||
} else {
|
||
setError(error.message);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="p-8 min-h-screen flex flex-col justify-center items-center text-[#F2F2F2]">
|
||
<div className="space-y-12 w-[400px] bg-[#151619] p-8 rounded-lg shadow">
|
||
<p className="text-2xl font-gilroy">Вход в личный кабинет</p>
|
||
<form onSubmit={auth} className="flex flex-col gap-12">
|
||
<div className="flex flex-col gap-8">
|
||
<div className="space-y-1">
|
||
<p className="text-[#C5C7CE] text-sm">Имя пользователя</p>
|
||
<input
|
||
ref={usernameRef}
|
||
required
|
||
type="text"
|
||
value={username}
|
||
onChange={(e) => setUsername(e.target.value)}
|
||
className="px-3 py-2 rounded bg-[#1C1D21] outline-none focus:outline-[#BC75FF] w-full transition-all"
|
||
/>
|
||
</div>
|
||
<div className="space-y-1">
|
||
<p className="text-[#C5C7CE] text-sm">Пароль</p>
|
||
<input
|
||
ref={passwordRef}
|
||
required
|
||
type="password"
|
||
value={password}
|
||
onChange={(e) => setPassword(e.target.value)}
|
||
className="px-3 py-2 rounded bg-[#1C1D21] outline-none focus:outline-[#BC75FF] w-full transition-all"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
type="submit"
|
||
disabled={isLoading}
|
||
className="px-4 py-2 rounded bg-gradient outline-none opacity-95 hover:opacity-100 transition-all disabled:opacity-50 flex justify-center items-center h-10"
|
||
>
|
||
{isLoading ? (
|
||
<svg
|
||
className="animate-spin h-5 w-5"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
fill="none"
|
||
viewBox="0 0 24 24"
|
||
>
|
||
<circle
|
||
className="opacity-25"
|
||
cx="12"
|
||
cy="12"
|
||
r="10"
|
||
stroke="currentColor"
|
||
strokeWidth="4"
|
||
></circle>
|
||
<path
|
||
className="opacity-75"
|
||
fill="currentColor"
|
||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||
></path>
|
||
</svg>
|
||
) : (
|
||
<span>Войти</span>
|
||
)}
|
||
</button>
|
||
</form>
|
||
|
||
<p className="text-sm text-red-500 min-h-[40px]">{error && error}</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default PersonalAreaLoginPage;
|