game_list/frontend/src/GameDetails.tsx

135 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { Game, Source } from "../items";
import { apiFetch, get_is_admin } from "./api";
interface Props {
onShowToast?: (message: string, type?: "success" | "error" | "info") => void;
}
export function GameDetails({ onShowToast }: Props) {
const { title } = useParams<{ title: string }>();
const navigate = useNavigate();
const [game, setGame] = useState<Game | null>(null);
const [loading, setLoading] = useState(true);
const isAdmin = get_is_admin();
useEffect(() => {
if (!title) return;
apiFetch(`/api/game/${encodeURIComponent(title)}`)
.then(async (res) => {
if (!res.ok) throw new Error("Game not found");
return Game.decode(new Uint8Array(await res.arrayBuffer()));
})
.then((data) => {
setGame(data);
setLoading(false);
})
.catch((err) => {
console.error(err);
setLoading(false);
});
}, [title]);
const handleDelete = async () => {
if (
!confirm(
`Are you sure you want to delete "${game?.title}"? This action cannot be undone.`
)
) {
return;
}
try {
const res = await apiFetch(`/api/game/${encodeURIComponent(title || "")}`, {
method: "DELETE",
});
if (res.ok) {
onShowToast?.(`"${game?.title}" deleted successfully`, "success");
navigate("/games");
} else {
onShowToast?.("Failed to delete game", "error");
}
} catch (err) {
console.error(err);
onShowToast?.("An error occurred while deleting the game", "error");
}
};
if (loading) return <div>Loading...</div>;
if (!game) return <div>Game not found</div>;
const getExternalLink = () => {
if (game.source === Source.STEAM) {
return `https://store.steampowered.com/app/${game.remoteId}`;
} else if (game.source === Source.ROBLOX) {
return `https://www.roblox.com/games/${game.remoteId}`;
}
return "#";
};
return (
<div className="card" style={{ maxWidth: "600px", margin: "0 auto" }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "1rem",
}}
>
<h2 style={{ margin: 0 }}>{game.title}</h2>
{isAdmin && (
<div style={{ display: "flex", gap: "0.5rem" }}>
<button
onClick={() => navigate(`/game/${encodeURIComponent(game.title)}/edit`)}
className="btn-secondary"
style={{ padding: "0.5rem 1rem", fontSize: "0.9rem" }}
>
Edit
</button>
<button
onClick={handleDelete}
className="btn-primary"
style={{
padding: "0.5rem 1rem",
fontSize: "0.9rem",
background: "#f44336",
border: "none",
}}
>
🗑 Delete
</button>
</div>
)}
</div>
<div style={{ display: "grid", gap: "1rem", marginTop: "1rem" }}>
<div>
<strong>Source:</strong>{" "}
{game.source === Source.STEAM ? "Steam" : "Roblox"}
</div>
<div>
<strong>Players:</strong> {game.minPlayers} - {game.maxPlayers}
</div>
<div>
<strong>Price:</strong> ${game.price}
</div>
</div>
<div style={{ marginTop: "2rem" }}>
<a
href={getExternalLink()}
target="_blank"
rel="noopener noreferrer"
className="btn-primary"
style={{ textDecoration: "none", display: "inline-block" }}
>
View on {game.source === Source.STEAM ? "Steam" : "Roblox"}
</a>
</div>
</div>
);
}