Skip to content

Commit

Permalink
feat: add maintenance & unexpected error screens (#22)
Browse files Browse the repository at this point in the history
* 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
irisdv authored Aug 19, 2024
1 parent 868dc63 commit 8b80aa8
Show file tree
Hide file tree
Showing 12 changed files with 601 additions and 34 deletions.
1 change: 1 addition & 0 deletions abi/ethbutton.json

Large diffs are not rendered by default.

89 changes: 89 additions & 0 deletions app/components/countdownWithHours.tsx
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;
2 changes: 1 addition & 1 deletion app/components/leaderboard/leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ const Leaderboard: FunctionComponent<DataTableProps> = ({
<TableRow>
<TableHead>Ranking</TableHead>
<TableHead>Address</TableHead>
<TableHead>Time clicked</TableHead>
<TableHead>Total clicks</TableHead>
<TableHead>Reward</TableHead>
</TableRow>
</TableHeader>
Expand Down
118 changes: 118 additions & 0 deletions app/components/maintenance/maintenance.tsx
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;
120 changes: 120 additions & 0 deletions app/components/maintenance/unexpectedError.tsx
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;
2 changes: 1 addition & 1 deletion app/components/stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const Stats: FunctionComponent<StatsProps> = ({
return (
<div className={styles.statsSections}>
<div className={styles.statsSection}>
<p>Time clicked</p>
<p>Total clicks</p>
<p>{totalClicks !== 0 ? totalClicks.toLocaleString("en-US") : "--"}</p>
</div>
<div className={styles.statsSection}>
Expand Down
Loading

0 comments on commit 8b80aa8

Please sign in to comment.