diff --git a/backend/src/main.rs b/backend/src/main.rs index 2e5dee6..a821d7c 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -65,7 +65,9 @@ async fn add_game( let mut games = game_list.lock().await; let mut game = game.into_inner(); - if games.iter().any(|g| g.title == game.title) { + if games.iter().any(|g| { + g.title == game.title || (g.remote_id == game.remote_id && g.source == game.source) + }) { return None; } diff --git a/frontend/src/GameList.tsx b/frontend/src/GameList.tsx index c1e1573..95e56aa 100644 --- a/frontend/src/GameList.tsx +++ b/frontend/src/GameList.tsx @@ -12,6 +12,7 @@ export function GameList() { const [price, setPrice] = useState(0); const [remoteId, setRemoteId] = useState(0); const [message, setMessage] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); const fetchGames = () => { apiFetch("/api/games") @@ -33,6 +34,7 @@ export function GameList() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + setIsSubmitting(true); const game = { title, source, @@ -53,106 +55,378 @@ export function GameList() { }); if (res.ok) { - setMessage("Game added successfully!"); + setMessage("success"); setTitle(""); + setMinPlayers(1); + setMaxPlayers(1); + setPrice(0); + setRemoteId(0); fetchGames(); - // Reset other fields if needed + setTimeout(() => setMessage(""), 3000); } else { - setMessage("Failed to add game."); + setMessage("error"); + setTimeout(() => setMessage(""), 3000); } } catch (err) { console.error(err); - setMessage("Error adding game."); + setMessage("error"); + setTimeout(() => setMessage(""), 3000); + } finally { + setIsSubmitting(false); } }; + const formCardStyles: React.CSSProperties = { + background: + "linear-gradient(135deg, var(--secondary-bg) 0%, var(--secondary-alt-bg) 100%)", + borderRadius: "20px", + padding: "0", + maxWidth: "520px", + boxShadow: "0 20px 40px rgba(0, 0, 0, 0.3), 0 0 0 1px var(--border-color)", + overflow: "hidden", + }; + + const formHeaderStyles: React.CSSProperties = { + background: "linear-gradient(135deg, var(--accent-color) 0%, #0a4f8c 100%)", + padding: "1.5rem 2rem", + display: "flex", + alignItems: "center", + gap: "1rem", + }; + + const formBodyStyles: React.CSSProperties = { + padding: "2rem", + }; + + const sectionStyles: React.CSSProperties = { + marginBottom: "1.5rem", + }; + + const sectionTitleStyles: React.CSSProperties = { + fontSize: "0.75rem", + textTransform: "uppercase", + letterSpacing: "0.1em", + color: "var(--text-muted)", + marginBottom: "1rem", + display: "flex", + alignItems: "center", + gap: "0.5rem", + }; + + const inputGroupStyles: React.CSSProperties = { + position: "relative", + marginBottom: "1rem", + }; + + const labelStyles: React.CSSProperties = { + fontSize: "0.85rem", + color: "var(--text-muted)", + marginBottom: "0.5rem", + display: "block", + fontWeight: 500, + }; + + const inputStyles: React.CSSProperties = { + width: "100%", + padding: "0.875rem 1rem", + backgroundColor: "var(--tertiary-bg)", + border: "2px solid var(--border-color)", + borderRadius: "12px", + color: "var(--text-color)", + fontSize: "1rem", + transition: "all 0.2s ease", + boxSizing: "border-box", + }; + + const gridStyles: React.CSSProperties = { + display: "grid", + gridTemplateColumns: "1fr 1fr", + gap: "1rem", + }; + + const dividerStyles: React.CSSProperties = { + height: "1px", + background: + "linear-gradient(90deg, transparent, var(--border-color), transparent)", + margin: "1.5rem 0", + }; + + const submitButtonStyles: React.CSSProperties = { + width: "100%", + padding: "1rem", + background: "linear-gradient(135deg, var(--accent-color) 0%, #0a4f8c 100%)", + border: "none", + borderRadius: "12px", + color: "white", + fontSize: "1rem", + fontWeight: 600, + cursor: isSubmitting ? "not-allowed" : "pointer", + transition: "all 0.3s ease", + display: "flex", + alignItems: "center", + justifyContent: "center", + gap: "0.5rem", + opacity: isSubmitting ? 0.7 : 1, + transform: isSubmitting ? "none" : undefined, + }; + + const messageStyles: React.CSSProperties = { + padding: "1rem", + borderRadius: "12px", + marginBottom: "1.5rem", + display: "flex", + alignItems: "center", + gap: "0.75rem", + animation: "slideIn 0.3s ease", + backgroundColor: + message === "success" + ? "rgba(76, 175, 80, 0.15)" + : "rgba(244, 67, 54, 0.15)", + border: `1px solid ${ + message === "success" + ? "rgba(76, 175, 80, 0.3)" + : "rgba(244, 67, 54, 0.3)" + }`, + color: message === "success" ? "#4caf50" : "#f44336", + }; + return (
-

Add New Game

- {message && ( -

- {message} -

- )} -
-
- - setTitle(e.target.value)} - required - placeholder="Game Title" - /> -
-
- - -
-
-
- - setMinPlayers(Number(e.target.value))} - /> + 🎮
-
- - setMaxPlayers(Number(e.target.value))} - /> +
+

+ Add New Game +

+

+ Add a game to your collection +

-
-
- - setPrice(Number(e.target.value))} - /> -
-
- - setRemoteId(Number(e.target.value))} - /> -
+ +
+ {message && ( +
+ + {message === "success" ? "✓" : "✕"} + + + {message === "success" + ? "Game added successfully!" + : "Failed to add game. Please try again."} + +
+ )} + + + {/* Basic Info Section */} +
+
+ 📝 + Basic Information +
+ +
+ + setTitle(e.target.value)} + required + placeholder="Enter game title..." + style={inputStyles} + className="add-game-input" + /> +
+ +
+ + +
+
+ +
+ + {/* Player Count Section */} +
+
+ 👥 + Player Count +
+ +
+
+ + setMinPlayers(Number(e.target.value))} + min="1" + style={inputStyles} + className="add-game-input" + /> +
+
+ + setMaxPlayers(Number(e.target.value))} + min="1" + style={inputStyles} + className="add-game-input" + /> +
+
+
+ +
+ + {/* Additional Info Section */} +
+
+ 💰 + Additional Details +
+ +
+
+ + setPrice(Number(e.target.value))} + min="0" + step="0.01" + style={inputStyles} + className="add-game-input" + /> +
+
+ + setRemoteId(Number(e.target.value))} + min="0" + style={inputStyles} + className="add-game-input" + /> +
+
+
+ + +
- - +
+ +

Existing Games