Skip to content

Commit

Permalink
feat(refactor): Abstract useTimeleft (#2321)
Browse files Browse the repository at this point in the history
  • Loading branch information
rossbulat authored Nov 7, 2024
1 parent fd7c52b commit 950081f
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 41 deletions.
40 changes: 14 additions & 26 deletions src/hooks/useTimeLeft/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@

import { setStateWithRef } from '@w3ux/utils';
import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNetwork } from 'contexts/Network';
import type {
TimeLeftAll,
TimeLeftFormatted,
TimeLeftRaw,
TimeleftDuration,
} from './types';
import type { TimeLeftAll, TimeLeftRaw, TimeleftDuration } from './types';
import { getDuration } from './utils';

export const useTimeLeft = () => {
const { network } = useNetwork();
const { t, i18n } = useTranslation();
export interface UseTimeleftProps {
// Dependencies to trigger re-calculation of timeleft.
depsTimeleft: unknown[];
// Dependencies to trigger re-render of timeleft, e.g. if language switching occurs.
depsFormat: unknown[];
}

export const useTimeLeft = (props?: UseTimeleftProps) => {
const depsTimeleft = props?.depsTimeleft || [];
const depsFormat = props?.depsFormat || [];

// check whether timeleft is within a minute of finishing.
const inLastHour = () => {
Expand All @@ -35,28 +35,16 @@ export const useTimeLeft = () => {
// calculate resulting timeleft object from latest duration.
const getTimeleft = (c?: TimeleftDuration): TimeLeftAll => {
const { days, hours, minutes, seconds } = c || getDuration(toRef.current);

const raw: TimeLeftRaw = {
days,
hours,
minutes,
};
const formatted: TimeLeftFormatted = {
days: [days, t('time.day', { count: days, ns: 'base' })],
hours: [hours, t('time.hr', { count: hours, ns: 'base' })],
minutes: [minutes, t('time.min', { count: minutes, ns: 'base' })],
};
if (!days && !hours) {
formatted.seconds = [
seconds,
t('time.second', { count: seconds, ns: 'base' }),
];
raw.seconds = seconds;
}

return {
raw,
formatted,
};
};

Expand Down Expand Up @@ -106,12 +94,12 @@ export const useTimeLeft = () => {
}, 60000);
setStateWithRef(interval, setMinInterval, minIntervalRef);
}
}, [to, inLastHour(), lastMinuteCountdown(), network]);
}, [to, inLastHour(), lastMinuteCountdown(), ...depsTimeleft]);

// re-render the timeleft upon langauge switch.
// re-render the timeleft upon formatting changes.
useEffect(() => {
setTimeleft(getTimeleft());
}, [i18n.resolvedLanguage]);
}, [...depsFormat]);

// clear intervals on unmount
useEffect(
Expand Down
1 change: 0 additions & 1 deletion src/hooks/useTimeLeft/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export interface TimeLeftFormatted {

export interface TimeLeftAll {
raw: TimeLeftRaw;
formatted: TimeLeftFormatted;
}

export interface TimeleftHookProps {
Expand Down
26 changes: 26 additions & 0 deletions src/library/Utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

import { planckToUnit, rmCommas } from '@w3ux/utils';
import BigNumber from 'bignumber.js';
import type { TimeLeftFormatted, TimeLeftRaw } from 'hooks/useTimeLeft/types';
import type { TFunction } from 'i18next';

// Return `planckToUnit` as a `BigNumber`.
export const planckToUnitBn = (val: BigNumber, units: number): BigNumber =>
Expand All @@ -11,3 +13,27 @@ export const planckToUnitBn = (val: BigNumber, units: number): BigNumber =>
// Converts a string to a BigNumber.
export const stringToBn = (value: string): BigNumber =>
new BigNumber(rmCommas(value));

// Formats a given time breakdown (days, hours, minutes, seconds) into a readable structure using a
// translation function. Falls back to displaying seconds if both days and hours are absent.
export const formatTimeleft = (
t: TFunction,
{ days, hours, minutes, seconds }: TimeLeftRaw
): TimeLeftFormatted => {
// Create a default object containing formatted time components for days, hours, and minutes
const formatted: TimeLeftFormatted = {
days: [days, t('time.day', { count: days, ns: 'base' })],
hours: [hours, t('time.hr', { count: hours, ns: 'base' })],
minutes: [minutes, t('time.min', { count: minutes, ns: 'base' })],
};

// If there are no days or hours but there are seconds, override with a formatted seconds object
if (!days && !hours && seconds) {
formatted['seconds'] = [
seconds,
t('time.second', { count: seconds, ns: 'base' }),
];
return formatted;
}
return formatted;
};
17 changes: 11 additions & 6 deletions src/modals/UnlockChunks/Chunk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,34 @@ import { ChunkWrapper } from './Wrappers';
import type { ChunkProps } from './types';
import { useApi } from 'contexts/Api';
import { ButtonSubmit } from 'kits/Buttons/ButtonSubmit';
import { planckToUnitBn } from 'library/Utils';
import { formatTimeleft, planckToUnitBn } from 'library/Utils';

export const Chunk = ({ chunk, bondFor, onRebond }: ChunkProps) => {
const { t } = useTranslation('modals');
const { t, i18n } = useTranslation('modals');

const { activeEra } = useApi();
const {
networkData: { units, unit },
network,
} = useNetwork();
const { activeAccount } = useActiveAccounts();
const { isFastUnstaking } = useUnstaking();
const { activeAccount } = useActiveAccounts();
const { erasToSeconds } = useErasToTimeLeft();
const { timeleft, setFromNow } = useTimeLeft();
const isStaking = bondFor === 'nominator';

const { timeleft, setFromNow } = useTimeLeft({
depsTimeleft: [network],
depsFormat: [i18n.resolvedLanguage],
});

const isStaking = bondFor === 'nominator';
const { era, value } = chunk;
const left = new BigNumber(era).minus(activeEra.index);
const start = activeEra.start.multipliedBy(0.001);
const erasDuration = erasToSeconds(left);

const dateFrom = fromUnixTime(start.toNumber());
const dateTo = fromUnixTime(start.plus(erasDuration).toNumber());
const formatted = formatTimeleft(t, timeleft.raw);

// reset timer on account or network change.
useEffect(() => {
Expand All @@ -55,7 +60,7 @@ export const Chunk = ({ chunk, bondFor, onRebond }: ChunkProps) => {
) : (
<>
{t('unlocksInEra')} {era} /&nbsp;
<Countdown timeleft={timeleft.formatted} markup={false} />
<Countdown timeleft={formatted} markup={false} />
</>
)}
</h4>
Expand Down
24 changes: 16 additions & 8 deletions src/pages/Overview/Stats/ActiveEraTimeLeft.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,44 @@
// SPDX-License-Identifier: GPL-3.0-only

import BigNumber from 'bignumber.js';
import { fromUnixTime } from 'date-fns';
import { fromUnixTime, getUnixTime } from 'date-fns';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useEraTimeLeft } from 'hooks/useEraTimeLeft';
import { useTimeLeft } from 'hooks/useTimeLeft';
import { fromNow } from 'hooks/useTimeLeft/utils';
import { Timeleft } from 'library/StatBoxList/Timeleft';
import { useApi } from 'contexts/Api';
import { formatTimeleft } from 'library/Utils';
import { useNetwork } from 'contexts/Network';

export const ActiveEraStat = () => {
const { t } = useTranslation('pages');
const { t, i18n } = useTranslation('pages');
const { activeEra } = useApi();
const { network } = useNetwork();
const { get: getEraTimeleft } = useEraTimeLeft();
const { timeleft, setFromNow } = useTimeLeft();

const { percentSurpassed, percentRemaining } = getEraTimeleft();

const { timeleft, setFromNow } = useTimeLeft({
depsTimeleft: [network],
depsFormat: [i18n.resolvedLanguage],
});

const timeleftResult = getEraTimeleft();
const dateFrom = fromUnixTime(Date.now() / 1000);
const formatted = formatTimeleft(t, timeleft.raw);
const dateTo = fromNow(timeleftResult.timeleft.toNumber());
const dateToUnix = getUnixTime(dateTo);

// re-set timer on era change (also covers network change).
useEffect(() => {
setFromNow(dateFrom, dateTo);
}, [activeEra, timeleftResult.end.toString()]);

// NOTE: this maybe should be called in an interval. Needs more testing.
const { percentSurpassed, percentRemaining } = timeleftResult;
}, [activeEra, dateToUnix]);

const params = {
label: t('overview.timeRemainingThisEra'),
timeleft: timeleft.formatted,
timeleft: formatted,
graph: {
value1: activeEra.index.isZero() ? 0 : percentSurpassed.toNumber(),
value2: activeEra.index.isZero() ? 100 : percentRemaining.toNumber(),
Expand Down

0 comments on commit 950081f

Please sign in to comment.