Fix homepage layout and add Polish language support
- Reorganize StartScreen: Start New Game button always visible at top - Move unfinished games section below the start button - Remove '2.' prefix from startGame translations (all languages) - Update startNewGame translations to be button-appropriate - Fix TypeScript errors: Update GameImportModal, GameAnalysisModal, and GameOverModal to use SupportedLanguage type instead of hardcoded language union - Delete local branches: codex/add-polish-as-ui-language and codex/add-option-to-continue-game-from-position
This commit is contained in:
@@ -5,12 +5,13 @@ import { Stockfish, StockfishEvaluation } from "@/lib/stockfish";
|
|||||||
import { OpeningMetadata, lookupOpening } from "@/lib/openings";
|
import { OpeningMetadata, lookupOpening } from "@/lib/openings";
|
||||||
import { getGenAIModel } from "@/lib/gemini";
|
import { getGenAIModel } from "@/lib/gemini";
|
||||||
import { Loader2, X, Brain, Trophy, AlertTriangle } from "lucide-react";
|
import { Loader2, X, Brain, Trophy, AlertTriangle } from "lucide-react";
|
||||||
|
import { SupportedLanguage } from "@/lib/i18n/translations";
|
||||||
|
|
||||||
interface GameAnalysisModalProps {
|
interface GameAnalysisModalProps {
|
||||||
fen: string;
|
fen: string;
|
||||||
stockfish: Stockfish | null;
|
stockfish: Stockfish | null;
|
||||||
apiKey: string | null;
|
apiKey: string | null;
|
||||||
language: 'en' | 'de' | 'fr' | 'it';
|
language: SupportedLanguage;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import { useState, useEffect } from "react";
|
|||||||
import { Chessboard } from "react-chessboard";
|
import { Chessboard } from "react-chessboard";
|
||||||
import { X, Loader2, Download, ExternalLink } from "lucide-react";
|
import { X, Loader2, Download, ExternalLink } from "lucide-react";
|
||||||
import { fetchChessComGames, fetchLichessGames, GameMetadata, Platform } from "@/lib/gameImport";
|
import { fetchChessComGames, fetchLichessGames, GameMetadata, Platform } from "@/lib/gameImport";
|
||||||
|
import { SupportedLanguage } from "@/lib/i18n/translations";
|
||||||
|
|
||||||
interface GameImportModalProps {
|
interface GameImportModalProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSelectGame: (pgn: string) => void;
|
onSelectGame: (pgn: string) => void;
|
||||||
language: 'en' | 'de' | 'fr' | 'it';
|
language: SupportedLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GameImportModal({ onClose, onSelectGame, language }: GameImportModalProps) {
|
export function GameImportModal({ onClose, onSelectGame, language }: GameImportModalProps) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Loader2, X, Trophy, AlertTriangle, RefreshCw } from "lucide-react";
|
|||||||
import { StockfishEvaluation } from "@/lib/stockfish";
|
import { StockfishEvaluation } from "@/lib/stockfish";
|
||||||
import { DetectedTactic } from "@/lib/tacticDetection";
|
import { DetectedTactic } from "@/lib/tacticDetection";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import { SupportedLanguage } from "@/lib/i18n/translations";
|
||||||
|
|
||||||
export interface MoveHistoryItem {
|
export interface MoveHistoryItem {
|
||||||
moveNumber: number;
|
moveNumber: number;
|
||||||
@@ -50,7 +51,7 @@ interface GameOverModalProps {
|
|||||||
winner: "White" | "Black" | "Draw";
|
winner: "White" | "Black" | "Draw";
|
||||||
history: MoveHistoryItem[];
|
history: MoveHistoryItem[];
|
||||||
apiKey: string | null;
|
apiKey: string | null;
|
||||||
language: 'en' | 'de' | 'fr' | 'it';
|
language: SupportedLanguage;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onNewGame: () => void;
|
onNewGame: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,11 +85,7 @@ export default function StartScreen({ onStartGame, onResumeGame, savedGames, onD
|
|||||||
return t.start.noEvaluation;
|
return t.start.noEvaluation;
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!hasSavedGames) {
|
|
||||||
setShowNewGameOptions(true);
|
|
||||||
}
|
|
||||||
}, [hasSavedGames]);
|
|
||||||
|
|
||||||
if (!mounted) return null;
|
if (!mounted) return null;
|
||||||
|
|
||||||
@@ -118,18 +114,24 @@ export default function StartScreen({ onStartGame, onResumeGame, savedGames, onD
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{hasSavedGames && (
|
{/* Start New Game Button - Always visible at top */}
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowNewGameOptions(!showNewGameOptions)}
|
||||||
|
className="w-full py-4 px-4 bg-blue-600 text-white rounded-xl hover:bg-blue-700 font-semibold shadow-lg transition-transform transform hover:scale-[1.02] flex items-center justify-center gap-2"
|
||||||
|
>
|
||||||
|
<Brain size={18} />
|
||||||
|
{t.start.startNewGame}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Unfinished Games Section */}
|
||||||
|
{hasSavedGames && !showNewGameOptions && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
{t.start.savedGamesTitle}
|
{t.start.savedGamesTitle}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
|
||||||
onClick={() => setShowNewGameOptions(true)}
|
|
||||||
className="text-sm text-blue-600 dark:text-blue-400 hover:underline"
|
|
||||||
>
|
|
||||||
{t.start.startNewGame}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{sortedSavedGames.length === 0 && (
|
{sortedSavedGames.length === 0 && (
|
||||||
@@ -210,7 +212,7 @@ export default function StartScreen({ onStartGame, onResumeGame, savedGames, onD
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* New Game Options */}
|
{/* New Game Options */}
|
||||||
{(!hasSavedGames || showNewGameOptions) && (
|
{showNewGameOptions && (
|
||||||
<div className="space-y-8 animate-in fade-in slide-in-from-top-4 duration-300">
|
<div className="space-y-8 animate-in fade-in slide-in-from-top-4 duration-300">
|
||||||
{/* Color Selection */}
|
{/* Color Selection */}
|
||||||
<div>
|
<div>
|
||||||
@@ -326,14 +328,12 @@ export default function StartScreen({ onStartGame, onResumeGame, savedGames, onD
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hasSavedGames && (
|
<button
|
||||||
<button
|
onClick={() => setShowNewGameOptions(false)}
|
||||||
onClick={() => setShowNewGameOptions(false)}
|
className="w-full py-3 text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
|
||||||
className="w-full py-3 text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
|
>
|
||||||
>
|
{t.common.cancel}
|
||||||
{t.common.cancel}
|
</button>
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -194,9 +194,9 @@ const en: Translations = {
|
|||||||
apiKey: 'Google Gemini API Key',
|
apiKey: 'Google Gemini API Key',
|
||||||
apiKeyPlaceholder: 'AIzaSy...',
|
apiKeyPlaceholder: 'AIzaSy...',
|
||||||
getApiKey: 'Get free key',
|
getApiKey: 'Get free key',
|
||||||
startGame: '2. Start Game',
|
startGame: 'Start Game',
|
||||||
resumeGame: 'Resume Previous Game',
|
resumeGame: 'Resume Previous Game',
|
||||||
startNewGame: 'Start New Game instead...',
|
startNewGame: 'Start New Game',
|
||||||
chooseCoach: 'Choose Your Coach:',
|
chooseCoach: 'Choose Your Coach:',
|
||||||
importPosition: 'Import Position or Game (FEN or PGN)',
|
importPosition: 'Import Position or Game (FEN or PGN)',
|
||||||
importPositionPlaceholder: 'Paste FEN position or PGN game here...',
|
importPositionPlaceholder: 'Paste FEN position or PGN game here...',
|
||||||
@@ -355,9 +355,9 @@ const de: Translations = {
|
|||||||
apiKey: 'Google Gemini API-Schlüssel',
|
apiKey: 'Google Gemini API-Schlüssel',
|
||||||
apiKeyPlaceholder: 'AIzaSy...',
|
apiKeyPlaceholder: 'AIzaSy...',
|
||||||
getApiKey: 'Kostenlosen Schlüssel erhalten',
|
getApiKey: 'Kostenlosen Schlüssel erhalten',
|
||||||
startGame: '2. Spiel starten',
|
startGame: 'Spiel starten',
|
||||||
resumeGame: 'Vorheriges Spiel fortsetzen',
|
resumeGame: 'Vorheriges Spiel fortsetzen',
|
||||||
startNewGame: 'Stattdessen neues Spiel starten...',
|
startNewGame: 'Neues Spiel starten',
|
||||||
chooseCoach: 'Wähle deinen Trainer:',
|
chooseCoach: 'Wähle deinen Trainer:',
|
||||||
importPosition: 'Position oder Partie importieren (FEN oder PGN)',
|
importPosition: 'Position oder Partie importieren (FEN oder PGN)',
|
||||||
importPositionPlaceholder: 'FEN-Position oder PGN-Partie hier einfügen...',
|
importPositionPlaceholder: 'FEN-Position oder PGN-Partie hier einfügen...',
|
||||||
@@ -516,9 +516,9 @@ const fr: Translations = {
|
|||||||
apiKey: 'Clé API Google Gemini',
|
apiKey: 'Clé API Google Gemini',
|
||||||
apiKeyPlaceholder: 'AIzaSy...',
|
apiKeyPlaceholder: 'AIzaSy...',
|
||||||
getApiKey: 'Obtenir une clé gratuite',
|
getApiKey: 'Obtenir une clé gratuite',
|
||||||
startGame: '2. Démarrer la partie',
|
startGame: 'Démarrer la partie',
|
||||||
resumeGame: 'Reprendre la partie précédente',
|
resumeGame: 'Reprendre la partie précédente',
|
||||||
startNewGame: 'Démarrer une nouvelle partie...',
|
startNewGame: 'Démarrer une nouvelle partie',
|
||||||
chooseCoach: 'Choisissez votre coach :',
|
chooseCoach: 'Choisissez votre coach :',
|
||||||
importPosition: 'Importer une position ou partie (FEN ou PGN)',
|
importPosition: 'Importer une position ou partie (FEN ou PGN)',
|
||||||
importPositionPlaceholder: 'Collez une position FEN ou partie PGN ici...',
|
importPositionPlaceholder: 'Collez une position FEN ou partie PGN ici...',
|
||||||
@@ -677,9 +677,9 @@ const it: Translations = {
|
|||||||
apiKey: 'Chiave API Google Gemini',
|
apiKey: 'Chiave API Google Gemini',
|
||||||
apiKeyPlaceholder: 'AIzaSy...',
|
apiKeyPlaceholder: 'AIzaSy...',
|
||||||
getApiKey: 'Ottieni chiave gratuita',
|
getApiKey: 'Ottieni chiave gratuita',
|
||||||
startGame: '2. Inizia partita',
|
startGame: 'Inizia partita',
|
||||||
resumeGame: 'Riprendi partita precedente',
|
resumeGame: 'Riprendi partita precedente',
|
||||||
startNewGame: 'Inizia nuova partita...',
|
startNewGame: 'Inizia nuova partita',
|
||||||
chooseCoach: 'Scegli il tuo allenatore:',
|
chooseCoach: 'Scegli il tuo allenatore:',
|
||||||
importPosition: 'Importa posizione o partita (FEN o PGN)',
|
importPosition: 'Importa posizione o partita (FEN o PGN)',
|
||||||
importPositionPlaceholder: 'Incolla posizione FEN o partita PGN qui...',
|
importPositionPlaceholder: 'Incolla posizione FEN o partita PGN qui...',
|
||||||
@@ -838,9 +838,9 @@ const pl: Translations = {
|
|||||||
apiKey: 'Klucz API Google Gemini',
|
apiKey: 'Klucz API Google Gemini',
|
||||||
apiKeyPlaceholder: 'AIzaSy...',
|
apiKeyPlaceholder: 'AIzaSy...',
|
||||||
getApiKey: 'Zdobądź darmowy klucz',
|
getApiKey: 'Zdobądź darmowy klucz',
|
||||||
startGame: '2. Rozpocznij grę',
|
startGame: 'Rozpocznij grę',
|
||||||
resumeGame: 'Wznów poprzednią partię',
|
resumeGame: 'Wznów poprzednią partię',
|
||||||
startNewGame: 'Zacznij nową partię...',
|
startNewGame: 'Rozpocznij nową partię',
|
||||||
chooseCoach: 'Wybierz swojego trenera:',
|
chooseCoach: 'Wybierz swojego trenera:',
|
||||||
importPosition: 'Importuj pozycję lub partię (FEN lub PGN)',
|
importPosition: 'Importuj pozycję lub partię (FEN lub PGN)',
|
||||||
importPositionPlaceholder: 'Wklej tutaj pozycję FEN lub partię PGN...',
|
importPositionPlaceholder: 'Wklej tutaj pozycję FEN lub partię PGN...',
|
||||||
|
|||||||
Reference in New Issue
Block a user