Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a countdown for bitz game #346

Merged
merged 3 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "explorer-dapp",
"description": "Itheum Explorer is a DApp for the public to explore and visualize data within the Itheum protocol",
"version": "1.13.12",
"version": "1.13.13",
"author": "Itheum",
"license": "GPL-3.0-or-later",
"dependencies": {
Expand Down
57 changes: 48 additions & 9 deletions src/components/Layout/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
import { FlaskRound, Home, Menu, Store, Wallet } from "lucide-react";
import { FlaskRound, Gift, Home, Menu, Store, Wallet } from "lucide-react";
import { Link } from "react-router-dom";
import { SUPPORTED_APPS } from "appsConfig";
import logo192 from "assets/img/logo192.png";
Expand Down Expand Up @@ -32,14 +32,17 @@ import { useTheme } from "../../libComponents/ThemeProvider";
import { useAccountStore } from "../../store/account";
import { Popover, PopoverContent, PopoverTrigger } from "../../libComponents/Popover";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import { BIT_GAME_WINDOW_HOURS, BIT_GAME_TOP_LEADER_BOARD_GROUP } from "../../pages/AppMarketplace/GetBitz";
import { BIT_GAME_WINDOW_HOURS } from "../../pages/AppMarketplace/GetBitz";
import Countdown from "react-countdown";

export const Navbar = () => {
const isLoggedIn = useGetIsLoggedIn();
const bitzBalance = useAccountStore((state: any) => state.bitzBalance);
const cooldown = useAccountStore((state: any) => state.cooldown);
const { address } = useGetAccount();
const { theme } = useTheme();
const [systemTheme, setSystemTheme] = useState<string>();

const getSystemTheme = () => {
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
return "dark";
Expand All @@ -58,6 +61,47 @@ export const Navbar = () => {
logout(`${window.location.origin}`, undefined, false);
};

const ClaimBitzButton = () => (
<Link className="relative inline-flex h-12 overflow-hidden rounded-full p-[1px] " to={"/getbitz"}>
<span className="absolute hover:bg-sky-300 inset-[-1000%] animate-[spin_2s_linear_infinite] bg-[conic-gradient(from_90deg_at_50%_50%,#E2CBFF03,#45d4ff_50%,#111111_50%)]" />
<span className="inline-flex h-full hover:bg-gradient-to-tl from-background to-sky-300 w-full cursor-pointer items-center justify-center rounded-full bg-background px-3 py-1 text-sm font-medium backdrop-blur-3xl">
{cooldown === -2 ? (
<span className="blinkMe">...</span>
) : cooldown > 0 ? (
<Countdown
date={cooldown}
renderer={(props: { hours: number; minutes: number; seconds: number; completed: boolean }) => {
if (props.completed) {
return (
<PopoverPrimitive.PopoverClose>
<div className="flex flex-row justify-center items-center">
<Gift className="mx-2 text-sky-300" />
<span> Claim your {`<BiTz>`} </span>
</div>
</PopoverPrimitive.PopoverClose>
);
} else {
return (
<span className="ml-1">
Play again in {props.hours > 0 ? (props.hours + props.hours === 1 ? " Hour " : " Hours ") : ""}
{props.minutes > 0 ? props.minutes + " Min : " : ""} {props.seconds} Sec
</span>
);
}
}}
/>
) : (
<PopoverPrimitive.PopoverClose>
<div className="flex flex-row justify-center items-center">
<Gift className="mx-2 text-sky-300" />
<span> Claim your {`<BiTz>`} </span>
</div>
</PopoverPrimitive.PopoverClose>
)}
</span>
</Link>
);

return (
<div className="flex flex-row justify-between items-center xl:mx-[7.5rem] md:mx-[4rem] h-20">
<div className="flex flex-row items-center text-xl">
Expand Down Expand Up @@ -162,16 +206,11 @@ export const Navbar = () => {
</div>
</div>
<p className="text-2xl text-center font-[Clash-Medium]">What is {`<BiTz>`} XP?</p>
<p className="text-sm font-[Satoshi-Regular] leading-relaxed py-4">
<p className="text-sm font-[Satoshi-Regular] leading-relaxed py-4 text-center">
{`<BiTz>`} are Itheum Protocol XP. {`<BiTz>`} can be collected every {BIT_GAME_WINDOW_HOURS} hours by playing the Get {`<BiTz>`} game
Data Widget. Top LEADERBOARD climbers get special perks and drops!
</p>
<Link className="relative inline-flex h-12 overflow-hidden rounded-full p-[1px] " to={"/getbitz"}>
<span className="absolute hover:bg-sky-300 inset-[-1000%] animate-[spin_2s_linear_infinite] bg-[conic-gradient(from_90deg_at_50%_50%,#E2CBFF03,#45d4ff_50%,#111111_50%)]" />
<span className="inline-flex h-full hover:bg-gradient-to-tl from-background to-sky-300 w-full cursor-pointer items-center justify-center rounded-full bg-background px-3 py-1 text-sm font-medium backdrop-blur-3xl">
Get {`<BiTz>`}
</span>
</Link>
<ClaimBitzButton />
</div>
</PopoverContent>
</Popover>
Expand Down
7 changes: 7 additions & 0 deletions src/libs/utils/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const computeRemainingCooldown = (startTime: number, cooldown: number) => {
const timePassedFromLastPlay = Date.now() - startTime;
console.log("timePassedFromLastPlay", timePassedFromLastPlay);
const _cooldown = cooldown - timePassedFromLastPlay;

return _cooldown > 0 ? _cooldown + Date.now() : 0;
};
2 changes: 1 addition & 1 deletion src/pages/AppMarketplace/GetBitz/BurningImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const BurningImage: React.FC<{ src: string; burnProgress: number }> = ({
};

return (
<div className="cursor-none relative select-none ">
<div className="cursor-none relative select-none">
<motion.img
className="rounded-[.1rem] w-[250px] max-h-[250px] md:w-[310px] md:max-h-[310px] m-auto -z-1"
src={src}
Expand Down
72 changes: 57 additions & 15 deletions src/pages/AppMarketplace/GetBitz/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import Meme16 from "assets/img/getbitz/memes/16.jpg";
import Meme17 from "assets/img/getbitz/memes/17.jpg";
import Torch from "./Torch";
import Faq from "./Faq";
import { computeRemainingCooldown } from "libs/utils/functions";

interface LeaderBoardItemType {
playerAddr: string;
Expand All @@ -73,8 +74,11 @@ export const GetBitz = () => {
const [checkingIfHasGameDataNFT, setCheckingIfHasGameDataNFT] = useState<boolean>(true);
const [hasGameDataNFT, setHasGameDataNFT] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(true);

const bitzBalance = useAccountStore((state: any) => state.bitzBalance);
const cooldown = useAccountStore((state: any) => state.cooldown);
const updateBitzBalance = useAccountStore((state) => state.updateBitzBalance);
const updateCooldown = useAccountStore((state) => state.updateCooldown);

// a single game-play related (so we have to reset these if the user wants to "replay")
const [isFetchingDataMarshal, setIsFetchingDataMarshal] = useState<boolean>(false);
Expand Down Expand Up @@ -243,6 +247,17 @@ export const GetBitz = () => {
setGameDataFetched(true);
setIsFetchingDataMarshal(false);
setViewDataRes(viewDataPayload);
console.log("viewDataPayload", viewDataPayload.data.gamePlayResult);
console.log(
"cooldown",
computeRemainingCooldown(viewDataPayload.data.gamePlayResult.lastPlayedAndCommitted, viewDataPayload.data.gamePlayResult.configCanPlayEveryMSecs)
);
updateCooldown(
computeRemainingCooldown(
Math.max(viewDataPayload.data.gamePlayResult.lastPlayedAndCommitted, viewDataPayload.data.gamePlayResult.lastPlayedBeforeThisPlay),
viewDataPayload.data.gamePlayResult.configCanPlayEveryMSecs
)
);

if (viewDataPayload.data.gamePlayResult.bitsScoreAfterPlay > -1) {
updateBitzBalance(viewDataPayload.data.gamePlayResult.bitsScoreAfterPlay);
Expand Down Expand Up @@ -326,7 +341,7 @@ export const GetBitz = () => {
}

// user is logged in and we are checking if they have the data nft to proceed with a play
if (address && checkingIfHasGameDataNFT && !hasGameDataNFT) {
if ((address && checkingIfHasGameDataNFT && !hasGameDataNFT) || cooldown === -2) {
return (
<div>
<img className="rounded-[3rem] w-full cursor-pointer" src={ImgLoadingGame} alt={"Checking if you have <BiTz> Data NFT"} />
Expand All @@ -347,19 +362,6 @@ export const GetBitz = () => {
</div>
);
}

// user has data nft, so load the "start game" view
if (!_loadBlankGameCanvas && !_isFetchingDataMarshal) {
return (
<div
onClick={() => {
setLoadBlankGameCanvas(true);
}}>
<img className="rounded-[3rem] w-full cursor-pointer" src={ImgPlayGame} alt={"Start Game"} />
</div>
);
}

const CountDownComplete = () => (
<div
className="cursor-pointer relative inline-flex h-12 overflow-hidden rounded-full p-[1px] "
Expand All @@ -382,12 +384,52 @@ export const GetBitz = () => {
// Render a countdown
return (
<span>
{props.hours}H:{props.minutes}M:{props.seconds}S
{props.hours > 0 ? (props.hours + props.hours === 1 ? " Hour " : " Hours ") : ""}
{props.minutes > 0 ? props.minutes + " Min : " : ""} {props.seconds} Sec
</span>
);
}
};

// user has data nft, so load the "start game" view
if (!_loadBlankGameCanvas && !_isFetchingDataMarshal) {
return (
<div className="relative">
{cooldown > 0 && (
<Countdown
className="mx-auto text-3"
date={cooldown}
renderer={(props: { hours: number; minutes: number; seconds: number; completed: boolean }) => {
if (props.completed) {
return <> </>;
} else {
return (
<div className="absolute z-10 w-full h-full rounded-[3rem] bg-black/90 ">
<div className="flex w-full h-full items-center justify-center">
<div className="text-3xl md:text-5xl flex flex-col items-center justify-center ">
<p className="my-4 text-xl md:text-3xl "> You can play again in: </p>{" "}
{props.hours > 0 ? (props.hours + props.hours === 1 ? " Hour " : " Hours ") : ""}
{props.minutes > 0 ? props.minutes + " Min : " : ""} {props.seconds} Sec
</div>
</div>
</div>
);
}
}}
/>
)}
<img
onClick={() => {
setLoadBlankGameCanvas(true);
}}
className="rounded-[3rem] w-full cursor-pointer"
src={ImgPlayGame}
alt={"Start Game"}
/>
</div>
);
}

// user clicked on the start game view, so load the empty blank game canvas
if (_loadBlankGameCanvas && !_gameDataFetched) {
return (
Expand Down
6 changes: 3 additions & 3 deletions src/pages/AppMarketplace/MultiversxInfographics/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,11 @@ export const MultiversxInfographics = () => {
"authorization": `Bearer ${tokenLogin.nativeAuthToken}`,
},
};
// console.log("arg", arg);
if (!dataNft.dataMarshal || dataNft.dataMarshal === "") {

if (!dataNft.dataMarshal || dataNft.dataMarshal === "") {
dataNft.updateDataNft({ dataMarshal: getApiDataMarshal(chainID) });
}
res = await dataNft.viewDataViaMVXNativeAuth(arg);
res = await dataNft.viewDataViaMVXNativeAuth(arg);

let blobDataType = BlobDataType.TEXT;

Expand Down
1 change: 0 additions & 1 deletion src/pages/PlayStationGamer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export const PlayStationGamer = () => {
const _nfts: DataNft[] = await DataNft.createManyFromApi(
PLAYSTATION_GAMER_PASSPORT_TOKENS.map((v) => ({ nonce: v.nonce, tokenIdentifier: v.tokenIdentifier }))
);
// console.log("ccDataNfts", _nfts);
setCcDataNfts(_nfts);
setIsLoading(false);
} else {
Expand Down
9 changes: 9 additions & 0 deletions src/store/StoreProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { useGetAccount } from "hooks";
import { decodeNativeAuthToken } from "libs/utils";
import { useAccountStore } from "./account";
import { viewDataJSONCore } from "../pages/AppMarketplace/GetBitz";
import { computeRemainingCooldown } from "libs/utils/functions";

export const StoreProvider = ({ children }: PropsWithChildren) => {
const { address } = useGetAccount();
const { tokenLogin } = useGetLoginInfo();

// ACCOUNT STORE
const updateBitzBalance = useAccountStore((state) => state.updateBitzBalance);
const updateCooldown = useAccountStore((state) => state.updateCooldown);

useEffect(() => {
if (!address || !(tokenLogin && tokenLogin.nativeAuthToken)) {
Expand Down Expand Up @@ -46,10 +48,17 @@ export const StoreProvider = ({ children }: PropsWithChildren) => {

if (getBitzGameResult) {
updateBitzBalance(getBitzGameResult.data.gamePlayResult.bitsScoreBeforePlay);
updateCooldown(
computeRemainingCooldown(
getBitzGameResult.data.gamePlayResult.lastPlayedBeforeThisPlay,
getBitzGameResult.data.gamePlayResult.configCanPlayEveryMSecs
)
);
}
} else {
console.log("info: user does NOT OWN the bitz score data nft");
updateBitzBalance(-1);
updateCooldown(-1);
}
})();
}, [address, tokenLogin]);
Expand Down
4 changes: 4 additions & 0 deletions src/store/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ import { create } from "zustand";

type State = {
bitzBalance: number;
cooldown: number;
};

type Action = {
updateBitzBalance: (bitzBalance: State["bitzBalance"]) => void;
updateCooldown: (cooldown: State["cooldown"]) => void;
};

export const useAccountStore = create<State & Action>((set) => ({
bitzBalance: -2,
cooldown: -2,
updateBitzBalance: (value: number) => set(() => ({ bitzBalance: value })),
updateCooldown: (value: number) => set(() => ({ cooldown: value })),
}));