add web ui and user route
This commit is contained in:
37
web/src/components/AuthModal.tsx
Normal file
37
web/src/components/AuthModal.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
31
web/src/components/Header.tsx
Normal file
31
web/src/components/Header.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
60
web/src/components/LoginForm.tsx
Normal file
60
web/src/components/LoginForm.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
59
web/src/components/RegisterForm.tsx
Normal file
59
web/src/components/RegisterForm.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
38
web/src/components/ui/Modal.tsx
Normal file
38
web/src/components/ui/Modal.tsx
Normal 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,
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user