diff --git a/src/renderer/pages/home/sidebar/ranked_status.tsx b/src/renderer/pages/home/sidebar/ranked_status.tsx new file mode 100644 index 000000000..902eff3fe --- /dev/null +++ b/src/renderer/pages/home/sidebar/ranked_status.tsx @@ -0,0 +1,182 @@ +import { Button, Card, Typography } from "@mui/material"; +import * as stylex from "@stylexjs/stylex"; +import type { Duration } from "date-fns"; +import { formatDuration, intervalToDuration } from "date-fns"; +import { chain } from "lodash"; +import React from "react"; + +import { ExternalLink } from "@/components/external_link"; +import { useAccount } from "@/lib/hooks/use_account"; +import { shortEnLocale } from "@/lib/time"; +import { ReactComponent as RankedDayActiveIcon } from "@/styles/images/ranked_day_active.svg"; +import { ReactComponent as RankedDayInactiveIcon } from "@/styles/images/ranked_day_inactive.svg"; +import { colors } from "@/styles/tokens.stylex"; + +const FREE_ACCESS_START_AT = new Date(Date.UTC(2024, 3, 15, 14, 0, 0)); // Note: Month is 0-indexed, so 3 is April +const FREE_ACCESS_OFFSET_FROM = new Date(Date.UTC(2024, 3, 15, 8, 0, 0)); // Note: Month is 0-indexed, so 3 is April + +const styles = stylex.create({ + container: { + position: "relative", + flex: "1", + overflow: "hidden", + backgroundColor: colors.purpleDarker, + }, + card: { + margin: "6px", + padding: "10px", + }, + centerStack: { + display: "grid", + justifyContent: "center", + justifyItems: "center", + alignItems: "center", + }, + stroke: { + textShadow: + "-2px -2px 0 #231232, 0 -2px 0 #231232, 2px -2px 0 #231232, 2px 0 0 #231232, 2px 2px 0 #231232, 0 2px 0 #231232, -2px 2px 0 #231232, -2px 0 0 #231232", + }, + separator: { + width: "50%", + height: "2px", + backgroundColor: "#D9D9D919", + margin: "12px 25%", + }, + buttonContainer: { + margin: "16px 0 4px 0", + }, +}); + +const getFullAccessTimes = (now: Date): { isActive: boolean; nextStartTime: Date; nextEndTime: Date } => { + const msPerDay = 24 * 60 * 60 * 1000; + const startTime = FREE_ACCESS_START_AT; + const offsetTime = FREE_ACCESS_OFFSET_FROM; + if (now < startTime) { + return { isActive: false, nextStartTime: startTime, nextEndTime: new Date(offsetTime.getTime() + msPerDay) }; + } + + const daysSinceStart = Math.floor((now.getTime() - offsetTime.getTime()) / msPerDay); + let daysUntilNextRankedDay = 4 - (daysSinceStart % 4); + if (daysUntilNextRankedDay === 4) { + daysUntilNextRankedDay = 0; + } + const nextRankedDayTime = new Date(offsetTime.getTime() + (daysSinceStart + daysUntilNextRankedDay) * msPerDay); + + return { + isActive: daysUntilNextRankedDay === 0, + nextStartTime: nextRankedDayTime, + nextEndTime: new Date(nextRankedDayTime.getTime() + msPerDay), + }; +}; + +const convertCodeToSlug = (code: string | undefined) => { + return chain(code).toLower().replace("#", "-").value(); +}; + +const InternalRankedStatus = ({ + isFullAccess, + countdown, + nextTime, +}: { + isFullAccess: boolean; + countdown: string; + nextTime: Date; +}) => { + const userData = useAccount((store) => store.userData); + const connectCode = userData?.playKey?.connectCode; + + return ( +
+ +
+ + RANKED DAY + + {isFullAccess ? : } + + {isFullAccess ? "ACTIVE" : "STARTING SOON"} + +
+
+
+ + {isFullAccess ? "ENDING IN" : "STARTING IN"} + + + {countdown} + + + {nextTime.toLocaleString()} + +
+ + {isFullAccess + ? "Ranked play is currently available for everyone. Try it now! Available once every 4 days." + : "Once every 4 days, ranked play is available to all users including non-subs. Check back soon!"} + +
+ +
+ +
+ ); +}; + +export const RankedStatus = React.memo(function RankedStatus() { + const [isFullAccess, setFullAccess] = React.useState(false); + const [countdown, setCountdown] = React.useState(""); + const [nextTime, setNextTime] = React.useState(new Date()); + + React.useEffect(() => { + const checkTime = () => { + const now = new Date(); + // // TODO: Comment this test code. Used to look at different state. + // now = new Date(now.getTime() + 24 * 60 * 60 * 1000 * 3); + const fullAccess = getFullAccessTimes(now); + + const nextTime = fullAccess.isActive ? fullAccess.nextEndTime : fullAccess.nextStartTime; + const duration = intervalToDuration({ start: now, end: nextTime }); + + setNextTime(nextTime); + setFullAccess(fullAccess.isActive); + + const format: (keyof Duration)[] = + (duration.hours ?? 0) < 1 && (duration.days ?? 0) < 1 ? ["minutes", "seconds"] : ["days", "hours", "minutes"]; + setCountdown(formatDuration(duration, { format, locale: shortEnLocale })); + }; + checkTime(); + + const interval = window.setInterval(checkTime, 1000); + return () => window.clearInterval(interval); + }, []); + + return ; +}); diff --git a/src/renderer/pages/home/sidebar/sidebar.tsx b/src/renderer/pages/home/sidebar/sidebar.tsx index 5eb231316..6279de2a5 100644 --- a/src/renderer/pages/home/sidebar/sidebar.tsx +++ b/src/renderer/pages/home/sidebar/sidebar.tsx @@ -1,6 +1,6 @@ import styled from "@emotion/styled"; -import { SlippiStore } from "./slippi_store"; +import { RankedStatus } from "./ranked_status"; import { TournamentLinks } from "./tournament_links"; const Outer = styled.div` @@ -13,7 +13,7 @@ const Outer = styled.div` export const Sidebar = () => { return ( - + ); diff --git a/src/renderer/styles/images/ranked_day_active.svg b/src/renderer/styles/images/ranked_day_active.svg new file mode 100644 index 000000000..6c771685d --- /dev/null +++ b/src/renderer/styles/images/ranked_day_active.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/renderer/styles/images/ranked_day_inactive.svg b/src/renderer/styles/images/ranked_day_inactive.svg new file mode 100644 index 000000000..3fb0f18c1 --- /dev/null +++ b/src/renderer/styles/images/ranked_day_inactive.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + +