Skip to content

Commit

Permalink
Merge pull request #240 from moonstream-to/notifications
Browse files Browse the repository at this point in the history
Notifications
  • Loading branch information
Anton-Mushnin authored Apr 4, 2024
2 parents d7e5e8b + 8a1c302 commit ba3bc9e
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 27 deletions.
33 changes: 17 additions & 16 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"scripts": {
"dev": "next dev",
"devHttps": "node server.js",
"build": "next build && next export -o build",
"start": "next start",
"lint": "next lint",
Expand All @@ -20,6 +21,7 @@
"@types/react-dom": "18.0.10",
"axios": "^1.3.0",
"eslint-config-next": "13.1.1",
"express": "^4.19.2",
"framer-motion": "^6.5.1",
"next": "13.1.1",
"react": "18.2.0",
Expand Down
28 changes: 28 additions & 0 deletions web/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { createServer } = require("https");
const { parse } = require("url");
const next = require("next");
const fs = require("fs");
const express = require("express");

const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
const server = express();

server.get("*", (req, res) => {
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
});

const options = {
key: fs.readFileSync("./localhost-key.pem"),
cert: fs.readFileSync("./localhost.pem"),
};

createServer(options, server).listen(3000, (err) => {
if (err) throw err;
console.log("> Ready on https://localhost:3000");
});
});
75 changes: 70 additions & 5 deletions web/src/components/Playing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import SessionsView from "./sessions/SessionsView";
import PlayView from "./playing/PlayView";
import styles from "./Playing.module.css";
import { useQuery } from "react-query";
import { OwnedToken } from "../types";
import { fetchOwnedBLBTokens } from "../tokenInterfaces/BLBTokenAPI";
import { AtBat, OwnedToken } from "../types";
import { fetchFullcountPlayerTokens } from "../tokenInterfaces/FullcountPlayerAPI";
import queryCacheProps from "../hooks/hookCommon";
import useUser from "../contexts/UserContext";
Expand All @@ -15,8 +14,15 @@ import PlayingLayout from "./layout/PlayingLayout";
import ChooseToken from "./tokens/ChooseToken";
import HomePage from "./HomePage/HomePage";
import { getAtBats } from "../services/fullcounts";
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { FULLCOUNT_ASSETS_PATH } from "../constants";
import { playSound } from "../utils/notifications";
import { getContracts } from "../utils/getWeb3Contracts";
import { getMulticallResults } from "../utils/multicall";

import { AbiItem } from "web3-utils";
import FullcountABIImported from "../web3/abi/FullcountABI.json";
const FullcountABI = FullcountABIImported as unknown as AbiItem[];

const Playing = () => {
const {
Expand All @@ -28,6 +34,7 @@ const Playing = () => {
invitedTo,
isCreateCharacter,
tokensCache,
joinedNotification,
} = useGameContext();
const { user } = useUser();

Expand All @@ -47,6 +54,63 @@ const Playing = () => {
},
);

const tokenStatuses = useQuery(
["token_statuses", ownedTokens.data, joinedNotification],
async () => {
if (!ownedTokens.data || ownedTokens.data.length < 1 || !joinedNotification) {
return;
}
const { gameContract } = getContracts();
const queries: { target: string; callData: string }[] = [];
ownedTokens.data.forEach((ownedToken) => {
queries.push({
target: gameContract.options.address,
callData: gameContract.methods
.StakedSession(ownedToken.address, ownedToken.id)
.encodeABI(),
});
let stakedSession;
if (tokenStatuses.data) {
stakedSession = tokenStatuses.data.find(
(t) => t.address === ownedToken.address && t.id === ownedToken.id,
)?.stakedSessionID;
}
queries.push({
target: gameContract.options.address,
callData: gameContract.methods.sessionProgress(stakedSession ?? 0).encodeABI(),
});
});

const [stakedSessions, progresses] = await getMulticallResults(
FullcountABI,
["StakedSession", "sessionProgress"],
queries,
);
const result = ownedTokens.data.map((t, idx) => ({
...t,
stakedSession: stakedSessions[idx],
progress: progresses[idx],
}));
if (
tokenStatuses.data &&
tokenStatuses.data.some(
(ts) =>
ts.progress === "2" &&
result.some((t) => t.address === ts.address && t.id === ts.id && t.progress === "3"),
)
) {
playSound("clapping");
}

return result;
},
{
enabled: !!ownedTokens.data && joinedNotification,
refetchIntervalInBackground: true,
refetchInterval: 10000,
},
);

const atBats = useQuery(
["atBats"],
async () => {
Expand All @@ -55,7 +119,6 @@ const Playing = () => {
{
refetchInterval: 5000,
onSuccess: (data: any) => {
console.log(data);
if (data.tokens.length !== tokensCache.length) {
updateContext({ tokensCache: [...data.tokens] });
}
Expand Down Expand Up @@ -120,7 +183,9 @@ const Playing = () => {
<CreateCharacterForm onClose={() => updateContext({ isCreateCharacter: false })} />
)}

{ownedTokens.data && ownedTokens.data.length < 1 && <CreateCharacterForm />}
{ownedTokens.data && ownedTokens.data.length < 1 && !ownedTokens.error && (
<CreateCharacterForm />
)}

{!selectedSession &&
ownedTokens.data &&
Expand Down
30 changes: 26 additions & 4 deletions web/src/components/atbat/AtBatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import ExitIcon from "../icons/ExitIcon";
import TokenCard from "./TokenCard";
import ScoreForDesktop from "./ScoreForDesktop";
import { sendReport } from "../../utils/humbug";
import { playSound } from "../../utils/notifications";

const outcomes = [
"In Progress",
Expand Down Expand Up @@ -57,7 +58,7 @@ const AtBatView: React.FC = () => {
const router = useRouter();
const [atBatId, setAtBatId] = useState<string | null>(null);
const [sessionId, setSessionId] = useState<string | null>(null);
const { tokensCache, updateContext, selectedToken } = useGameContext();
const { tokensCache, updateContext, selectedToken, joinedNotification } = useGameContext();
const [showPitchOutcome, setShowPitchOutcome] = useState(false);
const [currentSessionId, setCurrentSessionId] = useState(0);
const [currentSessionIdx, setCurrentSessionIdx] = useState(0);
Expand Down Expand Up @@ -128,9 +129,30 @@ const AtBatView: React.FC = () => {
},
);

useEffect(() => {
console.log(selectedToken, atBatState.data?.atBat.batter);
}, [atBatState.data?.atBat.batter, selectedToken]);
const currentSessionProgress = useQuery(
["currentSessionProgress", currentSessionId, joinedNotification],
async () => {
if (
!currentSessionId ||
!joinedNotification ||
!atBatState.data ||
atBatState.data?.atBat.pitches.length > 1
) {
return;
}
const { gameContract } = getContracts();
const progress = await gameContract.methods.sessionProgress(currentSessionId).call();
if (Number(currentSessionProgress.data) === 2 && Number(progress) === 3) {
playSound("joinedNotification");
}
return progress;
},
{
refetchIntervalInBackground: true,
refetchInterval: 10000,
enabled: !!currentSessionId && joinedNotification,
},
);

const isSameToken = (a: Token | undefined, b: Token | undefined) => {
if (!a || !b) return false;
Expand Down
12 changes: 12 additions & 0 deletions web/src/components/layout/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import VolumeOn from "../icons/VolumeOn";
import MoreHorizontal from "../icons/MoreHorizontal";
import useLogout from "../../hooks/useLogout";
import { FEEDBACK_FORM_URL, FULLCOUNT_ASSETS_PATH } from "../../constants";
import { setLocalStorageItem } from "../../utils/localStorage";
import { useGameContext } from "../../contexts/GameContext";

const Navbar = () => {
const [isSmallScreen, isMediumScreen] = useMediaQuery([
Expand All @@ -26,6 +28,12 @@ const Navbar = () => {
}
};

const { joinedNotification, updateContext } = useGameContext();
const handleNotificationClick = () => {
setLocalStorageItem("joinedNotification", !joinedNotification);
updateContext({ joinedNotification: !joinedNotification });
};

useEffect(() => {
if (isMenuOpen) {
document.addEventListener("click", handleClickOutside, true);
Expand Down Expand Up @@ -63,6 +71,10 @@ const Navbar = () => {
{/*<div className={styles.menuItem}>About</div>*/}
{/*<div className={styles.menuItem}>Achievements</div>*/}
{/*<div className={styles.menuItem}>Leaderboards</div>*/}
<div
className={styles.menuItem}
onClick={handleNotificationClick}
>{`Notifications - ${joinedNotification ? "on" : "off"}`}</div>
<div
className={styles.menuItem}
onClick={() => {
Expand Down
10 changes: 9 additions & 1 deletion web/src/components/layout/PlayingLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React, { ReactNode } from "react";
import React, { ReactNode, useEffect } from "react";
import { Flex } from "@chakra-ui/react";

import Navbar from "./Navbar";
import { FULLCOUNT_ASSETS_PATH } from "../../constants";
import { useGameContext } from "../../contexts/GameContext";
import { getLocalStorageItem } from "../../utils/localStorage";

const sounds = {
whoosh: `${FULLCOUNT_ASSETS_PATH}/sounds/whoosh.wav`,
Expand All @@ -16,6 +18,12 @@ const sounds = {
};

const PlayingLayout = ({ children }: { children: ReactNode }) => {
const { updateContext } = useGameContext();
useEffect(() => {
const joinedNotification = getLocalStorageItem("joinedNotification");
updateContext({ joinedNotification: !!joinedNotification });
}, []);

return (
<Flex
direction="column"
Expand Down
8 changes: 7 additions & 1 deletion web/src/components/layout/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import Head from "next/head";

import { Flex } from "@chakra-ui/react";

import { FULLCOUNT_ASSETS } from "../../constants";
import { FULLCOUNT_ASSETS, FULLCOUNT_ASSETS_PATH } from "../../constants";
import React from "react";

export const siteTitle = "Fullcount - baseball game";

Expand Down Expand Up @@ -36,6 +37,11 @@ export default function Layout({
fontFamily="Pangolin, cursive"
direction={"column"}
>
<audio
id="joinedNotification"
src={`${FULLCOUNT_ASSETS_PATH}/sounds/clapping-male-crowd.wav`}
preload={"auto"}
/>
{children}
</Flex>
</div>
Expand Down
2 changes: 2 additions & 0 deletions web/src/contexts/GameContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface GameContextProps {
atBatsForPractice: (AtBat | undefined)[] | undefined;
selectedMode: number;
selectedTokenIdx: number;
joinedNotification: boolean;
}

interface GameContextType extends GameContextProps {
Expand Down Expand Up @@ -72,6 +73,7 @@ export const GameContextProvider: FC<ProviderProps> = ({ children }) => {
atBatsForPractice: undefined,
selectedMode: 1,
selectedTokenIdx: 0,
joinedNotification: false,
});

useEffect(() => {
Expand Down
Loading

0 comments on commit ba3bc9e

Please sign in to comment.