// /$$$$$$ /$$$$$$ /$$ /$$ /$$$$$$$$ /$$ /$$ /$$$$$$$$ /$$$$$$ /$$$$$ /$$$$$$ /$$$$$$$ // /$$__ $$|_ $$_/| $$ | $$| $$_____/ | $$$ /$$$| $$_____/ /$$__ $$ |__ $$ /$$__ $$| $$__ $$ // | $$ \__/ | $$ | $$ | $$| $$ | $$$$ /$$$$| $$ | $$ \ $$ | $$| $$ \ $$| $$ \ $$ // | $$ /$$$$ | $$ | $$ / $$/| $$$$$ | $$ $$/$$ $$| $$$$$ | $$$$$$$$ | $$| $$ | $$| $$$$$$$ // | $$|_ $$ | $$ \ $$ $$/ | $$__/ | $$ $$$| $$| $$__/ | $$__ $$ /$$ | $$| $$ | $$| $$__ $$ // | $$ \ $$ | $$ \ $$$/ | $$ | $$\ $ | $$| $$ | $$ | $$ | $$ | $$| $$ | $$| $$ \ $$ // | $$$$$$/ /$$$$$$ \ $/ | $$$$$$$$ | $$ \/ | $$| $$$$$$$$ | $$ | $$ | $$$$$$/| $$$$$$/| $$$$$$$/ // \______/ |______/ \_/ |________/ |__/ |__/|________/ |__/ |__/ \______/ \______/ |_______/ // // Hi, I'm Roland and i'm looking for a job. // Resume in /public/resume.pdf // roland.vrignon@roland.com // https://www.linkedin.com/in/roland-vrignon/ // 'use client'; import { FC, useState, useEffect, Dispatch, SetStateAction } from 'react'; import Image from 'next/image'; interface Message { content: string; role: 'lawyer' | 'judge'; } interface Chat { messages: Message[]; } interface DefenseSceneProps { language: 'fr' | 'en' | 'es'; requiredWords: string[]; setNextScene: () => void; setChat: (chat: SetStateAction) => void; setCurrentQuestion: Dispatch>; setReaction: Dispatch>; setRequiredWords: Dispatch>; } const DefenseScene: FC = ({ language, requiredWords, setNextScene, setCurrentQuestion, setChat, setReaction, setRequiredWords }) => { const [answer, setAnswer] = useState(''); const [insertedWords, setInsertedWords] = useState([]); const [countdown, setCountdown] = useState(60); const [isTimeUp, setIsTimeUp] = useState(false); const [wordPositions, setWordPositions] = useState>([]); const [mandatoryWords, setMandatoryWords] = useState(requiredWords); const [isLoading, setIsLoading] = useState(true); const [ words ] = useState(requiredWords); // Initialisation des mots obligatoires useEffect(() => { if (requiredWords.length > 0) { setMandatoryWords(requiredWords); } setReaction(''); // eslint-disable-next-line react-hooks/exhaustive-deps }, [requiredWords]); // Génération des positions et initialisation useEffect(() => { if (mandatoryWords.length > 0) { const positions = generateWordPositions(mandatoryWords); setWordPositions(positions); setInsertedWords(new Array(mandatoryWords.length).fill(false)); setCurrentQuestion(""); setIsLoading(false); } }, [mandatoryWords]); // eslint-disable-line react-hooks/exhaustive-deps // Reset des required words après initialisation useEffect(() => { if (!isLoading && wordPositions.length > 0) { setRequiredWords([]); } }, [isLoading, wordPositions.length]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { if (isTimeUp) { handleSubmit(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isTimeUp]) // Timer et reset de la question useEffect(() => { // Timer const timer = setInterval(() => { setCountdown((prev) => { if (prev === 0) { clearInterval(timer); setIsTimeUp(true); } return prev - 1; }); }, 1000); return () => clearInterval(timer); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // On garde uniquement la dépendance answer car handleSubmit est stable // Génère un nombre aléatoire entre 9 et 15 const generateRandomNumber = () => { return Math.floor(Math.random() * (15 - 9 + 1)) + 9; }; // Génère les positions pour les mots requis const generateWordPositions = (words: string[]) => { let currentPosition = generateRandomNumber(); // On commence à une position aléatoire return words.map(word => { const position = currentPosition; currentPosition += generateRandomNumber(); // Ajoute un nombre aléatoire de mots pour le prochain mot requis return { word, position }; }); }; // Fonction pour compter les mots const countWords = (text: string) => { // Remplacer temporairement les expressions requises par un seul mot let modifiedText = text; wordPositions.forEach(({ word }) => { if (modifiedText.includes(word)) { // Remplace l'expression complète par un placeholder unique modifiedText = modifiedText.replace(word, 'SINGLEWORD'); } }); // Maintenant compte les mots normalement return modifiedText.trim().split(/\s+/).length || 0; }; // Fonction pour vérifier si on doit insérer un mot obligatoire const checkAndInsertRequiredWord = (text: string) => { const currentWordCount = countWords(text); let newText = text; const newInsertedWords = [...insertedWords]; let wordInserted = false; wordPositions.forEach((word, index) => { if (!insertedWords[index] && currentWordCount === word.position && text.endsWith(' ')) { newText = `${text}${word.word} `; newInsertedWords[index] = true; wordInserted = true; } }); if (wordInserted) { setInsertedWords(newInsertedWords); } return newText; }; // Fonction pour vérifier si on peut supprimer du texte const canDeleteAt = (text: string, cursorPosition: number) => { // const textBeforeCursor = text.substring(0, cursorPosition); // const wordsBeforeCursor = countWords(textBeforeCursor); return !wordPositions.some((word, index) => { if (!insertedWords[index]) return false; const wordStartPosition = text.indexOf(word.word); const wordEndPosition = wordStartPosition + word.word.length; return cursorPosition > wordStartPosition && cursorPosition <= wordEndPosition; }); }; const handleTextChange = (e: React.ChangeEvent) => { const newText = e.target.value; setAnswer(newText); const cursorPosition = e.target.selectionStart; // Si c'est une suppression if (newText.length < answer.length) { if (!canDeleteAt(answer, cursorPosition)) { return; } } // Insertion normale + vérification des mots obligatoires const processedText = checkAndInsertRequiredWord(newText); setAnswer(processedText); }; const handleSubmit = () => { setChat(prevChat => ({ messages: [...prevChat.messages, { content: answer, role: 'lawyer', requiredWords: words }] })); setNextScene(); }; const formatTime = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs.toString().padStart(2, '0')}`; }; // Vérifie si la réponse est valide pour soumission const isAnswerValid = () => { const lastWordPosition = Math.max(...wordPositions.map(word => word.position)); const currentWordCount = countWords(answer); return currentWordCount >= lastWordPosition && insertedWords.every(inserted => inserted); }; return (
{/* Image de fond */} Background {/* Contenu avec overlay noir */}
{isLoading ? (
{language === 'fr' ? 'Chargement...' : language === 'en' ? 'Loading...' : 'Cargando...'}
) : (
{/* Header avec le compte à rebours */}
{formatTime(countdown)}
{/* Prochain mot requis */}
{wordPositions.map((item, index) => { // Ne montrer que le premier mot non inséré if (insertedWords[index] || index > 0 && !insertedWords[index - 1]) return null; const remainingWords = item.position - countWords(answer); return (
{item.word.toUpperCase()} {language === 'fr' ? `dans ` : language === 'en' ? `in ` : `en ` } {remainingWords} {language === 'fr' ? ` mots` : language === 'en' ? ` words` : ` palabras` }
); }).filter(Boolean)[0]}
{/* Zone de texte avec bouton submit en position absolue */}