diff --git a/web/src/components/atbat/AtBatView.module.css b/web/src/components/atbat/AtBatView.module.css index 424940d0..5ace3004 100644 --- a/web/src/components/atbat/AtBatView.module.css +++ b/web/src/components/atbat/AtBatView.module.css @@ -15,7 +15,7 @@ top: 45.5px; right: 10px; color: black; - z-index: 2; + z-index: 9; display: inline-flex; padding: 8px; justify-content: center; @@ -37,7 +37,6 @@ max-height: 36px; z-index: 2; white-space: nowrap; - } .positiveOutcome { color: #328449; @@ -46,7 +45,6 @@ font-style: normal; font-weight: 400; line-height: normal; - } .negativeOutcome { @@ -58,56 +56,58 @@ line-height: normal; } -.negativeOutcome2 { + +.positiveOutcome2, .negativeOutcome2 { display: flex; align-items: center; - padding-top: 5px; + padding-top: 7px; justify-content: center; position: absolute; right: 50%; - top: calc(12% + 25px); /*35.5 + 13.5 + x*/ transform: translateX(50%); - height: 49px; - width: 81px; - background-image: url(https://static.simiotics.com/fullcount/outcome-bubble-loss.svg); z-index: 3; - color: #CD7676; text-align: center; font-family: Bangers, cursive; - font-size: 16px; font-style: normal; font-weight: 400; line-height: normal; fill: rgba(255, 255, 255, 0.70); stroke-width: 1px; + + top: 35.5px; + font-size: 32px; + width: 162px; + height: 98px; +} + +.negativeOutcome2 { stroke: #CD7676; + color: #CD7676; + background-image: url(https://static.fullcount.xyz/web/elements/outcome-bubble-loss-medium.svg); } .positiveOutcome2 { - display: flex; - align-items: center; - padding-top: 5px; - justify-content: center; - position: absolute; - right: 50%; - top: calc(12% + 25px); /*35.5 + 13.5 + x*/ - transform: translateX(50%); - height: 49px; - width: 81px; - background-image: url(https://static.simiotics.com/fullcount/outcome-bubble-victory.svg); - z-index: 3; color: #669568; - text-align: center; - font-family: Bangers, cursive; - font-size: 16px; - font-style: normal; - font-weight: 400; - line-height: normal; - fill: rgba(255, 255, 255, 0.70); - stroke-width: 1px; + background-image: url(https://static.fullcount.xyz/web/elements/outcome-bubble-victory-medium.svg); stroke: #CD7676; } +@media (min-width: 1024px) { + .positiveOutcome2, .negativeOutcome2 { + font-size: 64px; + height: 196px; + width: 324px; + top: 0; + padding-top: 0; + } + .positiveOutcome2 { + background-image: url(https://static.fullcount.xyz/web/elements/outcome-bubble-victory-big.svg); + } + .negativeOutcome2 { + background-image: url(https://static.fullcount.xyz/web/elements/outcome-bubble-loss-big.svg); + } +} + .homeButton { position: absolute; diff --git a/web/src/components/atbat/AtBatView.tsx b/web/src/components/atbat/AtBatView.tsx index 0ec00e32..e50d2133 100644 --- a/web/src/components/atbat/AtBatView.tsx +++ b/web/src/components/atbat/AtBatView.tsx @@ -1,5 +1,5 @@ import { useRouter } from "next/router"; -import { useState, useEffect } from "react"; +import { useEffect, useState } from "react"; import styles from "./AtBatView.module.css"; import { useQuery, useQueryClient, UseQueryResult } from "react-query"; import { getAtBat } from "../../services/fullcounts"; @@ -11,15 +11,18 @@ import { AtBatStatus, OwnedToken, Token } from "../../types"; import BatterViewMobile from "../playing/BatterViewMobile"; import { getContracts } from "../../utils/getWeb3Contracts"; import { FULLCOUNT_ASSETS_PATH } from "../../constants"; -import { Image, useMediaQuery } from "@chakra-ui/react"; +import { Image, useDisclosure, useMediaQuery } from "@chakra-ui/react"; import Outcome2, { sessionOutcomeType } from "./Outcome2"; import ExitIcon from "../icons/ExitIcon"; import TokenCard from "./TokenCard"; import ScoreForDesktop from "./ScoreForDesktop"; import { sendReport } from "../../utils/humbug"; import { playSound } from "../../utils/notifications"; +import ExitDialog from "./ExitDialog"; +import useUser from "../../contexts/UserContext"; +import { fetchFullcountPlayerTokens } from "../../tokenInterfaces/FullcountPlayerAPI"; -const outcomes = [ +export const outcomes = [ "In Progress", "Strikeout", "Walk", @@ -56,13 +59,15 @@ export const outcomeType = ( const AtBatView: React.FC = () => { const router = useRouter(); + const { tokensCache, updateContext, selectedToken, joinedNotification } = useGameContext(); const [atBatId, setAtBatId] = useState(null); const [sessionId, setSessionId] = useState(null); - const { tokensCache, updateContext, selectedToken, joinedNotification } = useGameContext(); const [showPitchOutcome, setShowPitchOutcome] = useState(false); const [currentSessionId, setCurrentSessionId] = useState(0); const [currentSessionIdx, setCurrentSessionIdx] = useState(0); const [isBigView] = useMediaQuery("(min-width: 1024px)"); + const { isOpen, onOpen, onClose } = useDisclosure(); + const { user } = useUser(); useEffect(() => { window.scrollTo(0, 0); @@ -86,7 +91,7 @@ const AtBatView: React.FC = () => { if (router.query.session_id && typeof router.query.session_id === "string") { setSessionId(router.query.session_id); } - }, [router.query.id]); + }, [router.query.id, router.query.session_id]); const queryClient = useQueryClient(); const atBatState: UseQueryResult<{ atBat: AtBatStatus; tokens: Token[] }> = useQuery( @@ -108,6 +113,14 @@ const AtBatView: React.FC = () => { { onSuccess: (data) => { console.log(data); + if (data && !selectedToken && ownedTokens.data) { + const token = ownedTokens.data.find( + (t) => isSameToken(t, data.atBat.batter) || isSameToken(t, data.atBat.pitcher), + ); + if (token) { + updateContext({ selectedToken: { ...token } }); + } + } if (data && currentSessionId === 0) { setCurrentSessionId(data.atBat.pitches[data.atBat.numberOfSessions - 1].sessionID); } @@ -160,10 +173,27 @@ const AtBatView: React.FC = () => { }; const handleExitClick = () => { - sendReport("PlayView exit", "", ["type:click", "click:atBatExit"]); - router.push("/"); + if ( + atBatState.data?.atBat.pitches.length === 1 && + atBatState.data.atBat.pitches[0].progress == 2 + ) { + onOpen(); + } else { + sendReport("PlayView exit", "", ["type:click", "click:atBatExit"]); + router.push("/"); + } }; + const ownedTokens = useQuery( + ["ownedTokensAfterRefresh", user], + async () => { + return user ? await fetchFullcountPlayerTokens() : []; + }, + { + enabled: !selectedToken, + }, + ); + return (
{ >
+ {isOpen && selectedToken && ( + + )}
{ pitch={atBatState.data.atBat.pitches[currentSessionIdx]} /> )} - {atBatState.data && atBatState.data.atBat.outcome !== 0 && selectedToken && ( -
- {outcomes[atBatState.data.atBat.outcome]}! -
- )} {atBatState.data?.atBat && showPitchOutcome && atBatState.data.atBat.pitches.length > 0 && @@ -293,6 +313,7 @@ const AtBatView: React.FC = () => { {atBatState.data?.atBat && ( void; +}) => { + const toast = useMoonToast(); + const queryClient = useQueryClient(); + const { user } = useUser(); + const updateTokenInCache = (token: Token, sessionId: number) => { + const currentTokens = queryClient.getQueryData(["owned_tokens", user]); + const currentAtBats = queryClient.getQueryData<{ tokens: Token[]; atBats: AtBat[] }>([ + "atBats", + ]); + if (currentTokens) { + const updatedTokens = currentTokens.map((t) => { + if (t.id === token.id) { + return { + ...t, + isStaked: false, + tokenProgress: 0, + stakedSession: 0, + }; + } + return t; + }); + queryClient.setQueryData(["owned_tokens", user], updatedTokens); + } + if (currentAtBats) { + const updatedAtBats = currentAtBats.atBats.map((a) => { + if (a.lastSessionId === sessionId) { + return { + ...a, + progress: 1, + tokenProgress: 0, + stakedSession: 0, + }; + } + return a; + }); + queryClient.setQueryData(["atBats"], updatedAtBats); + } + }; + + const closeAtBat = useMutation( + ({ token, sessionId }: { token: Token; sessionId: number }) => { + return abortFullcountPlayerSession({ token, sessionId }); + }, + { + retry: (failureCount, error) => { + return ( + error instanceof Error && error.message === CANT_ABORT_SESSION_MSG && failureCount < 3 + ); + }, + retryDelay: () => { + return 3000; + }, + onError: (error: Error) => { + toast(`Can't close At-Bat - ${error.message}`, "error"); + }, + onSuccess: (_, variables) => { + updateTokenInCache(variables.token, variables.sessionId); + router.push("/"); + }, + }, + ); + const dialogRef = useRef(null); // Ref for the menu element, typed as HTMLDivElement + + 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); + }; + }, []); + + const handleCloseClick = () => { + closeAtBat.mutate({ token, sessionId }); + }; + const handleKeepClick = () => { + router.push("/"); + }; + return ( +
+
Do you want to end At-Bat?
+
+ You are returning to the home page. You can keep the At-Bat open though. +
+
+ + +
+
+ ); +}; + +export default ExitDialog; diff --git a/web/src/components/atbat/Outcome2.module.css b/web/src/components/atbat/Outcome2.module.css index a70caeee..6c675fe7 100644 --- a/web/src/components/atbat/Outcome2.module.css +++ b/web/src/components/atbat/Outcome2.module.css @@ -81,3 +81,31 @@ height: 7.82vh; z-index: 3; } + +.positiveOutcome, .negativeOutcome { + position: absolute; + right: 50%; + top: 100%; + transform: translateX(50%) translateY(10px); + max-height: 36px; + z-index: 2; + white-space: nowrap; + font-size: 20px; + font-family: Bangers, cursive; + font-style: normal; + font-weight: 400; + line-height: normal; +} +.positiveOutcome { + color: #328449; +} + +.negativeOutcome { + color: #CD7676; +} + +@media (min-width: 1024px) { + .positiveOutcome, .negativeOutcome { + font-size: 30px; + } +} diff --git a/web/src/components/atbat/Outcome2.tsx b/web/src/components/atbat/Outcome2.tsx index 074834d2..dbd8c644 100644 --- a/web/src/components/atbat/Outcome2.tsx +++ b/web/src/components/atbat/Outcome2.tsx @@ -4,6 +4,8 @@ import { AtBatStatus, SessionStatus, Token } from "../../types"; import OutcomeGrid from "./OutcomeGrid"; import React, { useEffect } from "react"; import { FULLCOUNT_ASSETS_PATH } from "../../constants"; +import { useGameContext } from "../../contexts/GameContext"; +import { outcomes, outcomeType } from "./AtBatView"; export const sessionOutcomeType = ( tokens: Token[], @@ -11,16 +13,13 @@ export const sessionOutcomeType = ( sessionStatus: SessionStatus, ): "positive" | "negative" | "neutral" | undefined => { const { pitcher, batter } = atBat; - if (sessionStatus.outcome === 3) { - return "neutral"; - } if (tokens.some((t) => t.address === pitcher?.address && t.id === pitcher.id)) { return sessionStatus.outcome === 0 || sessionStatus.outcome === 2 || atBat.outcome === 7 ? "positive" : "negative"; } if (tokens.some((t) => t.address === batter?.address && t.id === batter.id)) { - return atBat.outcome === 0 || sessionStatus.outcome === 2 || atBat.outcome === 7 + return sessionStatus.outcome === 0 || sessionStatus.outcome === 2 || atBat.outcome === 7 ? "negative" : "positive"; } @@ -34,10 +33,13 @@ const rowCenters = [1.955, 7.82, 15.64, 23.46, 29.33]; const Outcome2 = ({ atBat, sessionStatus, + forToken, }: { atBat: AtBatStatus; sessionStatus: SessionStatus; + forToken: Token | undefined; }) => { + const { selectedToken } = useGameContext(); useEffect(() => { console.log(sessionStatus); }, [sessionStatus]); @@ -46,6 +48,19 @@ const Outcome2 = ({
{/*
*/}
+ {atBat && atBat.outcome !== 0 && forToken && ( +
+ {outcomes[atBat.outcome]}! +
+ )}
{atBat?.pitcher && ( diff --git a/web/src/components/atbat/PitchHistory.tsx b/web/src/components/atbat/PitchHistory.tsx index f730f9ae..80583ca7 100644 --- a/web/src/components/atbat/PitchHistory.tsx +++ b/web/src/components/atbat/PitchHistory.tsx @@ -7,6 +7,9 @@ import { useGameContext } from "../../contexts/GameContext"; import { sessionOutcomeType } from "./Outcome2"; import { sessionOutcomes } from "./AtBatView"; +const completedPitchesNumber = (atBat: AtBatStatus) => + atBat.pitches.filter((p) => p.progress === 5).length; + const PitchHistory = ({ atBat }: { atBat: AtBatStatus }) => { const { selectedToken } = useGameContext(); @@ -14,7 +17,11 @@ const PitchHistory = ({ atBat }: { atBat: AtBatStatus }) => {
PITCH HISTORY
-
{`${atBat.pitches.length} PITCHES`}
+ {completedPitchesNumber(atBat) > 0 && ( +
{`${completedPitchesNumber(atBat)} PITCH${ + completedPitchesNumber(atBat) === 1 ? "" : "ES" + }`}
+ )}
{atBat.pitches diff --git a/web/src/components/atbat/Score.tsx b/web/src/components/atbat/Score.tsx index 05e9534a..34c0a677 100644 --- a/web/src/components/atbat/Score.tsx +++ b/web/src/components/atbat/Score.tsx @@ -8,9 +8,17 @@ import ChevronUp from "../icons/ChevronUp"; import ChevronDown from "../icons/ChevronDown"; import PitchHistory from "./PitchHistory"; -const Score = ({ atBat, pitch }: { atBat: AtBatStatus; pitch: SessionStatus }) => { +const Score = ({ + atBat, + pitch, + openHistory = false, +}: { + atBat: AtBatStatus; + pitch: SessionStatus; + openHistory?: boolean; +}) => { const { secondsPerPhase } = useGameContext(); - const [isHistoryOpen, setIsHistoryOpen] = useState(false); + const [isHistoryOpen, setIsHistoryOpen] = useState(openHistory); return (
diff --git a/web/src/components/atbat/ScoreForDesktop.tsx b/web/src/components/atbat/ScoreForDesktop.tsx index 7583faf4..c3a0326c 100644 --- a/web/src/components/atbat/ScoreForDesktop.tsx +++ b/web/src/components/atbat/ScoreForDesktop.tsx @@ -8,9 +8,17 @@ import ChevronUpLarge from "../icons/ChevronUpLarge"; import ChevronDownLarge from "../icons/ChevronDownLarge"; import DotsCounterLarge from "../sessions/DotsCounterLarge"; -const ScoreForDesktop = ({ atBat, pitch }: { atBat: AtBatStatus; pitch: SessionStatus }) => { +const ScoreForDesktop = ({ + atBat, + pitch, + openHistory = false, +}: { + atBat: AtBatStatus; + pitch: SessionStatus; + openHistory?: boolean; +}) => { const { secondsPerPhase } = useGameContext(); - const [isHistoryOpen, setIsHistoryOpen] = useState(false); + const [isHistoryOpen, setIsHistoryOpen] = useState(openHistory); return (
diff --git a/web/src/components/atbat/TokenCard.tsx b/web/src/components/atbat/TokenCard.tsx index 1d21908a..621183bb 100644 --- a/web/src/components/atbat/TokenCard.tsx +++ b/web/src/components/atbat/TokenCard.tsx @@ -18,7 +18,6 @@ const TokenCard = ({ token, isPitcher }: { token: Token; isPitcher: boolean }) = } const API_URL = "https://api.fullcount.xyz/stats"; const stat = await axios.get(`${API_URL}/${token.address}/${token.id}`); - console.log(stat.data); return stat.data; }, { @@ -34,7 +33,6 @@ const TokenCard = ({ token, isPitcher }: { token: Token; isPitcher: boolean }) = } const API_URL = "https://api.fullcount.xyz/stats"; const stat = await axios.get(`${API_URL}/${token.address}/${token.id}`); - console.log(stat.data); return stat.data; }, { diff --git a/web/src/components/campaign/CampaignView.tsx b/web/src/components/campaign/CampaignView.tsx index 30f9ad9a..677450a0 100644 --- a/web/src/components/campaign/CampaignView.tsx +++ b/web/src/components/campaign/CampaignView.tsx @@ -20,64 +20,70 @@ const CampaignView = ({ atBats }: { atBats: AtBat[] }) => { const { selectedToken } = useGameContext(); const [isPitching, setIsPitching] = useState(true); - const stats = useQuery(["stats", selectedToken?.id, selectedToken?.address], async () => { - if (!selectedToken) { - return undefined; - } - const fullcountAddress = GAME_CONTRACT; - const botsAddress = CAMPAIGN_BOTS_ADDRESS; - const playerTokenId = selectedToken.id; - const playerAddress = selectedToken.address; - - let batterUrl = `https://api.fullcount.xyz/batter_campaign_results?fullcount_address=${fullcountAddress}&bots_address=${botsAddress}&batter_address=${playerAddress}&batter_token_id=${playerTokenId}`; - const pitcherTokenIds = getAllPitchersIds(); - pitcherTokenIds.forEach((id) => { - batterUrl += `&pitcher_token_ids=${id}`; - }); - let pitcherUrl = `https://api.fullcount.xyz/pitcher_campaign_results?fullcount_address=${fullcountAddress}&bots_address=${botsAddress}&pitcher_address=${playerAddress}&pitcher_token_id=${playerTokenId}`; - const batterTokenIds = getAllBattersIds(); - batterTokenIds.forEach((id) => { - pitcherUrl += `&batter_token_ids=${id}`; - }); + const stats = useQuery( + ["stats", selectedToken?.id, selectedToken?.address], + async () => { + if (!selectedToken) { + return undefined; + } + const fullcountAddress = GAME_CONTRACT; + const botsAddress = CAMPAIGN_BOTS_ADDRESS; + const playerTokenId = selectedToken.id; + const playerAddress = selectedToken.address; - const batterCampaignWins = await axios - .get(batterUrl) - .then((response) => { - return response.data.wins_against_token_id; - }) - .catch((error) => { - console.error("Error:", error); + let batterUrl = `https://api.fullcount.xyz/batter_campaign_results?fullcount_address=${fullcountAddress}&bots_address=${botsAddress}&batter_address=${playerAddress}&batter_token_id=${playerTokenId}`; + const pitcherTokenIds = getAllPitchersIds(); + pitcherTokenIds.forEach((id) => { + batterUrl += `&pitcher_token_ids=${id}`; }); - const pitcherCampaignWins = await axios - .get(pitcherUrl) - .then((response) => { - return response.data.wins_against_token_id; - }) - .catch((error) => { - console.error("Error:", error); + let pitcherUrl = `https://api.fullcount.xyz/pitcher_campaign_results?fullcount_address=${fullcountAddress}&bots_address=${botsAddress}&pitcher_address=${playerAddress}&pitcher_token_id=${playerTokenId}`; + const batterTokenIds = getAllBattersIds(); + batterTokenIds.forEach((id) => { + pitcherUrl += `&batter_token_ids=${id}`; }); - const data = [...batterCampaignWins, ...pitcherCampaignWins].reduce((acc, curr) => { - acc[curr.token_id] = curr.wins; - return acc; - }, {}); - const stats: Record = {}; - Object.keys(data).forEach((c) => { - const stat = stats[getCharacterName(c) ?? "error"]; - if (!stat) { - stats[getCharacterName(c) ?? "error"] = data[c]; - } else { - stats[getCharacterName(c) ?? "error"] = stats[getCharacterName(c) ?? "error"] + data[c]; - } - }); - const pitchersCompleted = getAllPitchers().filter( - (p) => stats[p.name] && stats[p.name] >= 3, - ).length; - const battersCompleted = getAllBatters().filter( - (p) => stats[p.name] && stats[p.name] >= 3, - ).length; - return { stats, pitchersCompleted, battersCompleted }; - }); + const batterCampaignWins = await axios + .get(batterUrl) + .then((response) => { + return response.data.wins_against_token_id; + }) + .catch((error) => { + console.error("Error:", error); + }); + const pitcherCampaignWins = await axios + .get(pitcherUrl) + .then((response) => { + return response.data.wins_against_token_id; + }) + .catch((error) => { + console.error("Error:", error); + }); + const data = [...batterCampaignWins, ...pitcherCampaignWins].reduce((acc, curr) => { + acc[curr.token_id] = curr.wins; + return acc; + }, {}); + const stats: Record = {}; + Object.keys(data).forEach((c) => { + const stat = stats[getCharacterName(c) ?? "error"]; + if (!stat) { + stats[getCharacterName(c) ?? "error"] = data[c]; + } else { + stats[getCharacterName(c) ?? "error"] = stats[getCharacterName(c) ?? "error"] + data[c]; + } + }); + const pitchersCompleted = getAllPitchers().filter( + (p) => stats[p.name] && stats[p.name] >= 3, + ).length; + const battersCompleted = getAllBatters().filter( + (p) => stats[p.name] && stats[p.name] >= 3, + ).length; + + return { stats, pitchersCompleted, battersCompleted }; + }, + { + refetchInterval: 5000, + }, + ); return (
diff --git a/web/src/components/icons/ErrorIcon.tsx b/web/src/components/icons/ErrorIcon.tsx new file mode 100644 index 00000000..36aac6ee --- /dev/null +++ b/web/src/components/icons/ErrorIcon.tsx @@ -0,0 +1,23 @@ +import React from "react"; + +const ErrorIcon: React.FC> = (props) => ( + + + + + +); + +export default ErrorIcon; diff --git a/web/src/components/icons/TwitterLogoSmall.tsx b/web/src/components/icons/TwitterLogoSmall.tsx new file mode 100644 index 00000000..b63856b9 --- /dev/null +++ b/web/src/components/icons/TwitterLogoSmall.tsx @@ -0,0 +1,22 @@ +import React from "react"; + +const TwitterLogoSmall: React.FC> = (props) => ( + + + +); + +export default TwitterLogoSmall; diff --git a/web/src/components/landing/Footer.tsx b/web/src/components/landing/Footer.tsx index 4c7ce666..6afd5747 100644 --- a/web/src/components/landing/Footer.tsx +++ b/web/src/components/landing/Footer.tsx @@ -2,7 +2,8 @@ import styles from "./Footer.module.css"; import DiscordLogo from "../icons/DiscordLogo"; import FullcountLogo from "../icons/FullcountLogo"; import { Flex, useMediaQuery, Image } from "@chakra-ui/react"; -import { DISCORD_LINK, FULLCOUNT_ASSETS } from "../../constants"; +import { DISCORD_LINK, FULLCOUNT_ASSETS, TWITTER_LINK } from "../../constants"; +import TwitterLogoSmall from "../icons/TwitterLogoSmall"; const Footer = () => { const [is768View, is1024View] = useMediaQuery(["(min-width: 768px)", "(min-width: 1024px)"]); @@ -21,9 +22,6 @@ const Footer = () => {
Privacy Policy
- -
Terms of Service
-
{is1024View && ( @@ -36,6 +34,10 @@ const Footer = () => { cursor={"pointer"} onClick={() => window.open(DISCORD_LINK, "_blank", "noopener,noreferrer")} /> + window.open(TWITTER_LINK, "_blank", "noopener,noreferrer")} + />
@@ -57,6 +59,10 @@ const Footer = () => { cursor={"pointer"} onClick={() => window.open(DISCORD_LINK, "_blank", "noopener,noreferrer")} /> + window.open(TWITTER_LINK, "_blank", "noopener,noreferrer")} + />
@@ -64,9 +70,6 @@ const Footer = () => {
Privacy Policy
- -
Terms of Service
-
© 2024 Moonstream.to. All rights reserved
diff --git a/web/src/components/playing/DetailedStat.tsx b/web/src/components/playing/DetailedStat.tsx index 45c69eea..c24e5057 100644 --- a/web/src/components/playing/DetailedStat.tsx +++ b/web/src/components/playing/DetailedStat.tsx @@ -68,10 +68,6 @@ const DataRow = ({ label, data }: { label: string; data: string }) => { }; const DetailedStat = ({ stats, isPitcher }: { stats: PlayerStats; isPitcher: boolean }) => { - useEffect(() => { - console.log(stats); - }, []); - return (
season statistics
diff --git a/web/src/components/playing/MainStat.tsx b/web/src/components/playing/MainStat.tsx index 6dbb8683..016c4b92 100644 --- a/web/src/components/playing/MainStat.tsx +++ b/web/src/components/playing/MainStat.tsx @@ -26,9 +26,6 @@ const pitcherRecord = (stats: PlayerStats): string => { }; const MainStat = ({ stats, isPitcher }: { stats: PlayerStats; isPitcher: boolean }) => { - useEffect(() => { - console.log(stats, isPitcher); - }, []); return ( <> {isPitcher && stats.points_data?.pitching_data && ( diff --git a/web/src/components/tokens/Roster.tsx b/web/src/components/tokens/Roster.tsx index c2795391..4e3f4722 100644 --- a/web/src/components/tokens/Roster.tsx +++ b/web/src/components/tokens/Roster.tsx @@ -22,7 +22,11 @@ const Roster = ({ tokens }: { tokens: OwnedToken[] }) => {
{tokens[selectedTokenIdx].name}
{tokens[selectedTokenIdx].id}
- {selectedMode === 0 && } + {(selectedMode === 0 || + (!!tokens[selectedTokenIdx].stakedSessionID && + tokens[selectedTokenIdx].tokenProgress !== 6)) && ( + + )}
{tokens.map((t, idx) => ( diff --git a/web/src/constants.ts b/web/src/constants.ts index 8d897d0d..561e4e61 100644 --- a/web/src/constants.ts +++ b/web/src/constants.ts @@ -32,6 +32,7 @@ export const FULLCOUNT_ASSETS = "https://static.fullcount.xyz/web"; export const TOKEN_IMAGE_FALLBACK = `${FULLCOUNT_ASSETS_PATH}/question.png`; export const DISCORD_LINK = "https://discord.gg/K56VNUQGvA"; +export const TWITTER_LINK = "https://twitter.com/fullcount_xyz"; export type ChainName = "ethereum" | "localhost" | "mumbai" | "polygon" | "wyrm" | "gnosis"; export type ChainId = 1 | 1337 | 80001 | 137 | 322 | 100; diff --git a/web/src/hooks/useMoonToast.tsx b/web/src/hooks/useMoonToast.tsx index c1c23aac..33a799f7 100644 --- a/web/src/hooks/useMoonToast.tsx +++ b/web/src/hooks/useMoonToast.tsx @@ -1,5 +1,6 @@ -import { Box, useToast } from "@chakra-ui/react"; +import { Box, Flex, useToast } from "@chakra-ui/react"; import { useCallback } from "react"; +import ErrorIcon from "../components/icons/ErrorIcon"; const useMoonToast = () => { const chakraToast = useToast(); @@ -13,11 +14,18 @@ const useMoonToast = () => { ) => { const colors = { info: "white", - warning: "yellow", + warning: "white", success: "white", - error: "#F56646", + error: "#262019", loading: "white", }; + const bgColors = { + info: "#7E8E7F", + warning: "#7E8E7F", + success: "#7E8E7F", + error: "#d99c9c", + loading: "#7E8E7F", + }; const userTitle = title ?? message?.response?.statusText ?? type; const userMessage = @@ -33,18 +41,19 @@ const useMoonToast = () => { position: "top", duration, render: () => ( - + {type === "error" && } {message} - + ), }); } diff --git a/web/src/messages.ts b/web/src/messages.ts new file mode 100644 index 00000000..39ce1641 --- /dev/null +++ b/web/src/messages.ts @@ -0,0 +1 @@ +export const CANT_ABORT_SESSION_MSG = "At-Bat is active"; diff --git a/web/src/tokenInterfaces/FullcountPlayerAPI.ts b/web/src/tokenInterfaces/FullcountPlayerAPI.ts index 0a95cfe7..ebf378ff 100644 --- a/web/src/tokenInterfaces/FullcountPlayerAPI.ts +++ b/web/src/tokenInterfaces/FullcountPlayerAPI.ts @@ -8,6 +8,7 @@ import { getMulticallResults } from "../utils/multicall"; import { AbiItem } from "web3-utils"; import FullcountABIImported from "../web3/abi/FullcountABI.json"; import { sendReport } from "../utils/humbug"; +import { CANT_ABORT_SESSION_MSG } from "../messages"; const FullcountABI = FullcountABIImported as unknown as AbiItem[]; const parseActiveSession = (s: any) => { @@ -120,7 +121,7 @@ export async function startSessionFullcountPlayer({ return { sessionID: data.session_id, sign: "0x" + data.signature }; } -const delay = (delayInms: number) => { +export const delay = (delayInms: number) => { return new Promise((resolve) => setTimeout(resolve, delayInms)); }; @@ -185,6 +186,59 @@ export async function joinSessionFullcountPlayer({ }); } +export const abortFullcountPlayerSession = async ({ + token, + sessionId, +}: { + token: Token; + sessionId: number; +}) => { + const postData = { + fullcount_address: GAME_CONTRACT, + erc721_address: token.address, + token_id: token.id, + }; + const headers = getHeaders(); + const { gameContract } = getContracts(); + const progress = await gameContract.methods.sessionProgress(sessionId).call(); + if (Number(progress) !== 2) { + throw new Error(CANT_ABORT_SESSION_MSG); + } + console.log("closing...", { sessionId, progress }); + return axios + .post(`${FULLCOUNT_PLAYER_API}/game/abort`, postData, { headers }) + .then(async (response) => { + const { gameContract } = getContracts(); + let isSuccess = false; + for (let attempt = 1; attempt <= 30; attempt++) { + console.log("checking token state after closing at-bat, attempt: ", attempt); + const session = await gameContract.methods.StakedSession(token.address, token.id).call(); + if (Number(session) === 0) { + isSuccess = true; + break; + } else { + console.log(session, response.data); + } + await delay(3 * 1000); + } + if (!isSuccess) { + throw new Error("Time out. Something with server. Sorry. "); + } + console.log("Success:", response.data); + return response.data; + }) + .catch((e: any) => { + sendReport("Closing at-bat failed", "", [ + "type:error", + "error_domain:fcplayer", + `error:fcplayer-unstaking`, + `token_address:${token.address}`, + `token_id:${token.id}`, + ]); + throw e; + }); +}; + export const unstakeFullcountPlayer = async ({ token }: { token: Token }) => { const postData = { fullcount_address: GAME_CONTRACT,