add local storage, change alerts, add share functionality

This commit is contained in:
Hannah Park 2022-01-14 22:05:57 -05:00
parent 26df4881ea
commit 0ad35f44d6
6 changed files with 106 additions and 8 deletions

View file

@ -7,9 +7,15 @@ import { AboutModal } from "./components/modals/AboutModal";
import { InfoModal } from "./components/modals/InfoModal"; import { InfoModal } from "./components/modals/InfoModal";
import { WinModal } from "./components/modals/WinModal"; import { WinModal } from "./components/modals/WinModal";
import { isWordInWordList, isWinningWord, solution } from "./lib/words"; import { isWordInWordList, isWinningWord, solution } from "./lib/words";
import {
loadGameStateFromLocalStorage,
saveGameStateToLocalStorage,
} from "./lib/localStorage";
function App() { function App() {
const [guesses, setGuesses] = useState<string[]>([]); const [guesses, setGuesses] = useState<string[]>(
loadGameStateFromLocalStorage()?.guesses || []
);
const [currentGuess, setCurrentGuess] = useState(""); const [currentGuess, setCurrentGuess] = useState("");
const [isGameWon, setIsGameWon] = useState(false); const [isGameWon, setIsGameWon] = useState(false);
const [isWinModalOpen, setIsWinModalOpen] = useState(false); const [isWinModalOpen, setIsWinModalOpen] = useState(false);
@ -17,6 +23,11 @@ function App() {
const [isAboutModalOpen, setIsAboutModalOpen] = useState(false); const [isAboutModalOpen, setIsAboutModalOpen] = useState(false);
const [isWordNotFoundAlertOpen, setIsWordNotFoundAlertOpen] = useState(false); const [isWordNotFoundAlertOpen, setIsWordNotFoundAlertOpen] = useState(false);
const [isGameLost, setIsGameLost] = useState(false); const [isGameLost, setIsGameLost] = useState(false);
const [shareComplete, setShareComplete] = useState(false);
useEffect(() => {
saveGameStateToLocalStorage(guesses);
}, [guesses]);
useEffect(() => { useEffect(() => {
if (isGameWon) { if (isGameWon) {
@ -68,6 +79,11 @@ function App() {
message={`You lost, the word was ${solution}`} message={`You lost, the word was ${solution}`}
isOpen={isGameLost} isOpen={isGameLost}
/> />
<Alert
message="Game copied to clipboard"
isOpen={shareComplete}
variant="success"
/>
<div className="flex w-80 mx-auto items-center mb-8"> <div className="flex w-80 mx-auto items-center mb-8">
<h1 className="text-xl grow font-bold">Not Wordle</h1> <h1 className="text-xl grow font-bold">Not Wordle</h1>
<InformationCircleIcon <InformationCircleIcon
@ -86,6 +102,13 @@ function App() {
isOpen={isWinModalOpen} isOpen={isWinModalOpen}
handleClose={() => setIsWinModalOpen(false)} handleClose={() => setIsWinModalOpen(false)}
guesses={guesses} guesses={guesses}
handleShare={() => {
setIsWinModalOpen(false);
setShareComplete(true);
return setTimeout(() => {
setShareComplete(false);
}, 2000);
}}
/> />
<InfoModal <InfoModal
isOpen={isInfoModalOpen} isOpen={isInfoModalOpen}

View file

@ -1,12 +1,22 @@
import { Fragment } from "react"; import { Fragment } from "react";
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import classNames from "classnames";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
message: string; message: string;
variant?: "success" | "warning";
}; };
export const Alert = ({ isOpen, message }: Props) => { export const Alert = ({ isOpen, message, variant = "warning" }: Props) => {
const classes = classNames(
"fixed top-2.5 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",
}
);
return ( return (
<Transition <Transition
show={isOpen} show={isOpen}
@ -18,7 +28,7 @@ export const Alert = ({ isOpen, message }: Props) => {
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<div className="absolute top-2.5 left-1/2 transform -translate-x-1/2 max-w-sm w-full bg-rose-200 shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden"> <div className={classes}>
<div className="p-4"> <div className="p-4">
<p className="text-sm text-center font-medium text-gray-900"> <p className="text-sm text-center font-medium text-gray-900">
{message} {message}

View file

@ -2,14 +2,21 @@ import { Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { CheckIcon } from "@heroicons/react/outline"; import { CheckIcon } from "@heroicons/react/outline";
import { MiniGrid } from "../mini-grid/MiniGrid"; import { MiniGrid } from "../mini-grid/MiniGrid";
import { shareStatus } from "../../lib/share";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
handleClose: () => void; handleClose: () => void;
guesses: string[]; guesses: string[];
handleShare: () => void;
}; };
export const WinModal = ({ isOpen, handleClose, guesses }: Props) => { export const WinModal = ({
isOpen,
handleClose,
guesses,
handleShare,
}: Props) => {
return ( return (
<Transition.Root show={isOpen} as={Fragment}> <Transition.Root show={isOpen} as={Fragment}>
<Dialog <Dialog
@ -71,9 +78,12 @@ export const WinModal = ({ isOpen, handleClose, guesses }: Props) => {
<button <button
type="button" type="button"
className="inline-flex justify-center w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:text-sm" className="inline-flex justify-center w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:text-sm"
onClick={handleClose} onClick={() => {
shareStatus(guesses);
handleShare();
}}
> >
Exit Share
</button> </button>
</div> </div>
</div> </div>

18
src/lib/localStorage.ts Normal file
View file

@ -0,0 +1,18 @@
const gameStateKey = "gameState";
type StoredGameState = {
guesses: string[];
};
export const saveGameStateToLocalStorage = (guesses: string[]) => {
const gameState = {
guesses,
};
localStorage.setItem(gameStateKey, JSON.stringify(gameState));
};
export const loadGameStateFromLocalStorage = () => {
const state = localStorage.getItem(gameStateKey);
return state ? (JSON.parse(state) as StoredGameState) : null;
};

34
src/lib/share.ts Normal file
View file

@ -0,0 +1,34 @@
import { getGuessStatuses } from "./statuses";
import { solutionIndex } from "./words";
export const shareStatus = (guesses: string[]) => {
navigator.clipboard.writeText(
"Wordle " +
solutionIndex +
" " +
guesses.length +
"/6\n\n" +
generateEmojiGrid(guesses)
);
};
export const generateEmojiGrid = (guesses: string[]) => {
return guesses
.map((guess) => {
const status = getGuessStatuses(guess);
return guess
.split("")
.map((letter, i) => {
switch (status[i]) {
case "correct":
return "🟩";
case "present":
return "🟨";
default:
return "⬜";
}
})
.join("");
})
.join("\n");
};

View file

@ -19,7 +19,10 @@ export const getWordOfDay = () => {
const msInDay = 86400000; const msInDay = 86400000;
const index = Math.floor((now - epochMs) / msInDay); const index = Math.floor((now - epochMs) / msInDay);
return WORDS[index].toUpperCase(); return {
solution: WORDS[index].toUpperCase(),
solutionIndex: index,
};
}; };
export const solution = getWordOfDay(); export const { solution, solutionIndex } = getWordOfDay();