177 lines
5.3 KiB
TypeScript

import { useState, useEffect } from "react";
import { Person, PersonList as PersonListProto } from "../items";
import { Login } from "./Login";
import { PersonList } from "./PersonList";
import { PersonDetails } from "./PersonDetails";
import { GameList } from "./GameList";
import { GameFilter } from "./GameFilter";
import { GameDetails } from "./GameDetails";
import { ShaderBackground } from "./ShaderBackground";
import { BrowserRouter, Routes, Route, NavLink } from "react-router-dom";
import "./App.css";
import { apiFetch } from "./api";
import { Toast } from "./Toast";
import type { ToastType } from "./Toast";
interface ToastMessage {
id: number;
message: string;
type: ToastType;
}
function App() {
const [people, setPeople] = useState<Person[]>([]);
const [token, setToken] = useState<string>(
localStorage.getItem("token") || ""
);
const [theme, _setTheme] = useState<string>(
localStorage.getItem("theme") || "default"
);
const setTheme = (theme: string) => {
_setTheme(theme);
localStorage.setItem("theme", theme);
};
const [toasts, setToasts] = useState<ToastMessage[]>([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (theme !== "default" && theme !== "sakura") {
document.body.classList.remove("sakura-theme");
document.body.classList.add("shader-theme");
if (theme === "clouds" || theme === "blackhole" || theme === "ball") {
document.body.classList.add("black-theme");
return;
}
document.body.classList.remove("black-theme");
} else {
document.body.classList.remove("shader-theme");
document.body.classList.remove("black-theme");
if (theme === "sakura") {
document.body.classList.add("sakura-theme");
return;
}
document.body.classList.remove("sakura-theme");
}
}, [theme]);
const addToast = (message: string, type: ToastType = "info") => {
const id = Date.now();
setToasts((prev) => [...prev, { id, message, type }]);
};
const removeToast = (id: number) => {
setToasts((prev) => prev.filter((t) => t.id !== id));
};
const fetchPeople = () => {
if (!token) return;
setIsLoading(true);
apiFetch("/api")
.then((res) => res.arrayBuffer())
.then((buffer) => {
const list = PersonListProto.decode(new Uint8Array(buffer));
setPeople(list.person);
})
.catch((err) => {
console.error("Failed to fetch people:", err);
addToast("Failed to fetch people list", "error");
})
.finally(() => setIsLoading(false));
};
useEffect(() => {
fetchPeople();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]);
const handleLogin = (newToken: string) => {
setToken(newToken);
localStorage.setItem("token", newToken);
addToast("Welcome back!", "success");
};
const handleLogout = () => {
setToken("");
setPeople([]);
localStorage.removeItem("token");
addToast("Logged out successfully", "info");
};
if (!token) {
return <Login onLogin={handleLogin} />;
}
const themes = [
{ id: "default", label: "Default", icon: "🏠" },
{ id: "blackhole", label: "Blackhole", icon: "🕳️" },
{ id: "star", label: "Star", icon: "⭐" },
{ id: "ball", label: "Ball", icon: "⚽" },
{ id: "reflect", label: "Reflect", icon: "🪞" },
{ id: "clouds", label: "Clouds", icon: "☁️" },
{ id: "sakura", label: "Sakura", icon: "🌸" },
];
return (
<BrowserRouter>
{isLoading && <div className="loading-bar" style={{ width: "50%" }} />}
<div className="toast-container">
{toasts.map((toast) => (
<Toast
key={toast.id}
message={toast.message}
type={toast.type}
onClose={() => removeToast(toast.id)}
/>
))}
</div>
<div className="card">
<div className="navbar">
<div className="nav-links">
<NavLink to="/" className="nav-link">
People
</NavLink>
<NavLink to="/games" className="nav-link">
Games
</NavLink>
<NavLink to="/filter" className="nav-link">
Filter
</NavLink>
</div>
<div style={{ display: "flex", alignItems: "center", gap: "1.5rem" }}>
<div className="theme-switcher">
{themes.map((t) => (
<button
key={t.id}
className={`theme-btn theme-${t.id} ${
theme === t.id ? "active" : ""
}`}
onClick={() => setTheme(t.id)}
title={t.label}
>
{t.icon}
</button>
))}
</div>
<button onClick={handleLogout} className="btn-secondary">
Logout
</button>
</div>
</div>
<ShaderBackground theme={theme} />
<Routes>
<Route path="/" element={<PersonList people={people} />} />
<Route path="/games" element={<GameList onShowToast={addToast} />} />
<Route path="/filter" element={<GameFilter />} />
<Route path="/person/:name" element={<PersonDetails />} />
<Route path="/game/:title" element={<GameDetails />} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;