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 }) => (
-
-);
-
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 };
+}