Compare commits
10
Commits
b29018b1dd
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
461a537de1 | ||
|
|
d0c512fd15 | ||
|
|
0bf24c69ff | ||
|
|
649155c0c6 | ||
|
|
a3ca64dc8d | ||
|
|
6a1697048c | ||
|
|
643b525d1b | ||
|
|
39d3ff9918 | ||
|
|
8f1cba5f82 | ||
|
|
fbd48297e2 |
+2
-2
@@ -2,9 +2,9 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/GameUI-icon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React</title>
|
<title>GameUI</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
+21
-13
@@ -1,25 +1,33 @@
|
|||||||
import React from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Routes, Route, Navigate } from "react-router-dom";
|
import { Routes, Route, Navigate } from "react-router-dom";
|
||||||
import Login from "./pages/Login";
|
import Login from "./pages/Login";
|
||||||
import Register from "./pages/Register";
|
import Register from "./pages/Register";
|
||||||
import Dashboard from "./pages/Dashboard";
|
import Dashboard from "./pages/Dashboard";
|
||||||
import Layout from "./components/Layout";
|
import Layout from "./components/Layout";
|
||||||
|
import ProtectedRoute from "./components/ProtectedRoute";
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const token = localStorage.getItem("token");
|
const [token, setToken] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const storedToken = localStorage.getItem("token");
|
||||||
|
setToken(storedToken);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Routes>
|
||||||
<Routes>
|
<Route path="/login" element={<Login setToken={setToken} />} />
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/register" element={<Register />} />
|
||||||
<Route path="/register" element={<Register />} />
|
<Route
|
||||||
<Route
|
path="/dashboard"
|
||||||
path="/dashboard"
|
element={
|
||||||
element={token ? <Dashboard /> : <Navigate to="/login" />}
|
<ProtectedRoute>
|
||||||
/>
|
<Dashboard />
|
||||||
<Route path="*" element={<Navigate to="/login" />} />
|
</ProtectedRoute>
|
||||||
</Routes>
|
}
|
||||||
</Layout>
|
/>
|
||||||
|
<Route path="*" element={<Navigate to="/login" />} />
|
||||||
|
</Routes>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
const Layout = ({ children }) => {
|
const Layout = ({ children, token, setToken }) => {
|
||||||
const [token, setToken] = useState(null);
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const t = localStorage.getItem("token");
|
|
||||||
setToken(t);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
setToken(null);
|
setToken(null);
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const PageWrapper = ({ children, center = false }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: "100vh",
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: center ? "center" : "stretch",
|
||||||
|
justifyContent: center ? "center" : "flex-start",
|
||||||
|
backgroundColor: "#242424",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default PageWrapper;
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Navigate, useNavigate } from "react-router-dom";
|
||||||
|
import PageWrapper from "./PageWrapper";
|
||||||
|
import useIdleTimer from "../hooks/useIdleTimer";
|
||||||
|
|
||||||
|
const ProtectedRoute = ({ children }) => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
console.warn("No token found — skipping API logout.");
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/Auth/logout`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Logout API error:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
localStorage.removeItem("role");
|
||||||
|
navigate("/login");
|
||||||
|
};
|
||||||
|
|
||||||
|
useIdleTimer(() => {
|
||||||
|
if (token) {
|
||||||
|
handleLogout();
|
||||||
|
}
|
||||||
|
}, 60000); // 1 minute
|
||||||
|
|
||||||
|
if (!token) return <Navigate to="/login" />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageWrapper>
|
||||||
|
<button
|
||||||
|
onClick={handleLogout}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "1rem",
|
||||||
|
right: "1rem",
|
||||||
|
backgroundColor: "#1a1a1a",
|
||||||
|
color: "#fff",
|
||||||
|
padding: "0.5rem 1rem",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
{children}
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProtectedRoute;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
const useIdleTimer = (onIdle, timeout = 60000) => {
|
||||||
|
const timerRef = useRef(null);
|
||||||
|
|
||||||
|
const resetTimer = () => {
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
timerRef.current = setTimeout(onIdle, timeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const events = ["mousemove", "keydown", "mousedown", "touchstart"];
|
||||||
|
events.forEach(e => window.addEventListener(e, resetTimer));
|
||||||
|
|
||||||
|
resetTimer(); // initialize timer
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
events.forEach(e => window.removeEventListener(e, resetTimer));
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useIdleTimer;
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import config from "../config";
|
||||||
|
|
||||||
|
const thStyle = {
|
||||||
|
padding: "0.75rem",
|
||||||
|
backgroundColor: "#333",
|
||||||
|
borderBottom: "1px solid #555",
|
||||||
|
color: "#fff",
|
||||||
|
position: "sticky",
|
||||||
|
top: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
textAlign: "center",
|
||||||
|
};
|
||||||
|
|
||||||
|
const tdStyle = {
|
||||||
|
padding: "0.5rem 1rem",
|
||||||
|
borderBottom: "1px solid #444",
|
||||||
|
textAlign: "center",
|
||||||
|
color: "#fff",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Dashboard = () => {
|
||||||
|
const [role, setRole] = useState("");
|
||||||
|
const [currentUser, setCurrentUser] = useState("");
|
||||||
|
const [users, setUsers] = useState([]);
|
||||||
|
const [newRoles, setNewRoles] = useState({});
|
||||||
|
const [selectedUsers, setSelectedUsers] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const storedRole = localStorage.getItem("role");
|
||||||
|
const storedUsername = localStorage.getItem("username");
|
||||||
|
setRole(storedRole);
|
||||||
|
setCurrentUser(storedUsername);
|
||||||
|
|
||||||
|
if (storedRole === "SUPER") {
|
||||||
|
fetchUsers();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchUsers = async () => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${config.apiBaseUrl}/api/Auth/users`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error("Failed to fetch users");
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
setUsers(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching users:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRoleChange = (username, value) => {
|
||||||
|
setNewRoles({ ...newRoles, [username]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUserRole = async (username) => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
const newRole = newRoles[username];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${config.apiBaseUrl}/api/Auth/role`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username, newRole }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error("Failed to update role");
|
||||||
|
|
||||||
|
fetchUsers();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error updating role:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteUser = async (username) => {
|
||||||
|
if (!window.confirm(`Are you sure you want to delete ${username}?`)) return;
|
||||||
|
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${config.apiBaseUrl}/api/User?username=${encodeURIComponent(username)}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error("Failed to delete user");
|
||||||
|
|
||||||
|
fetchUsers();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error deleting user:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleUserSelection = (username) => {
|
||||||
|
setSelectedUsers((prev) =>
|
||||||
|
prev.includes(username)
|
||||||
|
? prev.filter((u) => u !== username)
|
||||||
|
: [...prev, username]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ color: "#fff", textAlign: "center", marginTop: "2rem" }}>
|
||||||
|
<h1>Dashboard</h1>
|
||||||
|
<p>Welcome! You are logged in.</p>
|
||||||
|
|
||||||
|
{role === "SUPER" && (
|
||||||
|
<>
|
||||||
|
<p style={{ fontWeight: "bold" }}>You are SUPER</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxHeight: "320px",
|
||||||
|
overflowY: "auto",
|
||||||
|
margin: "2rem auto 0 auto",
|
||||||
|
width: "fit-content",
|
||||||
|
border: "1px solid #444",
|
||||||
|
borderRadius: "4px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<table style={{ borderCollapse: "collapse", minWidth: "600px" }}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={thStyle}></th>
|
||||||
|
<th style={thStyle}>Username</th>
|
||||||
|
<th style={thStyle}>Role</th>
|
||||||
|
{selectedUsers.length > 0 && <th style={thStyle}>Action</th>}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{users
|
||||||
|
.filter((user) => user.username !== currentUser)
|
||||||
|
.map((user) => {
|
||||||
|
const isSelected = selectedUsers.includes(user.username);
|
||||||
|
const currentNewRole = newRoles[user.username] ?? user.role;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={user.username}>
|
||||||
|
<td style={tdStyle}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={isSelected}
|
||||||
|
onChange={() => toggleUserSelection(user.username)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td style={tdStyle}>{user.username}</td>
|
||||||
|
<td style={tdStyle}>
|
||||||
|
{isSelected ? (
|
||||||
|
<select
|
||||||
|
value={currentNewRole}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleRoleChange(
|
||||||
|
user.username,
|
||||||
|
e.target.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
padding: "0.3rem",
|
||||||
|
backgroundColor: "#2a2a2a",
|
||||||
|
color: "#fff",
|
||||||
|
border: "1px solid #444",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="USER">USER</option>
|
||||||
|
<option value="SUPER">SUPER</option>
|
||||||
|
</select>
|
||||||
|
) : (
|
||||||
|
user.role
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
{selectedUsers.length > 0 && (
|
||||||
|
<td style={tdStyle}>
|
||||||
|
{isSelected && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "0.5rem",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
updateUserRole(user.username)
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
padding: "0.4rem 0.8rem",
|
||||||
|
backgroundColor: "#444",
|
||||||
|
color: "#fff",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => deleteUser(user.username)}
|
||||||
|
style={{
|
||||||
|
padding: "0.4rem 0.8rem",
|
||||||
|
backgroundColor: "#aa3333",
|
||||||
|
color: "#fff",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
+42
-35
@@ -2,6 +2,7 @@ import React, { useState } from "react";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
import { useAuth } from "../AuthContext";
|
import { useAuth } from "../AuthContext";
|
||||||
|
import PageWrapper from "../components/PageWrapper";
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
form: {
|
form: {
|
||||||
@@ -81,48 +82,54 @@ const Login = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
login(data.token);
|
login(data.accessToken);
|
||||||
|
|
||||||
|
localStorage.setItem("token", data.accessToken);
|
||||||
|
localStorage.setItem("role", data.role);
|
||||||
|
localStorage.setItem("username", data.username);
|
||||||
|
|
||||||
navigate("/dashboard");
|
navigate("/dashboard");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Login error:", err);
|
console.error("Login error:", err);
|
||||||
alert("Login failed: " + err.message);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} style={styles.form}>
|
<PageWrapper center>
|
||||||
<h2 style={styles.title}>Login</h2>
|
<form onSubmit={handleSubmit} style={styles.form}>
|
||||||
<input
|
<h2 style={styles.title}>Login</h2>
|
||||||
type="text"
|
<input
|
||||||
name="username"
|
type="text"
|
||||||
placeholder="Username"
|
name="username"
|
||||||
value={form.username}
|
placeholder="Username"
|
||||||
onChange={handleChange}
|
value={form.username}
|
||||||
style={styles.input}
|
onChange={handleChange}
|
||||||
required
|
style={styles.input}
|
||||||
/>
|
required
|
||||||
<input
|
/>
|
||||||
type="password"
|
<input
|
||||||
name="password"
|
type="password"
|
||||||
placeholder="Password"
|
name="password"
|
||||||
value={form.password}
|
placeholder="Password"
|
||||||
onChange={handleChange}
|
value={form.password}
|
||||||
style={styles.input}
|
onChange={handleChange}
|
||||||
required
|
style={styles.input}
|
||||||
/>
|
required
|
||||||
<div style={styles.buttonRow}>
|
/>
|
||||||
<button type="submit" style={styles.button}>
|
<div style={styles.buttonRow}>
|
||||||
Login
|
<button type="submit" style={styles.button}>
|
||||||
</button>
|
Login
|
||||||
<button
|
</button>
|
||||||
type="button"
|
<button
|
||||||
onClick={() => navigate("/register")}
|
type="button"
|
||||||
style={styles.button}
|
onClick={() => navigate("/register")}
|
||||||
>
|
style={styles.button}
|
||||||
Register
|
>
|
||||||
</button>
|
Register
|
||||||
</div>
|
</button>
|
||||||
</form>
|
</div>
|
||||||
|
</form>
|
||||||
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+43
-13
@@ -1,6 +1,8 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
|
import { useAuth } from "../AuthContext";
|
||||||
|
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
form: {
|
form: {
|
||||||
@@ -45,6 +47,8 @@ const styles = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Register = () => {
|
const Register = () => {
|
||||||
|
const { login } = useAuth();
|
||||||
|
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
username: "",
|
username: "",
|
||||||
email: "",
|
email: "",
|
||||||
@@ -60,29 +64,55 @@ const Register = () => {
|
|||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
username: form.username,
|
||||||
|
password: form.password,
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${config.apiBaseUrl}/api/Auth/register`, {
|
// Step 1: Register the user
|
||||||
|
const registerRes = await fetch(
|
||||||
|
`${config.apiBaseUrl}/api/Auth/register`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!registerRes.ok) {
|
||||||
|
const errorData = await registerRes.json();
|
||||||
|
throw new Error(errorData.message || "Registration failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume body even if empty to ensure flush
|
||||||
|
await registerRes.text(); // <-- this is the key to making chaining clean
|
||||||
|
|
||||||
|
// Step 2: Immediately login
|
||||||
|
const loginRes = await fetch(`${config.apiBaseUrl}/api/Auth/login`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(payload),
|
||||||
username: form.username,
|
|
||||||
email: form.email,
|
|
||||||
password: form.password,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!loginRes.ok) {
|
||||||
const data = await res.json();
|
throw new Error("Registration succeeded but login failed");
|
||||||
throw new Error(data.message || "Registration failed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
alert("Registration successful!");
|
const loginData = await loginRes.json();
|
||||||
navigate("/login");
|
login(loginData.accessToken);
|
||||||
|
|
||||||
|
localStorage.setItem("token", loginData.accessToken);
|
||||||
|
localStorage.setItem("role", loginData.role);
|
||||||
|
localStorage.setItem("username", data.username);
|
||||||
|
|
||||||
|
navigate("/dashboard");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Register error:", err);
|
console.error("Register/login error:", err);
|
||||||
alert("Network or server error occurred.");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user