Skip to content

Commit

Permalink
Merge pull request #276 from moonstream-to/invites
Browse files Browse the repository at this point in the history
Invite codes
  • Loading branch information
Anton-Mushnin authored May 9, 2024
2 parents e6123a1 + 9e8f96a commit 12072ce
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 45 deletions.
11 changes: 11 additions & 0 deletions web/src/components/GlobalStyles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,15 @@

}

.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #262019;
opacity: 25%;
z-index: 1;
}


2 changes: 2 additions & 0 deletions web/src/components/HomePage/PvpView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ const PvpView = ({ atBats, tokens }: { atBats: AtBat[]; tokens: OwnedToken[] })
.map((openAtBat, idx) => {
return openAtBat.pitcher ? (
<TokenToPlay
requiresSignature={openAtBat.requiresSignature}
token={openAtBat.pitcher}
isPitcher={true}
onClick={() => {
Expand Down Expand Up @@ -200,6 +201,7 @@ const PvpView = ({ atBats, tokens }: { atBats: AtBat[]; tokens: OwnedToken[] })
.map((openAtBat, idx) => {
return openAtBat.batter ? (
<TokenToPlay
requiresSignature={openAtBat.requiresSignature}
token={openAtBat.batter}
isPitcher={false}
onClick={() => {
Expand Down
18 changes: 18 additions & 0 deletions web/src/components/HomePage/TokenToPlay.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 6 additions & 3 deletions web/src/components/HomePage/TokenToPlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ const TokenToPlay = ({
isLoading,
isForGame,
showId = true,
requiresSignature = false,
}: {
token: Token | undefined;
isPitcher: boolean;
onClick?: () => void;
isLoading?: boolean;
isForGame?: boolean;
showId?: boolean;
requiresSignature?: boolean;
}) => {
if (!token) {
return <></>;
Expand Down Expand Up @@ -45,10 +47,11 @@ const TokenToPlay = ({
</div>
{showId && <div className={styles.id}>{token.id}</div>}
</div>
{onClick && (
<div className={styles.button} onClick={onClick}>
{onClick && requiresSignature && <div className={styles.private}>Private</div>}
{onClick && !requiresSignature && (
<button type="button" className={styles.button} onClick={onClick}>
{isLoading ? <Spinner h={4} w={4} /> : isPitcher ? "Bat" : "Pitch"}
</div>
</button>
)}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/Playing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ const Playing = () => {
)}
{inviteFrom && inviteSession && ownedTokens.data && ownedTokens.data.length > 0 && (
<ChooseToken
tokens={ownedTokens.data.filter((t) => !t.isStaked)}
tokens={ownedTokens.data.filter((t) => !t.isStaked || t.tokenProgress === 6)}
sessionID={Number(inviteSession)}
inviteCode={inviteCode}
inviteFrom={inviteFrom}
Expand Down
11 changes: 10 additions & 1 deletion web/src/components/atbat/InviteLinkView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
79 changes: 42 additions & 37 deletions web/src/components/tokens/PlayButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ 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();
const toast = useMoonToast();
const router = useRouter();
const { user } = useUser();
const playSound = useSound();
const [role, setRole] = useState<0 | 1 | undefined>(undefined);

const startSession = useMutation(
async ({
Expand All @@ -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 };
},
Expand Down Expand Up @@ -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 (
<div className={styles.buttonsContainer}>
{role !== undefined && (
<StartAtBatDialog onClick={handleChoice} onClose={() => setRole(undefined)} />
)}
{role !== undefined && <div className={globalStyles.overlay} />}

{(!token.isStaked ||
token.tokenProgress === 6 ||
(token.activeSession?.batterNFT.nftAddress === token.address &&
token.activeSession?.batterNFT.tokenID === token.id)) && (
<div
<button
className={styles.button}
onClick={() => {
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 ? (
<Spinner h={4} w={4} />
) : (
"Bat"
)}
</div>
</button>
)}
{(!token.isStaked ||
token.tokenProgress === 6 ||
(token.activeSession?.pitcherNFT.nftAddress === token.address &&
token.activeSession?.pitcherNFT.tokenID === token.id)) && (
<div
<button
disabled={startSession.isLoading}
className={styles.button}
onClick={() => {
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 ? (
<Spinner h={4} w={4} />
) : (
"Pitch"
)}
</div>
</button>
)}
</div>
);
Expand Down
36 changes: 36 additions & 0 deletions web/src/components/tokens/Roster.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,11 @@
gap: 5px;
align-self: stretch;
width: 130px;
position: relative;
}

.button {
width: 100%;
display: flex;
height: 40px;
padding: 5px 20px;
Expand Down Expand Up @@ -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 {
Expand Down
35 changes: 35 additions & 0 deletions web/src/components/tokens/StartAtBatDialog.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>(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 (
<div className={styles.dialog} ref={dialogRef}>
<button type={"button"} className={styles.button} onClick={() => onClick(false)}>
OPEN&nbsp;CHALLENGE
</button>
<button type={"button"} className={styles.button} onClick={() => onClick(true)}>
BY&nbsp;INVITE&nbsp;LINK&nbsp;ONLY
</button>
</div>
);
};

export default StartAtBatDialog;
11 changes: 9 additions & 2 deletions web/src/services/fullcounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);

Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion web/src/tokenInterfaces/FullcountPlayerAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
1 change: 1 addition & 0 deletions web/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ interface AtBat {
numberOfSessions?: number;
lastSession?: SessionState;
progress: number;
requiresSignature: boolean;
}

interface AtBatStatus {
Expand Down

0 comments on commit 12072ce

Please sign in to comment.