login and register tabs, initial user dashboard
This commit is contained in:
@@ -1,21 +1,64 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { FormEvent } from "react";
|
|
||||||
import { useRef, useState, useContext } from "react";
|
|
||||||
import { SessionContext } from "@/context/session";
|
import { SessionContext } from "@/context/session";
|
||||||
import { setGuestSession } from "@/lib/auth";
|
import { login, logout, register, setGuestSession, updateUser } from "@/lib/auth";
|
||||||
|
import type { FormEvent } from "react";
|
||||||
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
// TODO: add login and register views
|
import Guest from "./Guest";
|
||||||
|
import Login from "./Login";
|
||||||
|
import Register from "./Register";
|
||||||
|
|
||||||
export default function AuthModal() {
|
export default function AuthModal() {
|
||||||
const session = useContext(SessionContext);
|
const session = useContext(SessionContext);
|
||||||
|
const [activeTab, setActiveTab] = useState<"guest" | "login" | "register">("guest");
|
||||||
|
const [serverMessage, setServerMessage] = useState<string | null>(null);
|
||||||
const [buttonLoading, setButtonLoading] = useState(false);
|
const [buttonLoading, setButtonLoading] = useState(false);
|
||||||
const modalToggleRef = useRef<HTMLInputElement>(null);
|
const modalToggleRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
async function updateGuestName(e: FormEvent<HTMLFormElement>) {
|
async function clickLogout() {
|
||||||
|
if (serverMessage) {
|
||||||
|
setServerMessage(null);
|
||||||
|
}
|
||||||
|
await logout();
|
||||||
|
session?.setUser(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateAccount(e: FormEvent<HTMLFormElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
const target = e.target as HTMLFormElement;
|
||||||
|
const updateEmail = target.elements.namedItem("updateEmail") as HTMLInputElement;
|
||||||
|
const updatePassword = target.elements.namedItem("updatePassword") as HTMLInputElement;
|
||||||
|
if (
|
||||||
|
!updateEmail ||
|
||||||
|
!updatePassword ||
|
||||||
|
((!updateEmail.value || updateEmail.value === session?.user?.email) && !updatePassword.value)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
setButtonLoading(true);
|
||||||
|
const user = await updateUser(
|
||||||
|
updateEmail.value || undefined,
|
||||||
|
updatePassword.value || undefined
|
||||||
|
);
|
||||||
|
if (typeof user === "string") {
|
||||||
|
setServerMessage(user);
|
||||||
|
} else if (user?.id) {
|
||||||
|
session?.setUser(user);
|
||||||
|
setServerMessage("Account updated successfully");
|
||||||
|
setTimeout(() => {
|
||||||
|
setServerMessage(null);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
updatePassword.value = "";
|
||||||
|
setButtonLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitAuth(e: FormEvent<HTMLFormElement>) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const target = e.target as HTMLFormElement;
|
const target = e.target as HTMLFormElement;
|
||||||
|
if (activeTab === "guest") {
|
||||||
const guestName = target.elements.namedItem("guestName") as HTMLInputElement;
|
const guestName = target.elements.namedItem("guestName") as HTMLInputElement;
|
||||||
if (!guestName || !guestName.value) return;
|
if (!guestName || !guestName.value) return;
|
||||||
|
|
||||||
@@ -28,9 +71,58 @@ export default function AuthModal() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
guestName.value = "";
|
guestName.value = "";
|
||||||
|
} else if (activeTab === "login") {
|
||||||
|
const loginName = target.elements.namedItem("loginName") as HTMLInputElement;
|
||||||
|
const loginPassword = target.elements.namedItem("loginPassword") as HTMLInputElement;
|
||||||
|
if (!loginName || !loginName.value || !loginPassword || !loginPassword.value) return;
|
||||||
|
|
||||||
|
setButtonLoading(true);
|
||||||
|
const user = await login(loginName.value, loginPassword.value);
|
||||||
|
if (typeof user === "string") {
|
||||||
|
setServerMessage(user);
|
||||||
|
} else if (user?.id) {
|
||||||
|
session?.setUser(user);
|
||||||
|
if (serverMessage) {
|
||||||
|
setServerMessage(null);
|
||||||
|
}
|
||||||
|
if (modalToggleRef.current?.checked) {
|
||||||
|
modalToggleRef.current.checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (activeTab === "register") {
|
||||||
|
const registerName = target.elements.namedItem("registerName") as HTMLInputElement;
|
||||||
|
const registerEmail = target.elements.namedItem("registerEmail") as HTMLInputElement;
|
||||||
|
const registerPassword = target.elements.namedItem("registerPassword") as HTMLInputElement;
|
||||||
|
if (!registerName || !registerName.value || !registerPassword || !registerPassword.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setButtonLoading(true);
|
||||||
|
const user = await register(
|
||||||
|
registerName.value,
|
||||||
|
registerPassword.value,
|
||||||
|
registerEmail.value || undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
if (typeof user === "string") {
|
||||||
|
setServerMessage(user);
|
||||||
|
} else if (user?.id) {
|
||||||
|
session?.setUser(user);
|
||||||
|
if (serverMessage) {
|
||||||
|
setServerMessage(null);
|
||||||
|
}
|
||||||
|
if (modalToggleRef.current?.checked) {
|
||||||
|
modalToggleRef.current.checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
setButtonLoading(false);
|
setButtonLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setServerMessage(null);
|
||||||
|
}, [activeTab]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<input type="checkbox" id="auth-modal" className="modal-toggle" ref={modalToggleRef} />
|
<input type="checkbox" id="auth-modal" className="modal-toggle" ref={modalToggleRef} />
|
||||||
@@ -39,47 +131,116 @@ export default function AuthModal() {
|
|||||||
htmlFor="auth-modal"
|
htmlFor="auth-modal"
|
||||||
className={"modal" + (session?.user === null ? " modal-open" : "")}
|
className={"modal" + (session?.user === null ? " modal-open" : "")}
|
||||||
>
|
>
|
||||||
<label className="modal-box flex flex-col gap-4 pt-2">
|
<label className="modal-box flex max-w-sm flex-col gap-4 pt-2">
|
||||||
<div className="flex w-full gap-2">
|
{session?.user?.id && typeof session.user.id === "number" ? (
|
||||||
<div className="tabs flex-grow">
|
<div className="flex flex-col pt-2">
|
||||||
<a className="tab tab-bordered tab-active flex-grow">Guest</a>
|
<div className="flex w-full justify-between">
|
||||||
<a className="tab tab-bordered tab-disabled flex-grow">Login</a>
|
<div className="div">
|
||||||
<a className="tab tab-bordered tab-disabled flex-grow">Register</a>
|
Logged in as <b>{session.user.name}</b>
|
||||||
</div>
|
</div>
|
||||||
{session?.user !== null && (
|
<a className="link" onClick={clickLogout}>
|
||||||
<label htmlFor="auth-modal" className="btn btn-sm btn-circle btn-ghost">
|
Logout
|
||||||
✕
|
</a>
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form className="flex flex-col" onSubmit={updateGuestName}>
|
<form className="form-control mt-2" onSubmit={updateAccount}>
|
||||||
<div className="form-control">
|
<label className="label" htmlFor="updateEmail">
|
||||||
<label className="label">
|
<span className="label-text">Email</span>
|
||||||
<span className="label-text">Hello, {session?.user?.name || "unknown guest"}!</span>
|
|
||||||
</label>
|
</label>
|
||||||
<label className="input-group">
|
|
||||||
<span>Name</span>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="email"
|
||||||
pattern="[A-Za-z0-9_]+"
|
|
||||||
title="Alphanumeric characters and underscores only"
|
|
||||||
id="guestName"
|
|
||||||
name="guestName"
|
|
||||||
placeholder="Enter name here..."
|
|
||||||
className="input input-bordered w-full"
|
className="input input-bordered w-full"
|
||||||
maxLength={16}
|
id="updateEmail"
|
||||||
minLength={2}
|
name="updateEmail"
|
||||||
required
|
placeholder={session.user.email || "Email address"}
|
||||||
|
defaultValue={session.user.email}
|
||||||
|
minLength={4}
|
||||||
/>
|
/>
|
||||||
|
<label className="label" htmlFor="updatePassword">
|
||||||
|
<span className="label-text">Password</span>
|
||||||
</label>
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
className="input input-bordered w-full"
|
||||||
|
id="updatePassword"
|
||||||
|
name="updatePassword"
|
||||||
|
placeholder="New password (optional)"
|
||||||
|
minLength={3}
|
||||||
|
/>
|
||||||
|
{serverMessage && (
|
||||||
|
<div
|
||||||
|
className={serverMessage.includes("successful") ? "text-success" : "text-error"}
|
||||||
|
>
|
||||||
|
{serverMessage}
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-action">
|
)}
|
||||||
<button className={"btn" + (buttonLoading ? " loading" : "")} type="submit">
|
<div className="mt-2">
|
||||||
|
<button type="submit" className={"btn" + (buttonLoading ? " loading" : "")}>
|
||||||
Update
|
Update
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<div className="modal-action">
|
||||||
|
<label htmlFor="auth-modal" className="btn">
|
||||||
|
Close
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="tabs flex-nowap w-full">
|
||||||
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTab("guest");
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
"tab tab-bordered flex-grow" + (activeTab === "guest" ? " tab-active" : "")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Guest
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
onClick={() => setActiveTab("login")}
|
||||||
|
className={
|
||||||
|
"tab tab-bordered flex-grow" + (activeTab === "login" ? " tab-active" : "")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
onClick={() => setActiveTab("register")}
|
||||||
|
className={
|
||||||
|
"tab tab-bordered flex-grow" + (activeTab === "register" ? " tab-active" : "")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Register
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form className="flex flex-col px-2" onSubmit={submitAuth}>
|
||||||
|
{activeTab === "guest" && (
|
||||||
|
<Guest currentName={session?.user?.name || "unknown user"} />
|
||||||
|
)}
|
||||||
|
{activeTab === "login" && <Login />}
|
||||||
|
{activeTab === "register" && <Register />}
|
||||||
|
|
||||||
|
{serverMessage && <div className="text-error mt-2">{serverMessage}</div>}
|
||||||
|
<div className="modal-action items-center">
|
||||||
|
{session?.user !== null && (
|
||||||
|
<label htmlFor="auth-modal" className="btn btn-ghost">
|
||||||
|
Close
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<button className={"btn" + (buttonLoading ? " loading" : "")} type="submit">
|
||||||
|
{activeTab === "guest" && "Confirm"}
|
||||||
|
{activeTab === "login" && "Login"}
|
||||||
|
{activeTab === "register" && "Register"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
</label>
|
</label>
|
||||||
</>
|
</>
|
||||||
|
|||||||
26
client/src/components/auth/Guest.tsx
Normal file
26
client/src/components/auth/Guest.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function Guest({ currentName }: { currentName: string }) {
|
||||||
|
return (
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">Hello, {currentName}!</span>
|
||||||
|
</label>
|
||||||
|
<label className="input-group">
|
||||||
|
<span>Name</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
pattern="[A-Za-z0-9]+"
|
||||||
|
title="Alphanumeric characters only"
|
||||||
|
id="guestName"
|
||||||
|
name="guestName"
|
||||||
|
placeholder="Enter name here..."
|
||||||
|
className="input input-bordered w-full"
|
||||||
|
maxLength={16}
|
||||||
|
minLength={2}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
35
client/src/components/auth/Login.tsx
Normal file
35
client/src/components/auth/Login.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function Login() {
|
||||||
|
return (
|
||||||
|
<div className="form-control">
|
||||||
|
<label htmlFor="loginName" className="label">
|
||||||
|
<span className="label-text">Username or email</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
pattern="[A-Za-z0-9]+"
|
||||||
|
title="Alphanumeric characters only"
|
||||||
|
id="loginName"
|
||||||
|
name="loginName"
|
||||||
|
placeholder="username"
|
||||||
|
className="input input-bordered"
|
||||||
|
maxLength={16}
|
||||||
|
minLength={2}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<label htmlFor="loginPassword" className="label">
|
||||||
|
<span className="label-text">Password</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="loginPassword"
|
||||||
|
name="loginPassword"
|
||||||
|
placeholder="password"
|
||||||
|
className="input input-bordered"
|
||||||
|
minLength={3}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
46
client/src/components/auth/Register.tsx
Normal file
46
client/src/components/auth/Register.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function Register() {
|
||||||
|
return (
|
||||||
|
<div className="form-control">
|
||||||
|
<label htmlFor="registerName" className="label">
|
||||||
|
<span className="label-text">Username</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
pattern="[A-Za-z0-9]+"
|
||||||
|
title="Alphanumeric characters only"
|
||||||
|
id="registerName"
|
||||||
|
name="registerName"
|
||||||
|
placeholder="username"
|
||||||
|
className="input input-bordered"
|
||||||
|
maxLength={16}
|
||||||
|
minLength={2}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<label htmlFor="registerEmail" className="label">
|
||||||
|
<span className="label-text">Email (optional)</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="registerEmail"
|
||||||
|
name="registerEmail"
|
||||||
|
placeholder="email"
|
||||||
|
className="input input-bordered"
|
||||||
|
minLength={4}
|
||||||
|
/>
|
||||||
|
<label htmlFor="registerPassword" className="label">
|
||||||
|
<span className="label-text">Password</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="registerPassword"
|
||||||
|
name="registerPassword"
|
||||||
|
placeholder="password"
|
||||||
|
className="input input-bordered"
|
||||||
|
minLength={3}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -430,7 +430,7 @@ export default function GamePage({ initialLobby }: { initialLobby: Game }) {
|
|||||||
function navigateMove(index: number | null | "prev") {
|
function navigateMove(index: number | null | "prev") {
|
||||||
const history = lobby.actualGame.history({ verbose: true });
|
const history = lobby.actualGame.history({ verbose: true });
|
||||||
|
|
||||||
if (index === null || index >= history.length - 1 || !history.length) {
|
if (index === null || (index !== "prev" && index >= history.length - 1) || !history.length) {
|
||||||
// last move
|
// last move
|
||||||
setNavIndex(null);
|
setNavIndex(null);
|
||||||
setNavFen(null);
|
setNavFen(null);
|
||||||
@@ -446,7 +446,7 @@ export default function GamePage({ initialLobby }: { initialLobby: Game }) {
|
|||||||
chessboardRef.current?.clearPremoves(false);
|
chessboardRef.current?.clearPremoves(false);
|
||||||
|
|
||||||
setNavIndex(index);
|
setNavIndex(index);
|
||||||
setNavFen(history[index + 1].fen);
|
setNavFen(history[index].after);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNavMoveSquares() {
|
function getNavMoveSquares() {
|
||||||
|
|||||||
Reference in New Issue
Block a user