add web ui and user route

This commit is contained in:
2026-02-15 19:29:09 +03:00
parent 8d9b5c32c6
commit 7ced62517a
24 changed files with 3025 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
import { useState } from "react";
import { Modal } from "./ui/Modal";
import { LoginForm } from "./LoginForm";
import { RegisterForm } from "./RegisterForm";
export type AuthModalProps = {
allowedToClose?: boolean;
open: boolean;
onClose: () => void;
};
export const AuthModal = ({
allowedToClose = true,
open,
onClose,
}: AuthModalProps) => {
const [isLogin, setIsLogin] = useState(true);
return (
<Modal
size={400}
allowedToClose={allowedToClose}
open={open}
onClose={onClose}
>
<h2 className="text-lg text-center font-medium">
{isLogin ? "Login" : "Register"}
</h2>
{isLogin ? (
<LoginForm onClose={onClose} onRegister={() => setIsLogin(false)} />
) : (
<RegisterForm onClose={onClose} onLogin={() => setIsLogin(true)} />
)}
</Modal>
);
};

View File

@@ -0,0 +1,31 @@
import { useQueryClient } from "@tanstack/react-query";
import { useLogoutMutation } from "../api/auth";
import { useUserQuery } from "../api/user";
export const Header = () => {
const queryClient = useQueryClient();
const { data: user } = useUserQuery();
const logoutMutation = useLogoutMutation();
const handleLogout = () => {
logoutMutation.mutate(undefined, {
onSuccess() {
queryClient.clear();
location.href = "/";
},
});
};
return (
<header className="flex justify-between items-center">
<h1 className="text-2xl font-semibold">games-wishlist</h1>
{user && (
<button type="button" className="cursor-pointer" onClick={handleLogout}>
Log out
</button>
)}
</header>
);
};

View File

@@ -0,0 +1,60 @@
import { useForm } from "react-hook-form";
import { type LoginData, useLoginMutation } from "../api/auth";
import { useQueryClient } from "@tanstack/react-query";
export type LoginFormProps = {
onClose: () => void;
onRegister: () => void;
};
export const LoginForm = ({ onClose, onRegister }: LoginFormProps) => {
const queryClient = useQueryClient();
const mutation = useLoginMutation();
const form = useForm<LoginData>({
defaultValues: {
login: "",
password: "",
},
});
const onSubmit = form.handleSubmit((data) => {
mutation.mutate(data, {
onSuccess() {
form.reset();
onClose();
queryClient.invalidateQueries({ queryKey: ["user"] });
},
});
});
return (
<form className="flex flex-col gap-2 mt-5" onSubmit={onSubmit}>
<input
type="text"
placeholder="login"
className="w-full"
{...form.register("login", {
required: true,
})}
/>
<input
type="password"
placeholder="password"
className="w-full"
{...form.register("password", {
required: true,
})}
/>
<button type="submit" className="w-full mt-2">
Continue
</button>
<p>
Don't have an account?{" "}
<button type="button" onClick={() => onRegister()}>
Register
</button>
</p>
</form>
);
};

View File

@@ -0,0 +1,59 @@
import { useForm } from "react-hook-form";
import { type RegisterData, useRegisterMutation } from "../api/auth";
import { useQueryClient } from "@tanstack/react-query";
export type RegisterFormProps = {
onClose: () => void;
onLogin: () => void;
};
export const RegisterForm = ({ onClose, onLogin }: RegisterFormProps) => {
const queryClient = useQueryClient();
const mutation = useRegisterMutation();
const form = useForm<RegisterData>({
defaultValues: {
login: "",
password: "",
},
});
const onSubmit = form.handleSubmit((data) => {
mutation.mutate(data, {
onSuccess() {
onClose();
queryClient.invalidateQueries({ queryKey: ["user"] });
},
});
});
return (
<form className="flex flex-col gap-2 mt-5" onSubmit={onSubmit}>
<input
type="text"
placeholder="login"
className="w-full"
{...form.register("login", {
required: true,
})}
/>
<input
type="password"
placeholder="password"
className="w-full"
{...form.register("password", {
required: true,
})}
/>
<button type="submit" className="w-full mt-2">
Continue
</button>
<p>
Already have an account?{" "}
<button type="button" onClick={() => onLogin()}>
Login
</button>
</p>
</form>
);
};

View File

@@ -0,0 +1,38 @@
import type { ReactNode } from "react";
import { createPortal } from "react-dom";
export type ModalProps = {
children: ReactNode;
open: boolean;
allowedToClose?: boolean;
size?: number | string;
onClose: () => void;
};
export const Modal = ({
children,
open,
allowedToClose = true,
size = 500,
onClose,
}: ModalProps) => {
if (!open) {
return null;
}
return createPortal(
<div
className="fixed inset-0 z-30 bg-neutral-900/60 backdrop-blur-lg flex items-center justify-center"
onPointerDown={() => allowedToClose && onClose()}
>
<div
className="bg-white rounded-md m-2 p-2 w-full"
style={{ maxWidth: size }}
onPointerDown={(e) => e.stopPropagation()}
>
{children}
</div>
</div>,
document.body,
);
};