reset endpoint + frontend reset and login pages add
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
import { API_URL, setAccessToken } from "../token";
|
||||
|
||||
export async function login(email: string, password: string) {
|
||||
const body = new URLSearchParams({ username: email, password });
|
||||
const res = await fetch(`${API_URL}/protected/token`, {
|
||||
method: "POST",
|
||||
body,
|
||||
credentials: "include",
|
||||
});
|
||||
if (!res.ok) throw new Error("Неверный email или пароль");
|
||||
const data = await res.json();
|
||||
setAccessToken(data.access_token);
|
||||
return data;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
export async function requestPasswordReset(email: string): Promise<void> {
|
||||
const res = await fetch(`http://localhost:8000/protected/reset?${new URLSearchParams({ email })}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => null);
|
||||
throw new Error(typeof body?.detail === "string" ? body.detail : "Не удалось отправить запрос");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export const API_URL = import.meta.env.VITE_BACKEND_URL;
|
||||
|
||||
let accessToken: string | null = null;
|
||||
|
||||
export function setAccessToken(token: string | null) {
|
||||
accessToken = token;
|
||||
}
|
||||
|
||||
export function getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
export async function authFetch(path: string, init: RequestInit = {}) {
|
||||
const headers = new Headers(init.headers);
|
||||
if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
|
||||
return fetch(`${API_URL}${path}`, { ...init, headers, credentials: "include" });
|
||||
}
|
||||
@@ -142,3 +142,16 @@
|
||||
height: 25px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.wrapper_1 .inputError {
|
||||
border-color: #d93025;
|
||||
background-color: #fdf2f1;
|
||||
}
|
||||
.errorText {
|
||||
color: #d93025;
|
||||
font-size: 14px;
|
||||
padding-top: 6px;
|
||||
position: absolute;
|
||||
left: 4px;
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,42 @@
|
||||
import { useState, type ReactElement } from "react";
|
||||
import { useState, type SubmitEvent, type ReactElement } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import styles from "./AuthComponent.module.css";
|
||||
import { login } from "../../../api/LoginAPI/Auth";
|
||||
|
||||
export function Auth():ReactElement {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [rememberMe, setRememberMe] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [errors, setErrors] = useState<{ email?: string; password?: string }>({});
|
||||
|
||||
const validateEmail = (value: string) => {
|
||||
if (!value) return "Введите email";
|
||||
if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
|
||||
return undefined;
|
||||
};
|
||||
const validatePassword = (value: string) =>
|
||||
value ? undefined : "Введите пароль";
|
||||
|
||||
const handleSubmit = async (e: SubmitEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const next = { email: validateEmail(email), password: validatePassword(password) };
|
||||
setErrors(next);
|
||||
if (next.email || next.password) return;
|
||||
|
||||
try {
|
||||
await login(email, password);
|
||||
window.location.href = "http://localhost:8000/docs";
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setErrors({ password: "Неверный email или пароль" });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.wrapper_1}>
|
||||
<form>
|
||||
<form noValidate onSubmit={handleSubmit}>
|
||||
<h1 className={styles.Title}> Добро Пожаловать <br /> в The DisExcel Project </h1>
|
||||
<p className={styles.Description}>Войдите в систему, чтобы продолжить работу с вашими процессами</p>
|
||||
<label className={styles.Fields} htmlFor="email">Email</label>
|
||||
@@ -23,13 +48,16 @@ export function Auth():ReactElement {
|
||||
autoComplete="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onBlur={() => setErrors((p) => ({ ...p, email: validateEmail(email) }))}
|
||||
placeholder="Email"
|
||||
required
|
||||
aria-invalid={!!errors.email}
|
||||
className={errors.email ? styles.inputError : undefined}
|
||||
/>
|
||||
<svg className={styles["email-icon"]} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="3" y="5" width="18" height="14" rx="2.5" stroke="#72879A" strokeWidth="1.8"/>
|
||||
<path d="m4.5 7 7.5 6 7.5-6" stroke="#72879A" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
{errors.email && <p className={styles.errorText}>{errors.email}</p>}
|
||||
</div>
|
||||
<label className={styles.Fields} htmlFor="password">Password</label>
|
||||
<div className={styles.pass_field}>
|
||||
@@ -41,8 +69,10 @@ export function Auth():ReactElement {
|
||||
autoComplete="current-password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
onBlur={() => setErrors((p) => ({ ...p, password: validatePassword(password) }))}
|
||||
placeholder="Password"
|
||||
required
|
||||
aria-invalid={!!errors.password}
|
||||
className={errors.password ? styles.inputError : undefined}
|
||||
/>
|
||||
<svg className={styles["pass-icon"]} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="4" y="10" width="16" height="11" rx="2.5" stroke="#72879A" strokeWidth="1.8"/>
|
||||
@@ -67,6 +97,7 @@ export function Auth():ReactElement {
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
{errors.password && <p className={styles.errorText}>{errors.password}</p>}
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<div className={styles["remember-me"]}>
|
||||
|
||||
@@ -91,3 +91,15 @@
|
||||
color: #087A55;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wrapper_1 .inputError {
|
||||
border-color: #d93025;
|
||||
background-color: #fdf2f1;
|
||||
}
|
||||
.errorText {
|
||||
color: #d93025;
|
||||
font-size: 14px;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 4px;
|
||||
}
|
||||
@@ -1,14 +1,37 @@
|
||||
import styles from "./ResetComponent.module.css";
|
||||
import { useState, type ReactElement } from "react";
|
||||
import { useState, type ReactElement, type SubmitEvent } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { requestPasswordReset } from "../../../api/ResetAPI/Reset";
|
||||
|
||||
export function Reset():ReactElement {
|
||||
const [email, setEmail] = useState("");
|
||||
const [errors, setErrors] = useState<{ email?: string;}>({});
|
||||
|
||||
const validateEmail = (value: string) => {
|
||||
if (!value) return "Введите email";
|
||||
if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
|
||||
return undefined;
|
||||
};
|
||||
const handleSubmit = async (e: SubmitEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const next = {
|
||||
email: validateEmail(email),
|
||||
};
|
||||
setErrors(next);
|
||||
if (next.email) return;
|
||||
try {
|
||||
await requestPasswordReset(email);
|
||||
window.location.href = "http://localhost:8000/docs";
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setErrors({ email: "Ошибка, попробуйте позже" });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.wrapper_1}>
|
||||
<form>
|
||||
<form noValidate onSubmit={handleSubmit}>
|
||||
<h1 className={styles.Title}> Восстановление пароля </h1>
|
||||
<p className={styles.Description}>Введите Email, и мы отправим на него новый пароль</p>
|
||||
<label className={styles.Fields} htmlFor="email">Email</label>
|
||||
@@ -20,13 +43,16 @@ export function Reset():ReactElement {
|
||||
autoComplete="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onBlur={() => setErrors((p) => ({ ...p, email: validateEmail(email) }))}
|
||||
placeholder="Email"
|
||||
required
|
||||
aria-invalid={!!errors.email}
|
||||
className={errors.email ? styles.inputError : undefined}
|
||||
/>
|
||||
<svg className={styles["email-icon"]} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="3" y="5" width="18" height="14" rx="2.5" stroke="#72879A" strokeWidth="1.8"/>
|
||||
<path d="m4.5 7 7.5 6 7.5-6" stroke="#72879A" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
{errors.email && <p className={styles.errorText}>{errors.email}</p>}
|
||||
</div>
|
||||
<div className={styles.submitbtn}>
|
||||
<button className={styles.submit} type="submit">
|
||||
|
||||
Reference in New Issue
Block a user