diff --git a/client/src/app/components/nova/landing/LandingEpochSection.scss b/client/src/app/components/nova/landing/LandingEpochSection.scss index 50d57c36c..8f6bd5ebc 100644 --- a/client/src/app/components/nova/landing/LandingEpochSection.scss +++ b/client/src/app/components/nova/landing/LandingEpochSection.scss @@ -7,18 +7,18 @@ background-color: $gray-1; border-radius: 8px; + .epoch-section__header { + width: fit-content; + margin: 0 auto; + padding: 20px; + } + .epoch-progress__wrapper { display: flow-root; background-color: $gray-3; - margin: 20px; + margin: 0 20px 20px; border-radius: 8px; - .epoch-progress__header { - width: fit-content; - margin: 0 auto; - padding: 20px; - } - .epoch-progress__stats-wrapper { display: flex; padding: 12px; @@ -33,37 +33,6 @@ text-align: center; } } - - .progress-bar__wrapper { - $bar-height: 32px; - - .progress-bar { - position: relative; - background-color: $gray-5; - margin: 20px 12px; - height: $bar-height; - border-radius: 4px; - text-align: center; - overflow: hidden; - - .progress-bar__label { - position: absolute; - left: 0; - right: 0; - line-height: $bar-height; - margin: 0 auto; - font-weight: 600; - } - - .progress-bar__fill { - position: absolute; - width: 100%; - height: 100%; - background-color: #36c636; - transform: translateX(-100%); - } - } - } } .epoch-section__controls { diff --git a/client/src/app/components/nova/landing/LandingEpochSection.tsx b/client/src/app/components/nova/landing/LandingEpochSection.tsx index 249d2f422..7cc45e0e9 100644 --- a/client/src/app/components/nova/landing/LandingEpochSection.tsx +++ b/client/src/app/components/nova/landing/LandingEpochSection.tsx @@ -1,6 +1,7 @@ import moment from "moment"; import React from "react"; import { useCurrentEpochProgress } from "~/helpers/nova/hooks/useCurrentEpochProgress"; +import ProgressBar from "./ProgressBar"; import "./LandingEpochSection.scss"; const LandingEpochSection: React.FC = () => { @@ -29,8 +30,8 @@ const LandingEpochSection: React.FC = () => { return (
+

Epoch {epochIndex} Progress

-

Epoch {epochIndex} Progress

Registration end: {registrationTimeRemaining}
Time remaining: {epochTimeRemaining}
@@ -40,7 +41,7 @@ const LandingEpochSection: React.FC = () => { {epochTo}
- +
previous
@@ -51,13 +52,4 @@ const LandingEpochSection: React.FC = () => { ); }; -const ProgressBar: React.FC<{ progress: number }> = ({ progress }) => ( -
-
-
-
{progress}%
-
-
-); - export default LandingEpochSection; diff --git a/client/src/app/components/nova/landing/LandingSlotSection.scss b/client/src/app/components/nova/landing/LandingSlotSection.scss new file mode 100644 index 000000000..c680a3bc6 --- /dev/null +++ b/client/src/app/components/nova/landing/LandingSlotSection.scss @@ -0,0 +1,37 @@ +@import "../../../../scss/variables"; +@import "../../../../scss/fonts"; + +.slots-section { + font-family: $metropolis; + margin-top: 40px; + background-color: $gray-1; + border-radius: 8px; + + .slots-section__header { + width: fit-content; + margin: 0 auto; + padding: 20px; + } + + .slots-feed__wrapper { + margin: 0 20px 20px; + + .slots-feed__item { + display: flex; + margin: 0px 12px; + align-items: center; + line-height: 32px; + justify-content: center; + background-color: $gray-5; + border-radius: 4px; + + &.transparent { + background-color: transparent; + } + + &:not(:last-child) { + margin-bottom: 20px; + } + } + } +} diff --git a/client/src/app/components/nova/landing/LandingSlotSection.tsx b/client/src/app/components/nova/landing/LandingSlotSection.tsx new file mode 100644 index 000000000..678e825a2 --- /dev/null +++ b/client/src/app/components/nova/landing/LandingSlotSection.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import useSlotsFeed from "~/helpers/nova/hooks/useSlotsFeed"; +import "./LandingSlotSection.scss"; +import ProgressBar from "./ProgressBar"; + +const LandingSlotSection: React.FC = () => { + const { currentSlot, currentSlotProgressPercent, latestSlots } = useSlotsFeed(); + + if (currentSlot === null || currentSlotProgressPercent === null) { + return null; + } + + return ( +
+

Latest Slots

+
+ +
{currentSlot}
+
+ {latestSlots?.map((slot) => ( +
+ {slot} +
+ ))} +
+
+ ); +}; + +export default LandingSlotSection; diff --git a/client/src/app/components/nova/landing/ProgressBar.scss b/client/src/app/components/nova/landing/ProgressBar.scss new file mode 100644 index 000000000..97200025e --- /dev/null +++ b/client/src/app/components/nova/landing/ProgressBar.scss @@ -0,0 +1,40 @@ +@import "../../../../scss/variables"; + +.progress-bar__wrapper { + $bar-height: 32px; + + .progress-bar { + position: relative; + background-color: $gray-5; + margin: 20px 12px; + height: $bar-height; + border-radius: 4px; + text-align: center; + overflow: hidden; + + .progress-bar__label { + position: absolute; + left: 0; + right: 0; + line-height: $bar-height; + margin: 0 auto; + font-weight: 600; + } + + .progress-bar__children { + position: absolute; + left: 0; + right: 0; + line-height: $bar-height; + margin: 0 auto; + } + + .progress-bar__fill { + position: absolute; + width: 100%; + height: 100%; + background-color: #36c636; + transform: translateX(-100%); + } + } +} diff --git a/client/src/app/components/nova/landing/ProgressBar.tsx b/client/src/app/components/nova/landing/ProgressBar.tsx new file mode 100644 index 000000000..b67f95955 --- /dev/null +++ b/client/src/app/components/nova/landing/ProgressBar.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import "./ProgressBar.scss"; + +interface ProgressBarProps { + progress: number; + showLabel: boolean; + children?: React.ReactNode | React.ReactElement; +} + +const ProgressBar: React.FC = ({ progress, showLabel, children }) => ( +
+
+
+ {showLabel &&
{progress}%
} + {children &&
{children}
} +
+
+); + +export default ProgressBar; diff --git a/client/src/app/routes/nova/landing/Landing.tsx b/client/src/app/routes/nova/landing/Landing.tsx index 68cc9158a..573a78a0b 100644 --- a/client/src/app/routes/nova/landing/Landing.tsx +++ b/client/src/app/routes/nova/landing/Landing.tsx @@ -1,6 +1,7 @@ import React from "react"; import { RouteComponentProps } from "react-router-dom"; import LandingEpochSection from "~/app/components/nova/landing/LandingEpochSection"; +import LandingSlotSection from "~/app/components/nova/landing/LandingSlotSection"; import { useNetworkConfig } from "~helpers/hooks/useNetworkConfig"; import { LandingRouteProps } from "../../LandingRouteProps"; import "./Landing.scss"; @@ -29,6 +30,7 @@ const Landing: React.FC> = ({
+
diff --git a/client/src/helpers/nova/hooks/useSlotsFeed.ts b/client/src/helpers/nova/hooks/useSlotsFeed.ts new file mode 100644 index 000000000..626250c0b --- /dev/null +++ b/client/src/helpers/nova/hooks/useSlotsFeed.ts @@ -0,0 +1,53 @@ +import moment from "moment"; +import { useEffect, useState } from "react"; +import { useNovaTimeConvert } from "./useNovaTimeConvert"; + +const DEFAULT_SLOT_LIMIT = 10; + +export default function useSlotsFeed(slotsLimit: number = DEFAULT_SLOT_LIMIT): { + currentSlot: number | null; + currentSlotProgressPercent: number | null; + latestSlots: number[] | null; +} { + const { unixTimestampToSlotIndex, slotIndexToUnixTimeRange } = useNovaTimeConvert(); + const [currentSlot, setCurrentSlot] = useState(null); + const [latestSlots, setLatestSlots] = useState(null); + const [currentSlotProgressPercent, setCurrentSlotProgressPercent] = useState(null); + const [slotTimeUpdateHandle, setSlotTimeUpdateHandle] = useState(null); + + const checkCurrentSlot = () => { + if (unixTimestampToSlotIndex && slotIndexToUnixTimeRange) { + const now = moment().unix(); + const currentSlotIndex = unixTimestampToSlotIndex(now); + const slotTimeRange = slotIndexToUnixTimeRange(currentSlotIndex); + + const slotProgressPercent = Math.trunc(((now - slotTimeRange.from) / (slotTimeRange.to - 1 - slotTimeRange.from)) * 100); + setCurrentSlot(currentSlotIndex); + setCurrentSlotProgressPercent(slotProgressPercent); + setLatestSlots(Array.from({ length: slotsLimit - 1 }, (_, i) => currentSlotIndex - 1 - i)); + } + }; + + useEffect(() => { + if (slotTimeUpdateHandle === null) { + checkCurrentSlot(); + const intervalTimerHandle = setInterval(() => { + checkCurrentSlot(); + }, 950); + + setSlotTimeUpdateHandle(intervalTimerHandle); + } + + return () => { + if (slotTimeUpdateHandle) { + clearInterval(slotTimeUpdateHandle); + } + setSlotTimeUpdateHandle(null); + setCurrentSlot(null); + setCurrentSlotProgressPercent(null); + setLatestSlots(null); + }; + }, []); + + return { currentSlot, currentSlotProgressPercent, latestSlots }; +}