Updating auth
This commit is contained in:
162
ui/src/components/LoginModal.tsx
Normal file
162
ui/src/components/LoginModal.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import { useState } from "react";
|
||||
import { auth } from "../api";
|
||||
import type { UserInfo } from "../types";
|
||||
import "./LoginModal.css";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
onLogin: (user: UserInfo) => void;
|
||||
}
|
||||
|
||||
type Tab = "login" | "register";
|
||||
|
||||
export default function LoginModal({ onClose, onLogin }: Props) {
|
||||
const [tab, setTab] = useState<Tab>("login");
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirm, setConfirm] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
|
||||
if (tab === "register" && password !== confirm) {
|
||||
setError("Passwords do not match");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
if (tab === "login") {
|
||||
await auth.loginLocal(username, password);
|
||||
} else {
|
||||
await auth.register(username, password);
|
||||
}
|
||||
// Cookie is now set server-side; fetch user info to update parent
|
||||
const user = await auth.me();
|
||||
if (user) {
|
||||
onLogin(user);
|
||||
onClose();
|
||||
} else {
|
||||
setError("Login succeeded but could not load user info.");
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
// Extract the human-readable part (strip leading status code)
|
||||
setError(
|
||||
msg
|
||||
.replace(/^\d+:\s*/, "")
|
||||
.replace(/\{.*\}/s, "")
|
||||
.trim() || "Authentication failed",
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDiscordLogin() {
|
||||
try {
|
||||
await auth.loginDiscord(window.location.origin + "/map");
|
||||
} catch (err) {
|
||||
console.error("Discord login failed:", err);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="modal-backdrop" onClick={onClose}>
|
||||
<div className="modal" onClick={(e) => e.stopPropagation()}>
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
✕
|
||||
</button>
|
||||
|
||||
{/* Tab switcher */}
|
||||
<div className="modal-tabs">
|
||||
<button
|
||||
className={`modal-tab ${tab === "login" ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setTab("login");
|
||||
setError(null);
|
||||
}}
|
||||
>
|
||||
Log In
|
||||
</button>
|
||||
<button
|
||||
className={`modal-tab ${tab === "register" ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setTab("register");
|
||||
setError(null);
|
||||
}}
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Username / password form */}
|
||||
<form className="modal-form" onSubmit={handleSubmit}>
|
||||
<label>
|
||||
Username
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
autoComplete="username"
|
||||
required
|
||||
minLength={1}
|
||||
maxLength={32}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Password
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
autoComplete={
|
||||
tab === "login" ? "current-password" : "new-password"
|
||||
}
|
||||
required
|
||||
minLength={8}
|
||||
/>
|
||||
</label>
|
||||
{tab === "register" && (
|
||||
<label>
|
||||
Confirm Password
|
||||
<input
|
||||
type="password"
|
||||
value={confirm}
|
||||
onChange={(e) => setConfirm(e.target.value)}
|
||||
autoComplete="new-password"
|
||||
required
|
||||
minLength={8}
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
|
||||
{error && <p className="modal-error">{error}</p>}
|
||||
|
||||
<button type="submit" className="btn-primary" disabled={loading}>
|
||||
{loading
|
||||
? "Loading…"
|
||||
: tab === "login"
|
||||
? "Log In"
|
||||
: "Create Account"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="modal-divider">
|
||||
<span>or</span>
|
||||
</div>
|
||||
|
||||
{/* Discord OAuth */}
|
||||
<button className="btn-discord" onClick={handleDiscordLogin}>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z" />
|
||||
</svg>
|
||||
Log In with Discord
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user