-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add maintenance & unexpected error screens (#22)
* feat: add maintenance & unexpected error screens * fix: remove isLaunched & countdown * fix: total clicks * fix: coquille chances * fix: remove unused variable * fix: build error
- Loading branch information
Showing
12 changed files
with
601 additions
and
34 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
"use client"; | ||
|
||
import React, { FunctionComponent, useEffect, useState } from "react"; | ||
import styles from "../styles/components/countdownWithHours.module.css"; | ||
import { formatTime } from "@/utils/stringService"; | ||
import { Skeleton } from "@mui/material"; | ||
|
||
type countdownWithHoursProps = { | ||
timestamp: number; | ||
isLoaded: boolean; | ||
}; | ||
|
||
const countdownWithHours: FunctionComponent<countdownWithHoursProps> = ({ | ||
timestamp, | ||
isLoaded, | ||
}) => { | ||
const [timeRemaining, setTimeRemaining] = useState(0); // in seconds | ||
const formattedTime = formatTime(timeRemaining); | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const [days, hours, minutes, seconds] = formattedTime.split(":"); // Days and hours are not used in this component | ||
|
||
const skeleton = ( | ||
<> | ||
<Skeleton | ||
variant="rectangular" | ||
width={50} | ||
height={50} | ||
className="mx-auto" | ||
/> | ||
</> | ||
); | ||
|
||
useEffect(() => { | ||
// any time the timestamp is updated we update the timeRemaining | ||
const now = new Date().getTime(); // Current time in milliseconds | ||
const timestampExpiration = | ||
timestamp + | ||
parseInt(process.env.NEXT_PUBLIC_GAME_DURATION as string) * 1000; // Expiration time based on the received timestamp | ||
const secondsUntilExpiration = Math.floor( | ||
(timestampExpiration - now) / 1000 | ||
); | ||
|
||
setTimeRemaining(secondsUntilExpiration > 0 ? secondsUntilExpiration : 0); | ||
}, [timestamp]); | ||
|
||
useEffect(() => { | ||
const timer = setInterval(() => { | ||
setTimeRemaining((prevTime) => (prevTime > 0 ? prevTime - 1 : 0)); | ||
}, 1000); | ||
|
||
// Cleanup interval on component unmount | ||
return () => clearInterval(timer); | ||
}, []); | ||
|
||
return ( | ||
<> | ||
<div className={styles.countdownWrapper}> | ||
<div className={styles.countdown}>{isLoaded ? hours[0] : skeleton}</div> | ||
</div> | ||
<div className={styles.countdownWrapper}> | ||
<div className={styles.countdown}>{isLoaded ? hours[1] : skeleton}</div> | ||
</div> | ||
<div className={styles.separator}>:</div> | ||
<div className={styles.countdownWrapper}> | ||
<div className={styles.countdown}> | ||
{isLoaded ? minutes[0] : skeleton} | ||
</div> | ||
</div> | ||
<div className={styles.countdownWrapper}> | ||
<div className={styles.countdown}> | ||
{isLoaded ? minutes[1] : skeleton} | ||
</div> | ||
</div> | ||
<div className={styles.separator}>:</div> | ||
<div className={styles.countdownWrapper}> | ||
<div className={styles.countdown}> | ||
{isLoaded ? seconds[0] : skeleton} | ||
</div> | ||
</div> | ||
<div className={styles.countdownWrapper}> | ||
<div className={styles.countdown}> | ||
{isLoaded ? seconds[1] : skeleton} | ||
</div> | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
export default countdownWithHours; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
"use client"; | ||
|
||
import React, { FunctionComponent, useEffect, useState } from "react"; | ||
import styles from "../../styles/home.module.css"; | ||
import statsStyle from "../../styles/components/stats.module.css"; | ||
import VideoBackground from "../videoBackground"; | ||
import { Skeleton } from "@mui/material"; | ||
import Button from "../button"; | ||
import { minifyAddress } from "@/utils/stringService"; | ||
import { ethers } from "ethers"; | ||
import CountdownWithHours from "../countdownWithHours"; | ||
import { useContractRead } from "@starknet-react/core"; | ||
import ethbutton_abi from "../../../abi/ethbutton.json"; | ||
import { Abi } from "starknet"; | ||
import { decimalToHex } from "@/utils/feltService"; | ||
|
||
type MaintenanceProps = { | ||
isLoaded: boolean; | ||
isMobile: boolean; | ||
priceValue: string | undefined; | ||
}; | ||
|
||
const Maintenance: FunctionComponent<MaintenanceProps> = ({ | ||
isLoaded, | ||
isMobile, | ||
priceValue, | ||
}) => { | ||
const [currentWinner, setCurrentWinner] = useState<string | undefined>(); | ||
const { data: winnerData, error } = useContractRead({ | ||
address: process.env.NEXT_PUBLIC_ETH_BUTTON_CONTRACT, | ||
abi: ethbutton_abi.abi as Abi, | ||
functionName: "get_last_clicker", | ||
args: [], | ||
}); | ||
|
||
useEffect(() => { | ||
if (!winnerData || error) return; | ||
setCurrentWinner(decimalToHex(winnerData as string)); | ||
}, [winnerData, error]); | ||
|
||
const getExplorerLink = (address: string) => { | ||
ethers.isAddress(address) | ||
? `https://etherscan.io/address/${address}` | ||
: `https://starkscan.co/contract/${address}`; | ||
}; | ||
|
||
return ( | ||
<main className={styles.main}> | ||
<VideoBackground /> | ||
<div className={styles.leftContainer}> | ||
<> | ||
{!isLoaded ? ( | ||
<Skeleton | ||
variant="rectangular" | ||
width={120} | ||
height={25} | ||
className="mx-auto" | ||
/> | ||
) : priceValue ? ( | ||
isMobile ? ( | ||
<div className={styles.ethPrice}> | ||
<img src="/visuals/eth.svg" width={14} /> | ||
{priceValue} | ||
</div> | ||
) : ( | ||
<Button | ||
icon={<img src="/visuals/eth.svg" width={14} />} | ||
enableHover={false} | ||
> | ||
{priceValue} | ||
</Button> | ||
) | ||
) : null} | ||
</> | ||
</div> | ||
<div className={styles.centralSection}> | ||
<div className={styles.backgroundWrapper}> | ||
<h1 className={styles.maintenanceTitle}> | ||
MAINTENANCE <span className={styles.pinkTitle}>IN</span> | ||
<br /> | ||
<span className={styles.blueTitle}>PROGRESS</span> | ||
</h1> | ||
<div className={styles.countdownContainer}> | ||
<CountdownWithHours | ||
timestamp={ | ||
parseInt( | ||
process.env.NEXT_PUBLIC_MAINTENANCE_END_MS as string | ||
) ?? 0 | ||
} | ||
isLoaded={isLoaded} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
<div className={statsStyle.statsSections}> | ||
{currentWinner ? ( | ||
<> | ||
<div className={styles.statsSection}> | ||
<p>Last click</p> | ||
</div> | ||
<div className={statsStyle.winnerWrapper}> | ||
<div className={statsStyle.winnerSection}> | ||
<div | ||
className="cursor-pointer flex flex-row items-center gap-3" | ||
onClick={() => getExplorerLink(currentWinner as string)} | ||
> | ||
<p>{minifyAddress(currentWinner, true)}</p> | ||
</div> | ||
</div> | ||
</div> | ||
</> | ||
) : null} | ||
</div> | ||
</main> | ||
); | ||
}; | ||
|
||
export default Maintenance; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
"use client"; | ||
|
||
import React, { FunctionComponent, useEffect, useState } from "react"; | ||
import styles from "../../styles/home.module.css"; | ||
import statsStyle from "../../styles/components/stats.module.css"; | ||
import VideoBackground from "../videoBackground"; | ||
import { Skeleton } from "@mui/material"; | ||
import Button from "../button"; | ||
import { minifyAddress } from "@/utils/stringService"; | ||
import { ethers } from "ethers"; | ||
import { useContractRead } from "@starknet-react/core"; | ||
import { Abi } from "starknet"; | ||
import ethbutton_abi from "../../../abi/ethbutton.json"; | ||
import { decimalToHex } from "@/utils/feltService"; | ||
|
||
type unexpectedErrorProps = { | ||
isLoaded: boolean; | ||
isMobile: boolean; | ||
priceValue: string | undefined; | ||
}; | ||
|
||
const UnexpectedError: FunctionComponent<unexpectedErrorProps> = ({ | ||
isLoaded, | ||
isMobile, | ||
priceValue, | ||
}) => { | ||
const [currentWinner, setCurrentWinner] = useState<string | undefined>(); | ||
const { data: winnerData, error } = useContractRead({ | ||
address: process.env.NEXT_PUBLIC_ETH_BUTTON_CONTRACT, | ||
abi: ethbutton_abi.abi as Abi, | ||
functionName: "get_last_clicker", | ||
args: [], | ||
}); | ||
|
||
useEffect(() => { | ||
if (!winnerData || error) return; | ||
setCurrentWinner(decimalToHex(winnerData as string)); | ||
}, [winnerData, error]); | ||
|
||
const getExplorerLink = (address: string) => { | ||
ethers.isAddress(address) | ||
? `https://etherscan.io/address/${address}` | ||
: `https://starkscan.co/contract/${address}`; | ||
}; | ||
|
||
return ( | ||
<main className={styles.main}> | ||
<VideoBackground /> | ||
<div className={styles.leftContainer}> | ||
<> | ||
{!isLoaded ? ( | ||
<Skeleton | ||
variant="rectangular" | ||
width={120} | ||
height={25} | ||
className="mx-auto" | ||
/> | ||
) : priceValue ? ( | ||
isMobile ? ( | ||
<div className={styles.ethPrice}> | ||
<img src="/visuals/eth.svg" width={14} /> | ||
{priceValue} | ||
</div> | ||
) : ( | ||
<Button | ||
icon={<img src="/visuals/eth.svg" width={14} />} | ||
enableHover={false} | ||
> | ||
{priceValue} | ||
</Button> | ||
) | ||
) : null} | ||
</> | ||
</div> | ||
<div className={styles.centralSection}> | ||
<div className={styles.backgroundWrapper}> | ||
<h1 className={styles.maintenanceTitle}> | ||
UNEXPECTED <span className={styles.pinkTitle}>ERROR</span> | ||
<br /> | ||
<span className={styles.blueTitle}>OCCURRED</span> | ||
</h1> | ||
<div className={styles.unexpectedErrorContainer}> | ||
<div className={styles.unexpectedError}> | ||
An unexpected error has occurred. Your clicks have been recorded. | ||
Our team is working on a fix. | ||
</div> | ||
<div className={statsStyle.msgWrapper}> | ||
<div className={statsStyle.msgWrapperSection}> | ||
<div>More updates on our Twitter</div> | ||
<Button | ||
onClick={() => window.open("https://x.com/Starknet_id")} | ||
icon={<img src="/visuals/twitterIcon.svg" />} | ||
> | ||
Go on Twitter | ||
</Button> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<div className={statsStyle.statsSections}> | ||
<div className={styles.statsSection}> | ||
<p>Last click</p> | ||
</div> | ||
<div className={statsStyle.winnerWrapper}> | ||
<div className={statsStyle.winnerSection}> | ||
<div | ||
className="cursor-pointer flex flex-row items-center gap-3" | ||
onClick={() => getExplorerLink(currentWinner as string)} | ||
> | ||
<p>{minifyAddress(currentWinner, true)}</p> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</main> | ||
); | ||
}; | ||
|
||
export default UnexpectedError; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.