Skip to content

Commit

Permalink
Update event handlers (#21)
Browse files Browse the repository at this point in the history
## Why
Handlers were broken, requiring full page refresh and login when
submiting approval, using faucet, among other actions.
## How
By updating event handler logic, namely:
- adding a `isGuessCorrect` method that checks if the player's latest
guess is correct and sets the winning condition / prevents new guesses
to be submited;
- implementing a `logout` event handler that complements wagmi
`disconnect` with resetting all variables to an undefined state;
- relocate and spread balance checkers throughout "gate" actions
(`useFaucet`, `approveGame` and so on);
- adds a Victory screen (to be styled and added later);

Closes #14
  • Loading branch information
rccsousa authored Nov 8, 2024
1 parent f788d21 commit aab0f3b
Show file tree
Hide file tree
Showing 19 changed files with 3,030 additions and 131 deletions.
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@heroicons/react": "^2.1.5",
"@tanstack/react-query": "^5.59.20",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/App.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;

/* Colors */
Expand All @@ -21,6 +20,29 @@
margin: 10px auto;
}

.victory {
margin: auto;
background: white;
color: black;
padding: 40px;
width: 200px;
}

.header {
display: flex;
max-width: 60%;
margin: 5px auto;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #424242;
padding: 10px;
}

.balance {
display: flex;
gap: 20px;
}

.logo {
height: 6em;
padding: 1.5em;
Expand Down
106 changes: 75 additions & 31 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getPlayerGuesses,
initAttempts,
tryGuess,
getOwner,
} from './hooks/WordleHooks'

// import components
Expand All @@ -18,61 +19,102 @@ import { LowFunds } from './components/LowFunds/LowFunds'
import { AlreadyPlayed } from './components/AlreadyPlayed/AlreadyPlayed'

// wagmi connection
import { ConnectWallet } from './clients/walletClient/ConnectWallet'
import { wagmiConfig } from '../wagmi.config'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ConnectWallet } from './components/ConnectWallet/ConnectWallet'
import { useAccount } from 'wagmi'
import { AdminPanel } from './components/AdminPannel/AdminPanel'

function App() {

const { address } = useAccount()

// Current account and respetive variables
const [currentAddress, setCurrentAddress] = useState(undefined)
const [alphabet, setAlphabet] = useState(undefined)
const [isAdmin, setIsAdmin] = useState(undefined)

// TODO: remove after testing and prototyping
const [wdtBalance, setWdtBalance] = useState(undefined)

// Game variables
const [playerGuesses, setPlayerGuesses] = useState([])
const [guess, setGuess] = useState('')
const [victory, setVictory] = useState(undefined)

// Wallet operations handlers
const [approvalModal, setApprovalModal] = useState(false) // approval modal to approve spending
const [lowFundsWarn, setLowFundsWarn] = useState(false) // low funds error message
const [playedToday, setPlayedToday] = useState(false) // played today error message
const [isPlaying, setIsPlaying] = useState(false) // start the game
const [approvalModal, setApprovalModal] = useState(undefined) // approval modal to approve spending
const [lowFundsWarn, setLowFundsWarn] = useState(undefined) // low funds error message
const [playedToday, setPlayedToday] = useState(undefined) // played today error message
const [isPlaying, setIsPlaying] = useState(undefined) // start the game

// Gets the current account's WDT balance, eligibility to play, player attempts, guesses and alphabet.
// this is constantly pinging the network, so i'm unsure if this is the right way to go
useEffect(() => {

const getParams = async (address) => {
// checks if the player is the admin
if (address === await getOwner()) {setIsAdmin(true)} else {setIsAdmin(false)}
const balance = await getWDTBalance(address)
const alphabet = await getAlphabet(address)
const playerGuesses = await getPlayerGuesses(address)
if (alphabet.length > 0) {setIsPlaying(true)}
if (balance) setWdtBalance(balance)
if (alphabet) setAlphabet(alphabet)
if (playerGuesses) setPlayerGuesses(playerGuesses)
// check if player already won
if (playerGuesses.length != 0) {
const latestGuess = playerGuesses[playerGuesses.length - 1];
setVictory(isGuessCorrect(latestGuess))
}

}

if (currentAddress) {
getParams(currentAddress)
const logout = () => {
setIsAdmin(undefined)
setIsPlaying(undefined)
setAlphabet(undefined)
setWdtBalance(undefined)
setPlayerGuesses([])
setVictory(undefined)
}

}, [currentAddress])
if (address) getParams(address)
if (!address) logout()

}, [address])

// handle the guess input
const handleGuessChange = (e) => setGuess(e.target.value)

// handle new guess
const isGuessCorrect = (array) =>
array.every((element) => element.state === 2n);

const handleNewGuess = (account, guess) => {
tryGuess(account, guess)
setTimeout(() => getPlayerGuesses(account).then(setPlayerGuesses), 500)
}
// check if the previous guess was correct and
// disables new guess acceptance

const previousGuess = playerGuesses[playerGuesses.length - 1] || [];
if (previousGuess.length != 0 && isGuessCorrect(previousGuess)) {
return;
}

tryGuess(account, guess);
setTimeout(() => {
// update player guesses
getPlayerGuesses(account).then(guesses => {
setPlayerGuesses(guesses)

// check if the latest (the one submited) is correct
const latest = guesses[guesses.length - 1];
setVictory(isGuessCorrect(latest))
})
}, 500);
};

const handleInitAttempts = async (account) => {
try {
await initAttempts(account)
await initAttempts(account).then(async () => {
await getWDTBalance(account).then(setWdtBalance)
if (!isPlaying) setIsPlaying(true)
})
} catch (err) {
// placeholder for token faucet user flow
if (err.message.includes("You don't have enough tokens to play")) {
Expand All @@ -90,35 +132,37 @@ function App() {
}
}
}

return (
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={new QueryClient()}>
<>
<div>
<header>
<ConnectWallet onAddressChange={setCurrentAddress} />
</header>

<div className='header'>
<div className='balance'>
{isAdmin && <AdminPanel />}
{address && <p>{wdtBalance} 🪙</p>}
</div>
<ConnectWallet />
</div>
{/* Modals for approving transactions and messages */}
{approvalModal && <ApprovalModal account={currentAddress} close={() => setApprovalModal(false)}/>}
{lowFundsWarn && <LowFunds account={currentAddress} close={() => setLowFundsWarn(false)}/>}
{approvalModal && <ApprovalModal account={address} close={() => setApprovalModal(false)} init={() => handleInitAttempts(address)}/>}
{lowFundsWarn && <LowFunds account={address} updateBalance={setWdtBalance} close={() => setLowFundsWarn(false)}/>}
{playedToday && <AlreadyPlayed close={() => setPlayedToday(false)}/>}

<p>WDT Balance: {wdtBalance ? `${wdtBalance.toString()} WDT` : '???'}</p>
{victory && <div className={"victory"}>TBD: Victory Component</div>}

{/* Game space */}
{!isPlaying ?
(<div className={"game-buttons"}>
<button onClick={() => handleInitAttempts(currentAddress)}>Play</button>
{address && <button onClick={() => handleInitAttempts(address)}>Play</button>}
</div>) : (
<div>
<input placeholder='Enter guess' onChange={handleGuessChange}/>
<button onClick={() => handleNewGuess(currentAddress, guess)}>Submit</button>
<button onClick={() => handleNewGuess(address, guess)}>Submit</button>
<GameSpace playerGuesses={playerGuesses}/>
<Alphabet playerAlphabet={alphabet}/>
</div>)}
</div>
</QueryClientProvider>
</WagmiProvider>
</>
)
}

Expand Down
13 changes: 13 additions & 0 deletions frontend/src/abis/WordleABI.json
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,19 @@
],
"stateMutability": "view"
},
{
"type": "function",
"name": "owner",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "token",
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/assets/icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";

export function SignOutIcon(): JSX.Element {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24px"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M8.25 9V5.25A2.25 2.25 0 0 1 10.5 3h6a2.25 2.25 0 0 1 2.25 2.25v13.5A2.25 2.25 0 0 1 16.5 21h-6a2.25 2.25 0 0 1-2.25-2.25V15M12 9l3 3m0 0-3 3m3-3H2.25"
/>
</svg>
);
}
50 changes: 0 additions & 50 deletions frontend/src/clients/walletClient/ConnectWallet.tsx

This file was deleted.

48 changes: 48 additions & 0 deletions frontend/src/components/AdminPannel/AdminPanel.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.admin {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

.admin-button {
display: flex;
gap: 10px;
align-items: center;
padding: 10px;
border: 1px solid var(--soft-white);
border-radius: 100px;
}

.admin-options {
position: fixed;
top: 0px;
left: 20%;
display: flex;
flex-direction: column;
text-align: left;
gap: 20px;
border: 2px solid #424242;
padding: 20px;
background: #242424;
}

.change-word {
display: flex;
}

.change-word input {
margin: -10px;
}

.change-word input:focus,
.change-word input:active,
.change-word input:focus-visible {
outline: none;
box-shadow: none;
}

.change-word button {
margin: -10px;
}

29 changes: 29 additions & 0 deletions frontend/src/components/AdminPannel/AdminPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { useState } from "react"
import { useAccount } from "wagmi"
import './AdminPanel.css'
import { changeWord } from "../../hooks/WordleHooks"

export function AdminPanel() {
const [newWord, setNewWord] = useState("")
const [isOpen, setIsOpen] = useState(false)

const { address } = useAccount()

function handleChange (e) {
setNewWord(e.target.value)
}
return (
<div className="admin">
{!isOpen && <button className="admin-button" onClick={() => setIsOpen(!isOpen)}>Admin</button>}
{isOpen && <div className={'admin-options transition-in'}>
<button className='close-button'onClick={() => setIsOpen(false)}>X</button>
<p className='title'>Change word:</p>
<div className={'change-word'}>
<input placeholder="new word" onChange={handleChange}/>
<button className={'change-word-button'} onClick={() => changeWord(address, newWord)}>Submit</button>
</div>
</div>}
{isOpen && <div className="admin-overlay" onClick={() => setIsOpen(false)}></div>}
</div>
)
}
Loading

0 comments on commit aab0f3b

Please sign in to comment.