Compare commits

...

2 Commits

Author SHA1 Message Date
eb12afe763 feat: fixed login 2025-04-23 13:47:16 +03:00
c629f0dcf8 feat: form hook 2025-04-23 12:32:27 +03:00
7 changed files with 71 additions and 44 deletions

View File

@@ -14,6 +14,7 @@
"preact-iso": "^2.9.1", "preact-iso": "^2.9.1",
"primelocale": "^2.1.2", "primelocale": "^2.1.2",
"primereact": "^10.9.4", "primereact": "^10.9.4",
"react-hook-form": "^7.56.1",
"tailwind-merge": "^3.0.2", "tailwind-merge": "^3.0.2",
"tailwind-variants": "^1.0.0", "tailwind-variants": "^1.0.0",
"tailwindcss": "^4.0.17", "tailwindcss": "^4.0.17",
@@ -612,6 +613,8 @@
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="], "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
"react-hook-form": ["react-hook-form@7.56.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-qWAVokhSpshhcEuQDSANHx3jiAEFzu2HAaaQIzi/r9FNPm1ioAvuJSD4EuZzWd7Al7nTRKcKPnBKO7sRn+zavQ=="],
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
"react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="], "react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],

View File

@@ -19,6 +19,7 @@
"preact-iso": "^2.9.1", "preact-iso": "^2.9.1",
"primelocale": "^2.1.2", "primelocale": "^2.1.2",
"primereact": "^10.9.4", "primereact": "^10.9.4",
"react-hook-form": "^7.56.1",
"tailwind-merge": "^3.0.2", "tailwind-merge": "^3.0.2",
"tailwind-variants": "^1.0.0", "tailwind-variants": "^1.0.0",
"tailwindcss": "^4.0.17" "tailwindcss": "^4.0.17"

View File

@@ -1,5 +1,5 @@
@reference '../../index.scss'; @reference '../../index.scss';
.button { .button {
@apply rounded-4xl px-4 py-3 text-xl text-black shadow-[0px_4px_4px_0px_rgba(0,0,0,0.25)] transition-colors hover:cursor-pointer; @apply rounded-4xl px-4 py-3 text-xl text-black shadow-[0px_4px_4px_0px_rgba(0,0,0,0.25)] transition-colors hover:cursor-pointer focus:outline-1;
} }

View File

@@ -1,4 +1,5 @@
import { FunctionComponent } from "preact"; import { FunctionComponent } from "preact";
import { HTMLProps } from "preact/compat";
import { tv } from "tailwind-variants"; import { tv } from "tailwind-variants";
import classes from "./Button.module.scss"; import classes from "./Button.module.scss";
const button = tv({ const button = tv({
@@ -12,9 +13,8 @@ const button = tv({
}, },
}); });
interface ButtonProps { interface ButtonProps extends HTMLProps<HTMLButtonElement> {
color?: "primary" | "secondary" | "red"; color?: "primary" | "secondary" | "red";
onClick?: () => void;
className?: string; className?: string;
} }
@@ -23,12 +23,15 @@ const Button: FunctionComponent<ButtonProps> = ({
onClick = () => {}, onClick = () => {},
color = "primary", color = "primary",
className = "", className = "",
type = "button",
}) => { }) => {
return ( return (
<button type="button" class={button({ color: color, class: className })} onClick={onClick}> <button type={type} class={button({ color: color, class: className })} onClick={onClick}>
{children} {children}
</button> </button>
); );
}; };
Button.displayName = "AHButton";
export default Button; export default Button;

View File

@@ -1,5 +1,5 @@
import { cn } from "@/utils/class-merge"; import { cn } from "@/utils/class-merge";
import { FunctionComponent, Ref } from "preact"; import { forwardRef, HTMLProps, useEffect } from "preact/compat";
import { tv } from "tailwind-variants"; import { tv } from "tailwind-variants";
import classes from "./Input.module.scss"; import classes from "./Input.module.scss";
@@ -20,29 +20,19 @@ const input = tv({
}, },
}); });
interface InputProps { interface InputProps extends HTMLProps<HTMLInputElement> {
isPassword?: boolean;
placeholder?: string;
textAlign?: "center" | "left"; textAlign?: "center" | "left";
error?: string; error?: string;
textRef?: Ref<HTMLInputElement> | null;
} }
const Input: FunctionComponent<InputProps> = ({ const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
isPassword = false, const { textAlign, error, type = "text", ...rest } = props;
placeholder = "", useEffect(() => {
textAlign, console.log(`error: ${error}`);
error = "", }, [error]);
textRef = null,
}: InputProps) => {
return ( return (
<div class="flex w-full flex-col items-center gap-1"> <div class="flex w-full flex-col items-center gap-1">
<input <input class={input({ "text-align": textAlign, "border-error": !!error })} ref={ref} type={type} {...rest} />
type={isPassword ? "password" : "text"}
class={input({ "text-align": textAlign, "border-error": error !== "" })}
placeholder={placeholder}
ref={textRef}
/>
<p <p
class={cn("invisible h-10 w-[80%] text-center text-[0.7rem] break-words text-red-500", { class={cn("invisible h-10 w-[80%] text-center text-[0.7rem] break-words text-red-500", {
visible: error !== "", visible: error !== "",
@@ -52,6 +42,8 @@ const Input: FunctionComponent<InputProps> = ({
</p> </p>
</div> </div>
); );
}; });
Input.displayName = "AHInput";
export default Input; export default Input;

4
src/pages/login.dto.ts Normal file
View File

@@ -0,0 +1,4 @@
export interface ILoginForm {
login: string;
password: string;
}

View File

@@ -6,7 +6,8 @@ import { useAppContext } from "@/providers/AuthProvider";
import { FunctionComponent } from "preact"; import { FunctionComponent } from "preact";
import { useLocation } from "preact-iso"; import { useLocation } from "preact-iso";
import "preact/debug"; import "preact/debug";
import { useRef, useState } from "preact/hooks"; import { Controller, SubmitHandler, useForm } from "react-hook-form";
import { ILoginForm } from "./login.dto";
import classes from "./login.module.scss"; import classes from "./login.module.scss";
const testUser = { const testUser = {
@@ -16,21 +17,18 @@ const testUser = {
const LoginPage: FunctionComponent = () => { const LoginPage: FunctionComponent = () => {
const { isLoggedIn } = useAppContext(); const { isLoggedIn } = useAppContext();
const { route } = useLocation(); const { route } = useLocation();
const loginRef = useRef<HTMLInputElement | null>(null); const { control, handleSubmit, formState, setError } = useForm({
const passwordRef = useRef<HTMLInputElement | null>(null); defaultValues: {
const [loginError, setLoginError] = useState(""); login: "",
const [passwordError, setPasswordError] = useState(""); password: "",
const login = async () => { },
if (!loginRef.current || !passwordRef.current) return; mode: "onChange",
setLoginError(""); });
setPasswordError(""); const login: SubmitHandler<ILoginForm> = async (data) => {
if (!loginRef.current.value.length || !passwordRef.current.value.length) { console.log(data);
if (!loginRef.current.value.length) setLoginError("Введите логин"); if (data.login !== testUser.login || data.password !== testUser.password) {
if (!passwordRef.current.value.length) setPasswordError("Введите пароль"); setError("login", { message: "Неверный логин или пароль" });
return; setError("password", { message: "Неверный логин или пароль" });
}
if (loginRef.current.value !== testUser.login || passwordRef.current.value !== testUser.password) {
setLoginError("Неправильный логин или пароль");
return; return;
} }
isLoggedIn.value = true; isLoggedIn.value = true;
@@ -42,11 +40,37 @@ const LoginPage: FunctionComponent = () => {
<div class={classes.login_container}> <div class={classes.login_container}>
<div class={classes.login_card}> <div class={classes.login_card}>
<p class={classes.login_card_name}>Антихвост</p> <p class={classes.login_card_name}>Антихвост</p>
<Input placeholder="Логин" textAlign="center" textRef={loginRef} error={loginError} /> <form onSubmit={handleSubmit(login)}>
<Input isPassword placeholder="Пароль" textAlign="center" textRef={passwordRef} error={passwordError} /> <Controller
<Button color="secondary" onClick={login}> name="login"
Войти control={control}
</Button> rules={{
required: "Введите логин",
}}
render={({ field }) => (
<Input placeholder="Логин" textAlign="center" error={formState.errors.login?.message} {...field} />
)}
/>
<Controller
name="password"
control={control}
rules={{
required: "Введите пароль",
}}
render={({ field }) => (
<Input
placeholder="Пароль"
textAlign="center"
type="password"
error={formState.errors.password?.message}
{...field}
/>
)}
/>
<Button type="submit" color="secondary" className="w-full">
Войти
</Button>
</form>
</div> </div>
</div> </div>
) : ( ) : (