diff --git a/package-lock.json b/package-lock.json index 4cbbd64..139b19c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@types/react-dom": "^17.0.11", "classnames": "^2.3.1", "react": "^17.0.2", + "react-countdown": "^2.3.2", "react-dom": "^17.0.2", "react-scripts": "5.0.0", "typescript": "^4.5.4", @@ -12901,6 +12902,18 @@ "node": ">=14" } }, + "node_modules/react-countdown": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/react-countdown/-/react-countdown-2.3.2.tgz", + "integrity": "sha512-Q4SADotHtgOxNWhDdvgupmKVL0pMB9DvoFcxv5AzjsxVhzOVxnttMbAywgqeOdruwEAmnPhOhNv/awAgkwru2w==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": ">= 15", + "react-dom": ">= 15" + } + }, "node_modules/react-dev-utils": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz", @@ -25058,6 +25071,14 @@ "whatwg-fetch": "^3.6.2" } }, + "react-countdown": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/react-countdown/-/react-countdown-2.3.2.tgz", + "integrity": "sha512-Q4SADotHtgOxNWhDdvgupmKVL0pMB9DvoFcxv5AzjsxVhzOVxnttMbAywgqeOdruwEAmnPhOhNv/awAgkwru2w==", + "requires": { + "prop-types": "^15.7.2" + } + }, "react-dev-utils": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz", diff --git a/package.json b/package.json index 0e4aced..08483b5 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "react-dom": "^17.0.2", "react-scripts": "5.0.0", "typescript": "^4.5.4", - "web-vitals": "^2.1.3" + "web-vitals": "^2.1.3", + "react-countdown": "^2.3.2" }, "scripts": { "build": "react-scripts build", diff --git a/src/App.tsx b/src/App.tsx index 9a9b9ec..5a3c546 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,8 +6,8 @@ import { Grid } from './components/grid/Grid' import { Keyboard } from './components/keyboard/Keyboard' import { AboutModal } from './components/modals/AboutModal' import { InfoModal } from './components/modals/InfoModal' -import { WinModal } from './components/modals/WinModal' import { StatsModal } from './components/modals/StatsModal' +import { WIN_MESSAGES } from './constants/strings' import { isWordInWordList, isWinningWord, solution } from './lib/words' import { addStatsForCompletedGame, loadStats } from './lib/stats' import { @@ -15,17 +15,18 @@ import { saveGameStateToLocalStorage, } from './lib/localStorage' +const ALERT_TIME_MS = 2000; + function App() { const [currentGuess, setCurrentGuess] = useState('') const [isGameWon, setIsGameWon] = useState(false) - const [isWinModalOpen, setIsWinModalOpen] = useState(false) const [isInfoModalOpen, setIsInfoModalOpen] = useState(false) const [isAboutModalOpen, setIsAboutModalOpen] = useState(false) const [isNotEnoughLetters, setIsNotEnoughLetters] = useState(false) const [isStatsModalOpen, setIsStatsModalOpen] = useState(false) const [isWordNotFoundAlertOpen, setIsWordNotFoundAlertOpen] = useState(false) const [isGameLost, setIsGameLost] = useState(false) - const [shareComplete, setShareComplete] = useState(false) + const [successAlert, setSuccessAlert] = useState('') const [guesses, setGuesses] = useState(() => { const loaded = loadGameStateFromLocalStorage() if (loaded?.solution !== solution) { @@ -49,9 +50,18 @@ function App() { useEffect(() => { if (isGameWon) { - setIsWinModalOpen(true) + setSuccessAlert(WIN_MESSAGES[Math.floor(Math.random()*WIN_MESSAGES.length)]); + setTimeout(() => { + setSuccessAlert(''); + setIsStatsModalOpen(true); + }, ALERT_TIME_MS); } - }, [isGameWon]) + if (isGameLost) { + setTimeout(() => { + setIsStatsModalOpen(true); + }, ALERT_TIME_MS); + } + }, [isGameWon, isGameLost]) const onChar = (value: string) => { if (currentGuess.length < 5 && guesses.length < 6 && !isGameWon) { @@ -64,18 +74,19 @@ function App() { } const onEnter = () => { - if (!(currentGuess.length === 5) && !isGameLost) { + if (isGameWon || isGameLost) { return; } + if (!(currentGuess.length === 5)) { setIsNotEnoughLetters(true) return setTimeout(() => { setIsNotEnoughLetters(false) - }, 2000) + }, ALERT_TIME_MS) } if (!isWordInWordList(currentGuess)) { setIsWordNotFoundAlertOpen(true) return setTimeout(() => { setIsWordNotFoundAlertOpen(false) - }, 2000) + }, ALERT_TIME_MS) } const winningWord = isWinningWord(currentGuess) @@ -116,18 +127,6 @@ function App() { onEnter={onEnter} guesses={guesses} /> - setIsWinModalOpen(false)} - guesses={guesses} - handleShare={() => { - setIsWinModalOpen(false) - setShareComplete(true) - return setTimeout(() => { - setShareComplete(false) - }, 2000) - }} - /> setIsInfoModalOpen(false)} @@ -135,7 +134,14 @@ function App() { setIsStatsModalOpen(false)} + guesses={guesses} gameStats={stats} + isGameLost={isGameLost} + isGameWon={isGameWon} + handleShare={() => { + setSuccessAlert("Game copied to clipboard"); + return setTimeout(() => setSuccessAlert(''), ALERT_TIME_MS); + }} /> diff --git a/src/components/alerts/Alert.tsx b/src/components/alerts/Alert.tsx index 593b75e..6b0a29c 100644 --- a/src/components/alerts/Alert.tsx +++ b/src/components/alerts/Alert.tsx @@ -13,7 +13,7 @@ export const Alert = ({ isOpen, message, variant = 'warning' }: Props) => { 'fixed top-20 left-1/2 transform -translate-x-1/2 max-w-sm w-full shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden', { 'bg-rose-200': variant === 'warning', - 'bg-green-200': variant === 'success', + 'bg-green-200 z-20': variant === 'success', } ) diff --git a/src/components/mini-grid/MiniCell.tsx b/src/components/mini-grid/MiniCell.tsx deleted file mode 100644 index 8e96bc6..0000000 --- a/src/components/mini-grid/MiniCell.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { CharStatus } from '../../lib/statuses' -import classnames from 'classnames' - -type Props = { - status: CharStatus -} - -export const MiniCell = ({ status }: Props) => { - const classes = classnames( - 'w-10 h-10 border-solid border-2 border-slate-200 flex items-center justify-center mx-0.5 text-lg font-bold rounded', - { - 'bg-white': status === 'absent', - 'bg-green-500': status === 'correct', - 'bg-yellow-500': status === 'present', - } - ) - - return ( - <> -
- - ) -} diff --git a/src/components/mini-grid/MiniCompletedRow.tsx b/src/components/mini-grid/MiniCompletedRow.tsx deleted file mode 100644 index 82ce014..0000000 --- a/src/components/mini-grid/MiniCompletedRow.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { getGuessStatuses } from '../../lib/statuses' -import { MiniCell } from './MiniCell' - -type Props = { - guess: string -} - -export const MiniCompletedRow = ({ guess }: Props) => { - const statuses = getGuessStatuses(guess) - - return ( -
- {guess.split('').map((letter, i) => ( - - ))} -
- ) -} diff --git a/src/components/mini-grid/MiniGrid.tsx b/src/components/mini-grid/MiniGrid.tsx deleted file mode 100644 index c0e1ae4..0000000 --- a/src/components/mini-grid/MiniGrid.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { MiniCompletedRow } from './MiniCompletedRow' - -type Props = { - guesses: string[] -} - -export const MiniGrid = ({ guesses }: Props) => { - return ( -
- {guesses.map((guess, i) => ( - - ))} -
- ) -} diff --git a/src/components/modals/StatsModal.tsx b/src/components/modals/StatsModal.tsx index 4b0ad02..fb0c5af 100644 --- a/src/components/modals/StatsModal.tsx +++ b/src/components/modals/StatsModal.tsx @@ -1,15 +1,29 @@ +import Countdown from "react-countdown" import { StatBar } from '../stats/StatBar' import { Histogram } from '../stats/Histogram' import { GameStats } from '../../lib/localStorage' +import { shareStatus } from '../../lib/share' +import { tomorrow } from '../../lib/words' import { BaseModal } from './BaseModal' type Props = { isOpen: boolean handleClose: () => void + guesses: string[] gameStats: GameStats + isGameLost: boolean + isGameWon: boolean + handleShare: () => void } -export const StatsModal = ({ isOpen, handleClose, gameStats }: Props) => { +export const StatsModal = ({ isOpen, handleClose, guesses, gameStats, isGameLost, isGameWon, handleShare }: Props) => { + if (gameStats.totalGames <= 0) { + return ( + + + + ) + } return ( @@ -17,6 +31,24 @@ export const StatsModal = ({ isOpen, handleClose, gameStats }: Props) => { Guess Distribution + {(isGameLost || isGameWon) && +
+
+
New word in
+ +
+ +
+ }
) } diff --git a/src/components/modals/WinModal.tsx b/src/components/modals/WinModal.tsx deleted file mode 100644 index a0979da..0000000 --- a/src/components/modals/WinModal.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Dialog } from '@headlessui/react' -import { CheckIcon } from '@heroicons/react/outline' -import { MiniGrid } from '../mini-grid/MiniGrid' -import { shareStatus } from '../../lib/share' -import { BaseModal } from './BaseModal' - -type Props = { - isOpen: boolean - handleClose: () => void - guesses: string[] - handleShare: () => void -} - -export const WinModal = ({ - isOpen, - handleClose, - guesses, - handleShare, -}: Props) => { - return ( - -
-
-
-
- - You won! - -
- -

Great job.

-
-
-
-
- -
-
- ) -} diff --git a/src/components/stats/Histogram.tsx b/src/components/stats/Histogram.tsx index 5f3aca7..f496309 100644 --- a/src/components/stats/Histogram.tsx +++ b/src/components/stats/Histogram.tsx @@ -6,7 +6,8 @@ type Props = { } export const Histogram = ({ gameStats }: Props) => { - const { totalGames, winDistribution } = gameStats + const winDistribution = gameStats.winDistribution + const maxValue = Math.max(...winDistribution) return (
@@ -14,7 +15,7 @@ export const Histogram = ({ gameStats }: Props) => { ))} diff --git a/src/constants/strings.ts b/src/constants/strings.ts new file mode 100644 index 0000000..7414730 --- /dev/null +++ b/src/constants/strings.ts @@ -0,0 +1 @@ +export const WIN_MESSAGES = ["Great Job!", "Awesome", "Well done!"]; diff --git a/src/lib/share.ts b/src/lib/share.ts index ec3777f..9d55706 100644 --- a/src/lib/share.ts +++ b/src/lib/share.ts @@ -1,9 +1,9 @@ import { getGuessStatuses } from './statuses' import { solutionIndex } from './words' -export const shareStatus = (guesses: string[]) => { +export const shareStatus = (guesses: string[], lost: boolean) => { navigator.clipboard.writeText( - `Not Wordle ${solutionIndex} ${guesses.length}/6\n\n` + + `Not Wordle ${solutionIndex} ${lost?"X":guesses.length}/6\n\n` + generateEmojiGrid(guesses) ) } diff --git a/src/lib/words.ts b/src/lib/words.ts index dd79d93..ba8122b 100644 --- a/src/lib/words.ts +++ b/src/lib/words.ts @@ -18,11 +18,13 @@ export const getWordOfDay = () => { const now = Date.now() const msInDay = 86400000 const index = Math.floor((now - epochMs) / msInDay) + const nextday = (index+1)*msInDay + epochMs; return { solution: WORDS[index].toUpperCase(), solutionIndex: index, + tomorrow: nextday, } } -export const { solution, solutionIndex } = getWordOfDay() +export const { solution, solutionIndex, tomorrow } = getWordOfDay()