From 232904bc61e750d5ab3cb207aa7af13ed73d93b4 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 30 Apr 2024 14:57:21 -0400 Subject: [PATCH] Rename Caesar Cipher to Cryptogram - Adjusting NewGameForm --- cypress/e2e/app.cy.js | 6 +- package-lock.json | 9 + package.json | 1 + src/app/__tests__/challenge-inputs.test.jsx | 4 +- src/app/__tests__/new-game-form.test.jsx | 20 +- .../{CaesarCipher.tsx => Cryptogram.tsx} | 6 +- .../components/createGame/NewChallenge.tsx | 6 +- src/app/components/createGame/NewGameForm.tsx | 421 ++++++++++-------- ...rChallenge.tsx => CryptogramChallenge.tsx} | 10 +- .../createGame/challengeInputs/README.md | 6 +- src/app/components/layout/GameList.tsx | 2 +- src/app/components/layout/Header.tsx | 2 +- src/app/game/[game]/[challenge]/page.tsx | 6 +- src/app/globals.css | 19 +- src/app/layout.tsx | 2 +- src/app/new-game/page.tsx | 29 +- src/app/page.tsx | 4 +- src/app/play/page.tsx | 2 +- src/app/ui-kit/page.tsx | 6 +- tsconfig.json | 2 +- 20 files changed, 333 insertions(+), 230 deletions(-) rename src/app/components/challenges/{CaesarCipher.tsx => Cryptogram.tsx} (93%) rename src/app/components/createGame/challengeInputs/{CaesarCipherChallenge.tsx => CryptogramChallenge.tsx} (94%) diff --git a/cypress/e2e/app.cy.js b/cypress/e2e/app.cy.js index 04d3665..c2bdafa 100644 --- a/cypress/e2e/app.cy.js +++ b/cypress/e2e/app.cy.js @@ -71,10 +71,10 @@ describe('Game Creation and Editing while logged out', () => { 'Trivia answer #1' ); - // Test Caesar cipher input - cy.getByData('challenge-1-caesar-cipher-answer').type('Encrypt this clue'); + // Test Cryptogram input + cy.getByData('challenge-1-cryptogram-answer').type('Encrypt this clue'); cy.getByData('1-encrypt-button').click(); - cy.getByData('challenge-1-caesar-cipher-clue').should('not.have.value', ''); + cy.getByData('challenge-1-cryptogram-clue').should('not.have.value', ''); // Test Word Scramble input cy.getByData('challenge-2-word-scramble-answer').type( diff --git a/package-lock.json b/package-lock.json index bf5f41b..977d5ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/react-fontawesome": "^0.2.0", "@gsap/react": "^2.1.0", + "@heroicons/react": "^2.1.3", "@tailwindcss/container-queries": "^0.1.1", "gsap": "^3.12.5", "lodash.shuffle": "^4.2.0", @@ -1811,6 +1812,14 @@ "react": ">=16" } }, + "node_modules/@heroicons/react": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.1.3.tgz", + "integrity": "sha512-fEcPfo4oN345SoqdlCDdSa4ivjaKbk0jTd+oubcgNxnNgAfzysfwWfQUr+51wigiWHQQRiZNd1Ao0M5Y3M2EGg==", + "peerDependencies": { + "react": ">= 16" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", diff --git a/package.json b/package.json index d2e1971..f68753b 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/react-fontawesome": "^0.2.0", "@gsap/react": "^2.1.0", + "@heroicons/react": "^2.1.3", "@tailwindcss/container-queries": "^0.1.1", "gsap": "^3.12.5", "lodash.shuffle": "^4.2.0", diff --git a/src/app/__tests__/challenge-inputs.test.jsx b/src/app/__tests__/challenge-inputs.test.jsx index cb9161e..12b7784 100644 --- a/src/app/__tests__/challenge-inputs.test.jsx +++ b/src/app/__tests__/challenge-inputs.test.jsx @@ -1,11 +1,11 @@ import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import { render } from '@testing-library/react'; -import { encrypt } from '../components/createGame/challengeInputs/CaesarCipherChallenge'; +import { encrypt } from '../components/createGame/challengeInputs/CryptogramChallenge'; import { shuffleWords } from '../components/createGame/challengeInputs/WordScrambleChallenge'; import WordScrambleChallenge from '../components/createGame/challengeInputs/WordScrambleChallenge'; -describe('Caesar Cipher encryptor', () => { +describe('Cryptogram encryptor', () => { it('encrypts a string', () => { const clue = 'apple banana'; const seed = 4; diff --git a/src/app/__tests__/new-game-form.test.jsx b/src/app/__tests__/new-game-form.test.jsx index bc0532a..4ef1294 100644 --- a/src/app/__tests__/new-game-form.test.jsx +++ b/src/app/__tests__/new-game-form.test.jsx @@ -104,14 +104,14 @@ describe('Handle form inputs and submission', () => { await user.click(triviaAnswer); await user.keyboard('Trivia answer #1'); - // Add Caesar cipher description - const cipherDesc = getByTestId('challenge-1-caesar-cipher-answer'); + // Add Cryptogram description + const cipherDesc = getByTestId('challenge-1-cryptogram-answer'); await user.click(cipherDesc); await user.keyboard('Decrypt this phrase'); expect(cipherDesc.value).toBe('Decrypt this phrase'); const encryptButton = getByTestId('1-encrypt-button'); await user.click(encryptButton); - const cipherClue = getByTestId('challenge-1-caesar-cipher-clue'); + const cipherClue = getByTestId('challenge-1-cryptogram-clue'); expect(cipherClue.value).not.toBeNull(); // Add Word Scramble description & answer @@ -147,8 +147,8 @@ describe('Handle form inputs and submission', () => { expect(warning2).not.toBeInTheDocument(); // Remove a challenge - const removeCaesarcipher = getByTestId('remove-caesar-cipher-1'); - user.click(removeCaesarcipher); + const removeCryptogram = getByTestId('remove-cryptogram-1'); + user.click(removeCryptogram); const removeTrivia = getByTestId('remove-trivia-0'); user.click(removeTrivia); }); @@ -177,17 +177,17 @@ describe('Handle form inputs and submission', () => { const user = userEvent.setup(); // Verify that initially there are 3 challenges - expect(queryByTestId('caesar-cipher-3')).toBeNull(); + expect(queryByTestId('cryptogram-3')).toBeNull(); // Add a new challenge - const caesarcipherRadioButton = getByLabelText('Caesar Cipher'); - await user.click(caesarcipherRadioButton); + const cryptogramRadioButton = getByLabelText('Cryptogram'); + await user.click(cryptogramRadioButton); - const addChallengeButton = getByTestId('add-caesar-cipher-challenge'); + const addChallengeButton = getByTestId('add-cryptogram-challenge'); await user.click(addChallengeButton); // Verify the new challenge was added - const newChallenge = getByTestId('caesar-cipher-3'); + const newChallenge = getByTestId('cryptogram-3'); expect(newChallenge).toBeInTheDocument(); }); diff --git a/src/app/components/challenges/CaesarCipher.tsx b/src/app/components/challenges/Cryptogram.tsx similarity index 93% rename from src/app/components/challenges/CaesarCipher.tsx rename to src/app/components/challenges/Cryptogram.tsx index 5d020cb..6722351 100644 --- a/src/app/components/challenges/CaesarCipher.tsx +++ b/src/app/components/challenges/Cryptogram.tsx @@ -4,7 +4,7 @@ import Input from '../ui/Input'; import Modal from '../ui/Modal'; import { Game } from '@/app/types/types'; -interface CaesarCipherChallengeProps { +interface CryptogramChallengeProps { currentChallenge: { id: string; type: string; @@ -16,11 +16,11 @@ interface CaesarCipherChallengeProps { currentGame: Game; } -export default function CaesarCipherChallenge({ +export default function CryptogramChallenge({ currentChallenge, nextChallenge, currentGame, -}: CaesarCipherChallengeProps) { +}: CryptogramChallengeProps) { const [answer, setAnswer] = useState(''); const [incorrect, setIncorrect] = useState(false); const router = useRouter(); diff --git a/src/app/components/createGame/NewChallenge.tsx b/src/app/components/createGame/NewChallenge.tsx index 4757870..25bef0f 100644 --- a/src/app/components/createGame/NewChallenge.tsx +++ b/src/app/components/createGame/NewChallenge.tsx @@ -1,5 +1,5 @@ import TriviaChallenge from './challengeInputs/TriviaChallenge'; -import CaesarCipherChallenge from './challengeInputs/CaesarCipherChallenge'; +import CryptogramChallenge from './challengeInputs/CryptogramChallenge'; import WordScrambleChallenge from './challengeInputs/WordScrambleChallenge'; import FillInTheBlankChallenge from './challengeInputs/FillInTheBlankChallenge'; import { Challenge } from '@/app/types/types'; @@ -48,9 +48,9 @@ export default function NewChallenge({ /> ); } - case 'caesar-cipher': { + case 'cryptogram': { return ( - ('trivia'); @@ -63,9 +47,9 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { const [editError, setEditError] = useState(false); const [editMessage, setEditMessage] = useState(''); const [tooManyGames, setTooManyGames] = useState(false); - const [challengeId, setChallengeId] = useState(3) + const [challengeId, setChallengeId] = useState(0); // 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. + // and not decrementing whenever a challenge is deleted. const { setSavedGames } = useContext(SavedGamesContext); const { setLoadedGames } = useContext(LoadedGamesContext); @@ -144,7 +128,7 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { useEffect(() => { // adding newGame.challenges scrolls to the newest challenge whenever any challenge value changes - if (newGame.challenges.length > 4) { + if (newGame.challenges.length > 0) { const index = newGame.challenges.length - 1; const challengeType = newGame.challenges[index].type; const challengeId = document.getElementById(`${challengeType}-${index}`); @@ -184,7 +168,7 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { let clue: string | string[]; // Array clues come from the actual value passed to onClueChange, not the event target if ( - type === 'caesar-cipher' || + type === 'cryptogram' || type === 'word-scramble' || type === 'fill-in-the-blank' ) { @@ -246,7 +230,7 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { answer: '', }, ]; - setChallengeId(challengeId + 1) + setChallengeId(challengeId + 1); const updatedGame = { ...prevGame, challenges: newChallenges }; saveForm(updatedGame); return updatedGame; @@ -275,6 +259,8 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { setSubmitError(false); setSubmitMessage([]); + const hasNoClues = newGame.challenges.length === 0; + const hasEmptyClue = newGame.challenges.some( (challenge) => challenge.clue === '' || @@ -302,6 +288,13 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { ...prev, 'Please add a short description for your game.', ]); + case hasNoClues: + setSubmitError(true); + setSubmitMessage((prev) => [ + ...prev, + 'Please add at least one challenge', + ]); + break; case hasEmptyClue: setSubmitError(true); setSubmitMessage((prev) => [ @@ -316,51 +309,39 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { ]); } - if (submitError) { + if ( + newGame.gameTitle === '' || + newGame.gameDescription === '' || + hasNoClues || + hasEmptyClue || + hasEmptyAnswer + ) { + console.log('Not saving game'); setSavingToDB(false); return; - } - - // Format clues for DB - newGame.challenges.map((challenge) => { - if (typeof challenge.clue === 'string') { - challenge.clue = challenge.clue.trim(); - if (challenge.type === 'word-scramble') { - let sentenceArray: string[] = []; - sentenceArray = challenge.clue.split(' '); - challenge.clue = sentenceArray; + } else { + console.log('Saving game...'); + // Format clues for DB + newGame.challenges.map((challenge) => { + if (typeof challenge.clue === 'string') { + challenge.clue = challenge.clue.trim(); + if (challenge.type === 'word-scramble') { + let sentenceArray: string[] = []; + sentenceArray = challenge.clue.split(' '); + challenge.clue = sentenceArray; + } } - } - if (challenge.type === 'fill-in-the-blank') { - challenge.answer = challenge.answer.replaceAll('"', ''); - } - }); + if (challenge.type === 'fill-in-the-blank') { + challenge.answer = challenge.answer.replaceAll('"', ''); + } + }); - // Set this game to be the current game in state (if the player chooses to play the game) - setSingleGame(newGame); - localStorage.setItem('singleGame', JSON.stringify(newGame)); - - // Add this game to saved games in state & local storage - setSavedGames((prevGames: Game[]) => { - let newGames: Game[]; - if (editGame) { - newGames = prevGames.map((game) => { - if (game.id === editGame) { - return newGame; - } else { - return game; - } - }); - } else { - newGames = [...prevGames, newGame]; - } - localStorage.setItem('savedGames', JSON.stringify(newGames)); - return newGames; - }); + // Set this game to be the current game in state (if the player chooses to play the game) + setSingleGame(newGame); + localStorage.setItem('singleGame', JSON.stringify(newGame)); - if (!newGame.private) { - // Add to public games in front-end (no need for refetching) - setLoadedGames((prevGames: Game[]) => { + // Add this game to saved games in state & local storage + setSavedGames((prevGames: Game[]) => { let newGames: Game[]; if (editGame) { newGames = prevGames.map((game) => { @@ -373,63 +354,83 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { } else { newGames = [...prevGames, newGame]; } - localStorage.setItem('loadedGames', JSON.stringify(newGames)); + localStorage.setItem('savedGames', JSON.stringify(newGames)); return newGames; }); - } else { - // remove from public games in FE - if (editGame) { + + if (!newGame.private) { + // Add to public games in front-end (no need for refetching) setLoadedGames((prevGames: Game[]) => { let newGames: Game[]; - - newGames = prevGames.filter((game) => game.id !== editGame); + if (editGame) { + newGames = prevGames.map((game) => { + if (game.id === editGame) { + return newGame; + } else { + return game; + } + }); + } else { + newGames = [...prevGames, newGame]; + } + localStorage.setItem('loadedGames', JSON.stringify(newGames)); return newGames; }); + } else { + // remove from public games in FE + if (editGame) { + setLoadedGames((prevGames: Game[]) => { + let newGames: Game[]; + + newGames = prevGames.filter((game) => game.id !== editGame); + return newGames; + }); + } } - } - // Add to main games table in DB - if (user.id !== '') { - if (editGame) { - setEditError(false); - setEditMessage(''); - const response = await fetch(`/api/game/${editGame}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(newGame), - }); - const data = await response.json(); - if (data.updateGameResponse.$metadata.httpStatusCode === 200) { - setEditMessage('Your game was updated successfully!'); + // Add to main games table in DB + if (user.id !== '') { + if (editGame) { + setEditError(false); + setEditMessage(''); + const response = await fetch(`/api/game/${editGame}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(newGame), + }); + const data = await response.json(); + if (data.updateGameResponse.$metadata.httpStatusCode === 200) { + setEditMessage('Your game was updated successfully!'); + } else { + setEditError(true); + setEditMessage(data.message); + } } else { - setEditError(true); - setEditMessage(data.message); + // /api/createGame directly adds to DB with DynamoDB SDK + const response = await fetch('/api/createGame', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(newGame), + }); + const data = await response.json(); + console.log(`data from creating game: ${JSON.stringify(data)}`); + if (data.createGameResponse.$metadata.httpStatusCode === 200) { + setSubmitMessage(['Game saved!']); + } } - } else { - // /api/createGame directly adds to DB with DynamoDB SDK - const response = await fetch('/api/createGame', { + } + + // Add game ID to the user's saved games array in escape-room-users table in DB + if (user.id !== '' && !editGame) { + const response = await fetch(`/api/updateUser/${user.id}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(newGame), + body: JSON.stringify({ savedGame: newGame.id }), }); const data = await response.json(); - console.log(`data from creating game: ${JSON.stringify(data)}`); - if (data.createGameResponse.$metadata.httpStatusCode === 200) { - setSubmitMessage(['Game saved!']); - } } + setSavingToDB(false); } - - // Add game ID to the user's saved games array in escape-room-users table in DB - if (user.id !== '' && !editGame) { - const response = await fetch(`/api/updateUser/${user.id}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ savedGame: newGame.id }), - }); - const data = await response.json(); - } - setSavingToDB(false); }; // Reset the current game form @@ -561,7 +562,7 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { handleRemoveChallenge(e, index); }; - console.log(`challenge.id: ${challenge.id + '-' + challenge.type}`) + console.log(`challenge.id: ${challenge.id + '-' + challenge.type}`); return (
-
- Choose a challenge type: -
- - -
-
- - -
-
- - -
-
- - +
+ + Add a challenge: + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
@@ -655,22 +700,6 @@ export default function NewGameForm({ editGame }: { editGame?: string }) {
-
- -
{savingToDB && ( <> @@ -704,7 +733,22 @@ export default function NewGameForm({ editGame }: { editGame?: string }) { Create Game )} - +
+ +
{editMessage && (
{!editGame && ( - Reset + )} diff --git a/src/app/components/createGame/challengeInputs/CaesarCipherChallenge.tsx b/src/app/components/createGame/challengeInputs/CryptogramChallenge.tsx similarity index 94% rename from src/app/components/createGame/challengeInputs/CaesarCipherChallenge.tsx rename to src/app/components/createGame/challengeInputs/CryptogramChallenge.tsx index 9894d6b..6d99d78 100644 --- a/src/app/components/createGame/challengeInputs/CaesarCipherChallenge.tsx +++ b/src/app/components/createGame/challengeInputs/CryptogramChallenge.tsx @@ -23,7 +23,7 @@ export const encrypt = (answer: string | undefined, seed: number) => { return encryptedWord.toString().toLowerCase().replaceAll(',', ''); }; -export default function CaesarCipherChallenge({ +export default function CryptogramChallenge({ index, clue, description, @@ -176,21 +176,21 @@ export default function CaesarCipherChallenge({
)}
-
); } diff --git a/src/app/components/createGame/challengeInputs/README.md b/src/app/components/createGame/challengeInputs/README.md index e268bff..92753ee 100644 --- a/src/app/components/createGame/challengeInputs/README.md +++ b/src/app/components/createGame/challengeInputs/README.md @@ -3,16 +3,16 @@ ## Challenge Types - Trivia -- Caesar Cipher +- Cryptogram - Word Scramble For each type of challenge there will need to be a unique way to add them in the Create Game form. - Trivia challenges can have 3 input fields for the user: description, clue and answer -- Caesar Cipher challenges can have 2 input fields for the user: description and answer +- Cryptogram challenges can have 2 input fields for the user: description and answer - Word Scramble challenges can have 2 input fields for the user: description and answer -The clues for Caesar Cipher and Word Scramble will be automatically generated. +The clues for Cryptogram and Word Scramble will be automatically generated. Ideally, the clue will be generated on-the-fly by the client and displayed to the user. diff --git a/src/app/components/layout/GameList.tsx b/src/app/components/layout/GameList.tsx index dd30377..2d24291 100644 --- a/src/app/components/layout/GameList.tsx +++ b/src/app/components/layout/GameList.tsx @@ -23,7 +23,7 @@ export default function GameList() { }, [loadedGames, setLoadedGames]); return ( -
+
{/* head */} diff --git a/src/app/components/layout/Header.tsx b/src/app/components/layout/Header.tsx index c8cc73a..49cd928 100644 --- a/src/app/components/layout/Header.tsx +++ b/src/app/components/layout/Header.tsx @@ -81,7 +81,7 @@ export default function Header() {
{user.isAdmin && admin} -
+
diff --git a/src/app/game/[game]/[challenge]/page.tsx b/src/app/game/[game]/[challenge]/page.tsx index 2939ef9..c8a3f69 100644 --- a/src/app/game/[game]/[challenge]/page.tsx +++ b/src/app/game/[game]/[challenge]/page.tsx @@ -3,7 +3,7 @@ import { useContext, useEffect } from 'react'; import { SingleGameContext } from '@/app/contexts/singleGameContext'; import TriviaChallenge from '@/app/components/challenges/Trivia'; import WordScrambleChallenge from '@/app/components/challenges/WordScramble'; -import CaesarCipherChallenge from '@/app/components/challenges/CaesarCipher'; +import CryptogramChallenge from '@/app/components/challenges/Cryptogram'; import FillInTheBlankChallenge from '@/app/components/challenges/FillInTheBlank'; import { Challenge, Game } from '@/app/types/types'; @@ -38,9 +38,9 @@ const SingleChallenge: React.FC = ({ /> ); } - case 'caesar-cipher': { + case 'cryptogram': { return ( -
-
+
{children}
diff --git a/src/app/new-game/page.tsx b/src/app/new-game/page.tsx index 76b0b41..779cff3 100644 --- a/src/app/new-game/page.tsx +++ b/src/app/new-game/page.tsx @@ -6,14 +6,27 @@ export default function NewGame() { return (
-

- You can create and save a game on your device without logging in. -

-

- If you want to share your game with others or across devices, -
please Sign In or{' '} - Sign Up. -

+
+ + + + + You can create and save a game on your device without logging in. If + you want to share your game with others or across devices, + please Sign In or{' '} + Sign Up. + +
diff --git a/src/app/page.tsx b/src/app/page.tsx index 44d17d7..ed1ec44 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,8 +3,8 @@ import CreateButton from './components/ui/CreateButton'; export default function Home() { return ( -
-
+
+