bug fixes and first try of github docker

This commit is contained in:
Stefan
2025-11-23 16:43:47 +01:00
parent 74016f08c6
commit 0aa4370f64
12 changed files with 695 additions and 350 deletions
+102 -1
View File
@@ -1,5 +1,106 @@
"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import ChessGame from "@/components/ChessGame";
import StartScreen from "@/components/StartScreen";
import { Personality } from "@/lib/personalities";
type ViewState = 'start' | 'game';
export default function Home() {
return <ChessGame />;
const router = useRouter();
const [view, setView] = useState<ViewState>('start');
const [mounted, setMounted] = useState(false);
// Game Initialization State
const [gameProps, setGameProps] = useState<{
initialFen?: string;
initialPersonality: Personality;
initialColor: 'white' | 'black';
} | null>(null);
const [hasSavedGame, setHasSavedGame] = useState(false);
useEffect(() => {
// Check for API Key
const apiKey = localStorage.getItem("gemini_api_key");
if (!apiKey) {
router.push("/settings");
return;
}
// Check for saved game
const savedGame = localStorage.getItem("chess_tutor_save");
if (savedGame) {
setHasSavedGame(true);
}
setMounted(true);
}, [router]);
const handleStartGame = (options: {
personality: Personality;
color: 'white' | 'black' | 'random';
fen?: string;
}) => {
const color = options.color === 'random'
? (Math.random() < 0.5 ? 'white' : 'black')
: options.color;
setGameProps({
initialFen: options.fen,
initialPersonality: options.personality,
initialColor: color
});
setView('game');
};
const handleResumeGame = () => {
const savedGame = localStorage.getItem("chess_tutor_save");
if (savedGame) {
try {
const data = JSON.parse(savedGame);
if (data.fen && data.selectedPersonality) {
setGameProps({
initialFen: data.fen,
initialPersonality: data.selectedPersonality,
initialColor: data.playerColor || 'white'
});
setView('game');
}
} catch (e) {
console.error("Failed to resume game:", e);
}
}
};
const handleBackToMenu = () => {
setView('start');
// Re-check saved game status as it might have changed
const savedGame = localStorage.getItem("chess_tutor_save");
setHasSavedGame(!!savedGame);
};
if (!mounted) return null;
return (
<main>
{view === 'start' && (
<StartScreen
onStartGame={handleStartGame}
onResumeGame={handleResumeGame}
hasSavedGame={hasSavedGame}
/>
)}
{view === 'game' && gameProps && (
<ChessGame
initialFen={gameProps.initialFen}
initialPersonality={gameProps.initialPersonality}
initialColor={gameProps.initialColor}
onBack={handleBackToMenu}
/>
)}
</main>
);
}
+118
View File
@@ -0,0 +1,118 @@
"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import Header from "@/components/Header";
import { useTranslation } from "@/lib/i18n/useTranslation";
import { SupportedLanguage } from "@/lib/i18n/translations";
import { ArrowLeft, Save } from "lucide-react";
export default function SettingsPage() {
const router = useRouter();
const [apiKey, setApiKey] = useState("");
const [language, setLanguage] = useState<SupportedLanguage>('en');
const [mounted, setMounted] = useState(false);
// Load settings on mount
useEffect(() => {
const storedKey = localStorage.getItem("gemini_api_key");
const storedLang = localStorage.getItem("chess_tutor_language");
if (storedKey) setApiKey(storedKey);
if (storedLang) setLanguage(storedLang as SupportedLanguage);
setMounted(true);
}, []);
const t = useTranslation(language);
const handleSave = () => {
if (apiKey.trim()) {
localStorage.setItem("gemini_api_key", apiKey.trim());
} else {
localStorage.removeItem("gemini_api_key");
}
localStorage.setItem("chess_tutor_language", language);
// Go back to home
router.push("/");
};
if (!mounted) return null;
return (
<>
<Header language={language} />
<div className="min-h-screen bg-gray-100 dark:bg-gray-900 p-4">
<div className="max-w-2xl mx-auto pt-8">
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-8 space-y-8">
<div className="flex items-center gap-4 border-b border-gray-200 dark:border-gray-700 pb-6">
<button
onClick={() => router.push("/")}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full transition-colors"
>
<ArrowLeft size={24} className="text-gray-600 dark:text-gray-300" />
</button>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
{t.start.settings}
</h1>
</div>
<div className="space-y-6">
{/* Language Selection */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{t.start.language}
</label>
<div className="flex gap-3">
{(['en', 'de', 'fr', 'it'] as SupportedLanguage[]).map((lang) => (
<button
key={lang}
onClick={() => setLanguage(lang)}
className={`px-4 py-2 rounded-lg border text-sm font-medium transition-all ${language === lang
? 'bg-blue-600 text-white border-blue-600 shadow-md'
: 'bg-gray-50 dark:bg-gray-700 border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200'
}`}
>
{lang.toUpperCase()}
</button>
))}
</div>
</div>
{/* API Key Input */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{t.start.apiKey}
</label>
<div className="space-y-2">
<input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder={t.start.apiKeyPlaceholder}
className="w-full p-3 border rounded-lg dark:bg-gray-700 dark:border-gray-600 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 outline-none transition-all"
/>
<p className="text-xs text-gray-500 dark:text-gray-400">
{t.start.apiKeyRequired} <a href="https://aistudio.google.com/app/apikey" target="_blank" rel="noreferrer" className="text-blue-600 hover:underline">{t.start.getApiKey}</a>
</p>
</div>
</div>
</div>
<div className="pt-6 border-t border-gray-200 dark:border-gray-700 flex justify-end">
<button
onClick={handleSave}
className="flex items-center gap-2 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium shadow-md transition-transform transform hover:scale-[1.02]"
>
<Save size={20} />
{t.common?.save || "Save Settings"}
</button>
</div>
</div>
</div>
</div>
</>
);
}