diff --git a/web/src/components/GlobalStyles.module.css b/web/src/components/GlobalStyles.module.css index f404a93f..2d7e000d 100644 --- a/web/src/components/GlobalStyles.module.css +++ b/web/src/components/GlobalStyles.module.css @@ -117,4 +117,15 @@ } +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #262019; + opacity: 25%; + z-index: 1; +} + diff --git a/web/src/components/HomePage/PvpView.tsx b/web/src/components/HomePage/PvpView.tsx index 31c50145..9e0b82cb 100644 --- a/web/src/components/HomePage/PvpView.tsx +++ b/web/src/components/HomePage/PvpView.tsx @@ -169,6 +169,7 @@ const PvpView = ({ atBats, tokens }: { atBats: AtBat[]; tokens: OwnedToken[] }) .map((openAtBat, idx) => { return openAtBat.pitcher ? ( { @@ -200,6 +201,7 @@ const PvpView = ({ atBats, tokens }: { atBats: AtBat[]; tokens: OwnedToken[] }) .map((openAtBat, idx) => { return openAtBat.batter ? ( { diff --git a/web/src/components/HomePage/TokenToPlay.module.css b/web/src/components/HomePage/TokenToPlay.module.css index 818a5ad2..d2d23957 100644 --- a/web/src/components/HomePage/TokenToPlay.module.css +++ b/web/src/components/HomePage/TokenToPlay.module.css @@ -78,6 +78,24 @@ height: 31.5px; } +.private { + display: flex; + padding: 5px 20px; + justify-content: center; + align-items: center; + gap: 10px; + align-self: stretch; + background: #8EAB8F; + color: #FFF; + text-align: center; + line-height: normal; + height: 31.5px; + font-family: Pangolin, cursive; + font-size: 12px; + font-style: normal; + font-weight: 400; +} + .heatMapContainer { position: absolute; top: -1px; diff --git a/web/src/components/HomePage/TokenToPlay.tsx b/web/src/components/HomePage/TokenToPlay.tsx index e51831e8..691fea48 100644 --- a/web/src/components/HomePage/TokenToPlay.tsx +++ b/web/src/components/HomePage/TokenToPlay.tsx @@ -10,6 +10,7 @@ const TokenToPlay = ({ isLoading, isForGame, showId = true, + requiresSignature = false, }: { token: Token | undefined; isPitcher: boolean; @@ -17,6 +18,7 @@ const TokenToPlay = ({ isLoading?: boolean; isForGame?: boolean; showId?: boolean; + requiresSignature?: boolean; }) => { if (!token) { return <>; @@ -45,10 +47,11 @@ const TokenToPlay = ({ {showId &&
{token.id}
} - {onClick && ( -
+ {onClick && requiresSignature &&
Private
} + {onClick && !requiresSignature && ( +
+ )} diff --git a/web/src/components/Playing.tsx b/web/src/components/Playing.tsx index 3a7497ac..15b42b21 100644 --- a/web/src/components/Playing.tsx +++ b/web/src/components/Playing.tsx @@ -231,7 +231,7 @@ const Playing = () => { )} {inviteFrom && inviteSession && ownedTokens.data && ownedTokens.data.length > 0 && ( !t.isStaked)} + tokens={ownedTokens.data.filter((t) => !t.isStaked || t.tokenProgress === 6)} sessionID={Number(inviteSession)} inviteCode={inviteCode} inviteFrom={inviteFrom} diff --git a/web/src/components/atbat/InviteLinkView.tsx b/web/src/components/atbat/InviteLinkView.tsx index 0c4fd0bc..355598c2 100644 --- a/web/src/components/atbat/InviteLinkView.tsx +++ b/web/src/components/atbat/InviteLinkView.tsx @@ -5,13 +5,22 @@ import QuestionMarkIcon from "../icons/QuestionMarkIcon"; import BallIconWhite from "../icons/BallIconWhite"; import BatIconWhite from "../icons/BatIconWhite"; import LinkIcon from "../icons/LinkIcon"; +import { getLocalStorageInviteCodeKey, getLocalStorageItem } from "../../utils/localStorage"; +import { GAME_CONTRACT } from "../../constants"; const InviteLinkView = ({ atBat }: { atBat: AtBatStatus }) => { + const inviteCodeKey = getLocalStorageInviteCodeKey( + GAME_CONTRACT, + String(atBat.pitches[0].sessionID), + ); + const inviteCode = getLocalStorageItem(inviteCodeKey); const link = `${window.location.protocol}//${ window.location.host }/?invite_from=${encodeURIComponent( atBat.pitcher ? atBat.pitcher.name : atBat.batter ? atBat.batter.name : "", - )}&id=${atBat.pitches[0].sessionID}`; + )}&id=${atBat.pitches[0].sessionID}${inviteCode ? "&invite_code=" : ""}${ + inviteCode ? inviteCode : "" + }`; const { onCopy, hasCopied } = useClipboard(link); return ( diff --git a/web/src/components/tokens/PlayButtons.tsx b/web/src/components/tokens/PlayButtons.tsx index 2565d338..fa87d029 100644 --- a/web/src/components/tokens/PlayButtons.tsx +++ b/web/src/components/tokens/PlayButtons.tsx @@ -7,9 +7,12 @@ import { AtBat, OwnedToken, Token } from "../../types"; import { startSessionFullcountPlayer } from "../../tokenInterfaces/FullcountPlayerAPI"; import { getLocalStorageInviteCodeKey, setLocalStorageItem } from "../../utils/localStorage"; import { GAME_CONTRACT, ZERO_ADDRESS } from "../../constants"; -import router, { useRouter } from "next/router"; +import { useRouter } from "next/router"; import { sendReport } from "../../utils/humbug"; import { useSound } from "../../hooks/useSound"; +import { useState } from "react"; +import StartAtBatDialog from "./StartAtBatDialog"; +import globalStyles from "../GlobalStyles.module.css"; const PlayButtons = ({ token }: { token: OwnedToken }) => { const queryClient = useQueryClient(); @@ -17,6 +20,7 @@ const PlayButtons = ({ token }: { token: OwnedToken }) => { const router = useRouter(); const { user } = useUser(); const playSound = useSound(); + const [role, setRole] = useState<0 | 1 | undefined>(undefined); const startSession = useMutation( async ({ @@ -40,6 +44,7 @@ const PlayButtons = ({ token }: { token: OwnedToken }) => { pitcher: role === 0 ? token : undefined, outcome: 0, progress: 2, + requiresSignature: requireSignature, }; return { sessionID, sign, atBat, token }; }, @@ -99,67 +104,67 @@ const PlayButtons = ({ token }: { token: OwnedToken }) => { }, ); + const handleClick = (role: 0 | 1) => { + playSound(role === 0 ? "pitchButton" : "batButton"); + if (token.tokenProgress !== 6 && token.stakedSessionID) { + router.push(`atbats/?session_id=${token.stakedSessionID}`); + return; + } + setRole(role); + }; + + const handleChoice = (requireSignature: boolean) => { + if (role === undefined) { + return; + } + startSession.mutate({ + role, + token, + requireSignature, + }); + setRole(undefined); + }; + return (
+ {role !== undefined && ( + setRole(undefined)} /> + )} + {role !== undefined &&
} + {(!token.isStaked || token.tokenProgress === 6 || (token.activeSession?.batterNFT.nftAddress === token.address && token.activeSession?.batterNFT.tokenID === token.id)) && ( -
{ - playSound("batButton"); - if ( - token.activeSession?.batterNFT.nftAddress === token.address && - token.activeSession?.batterNFT.tokenID === token.id && - token.tokenProgress !== 6 - ) { - router.push(`atbats/?session_id=${token.stakedSessionID}`); - return; - } - startSession.mutate({ - role: 1, - token, - requireSignature: false, - }); - }} + disabled={startSession.isLoading} + style={{ zIndex: role === 0 ? 0 : 1, cursor: role === undefined ? "pointer" : "default" }} + onClick={() => handleClick(1)} > {startSession.isLoading && startSession.variables?.role === 1 ? ( ) : ( "Bat" )} -
+ )} {(!token.isStaked || token.tokenProgress === 6 || (token.activeSession?.pitcherNFT.nftAddress === token.address && token.activeSession?.pitcherNFT.tokenID === token.id)) && ( -
{ - playSound("pitchButton"); - if ( - token.activeSession?.pitcherNFT.nftAddress === token.address && - token.activeSession?.pitcherNFT.tokenID === token.id && - token.tokenProgress !== 6 - ) { - router.push(`atbats/?session_id=${token.stakedSessionID}`); - return; - } - startSession.mutate({ - role: 0, - token, - requireSignature: false, - }); - }} + style={{ zIndex: role === 1 ? 0 : 1, cursor: role === undefined ? "pointer" : "default" }} + onClick={() => handleClick(0)} > {startSession.isLoading && startSession.variables?.role === 0 ? ( ) : ( "Pitch" )} -
+ )}
); diff --git a/web/src/components/tokens/Roster.module.css b/web/src/components/tokens/Roster.module.css index 7a5cd0d0..11b2bdd4 100644 --- a/web/src/components/tokens/Roster.module.css +++ b/web/src/components/tokens/Roster.module.css @@ -76,9 +76,11 @@ gap: 5px; align-self: stretch; width: 130px; + position: relative; } .button { + width: 100%; display: flex; height: 40px; padding: 5px 20px; @@ -140,6 +142,40 @@ height: 130px; } +.dialog { + position: absolute; + z-index: 20; + top: calc(100% + 5px); + left: 0; + display: flex; + padding: 10px; + flex-direction: column; + align-items: flex-start; + gap: 5px; + border: 1px solid #262019; + background: #FFF; +} + +.dialogButton { + display: flex; + width: 145px; + padding: 5px 15px; + justify-content: center; + align-items: center; + gap: 10px; + background: #328449; + color: #FFF; + text-align: center; + font-family: Bangers, cursive; + font-size: 18px; + font-style: normal; + font-weight: 400; + line-height: normal; + white-space: nowrap; +} + + + @media (min-width: 768px) { .container { diff --git a/web/src/components/tokens/StartAtBatDialog.tsx b/web/src/components/tokens/StartAtBatDialog.tsx new file mode 100644 index 00000000..a7fb73cb --- /dev/null +++ b/web/src/components/tokens/StartAtBatDialog.tsx @@ -0,0 +1,35 @@ +import styles from "./Roster.module.css"; +import { useEffect, useRef } from "react"; + +const StartAtBatDialog = ({ + onClick, + onClose, +}: { + onClick: (requireSignature: boolean) => void; + onClose: () => void; +}) => { + const dialogRef = useRef(null); + const handleClickOutside = (event: MouseEvent) => { + if (dialogRef.current && !dialogRef.current.contains(event.target as Node)) { + onClose(); + event.stopPropagation(); + } + }; + useEffect(() => { + document.addEventListener("click", handleClickOutside, true); + return () => document.removeEventListener("click", handleClickOutside, true); + }, []); + + return ( +
+ + +
+ ); +}; + +export default StartAtBatDialog; diff --git a/web/src/services/fullcounts.ts b/web/src/services/fullcounts.ts index a78573e8..7ebdc3f1 100644 --- a/web/src/services/fullcounts.ts +++ b/web/src/services/fullcounts.ts @@ -183,11 +183,17 @@ export const getAtBats = async ({ tokensCache }: { tokensCache: Token[] }) => { .sessionProgress(lastSessions[i - oldestAtBatNumber]) .encodeABI(), }); + progressQueries.push({ + target: GAME_CONTRACT, + callData: gameContract.methods + .SessionRequiresSignature(lastSessions[i - oldestAtBatNumber]) + .encodeABI(), + }); } - const [progresses] = await getMulticallResults( + const [progresses, signatures] = await getMulticallResults( FullcountABI, - ["sessionProgress"], + ["sessionProgress", "SessionRequiresSignature"], progressQueries, ); @@ -233,6 +239,7 @@ export const getAtBats = async ({ tokensCache }: { tokensCache: Token[] }) => { numberOfSessions: Number(numbersOfSessions[idx]), lastSessionId: Number(lastSessions[idx]), progress: Number(progresses[idx]), + requiresSignature: signatures[idx], })) .reverse(), tokens, diff --git a/web/src/tokenInterfaces/FullcountPlayerAPI.ts b/web/src/tokenInterfaces/FullcountPlayerAPI.ts index d19dcd5b..30365497 100644 --- a/web/src/tokenInterfaces/FullcountPlayerAPI.ts +++ b/web/src/tokenInterfaces/FullcountPlayerAPI.ts @@ -118,7 +118,7 @@ export async function startSessionFullcountPlayer({ `token_address:${token.address}`, `token_id:${token.id}`, ]); - return { sessionID: data.session_id, sign: "0x" + data.signature }; + return { sessionID: data.session_id, sign: data.signature }; } export const delay = (delayInms: number) => { diff --git a/web/src/types.d.ts b/web/src/types.d.ts index 047c9b39..6778e25b 100644 --- a/web/src/types.d.ts +++ b/web/src/types.d.ts @@ -128,6 +128,7 @@ interface AtBat { numberOfSessions?: number; lastSession?: SessionState; progress: number; + requiresSignature: boolean; } interface AtBatStatus {