From 38e570b43d285d368ff7a57eac4ae22d38ab98b1 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 1 Jun 2024 20:38:43 -0400 Subject: [PATCH] Add toast messages & allow for no time limit --- package-lock.json | 34 + package.json | 2 + src/app/__tests__/new-game-form.test.jsx | 44 +- src/app/components/challenges/Trivia.tsx | 2 +- src/app/components/createGame/NewGameForm.tsx | 818 ++++++++++-------- .../challengeInputs/CryptogramChallenge.tsx | 2 +- .../FillInTheBlankChallenge.tsx | 2 +- .../challengeInputs/TriviaChallenge.tsx | 2 +- .../challengeInputs/WordScrambleChallenge.tsx | 2 +- src/app/components/ui/Input.tsx | 2 +- src/app/components/ui/TextArea.tsx | 10 +- src/app/components/ui/Timer.tsx | 2 +- src/app/contexts/savedGamesContext.tsx | 10 + src/app/contexts/timerContext.tsx | 4 +- src/app/game/[game]/[challenge]/layout.tsx | 4 +- src/app/game/[game]/page.tsx | 5 +- src/app/game/[game]/win/page.tsx | 19 +- src/app/globals.css | 6 +- src/app/new-game/page.tsx | 24 +- src/app/play/page.tsx | 9 +- src/app/ui-kit/page.tsx | 4 +- 21 files changed, 580 insertions(+), 427 deletions(-) diff --git a/package-lock.json b/package-lock.json index 977d5ab..93b2084 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,8 @@ "react-dom": "^18", "react-flip-move": "^3.0.5", "react-flip-toolkit": "^7.1.0", + "react-hot-toast": "^2.4.1", + "react-hotkeys-hook": "^4.5.0", "uuid": "^9.0.1" }, "devDependencies": { @@ -6858,6 +6860,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -10331,6 +10341,30 @@ "react-dom": ">= 16.x" } }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-hotkeys-hook": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.5.0.tgz", + "integrity": "sha512-Samb85GSgAWFQNvVt3PS90LPPGSf9mkH/r4au81ZP1yOIFayLC3QAvqTgGtJ8YEDMXtPmaVBs6NgipHO6h4Mug==", + "peerDependencies": { + "react": ">=16.8.1", + "react-dom": ">=16.8.1" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index f68753b..70eaef2 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "react-dom": "^18", "react-flip-move": "^3.0.5", "react-flip-toolkit": "^7.1.0", + "react-hot-toast": "^2.4.1", + "react-hotkeys-hook": "^4.5.0", "uuid": "^9.0.1" }, "devDependencies": { diff --git a/src/app/__tests__/new-game-form.test.jsx b/src/app/__tests__/new-game-form.test.jsx index 4ef1294..5715104 100644 --- a/src/app/__tests__/new-game-form.test.jsx +++ b/src/app/__tests__/new-game-form.test.jsx @@ -51,7 +51,7 @@ global.fetch = jest.fn(() => }) ); -describe('New Game Form', () => { +describe.only('New Game Form', () => { it('renders all inputs', () => { const { getByLabelText } = render(); @@ -85,7 +85,7 @@ describe('Handle form inputs and submission', () => { expect(timeLimit.value).toBe('1800'); }); - it('Adds inputs to challenges', async () => { + it.only('Adds inputs to challenges', async () => { const { getByLabelText, getAllByLabelText, @@ -96,6 +96,8 @@ describe('Handle form inputs and submission', () => { const user = userEvent.setup(); // Add Trivia clue + const addtriviaButton = getByTestId('add-trivia-challenge'); + await user.click(addtriviaButton); const triviaClue = getByLabelText('Question (required)'); await user.click(triviaClue); await user.keyboard('Trivia question #1'); @@ -105,6 +107,10 @@ describe('Handle form inputs and submission', () => { await user.keyboard('Trivia answer #1'); // Add Cryptogram description + const cryptogramLabel = getByTestId('add-cryptogram'); + await user.click(cryptogramLabel); + const addCryptogramButton = getByTestId('add-cryptogram-challenge'); + await user.click(addCryptogramButton); const cipherDesc = getByTestId('challenge-1-cryptogram-answer'); await user.click(cipherDesc); await user.keyboard('Decrypt this phrase'); @@ -115,6 +121,10 @@ describe('Handle form inputs and submission', () => { expect(cipherClue.value).not.toBeNull(); // Add Word Scramble description & answer + const wordScrambleLabel = getByTestId('add-cryptogram'); + await user.click(wordScrambleLabel); + const addWordScrambleButton = getByTestId('add-word-scramble-challenge'); + await user.click(addWordScrambleButton); const scrambleDescription = getByPlaceholderText( 'Describe the phrase to be solved' ); @@ -235,22 +245,30 @@ it('Adds a FITB challenge and renders the answer & clues', async () => { const addChallengeButton = getByTestId('add-fill-in-the-blank-challenge'); await user.click(addChallengeButton); - const answer = getByTestId('challenge-3-fill-in-the-blank-answer') - await user.click(answer) - await user.keyboard('Challenge answer with blank spaces') + const answer = getByTestId('challenge-3-fill-in-the-blank-answer'); + await user.click(answer); + await user.keyboard('Challenge answer with blank spaces'); // Remove a word - expect a blank space and - const wordToRemove = getByTestId('challenge-3-fill-in-the-blank-highlight-word-3') - await user.click(wordToRemove) - const blank = getByText('________') + const wordToRemove = getByTestId( + 'challenge-3-fill-in-the-blank-highlight-word-3' + ); + await user.click(wordToRemove); + const blank = getByText('________'); expect(blank).toBeInTheDocument(); - const correctClueWord = getByTestId('challenge-3-fill-in-the-blank-correct-clue-word-3') + const correctClueWord = getByTestId( + 'challenge-3-fill-in-the-blank-correct-clue-word-3' + ); expect(correctClueWord).toBeInTheDocument(); - const incorrectWordInput = getByTestId('challenge-3-fill-in-the-blank-incorrect-words') - await user.click(incorrectWordInput) - await user.keyboard('incorrect, words') - const incorrectClueWord = getByTestId('challenge-3-fill-in-the-blank-incorrect-clue-word-0') + const incorrectWordInput = getByTestId( + 'challenge-3-fill-in-the-blank-incorrect-words' + ); + await user.click(incorrectWordInput); + await user.keyboard('incorrect, words'); + const incorrectClueWord = getByTestId( + 'challenge-3-fill-in-the-blank-incorrect-clue-word-0' + ); expect(incorrectClueWord).toBeInTheDocument(); }); diff --git a/src/app/components/challenges/Trivia.tsx b/src/app/components/challenges/Trivia.tsx index 61e9cb0..855f182 100644 --- a/src/app/components/challenges/Trivia.tsx +++ b/src/app/components/challenges/Trivia.tsx @@ -42,7 +42,7 @@ export default function TriviaChallenge({

{currentChallenge.clue}

('trivia'); const [savingToDB, setSavingToDB] = useState(false); const [submitError, setSubmitError] = useState(false); const [submitMessage, setSubmitMessage] = useState([]); const [editError, setEditError] = useState(false); const [editMessage, setEditMessage] = useState(''); - const [tooManyGames, setTooManyGames] = useState(false); - const [challengeId, setChallengeId] = useState(0); + const [tooManyGames, setTooManyGames] = useState(false); + const [challengeId, setChallengeId] = useState(0); + const [editingDetails, setEditingDetails] = useState(true); // Instead of using a fancy way to get a unique ID for mapped challenges, we're just incrementing each time a challenge is added // and not decrementing whenever a challenge is deleted. @@ -105,6 +107,7 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { saveForm(gameData); } } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [user.id, newGame.id]); // Reusable function to save form to localStorage @@ -141,6 +144,8 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { }, [newGame.challenges.length]); const handleInputChange = (e: any) => { + setSubmitError(false); + setSubmitMessage([]); setNewGame((prevGame: Game) => { let saveGame: Game = prevGame; switch (e.target.dataset.type) { @@ -157,6 +162,8 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { }; const handleTimeLimitChange = (e: any) => { + setSubmitError(false); + setSubmitMessage([]); setNewGame((prevGame: Game) => { const newGame = { ...prevGame, timeLimit: e.target.value }; saveForm(newGame); @@ -164,7 +171,36 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { }); }; + const handleGoToChallenges = () => { + let hasError = false; + setSubmitMessage([]); + + if (newGame.gameTitle === '') { + hasError = true; + setSubmitMessage((prev) => [ + ...prev, + 'Please add a title for your game.', + ]); + } + + if (newGame.gameDescription === '') { + hasError = true; + setSubmitMessage((prev) => [ + ...prev, + 'Please add a short description for your game.', + ]); + } + + setSubmitError(hasError); + + if (!hasError) { + setEditingDetails(!editingDetails); + } + }; + const handleClueChange = (e: any, type: string, index: number) => { + setSubmitError(false); + setSubmitMessage([]); let clue: string | string[]; // Array clues come from the actual value passed to onClueChange, not the event target if ( @@ -190,6 +226,8 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { }; const handleDescriptionChange = (e: any, index: number) => { + setSubmitError(false); + setSubmitMessage([]); setNewGame((prevGame: Game) => { const newChallenges = prevGame.challenges.map((item, itemIndex) => { if (itemIndex === index) { @@ -204,6 +242,8 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { }; const handleAnswerChange = (e: any, index: number) => { + setSubmitError(false); + setSubmitMessage([]); setNewGame((prevGame: Game) => { const newChallenges = prevGame.challenges.map((item, itemIndex) => { if (itemIndex === index) { @@ -217,14 +257,13 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { }); }; - const handleAddChallenge = (e: any) => { - e.preventDefault(); + const handleAddChallenge = (challengeType: string) => { setNewGame((prevGame: Game) => { const newChallenges = [ ...prevGame.challenges, { id: `challenge-${challengeId}`, // this number should be the current length of the challenge array (challenges are zero-indexed) - type: nextChallenge, + type: challengeType, description: '', clue: '', answer: '', @@ -249,10 +288,6 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { }); }; - const handleNextChallenge = (e: any) => { - setNextChallenge(e.target.value); - }; - const handleSubmit = async (e: any) => { e.preventDefault(); setSavingToDB(true); @@ -360,6 +395,7 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { if (!newGame.private) { // Add to public games in front-end (no need for refetching) + console.log('Saving private game...') setLoadedGames((prevGames: Game[]) => { let newGames: Game[]; if (editGame) { @@ -378,6 +414,7 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { }); } else { // remove from public games in FE + console.log('saving public game...') if (editGame) { setLoadedGames((prevGames: Game[]) => { let newGames: Game[]; @@ -390,6 +427,7 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { // Add to main games table in DB if (user.id !== '') { + console.log('saving to DB...') if (editGame) { setEditError(false); setEditMessage(''); @@ -418,6 +456,36 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { setSubmitMessage(['Game saved!']); } } + } else { + console.log('not saving to DB') + toast.custom( + (t) => ( +
+ + + + + Your game is saved to your device. If you would like to share or + save across devices, please sign in{' '} + or sign up. + + +
+ ), + { position: 'bottom-center' } + ); } // Add game ID to the user's saved games array in escape-room-users table in DB @@ -443,266 +511,218 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { setNewGame(defaultGameData); }; - const minutes = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60]; + const maxTimeLimit = Array.from(Array(16).keys()); + return ( <> - {tooManyGames && ( -
- - - - - Only 3 games may be created per day. You can create another game in{' '} - {Number( - (86400000 - (Date.now() - user.recentGameTimestamps[0])) / - 1000 / - 60 / - 60 - ).toFixed(1)}{' '} - hours. - -
- )} + {tooManyGames && + toast.custom( + (t) => ( +
+ + + + + Only 3 games may be created per day. You can create another game + in{' '} + {Number( + (86400000 - (Date.now() - user.recentGameTimestamps[0])) / + 1000 / + 60 / + 60 + ).toFixed(1)}{' '} + hours. + + +
+ ), + { position: 'bottom-center' } + )} {/* Update to when this has been refactored to a server action */} - -
-

Create your Quiz

- - -
-
- -