Skip to content

Commit

Permalink
Feat: Add Epoch section on Landing (#1135)
Browse files Browse the repository at this point in the history
* feat: Add more util functions in novaTimeUtils. Fix novaTimeUtils to be aliged with the go implementation (https://github.com/iotaledger/iota.go/blob/develop/timeprovider.go)

* feat: Add nova Landing page. Add mock "epoch section" to landing (WiP)

* feat: Add utils to get the registration slotIndex from an epoch index

* feat: Wire up epoch stats in LandingEpochSection

---------

Co-authored-by: Begoña Álvarez de la Cruz <[email protected]>
  • Loading branch information
msarcev and begonaalvarezd authored Feb 16, 2024
1 parent c4667fb commit c168157
Show file tree
Hide file tree
Showing 9 changed files with 490 additions and 10 deletions.
81 changes: 81 additions & 0 deletions client/src/app/components/nova/landing/LandingEpochSection.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
@import "../../../../scss/variables";
@import "../../../../scss/fonts";

.epoch-section {
font-family: $metropolis;
margin-top: 40px;
background-color: $gray-1;
border-radius: 8px;

.epoch-progress__wrapper {
display: flow-root;
background-color: $gray-3;
margin: 20px;
border-radius: 8px;

.epoch-progress__header {
width: fit-content;
margin: 0 auto;
padding: 20px;
}

.epoch-progress__stats-wrapper {
display: flex;
padding: 12px;
flex-direction: row;
justify-content: space-evenly;

.epoch-progress__stat {
width: 160px;
background-color: $gray-4;
padding: 10px 20px;
border-radius: 8px;
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 {
display: flex;
margin: 20px;
flex-direction: row;
justify-content: space-between;

.epoch-section__button {
background-color: $gray-4;
padding: 10px 20px;
border-radius: 8px;
}
}
}
63 changes: 63 additions & 0 deletions client/src/app/components/nova/landing/LandingEpochSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import moment from "moment";
import React from "react";
import { useCurrentEpochProgress } from "~/helpers/nova/hooks/useCurrentEpochProgress";
import "./LandingEpochSection.scss";

const LandingEpochSection: React.FC = () => {
const { epochIndex, epochUnixTimeRange, epochProgressPercent, registrationTime } = useCurrentEpochProgress();

if (epochIndex === null || epochProgressPercent === null) {
return null;
}

let registrationTimeRemaining = "???";
let epochTimeRemaining = "???";
let epochFrom = "???";
let epochTo = "???";

if (epochUnixTimeRange && registrationTime) {
const epochStartTime = moment.unix(epochUnixTimeRange.from);
const epochEndTime = moment.unix(epochUnixTimeRange.to - 1);
epochFrom = epochStartTime.format("DD MMM HH:mm:ss");
epochTo = epochEndTime.format("DD MMM HH:mm:ss");

const diffToEpochEnd = epochEndTime.diff(moment());
epochTimeRemaining = moment(diffToEpochEnd).format("H:mm:ss");

registrationTimeRemaining = moment.unix(registrationTime).fromNow();
}

return (
<div className="epoch-section">
<div className="epoch-progress__wrapper">
<h2 className="epoch-progress__header">Epoch {epochIndex} Progress</h2>
<div className="epoch-progress__stats-wrapper">
<div className="epoch-progress__stat">Registration end: {registrationTimeRemaining}</div>
<div className="epoch-progress__stat">Time remaining: {epochTimeRemaining}</div>
<div className="epoch-progress__stat">
{epochFrom}
<br />
{epochTo}
</div>
</div>
<ProgressBar progress={epochProgressPercent} />
</div>
<div className="epoch-section__controls">
<div className="epoch-section__button">previous</div>
<div className="epoch-section__button">view more</div>
<div className="epoch-section__button">next</div>
</div>
</div>
);
};

const ProgressBar: React.FC<{ progress: number }> = ({ progress }) => (
<div className="progress-bar__wrapper">
<div className="progress-bar">
<div className="progress-bar__fill" style={{ transform: `translateX(-${100 - progress}%)` }}></div>
<div className="progress-bar__label">{progress}%</div>
</div>
</div>
);

export default LandingEpochSection;
2 changes: 2 additions & 0 deletions client/src/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import NovaAddressPage from "./routes/nova/AddressPage";
import StardustBlock from "./routes/stardust/Block";
import StardustFoundry from "./routes/stardust/Foundry";
import { Landing as StardustLanding } from "./routes/stardust/landing/Landing";
import NovaLanding from "./routes/nova/landing/Landing";
import NftRedirectRoute from "./routes/stardust/NftRedirectRoute";
import StardustOutputList from "./routes/stardust/OutputList";
import StardustOutputPage from "./routes/stardust/OutputPage";
Expand Down Expand Up @@ -171,6 +172,7 @@ const buildAppRoutes = (protocolVersion: string, withNetworkContext: (wrappedCom
];

const novaRoutes = [
<Route exact path="/:network" key={keys.next().value} component={NovaLanding} />,
<Route path="/:network/addr/:address" key={keys.next().value} component={NovaAddressPage} />,
<Route path="/:network/visualizer/" key={keys.next().value} component={NovaVisualizer} />,
<Route path="/:network/block/:blockId" key={keys.next().value} component={NovaBlockPage} />,
Expand Down
100 changes: 100 additions & 0 deletions client/src/app/routes/nova/landing/Landing.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
@import "../../../../scss/fonts";
@import "../../../../scss/mixins";
@import "../../../../scss/media-queries";
@import "../../../../scss/variables";
@import "../../../../scss/themes";

.landing-nova {
display: flex;
flex-direction: column;

.header-wrapper {
position: relative;
z-index: 0;
overflow: hidden;
background: var(--header-bg);

.inner {
position: relative;
display: flex;
justify-content: center;
flex: 1;

.header {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
min-height: 400px;

@include desktop-down {
justify-content: start;
}

@include tablet-down {
justify-content: center;
}

.header--title {
text-align: center;

@include desktop-down {
margin: 0;
margin-bottom: 28px;
margin-left: 160px;
}

@include tablet-down {
margin-bottom: 172px;
margin-left: 0px;
}

h1 {
@include font-size(48px, 58px);

font-family: $metropolis-semi-bold;
letter-spacing: 0.02em;

@include phone-down {
@include font-size(32px, 36px);
}
}

h2 {
@include font-size(16px, 24px);

color: $mint-green-6;
font-family: $metropolis-bold;
letter-spacing: 0.15em;
text-transform: uppercase;
}

.network-name {
text-align: center;
}
}

.switcher {
margin: 0 20px 20px 20px;
}

@include phone-down {
width: 100%;
}
}
}
}

.wrapper {
display: flex;
justify-content: center;
padding: 0 $inner-padding 44px;

.inner {
display: flex;
flex: 1;
flex-direction: column;
max-width: 960px;
}
}
}
38 changes: 38 additions & 0 deletions client/src/app/routes/nova/landing/Landing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from "react";
import { RouteComponentProps } from "react-router-dom";
import LandingEpochSection from "~/app/components/nova/landing/LandingEpochSection";
import { useNetworkConfig } from "~helpers/hooks/useNetworkConfig";
import { LandingRouteProps } from "../../LandingRouteProps";
import "./Landing.scss";

const Landing: React.FC<RouteComponentProps<LandingRouteProps>> = ({
match: {
params: { network },
},
}) => {
const [networkConfig] = useNetworkConfig(network);

return (
<div className="landing-nova">
<div className="header-wrapper">
<div className="inner">
<div className="header">
<div className="header--title">
<h2>{networkConfig.isEnabled ? "Explore network" : ""}</h2>
<div className="network-name">
<h1>{networkConfig.label}</h1>
</div>
</div>
</div>
</div>
</div>
<div className="wrapper">
<div className="inner">
<LandingEpochSection />
</div>
</div>
</div>
);
};

export default Landing;
63 changes: 63 additions & 0 deletions client/src/helpers/nova/hooks/useCurrentEpochProgress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import moment from "moment";
import { useEffect, useState } from "react";
import { useNovaTimeConvert } from "./useNovaTimeConvert";

export function useCurrentEpochProgress(): {
epochIndex: number | null;
epochUnixTimeRange: { from: number; to: number } | null;
epochProgressPercent: number | null;
registrationTime: number | null;
} {
const { slotIndexToUnixTimeRange, unixTimestampToEpochIndex, epochIndexToUnixTimeRange, getRegistrationSlotFromEpochIndex } =
useNovaTimeConvert();
const [intervalTimerHandle, setIntervalTimerHandle] = useState<NodeJS.Timeout | null>(null);
const [epochIndex, setEpochIndex] = useState<number | null>(null);
const [epochProgressPercent, setEpochProgressPercent] = useState<number | null>(null);
const [registrationTime, setRegistrationTime] = useState<number | null>(null);
const [epochUnixTimeRange, setEpochUnixTimeRange] = useState<{ from: number; to: number } | null>(null);

useEffect(() => {
if (intervalTimerHandle === null) {
checkCurrentEpochIndex();

const intervalTimerHandle = setInterval(() => {
checkCurrentEpochIndex();
}, 1000);

setIntervalTimerHandle(intervalTimerHandle);
}

return () => {
if (intervalTimerHandle) {
clearInterval(intervalTimerHandle);
}
setIntervalTimerHandle(null);
setEpochIndex(null);
};
}, []);

const checkCurrentEpochIndex = () => {
if (unixTimestampToEpochIndex && epochIndexToUnixTimeRange) {
const now = moment().unix();
const currentEpochIndex = unixTimestampToEpochIndex(now);

const epochTimeRange = epochIndexToUnixTimeRange(currentEpochIndex);

const epochProgressPercent = Math.trunc(((now - epochTimeRange.from) / (epochTimeRange.to - 1 - epochTimeRange.from)) * 100);

setEpochIndex(currentEpochIndex);
setEpochUnixTimeRange(epochTimeRange);
setEpochProgressPercent(epochProgressPercent);
}
};

useEffect(() => {
if (getRegistrationSlotFromEpochIndex && slotIndexToUnixTimeRange && epochIndex !== null) {
const slotIndex = getRegistrationSlotFromEpochIndex(epochIndex);
const slotTimeRange = slotIndexToUnixTimeRange(slotIndex);
setRegistrationTime(slotTimeRange.to - 1);
}
}, [epochIndex]);

return { epochIndex, epochUnixTimeRange, epochProgressPercent, registrationTime };
}
9 changes: 9 additions & 0 deletions client/src/helpers/nova/hooks/useNovaTimeConvert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ import {
slotIndexToUnixTimeRangeConverter,
unixTimestampToEpochIndexConverter,
unixTimestampToSlotIndexConverter,
epochIndexToSlotIndexRangeConverter,
epochIndexToUnixTimeRangeConverter,
getRegistrationSlotFromEpochIndex,
} from "../novaTimeUtils";

export function useNovaTimeConvert(): {
unixTimestampToSlotIndex: ((unixTimestampSeconds: number) => number) | null;
slotIndexToUnixTimeRange: ((slotIndex: number) => { from: number; to: number }) | null;
slotIndexToEpochIndex: ((targetSlotIndex: number) => number) | null;
unixTimestampToEpochIndex: ((unixTimestampSeconds: number) => number) | null;
epochIndexToSlotIndexRange: ((targetEpochIndex: number) => { from: number; to: number }) | null;
epochIndexToUnixTimeRange: ((targetEpochIndex: number) => { from: number; to: number }) | null;
getRegistrationSlotFromEpochIndex: ((targetEpochIndex: number) => number) | null;
} {
const { protocolInfo } = useNetworkInfoNova((s) => s.networkInfo);

Expand All @@ -19,5 +25,8 @@ export function useNovaTimeConvert(): {
slotIndexToUnixTimeRange: protocolInfo ? slotIndexToUnixTimeRangeConverter(protocolInfo) : null,
slotIndexToEpochIndex: protocolInfo ? slotIndexToEpochIndexConverter(protocolInfo) : null,
unixTimestampToEpochIndex: protocolInfo ? unixTimestampToEpochIndexConverter(protocolInfo) : null,
epochIndexToSlotIndexRange: protocolInfo ? epochIndexToSlotIndexRangeConverter(protocolInfo) : null,
epochIndexToUnixTimeRange: protocolInfo ? epochIndexToUnixTimeRangeConverter(protocolInfo) : null,
getRegistrationSlotFromEpochIndex: protocolInfo ? getRegistrationSlotFromEpochIndex(protocolInfo) : null,
};
}
Loading

0 comments on commit c168157

Please sign in to comment.