diff --git a/src/hooks/useCountdown.ts b/src/hooks/useCountdown.ts
new file mode 100644
index 0000000..71a3bf9
--- /dev/null
+++ b/src/hooks/useCountdown.ts
@@ -0,0 +1,10 @@
+import { relativeDateFormat } from '@i18n';
+
+import { useTicker } from './useTicker';
+
+export function useCountdown({ timestamp }: { timestamp?: Date | string | number | undefined }) {
+ const curTimestamp = useTicker(() => Date.now(), 1000);
+ const timeLeft = timestamp ? relativeDateFormat(curTimestamp, timestamp) : undefined;
+
+ return timeLeft;
+}
diff --git a/src/i18n/dateFormat.ts b/src/i18n/dateFormat.ts
index 10632f4..5e647c1 100644
--- a/src/i18n/dateFormat.ts
+++ b/src/i18n/dateFormat.ts
@@ -4,7 +4,7 @@ export function dateFormat(
value: Date | string | number | undefined,
tpl: 'dateTime' | 'date' | string = 'date',
) {
- if (!value) return null;
+ if (!value) return undefined;
if (tpl === 'dateTime') {
tpl = 'dd.MM.yyyy HH:mm:ss';
@@ -12,10 +12,10 @@ export function dateFormat(
tpl = 'dd.MM.yyyy';
}
- if (value.valueOf() == 0) return null;
+ if (value.valueOf() == 0) return undefined;
const date = new Date(value);
- if (!isValid(date)) return null;
+ if (!isValid(date)) return undefined;
return format(new Date(value), tpl);
}
diff --git a/src/pages/DashboardPage/Summary.tsx b/src/pages/DashboardPage/Summary.tsx
index 5449700..b4d0b8d 100644
--- a/src/pages/DashboardPage/Summary.tsx
+++ b/src/pages/DashboardPage/Summary.tsx
@@ -1,6 +1,5 @@
import React, { PropsWithChildren, useEffect, useMemo, useState } from 'react';
-import { relativeDateFormat } from '@i18n';
import {
bytesFormatter,
numberWithCommasFormatter,
@@ -26,7 +25,7 @@ import { useNetworkSummary } from '@api/subsquid-network-squid';
import SquaredChip from '@components/Chip/SquaredChip';
import { HelpTooltip } from '@components/HelpTooltip';
import { Loader } from '@components/Loader';
-import { useTicker } from '@hooks/useTicker';
+import { useCountdown } from '@hooks/useCountdown';
import { useContracts } from '@network/useContracts';
export function ColumnLabel({ children, color }: PropsWithChildren<{ color?: string }>) {
@@ -108,14 +107,13 @@ function OnlineInfo() {
}
function CurrentEpochEstimation({ epochEnd }: { epochEnd: number }) {
- const curTime = useTicker(() => Date.now(), 1000);
- const epochEndsIn = useMemo(() => relativeDateFormat(curTime, epochEnd), [curTime, epochEnd]);
+ const timeLeft = useCountdown({ timestamp: epochEnd });
return (
Ends in
~{epochEndsIn}}
+ label={~{timeLeft}}
color="warning"
/>
diff --git a/src/pages/GatewaysPage/GatewayUnstake.tsx b/src/pages/GatewaysPage/GatewayUnstake.tsx
index 6453ac2..fb93f14 100644
--- a/src/pages/GatewaysPage/GatewayUnstake.tsx
+++ b/src/pages/GatewaysPage/GatewayUnstake.tsx
@@ -1,8 +1,9 @@
import React, { useState } from 'react';
-import { LockOpen as LockOpenIcon } from '@mui/icons-material';
+import { dateFormat } from '@i18n';
+import { Lock } from '@mui/icons-material';
import { LoadingButton } from '@mui/lab';
-import { SxProps } from '@mui/material';
+import { Box, SxProps, Tooltip } from '@mui/material';
import toast from 'react-hot-toast';
import { useClient } from 'wagmi';
import * as yup from 'yup';
@@ -11,6 +12,7 @@ import { gatewayRegistryAbi } from '@api/contracts';
import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction';
import { errorMessage } from '@api/contracts/utils';
import { ContractCallDialog } from '@components/ContractCallDialog';
+import { useCountdown } from '@hooks/useCountdown';
import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks';
import { useContracts } from '@network/useContracts';
@@ -24,22 +26,66 @@ export const stakeSchema = yup.object({
// .max(yup.ref('max'), ({ max }) => `Amount should be less than ${formatSqd(max)} `),
});
-export function GatewayUnstakeButton({ sx, disabled }: { sx?: SxProps; disabled?: boolean }) {
+function UnlocksTooltip({ timestamp }: { timestamp?: Date | string | number | undefined }) {
+ const timeLeft = useCountdown({ timestamp });
+
+ return `Unlocks in ${timeLeft} (${dateFormat(timestamp, 'dateTime')})`;
+}
+
+export function GatewayUnstakeButton({
+ sx,
+ disabled,
+ source,
+}: {
+ sx?: SxProps;
+ disabled?: boolean;
+ source: {
+ locked: boolean;
+ unlockedAt?: string;
+ };
+}) {
const [open, setOpen] = useState(false);
return (
<>
- }
- disabled={disabled}
- loading={open}
- variant="contained"
- color="error"
- onClick={() => setOpen(true)}
- sx={sx}
+
+ ) : (
+ 'Auto-extension is enabled'
+ )
+ }
+ placement="top"
>
- WITHDRAW
-
+
+ {source.locked && !disabled && (
+
+ )}
+ }
+ disabled={disabled || source.locked}
+ loading={open}
+ variant="contained"
+ color="error"
+ onClick={() => setOpen(true)}
+ sx={sx}
+ >
+ WITHDRAW
+
+
+
setOpen(false)} />
>
);
diff --git a/src/pages/GatewaysPage/GatewaysPage.tsx b/src/pages/GatewaysPage/GatewaysPage.tsx
index 762aa33..745673c 100644
--- a/src/pages/GatewaysPage/GatewaysPage.tsx
+++ b/src/pages/GatewaysPage/GatewaysPage.tsx
@@ -9,6 +9,7 @@ import {
Avatar,
Box,
Button,
+ Card,
Collapse,
Divider,
IconButton,
@@ -44,6 +45,7 @@ import {
import SquaredChip from '@components/Chip/SquaredChip';
import { HelpTooltip } from '@components/HelpTooltip';
import { DashboardTable, NoItems } from '@components/Table';
+import { useCountdown } from '@hooks/useCountdown';
import { CenteredPageWrapper } from '@layouts/NetworkLayout';
import { ConnectedWalletRequired } from '@network/ConnectedWalletRequired';
import { useAccount } from '@network/useAccount';
@@ -57,6 +59,18 @@ import { GatewayStakeButton } from './GatewayStake';
import { GatewayUnregisterButton } from './GatewayUnregister';
import { GatewayUnstakeButton } from './GatewayUnstake';
+function AppliesTooltip({ timestamp }: { timestamp?: string }) {
+ const timeLeft = useCountdown({ timestamp });
+
+ return {`Applies in ${timeLeft} (${dateFormat(timestamp, 'dateTime')})`};
+}
+
+function ExpiresTooltip({ timestamp }: { timestamp?: string }) {
+ const timeLeft = useCountdown({ timestamp });
+
+ return {`Expires in ${timeLeft} (${dateFormat(timestamp, 'dateTime')})`};
+}
+
export function MyStakes() {
const theme = useTheme();
const narrowXs = useMediaQuery(theme.breakpoints.down('xs'));
@@ -95,6 +109,14 @@ export function MyStakes() {
const { data: lastL1Block, isLoading: isL1BlockLoading } = useBlock({
chainId: l1ChainId,
});
+ const { data: appliedAtL1Block, isLoading: isAppliedAtBlockLoading } = useBlock({
+ chainId: l1ChainId,
+ blockNumber: stake?.lockStart,
+ includeTransactions: false,
+ query: {
+ enabled: stake && stake?.lockStart <= (lastL1Block?.number || 0n),
+ },
+ });
const { data: unlockedAtL1Block, isLoading: isUnlockedAtBlockLoading } = useBlock({
chainId: l1ChainId,
blockNumber: stake?.lockEnd,
@@ -124,15 +146,27 @@ export function MyStakes() {
stake.lockEnd >= (lastL1Block?.number || 0n);
const isExpired = !!stake?.amount && stake.lockEnd < (lastL1Block?.number || 0n);
- const unlockDate = useMemo(() => {
+ const appliedAt = useMemo(() => {
if (!stake || !lastL1Block) return;
+ if (stake.lockStart < lastL1Block.number)
+ return new Date(Number(appliedAtL1Block?.timestamp || 0n) * 1000).toISOString();
+
+ return new Date(
+ Number(lastL1Block.timestamp) * 1000 +
+ getBlockTime(stake.lockStart - lastL1Block.number + 1n),
+ ).toISOString();
+ }, [appliedAtL1Block?.timestamp, lastL1Block, stake]);
+
+ const unlockedAt = useMemo(() => {
+ if (!stake || !lastL1Block || stake.autoExtension) return;
+
if (stake.lockEnd < lastL1Block.number)
- return Number(unlockedAtL1Block?.timestamp || 0n) * 1000;
+ return new Date(Number(unlockedAtL1Block?.timestamp || 0n) * 1000).toISOString();
- return (
- Number(lastL1Block.timestamp) * 1000 + getBlockTime(stake.lockEnd - lastL1Block.number + 1n)
- );
+ return new Date(
+ Number(lastL1Block.timestamp) * 1000 + getBlockTime(stake.lockEnd - lastL1Block.number + 1n),
+ ).toISOString();
}, [lastL1Block, stake, unlockedAtL1Block?.timestamp]);
const cuPerEpoch = useMemo(() => {
@@ -154,7 +188,13 @@ export function MyStakes() {
action={
-
+
}
>
@@ -181,14 +221,14 @@ export function MyStakes() {
lastL1Block &&
(isPending ? (
}
placement="top"
>
) : isActive ? (
}
placement="top"
>
@@ -210,7 +250,7 @@ export function MyStakes() {
{numberWithCommasFormatter(cuPerEpoch || 0)}
- } spacing={1} flex={1}>
+ {/* } spacing={1} flex={1}>
Expired At
@@ -221,7 +261,7 @@ export function MyStakes() {
: 'Auto-extension enabled'}
-
+ */}
@@ -352,25 +392,28 @@ const GettingStarted = () => {
];
return (
- }
- action={
- setOpen(!open)}>
-
-
- }
- >
- Getting started with your portal
+
+ }
+ action={
+
+
+
+ }
+ onClick={() => setOpen(!open)}
+ >
+ Getting started with your portal
+
-
-
+
+
{steps.map(({ primary, secondary }, i) => (
{
-
+
);
};
diff --git a/src/pages/WorkersPage/WorkerStatus.tsx b/src/pages/WorkersPage/WorkerStatus.tsx
index e2c3339..d731abb 100644
--- a/src/pages/WorkersPage/WorkerStatus.tsx
+++ b/src/pages/WorkersPage/WorkerStatus.tsx
@@ -1,12 +1,12 @@
import { useMemo } from 'react';
-import { dateFormat, relativeDateFormat } from '@i18n';
+import { dateFormat } from '@i18n';
import { CircleRounded } from '@mui/icons-material';
import { Box, Chip as MaterialChip, Tooltip, chipClasses, styled } from '@mui/material';
import capitalize from 'lodash-es/capitalize';
import { WorkerStatus as Status, Worker } from '@api/subsquid-network-squid';
-import { useTicker } from '@hooks/useTicker';
+import { useCountdown } from '@hooks/useCountdown';
export const Chip = styled(MaterialChip)(({ theme }) => ({
// [`&.${chipClasses.colorSuccess}`]: {
@@ -23,6 +23,12 @@ export const Chip = styled(MaterialChip)(({ theme }) => ({
},
}));
+function AppliesTooltip({ timestamp }: { timestamp?: string }) {
+ const timeLeft = useCountdown({ timestamp });
+
+ return `Applies in ${timeLeft} (${dateFormat(timestamp, 'dateTime')})`;
+}
+
export function WorkerStatusChip({
worker,
}: {
@@ -55,20 +61,9 @@ export function WorkerStatusChip({
return { label: capitalize(worker.status), color: 'primary' };
}, [worker.jailReason, worker.jailed, worker.online, worker.status]);
- const curTimestamp = useTicker(() => Date.now(), 1000);
- const timeLeft = useMemo(
- () =>
- worker.statusChangeAt ? relativeDateFormat(curTimestamp, worker.statusChangeAt) : undefined,
- [curTimestamp, worker.statusChangeAt],
- );
-
const chip = (
}
placement="top"
>
{`Unlocks in ${timeLeft} (${dateFormat(timestamp, 'dateTime')})`};
+}
+
export type SourceWalletWithDelegation = SourceWalletWithBalance & {
locked: boolean;
unlockedAt?: string;
@@ -58,8 +64,7 @@ export function WorkerUndelegate({
const isLocked = useMemo(() => !!sources?.length && !sources?.some(d => !d.locked), [sources]);
- const curTimestamp = useTicker(() => Date.now(), 1000);
- const { unlockedAt, timeLeft } = useMemo(() => {
+ const { unlockedAt } = useMemo(() => {
const min = sources?.reduce(
(r, d) => {
if (!d.unlockedAt) return r;
@@ -72,18 +77,14 @@ export function WorkerUndelegate({
return {
unlockedAt: min ? new Date(min).toISOString() : undefined,
- timeLeft: min ? relativeDateFormat(curTimestamp, min) : undefined,
};
- }, [curTimestamp, sources]);
+ }, [sources]);
return (
<>
-
- {isLocked && (
-
+ } placement="top">
+
+ {isLocked && !disabled && (
-
- )}
- setOpen(true)}
- variant="outlined"
- color="error"
- disabled={disabled || isLocked}
- >
- UNDELEGATE
-
-
+ )}
+ setOpen(true)}
+ variant="outlined"
+ color="error"
+ disabled={disabled || isLocked}
+ >
+ UNDELEGATE
+
+
+
setOpen(false)}
diff --git a/src/pages/WorkersPage/WorkerWithdraw.tsx b/src/pages/WorkersPage/WorkerWithdraw.tsx
index e2b3e6f..7653e9a 100644
--- a/src/pages/WorkersPage/WorkerWithdraw.tsx
+++ b/src/pages/WorkersPage/WorkerWithdraw.tsx
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
-import { dateFormat, relativeDateFormat } from '@i18n';
+import { dateFormat } from '@i18n';
import { peerIdToHex } from '@lib/network';
import { Lock } from '@mui/icons-material';
import { LoadingButton } from '@mui/lab';
@@ -13,10 +13,17 @@ import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction';
import { errorMessage } from '@api/contracts/utils';
import { AccountType, SourceWallet, Worker } from '@api/subsquid-network-squid';
import { ContractCallDialog } from '@components/ContractCallDialog';
+import { useCountdown } from '@hooks/useCountdown';
import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks';
import { useAccount } from '@network/useAccount';
import { useContracts } from '@network/useContracts';
+function UnlocksTooltip({ timestamp }: { timestamp?: string }) {
+ const timeLeft = useCountdown({ timestamp });
+
+ return {`Unlocks in ${timeLeft} (${dateFormat(timestamp, 'dateTime')})`};
+}
+
export function WorkerWithdrawButton({
worker,
source,
@@ -35,15 +42,12 @@ export function WorkerWithdrawButton({
return (
<>
-
- {source.locked && (
-
+ }
+ placement="top"
+ >
+
+ {source.locked && !disabled && (
-
- )}
- setOpen(true)}
- variant="outlined"
- color="error"
- disabled={disabled || source.locked}
- >
- WITHDRAW
-
-
+ )}
+ setOpen(true)}
+ variant="outlined"
+ color="error"
+ disabled={disabled || source.locked}
+ >
+ WITHDRAW
+
+
+
setOpen(false)}