From 468a3d510729fec5960aea8a2d4d724373780361 Mon Sep 17 00:00:00 2001 From: Daria <2395326+piggydoughnut@users.noreply.github.com> Date: Fri, 4 Apr 2025 12:01:20 +1300 Subject: [PATCH] Feedback/#11253 (#11304) * countdown component * filtering by parachain id in workplan and workload * lint * rename chainName to relayName, fixed subscan block link * always showing price even if the cores are sold out * moved some calculations to the coretime context * added broker context for page-broker, fix for wrong dates * showing time with the estimated date in places * removed extra code when calculating renewal status * tsc for page-broker * page-coretime tsc * fix showing correct dates for fixed price phase in sale history * minified workinforow as all rows show the same information now * default values for fn input * showing start price of the sale on the sale tab * replaced custom Countdown element with existing implementation * adjusted estimateTime function for broker * adjusted estimateTime function for coretime page * lint * added Renewal link for RegionX --- packages/page-broker/src/BrokerContext.tsx | 83 ++++++++ .../page-broker/src/Overview/CoreTable.tsx | 2 +- .../page-broker/src/Overview/CoresTables.tsx | 7 +- packages/page-broker/src/Overview/Filters.tsx | 2 +- packages/page-broker/src/Overview/Summary.tsx | 32 ++- .../page-broker/src/Overview/WorkInfoRow.tsx | 184 +++++------------- .../page-broker/src/Overview/Workload.tsx | 59 +++--- .../page-broker/src/Overview/Workplan.tsx | 5 +- packages/page-broker/src/Overview/index.tsx | 18 +- packages/page-broker/src/index.tsx | 18 +- packages/page-broker/src/types.ts | 8 + packages/page-broker/src/utils.ts | 67 ++++--- .../page-coretime/src/CoretimeContext.tsx | 39 +++- packages/page-coretime/src/CoretimePage.tsx | 10 +- .../page-coretime/src/Overview/Summary.tsx | 87 +++++---- packages/page-coretime/src/Overview/index.tsx | 10 +- .../page-coretime/src/ParachainTableRow.tsx | 5 +- .../page-coretime/src/ParachainsTable.tsx | 8 +- packages/page-coretime/src/Row.tsx | 38 +++- .../src/Sale/SaleDetailsView.tsx | 14 +- packages/page-coretime/src/Sale/Summary.tsx | 17 +- .../page-coretime/src/Sale/boxes/Cores.tsx | 10 +- .../page-coretime/src/Sale/boxes/Timeline.tsx | 11 +- packages/page-coretime/src/Sale/index.tsx | 17 +- packages/page-coretime/src/types.ts | 2 +- packages/page-coretime/src/utils/index.ts | 20 +- packages/page-coretime/src/utils/sale.ts | 34 ++-- packages/react-hooks/src/index.ts | 1 + 28 files changed, 449 insertions(+), 359 deletions(-) create mode 100644 packages/page-broker/src/BrokerContext.tsx diff --git a/packages/page-broker/src/BrokerContext.tsx b/packages/page-broker/src/BrokerContext.tsx new file mode 100644 index 0000000000..9aca571b0f --- /dev/null +++ b/packages/page-broker/src/BrokerContext.tsx @@ -0,0 +1,83 @@ +// Copyright 2017-2025 @polkadot/app-broker authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { ReactNode } from 'react'; +import type { ApiPromise } from '@polkadot/api'; +import type { BrokerStatus, ChainConstants, PalletBrokerConfigRecord, PalletBrokerSaleInfoRecord } from '@polkadot/react-hooks/types'; +import type { CurrentRegion } from './types.js'; + +import React, { createContext, useContext, useMemo } from 'react'; + +import { useBrokerConfig, useBrokerSalesInfo, useBrokerStatus, useCoretimeConsts } from '@polkadot/react-hooks'; + +import { estimateTime } from './utils.js'; + +interface BrokerProviderProps { + children: ReactNode; + api: ApiPromise; + isApiReady: boolean; +} + +interface BrokerContextProps { + config: PalletBrokerConfigRecord | null, + coretimeConsts: ChainConstants | null, + currentRegion: CurrentRegion, + saleInfo: PalletBrokerSaleInfoRecord | null, + status: BrokerStatus | null, +} + +const initialState = { + config: null, + coretimeConsts: null, + currentRegion: { + begin: null, + beginDate: null, + end: null, + endDate: null + }, + saleInfo: null, + status: null +}; + +export const BrokerContext = createContext(initialState); + +export const BrokerProvider = ({ api, children, isApiReady }: BrokerProviderProps) => { + const coretimeConsts = useCoretimeConsts(); + const config = useBrokerConfig(api, isApiReady); + const saleInfo = useBrokerSalesInfo(api, isApiReady); + const status = useBrokerStatus(api, isApiReady); + + const currentRegionEnd = useMemo(() => saleInfo?.regionBegin, [saleInfo]); + const currentRegionBegin = useMemo(() => saleInfo && config && saleInfo?.regionBegin - config?.regionLength, [saleInfo, config]); + const lastBlock = useMemo(() => status && coretimeConsts && status.lastTimeslice * coretimeConsts.relay.blocksPerTimeslice, [status, coretimeConsts]); + + const currentRegionEndDate = useMemo(() => currentRegionEnd && lastBlock && estimateTime(Number(currentRegionEnd), lastBlock)?.formattedDate, [currentRegionEnd, lastBlock]); + const currentRegionBeginDate = useMemo(() => currentRegionBegin && lastBlock && estimateTime(Number(currentRegionBegin), lastBlock)?.formattedDate, [currentRegionBegin, lastBlock]); + + const value = useMemo(() => { + if (!config || !saleInfo || !status || !currentRegionBegin || !currentRegionBeginDate || !currentRegionEnd || !currentRegionEndDate || !coretimeConsts) { + return initialState; + } + + return ({ + config, + coretimeConsts, + currentRegion: { + begin: currentRegionBegin, + beginDate: currentRegionBeginDate, + end: currentRegionEnd, + endDate: currentRegionEndDate + }, + saleInfo, + status + }); + }, [currentRegionBegin, currentRegionEnd, currentRegionBeginDate, currentRegionEndDate, config, saleInfo, status, coretimeConsts]); + + return ( + + {children} + + ); +}; + +export const useBrokerContext = () => useContext(BrokerContext); diff --git a/packages/page-broker/src/Overview/CoreTable.tsx b/packages/page-broker/src/Overview/CoreTable.tsx index 0fa40746e6..31a8d00da1 100644 --- a/packages/page-broker/src/Overview/CoreTable.tsx +++ b/packages/page-broker/src/Overview/CoreTable.tsx @@ -27,7 +27,7 @@ function CoreTable ({ api, config, core, workload, workplan }: Props): React.Rea [
{headerRef.current} {core}
, 'core', - 8, + 9, undefined ] ]; diff --git a/packages/page-broker/src/Overview/CoresTables.tsx b/packages/page-broker/src/Overview/CoresTables.tsx index eb0982a548..c154e4ef8d 100644 --- a/packages/page-broker/src/Overview/CoresTables.tsx +++ b/packages/page-broker/src/Overview/CoresTables.tsx @@ -6,8 +6,7 @@ import type { CoreInfo } from '../types.js'; import React from 'react'; -import { useBrokerConfig } from '@polkadot/react-hooks'; - +import { useBrokerContext } from '../BrokerContext.js'; import CoreTable from './CoreTable.js'; interface Props { @@ -16,8 +15,8 @@ interface Props { data: CoreInfo[]; } -function CoresTable ({ api, data, isApiReady }: Props): React.ReactElement { - const config = useBrokerConfig(api, isApiReady); +function CoresTable ({ api, data }: Props): React.ReactElement { + const { config } = useBrokerContext(); return ( <> diff --git a/packages/page-broker/src/Overview/Filters.tsx b/packages/page-broker/src/Overview/Filters.tsx index 265815bc14..697344892a 100644 --- a/packages/page-broker/src/Overview/Filters.tsx +++ b/packages/page-broker/src/Overview/Filters.tsx @@ -23,7 +23,7 @@ interface Props { const filterLoad = (parachainId: string, data: CoreInfo[], workloadCoreSelected: number): CoreInfo[] => { if (parachainId) { - return data.filter(({ workload }) => !!workload?.filter(({ info }) => info.task === parachainId).length); + return data.filter(({ workload, workplan }) => !!workload?.filter(({ info }) => info.task === parachainId).length || !!workplan?.filter(({ info }) => info.task === parachainId).length); } if (workloadCoreSelected === -1) { diff --git a/packages/page-broker/src/Overview/Summary.tsx b/packages/page-broker/src/Overview/Summary.tsx index 82310871d2..e807163538 100644 --- a/packages/page-broker/src/Overview/Summary.tsx +++ b/packages/page-broker/src/Overview/Summary.tsx @@ -4,17 +4,17 @@ import type { LinkOption } from '@polkadot/apps-config/endpoints/types'; import type { statsType } from '../types.js'; -import React, { useMemo } from 'react'; +import React from 'react'; import { CardSummary, styled, SummaryBox, UsageBar } from '@polkadot/react-components'; import { defaultHighlight } from '@polkadot/react-components/styles'; -import { useApi, useBrokerConfig, useBrokerSalesInfo, useBrokerStatus } from '@polkadot/react-hooks'; +import { useApi } from '@polkadot/react-hooks'; import { type CoreWorkload } from '@polkadot/react-hooks/types'; -import { useCoretimeConsts } from '@polkadot/react-hooks/useCoretimeConsts'; import { BN, BN_ZERO } from '@polkadot/util'; +import { useBrokerContext } from '../BrokerContext.js'; import { useTranslation } from '../translate.js'; -import { estimateTime, getStats } from '../utils.js'; +import { getStats } from '../utils.js'; import RegionLength from './Summary/RegionLength.js'; import Timeslice from './Summary/Timeslice.js'; import TimeslicePeriod from './Summary/TimeslicePeriod.js'; @@ -35,23 +35,17 @@ const StyledSection = styled.section` `; interface Props { - coreCount?: string apiEndpoint?: LinkOption | null; + coreCount?: string workloadInfos?: CoreWorkload[] } function Summary ({ coreCount, workloadInfos }: Props): React.ReactElement { const { t } = useTranslation(); - const { api, apiEndpoint, isApiReady } = useApi(); - const coretimeConstants = useCoretimeConsts(); + const { api, apiEndpoint } = useApi(); const uiHighlight = apiEndpoint?.ui.color || defaultHighlight; const { idles, pools, tasks }: statsType = React.useMemo(() => getStats(coreCount, workloadInfos), [coreCount, workloadInfos]); - - const saleInfo = useBrokerSalesInfo(api, isApiReady); - const config = useBrokerConfig(api, isApiReady); - const status = useBrokerStatus(api, isApiReady); - const currentRegionEnd = useMemo(() => saleInfo && config && saleInfo?.regionEnd - config?.regionLength, [saleInfo, config]); - const currentRegionStart = useMemo(() => currentRegionEnd && config && currentRegionEnd - config?.regionLength, [currentRegionEnd, config]); + const { config, currentRegion, saleInfo, status } = useBrokerContext(); return ( @@ -81,7 +75,7 @@ function Summary ({ coreCount, workloadInfos }: Props): React.ReactElement { progress={{ isBlurred: false, total: new BN(config?.regionLength || 0), - value: (config?.regionLength && currentRegionEnd && status && new BN(config?.regionLength - (currentRegionEnd - status?.lastTimeslice))) || BN_ZERO, + value: (config?.regionLength && currentRegion.end && status && new BN(config?.regionLength - (currentRegion.end - status?.lastTimeslice))) || BN_ZERO, withTime: false }} /> @@ -104,7 +98,7 @@ function Summary ({ coreCount, workloadInfos }: Props): React.ReactElement {
- {status && currentRegionStart && currentRegionEnd && + {currentRegion.begin && currentRegion.end && ( <>
-
{estimateTime(currentRegionStart, status?.lastTimeslice * 80, coretimeConstants?.relay)}
-
{estimateTime(currentRegionEnd, status?.lastTimeslice * 80, coretimeConstants?.relay)}
+
{currentRegion.beginDate}
+
{currentRegion.endDate}
-
{currentRegionStart}
-
{currentRegionEnd}
+
{currentRegion.begin}
+
{currentRegion.end}
diff --git a/packages/page-broker/src/Overview/WorkInfoRow.tsx b/packages/page-broker/src/Overview/WorkInfoRow.tsx index 67d33a875c..f6df50a7c7 100644 --- a/packages/page-broker/src/Overview/WorkInfoRow.tsx +++ b/packages/page-broker/src/Overview/WorkInfoRow.tsx @@ -50,150 +50,62 @@ function WorkInfoRow ({ data }: { data: InfoRow }): React.ReactElement { return ( <> no task - + ); } - switch (data.type) { - case (CoreTimeTypes.Reservation): { - return ( - <> - - - + return ( + <> + + + + + + + +
type
+ {data.type && + ( - - - - ); - } + ) + } +
+ {data.owner + ? +
{'Owner'}
+ - case (CoreTimeTypes.Lease): - return ( - <> - - - - - - -
type
- -
- - ); - - case (CoreTimeTypes['On Demand']): { - return ( - <> - - - - - - -
type
- -
- - ); - } - - default: { - return ( - <> - - - - - - -
type
- -
- {data.owner - ? -
{'Owner'}
- - -
- : } - ); - } - } +
+ : } + ); } export default React.memo(WorkInfoRow); diff --git a/packages/page-broker/src/Overview/Workload.tsx b/packages/page-broker/src/Overview/Workload.tsx index 23b27f61b3..56798781bb 100644 --- a/packages/page-broker/src/Overview/Workload.tsx +++ b/packages/page-broker/src/Overview/Workload.tsx @@ -3,17 +3,16 @@ import type { ApiPromise } from '@polkadot/api'; import type { PalletBrokerConfigRecord, RegionInfo } from '@polkadot/react-hooks/types'; -import type { Option } from '@polkadot/types'; -import type { PalletBrokerStatusRecord } from '@polkadot/types/lookup'; import type { CoreWorkloadType, CoreWorkplanType, InfoRow } from '../types.js'; import React, { useEffect, useMemo, useState } from 'react'; import { ExpandButton } from '@polkadot/react-components'; -import { useApi, useBrokerSalesInfo, useCall, useRegions, useToggle } from '@polkadot/react-hooks'; +import { useRegions, useToggle } from '@polkadot/react-hooks'; import { useCoretimeConsts } from '@polkadot/react-hooks/useCoretimeConsts'; -import { formatRowInfo } from '../utils.js'; +import { useBrokerContext } from '../BrokerContext.js'; +import { estimateTime, formatRowInfo } from '../utils.js'; import WorkInfoRow from './WorkInfoRow.js'; import Workplan from './Workplan.js'; @@ -26,57 +25,65 @@ interface Props { } function Workload ({ api, config, core, workload, workplan }: Props): React.ReactElement { - const { isApiReady } = useApi(); - const salesInfo = useBrokerSalesInfo(api, isApiReady); const coretimeConstants = useCoretimeConsts(); - const status = useCall>(isApiReady && api.query.broker?.status); const [isExpanded, toggleIsExpanded] = useToggle(false); const [workloadData, setWorkloadData] = useState(); const [workplanData, setWorkplanData] = useState(); - const currentTimeSlice = useMemo(() => { - if (status?.isSome) { - return status.unwrap().lastCommittedTimeslice.toNumber(); - } + const { currentRegion, status } = useBrokerContext(); - return 0; - }, [status]); + const currentTimeSlice = useMemo(() => + status?.lastTimeslice ?? 0 + , [status]); const regionInfo = useRegions(api); - const region: RegionInfo | undefined = useMemo(() => regionInfo?.find((v) => v.core === core && v.start <= currentTimeSlice && v.end > currentTimeSlice), [regionInfo, core, currentTimeSlice]); + const regionOwnerInfo: RegionInfo | undefined = useMemo(() => regionInfo?.find((v) => v.core === core && v.start <= currentTimeSlice && v.end > currentTimeSlice), [regionInfo, core, currentTimeSlice]); useEffect(() => { - if (!!workload?.length && !!salesInfo) { + if (!!workload?.length && currentTimeSlice > 0) { // saleInfo points to a regionEnd and regionBeing in the next cycle, but we want the start and end of the current cycle setWorkloadData(formatRowInfo( workload, core, - region, + regionOwnerInfo, currentTimeSlice, - { regionBegin: salesInfo.regionBegin - config.regionLength, regionEnd: salesInfo.regionEnd - config.regionLength }, + { + begin: currentRegion.begin || 0, + beginDate: currentRegion.beginDate || '', + end: currentRegion.end || 0, + endDate: currentRegion.endDate || '' + }, config.regionLength, coretimeConstants?.relay )); } else { return setWorkloadData([{ core }]); } - }, [workload, region, currentTimeSlice, core, salesInfo, config, coretimeConstants]); + }, [workload, regionOwnerInfo, currentTimeSlice, core, config, coretimeConstants, currentRegion]); useEffect(() => { - if (!!workplan?.length && !!salesInfo) { + if (workplan?.length && status && coretimeConstants && currentRegion.endDate) { + const futureRegionStart = currentRegion.end || 0; + const futureRegionEnd = futureRegionStart + config.regionLength; + const lastBlock = status.lastTimeslice * coretimeConstants?.relay.blocksPerTimeslice; + setWorkplanData(formatRowInfo( workplan, core, - region, - currentTimeSlice, - { regionBegin: salesInfo.regionBegin - config.regionLength, regionEnd: salesInfo.regionEnd - config.regionLength }, + regionOwnerInfo, + status.lastTimeslice, + { + begin: futureRegionStart, + beginDate: currentRegion.endDate, + end: futureRegionEnd, + endDate: estimateTime(futureRegionEnd, lastBlock)?.formattedDate ?? '' + }, config.regionLength, coretimeConstants?.relay )); } - } - , [workplan, region, currentTimeSlice, core, salesInfo, config, coretimeConstants]); + }, [workplan, regionOwnerInfo, status, core, config, coretimeConstants, currentRegion]); const hasWorkplan = workplan?.length; @@ -113,14 +120,12 @@ function Workload ({ api, config, core, workload, workplan }: Props): React.Reac <> workplans - + {workplanData?.map((workplanInfo) => ( ))} diff --git a/packages/page-broker/src/Overview/Workplan.tsx b/packages/page-broker/src/Overview/Workplan.tsx index c92b256115..c507200013 100644 --- a/packages/page-broker/src/Overview/Workplan.tsx +++ b/packages/page-broker/src/Overview/Workplan.tsx @@ -1,7 +1,6 @@ // Copyright 2017-2025 @polkadot/app-broker authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { RegionInfo } from '@polkadot/react-hooks/types'; import type { InfoRow } from '../types.js'; import React from 'react'; @@ -13,9 +12,7 @@ import WorkInfoRow from './WorkInfoRow.js'; interface Props { className?: string; workplanData: InfoRow; - currentTimeSlice: number isExpanded: boolean - region: RegionInfo | undefined } function Workplan ({ isExpanded, workplanData }: Props): React.ReactElement { @@ -26,7 +23,7 @@ function Workplan ({ isExpanded, workplanData }: Props): React.ReactElement - + ); } diff --git a/packages/page-broker/src/Overview/index.tsx b/packages/page-broker/src/Overview/index.tsx index 20d235d01f..eba13923c3 100644 --- a/packages/page-broker/src/Overview/index.tsx +++ b/packages/page-broker/src/Overview/index.tsx @@ -6,8 +6,9 @@ import type { CoreInfo } from '../types.js'; import React, { useEffect, useMemo, useState } from 'react'; -import { useApi, useBrokerLeases, useBrokerReservations, useBrokerStatus, useWorkloadInfos, useWorkplanInfos } from '@polkadot/react-hooks'; +import { useApi, useBrokerLeases, useBrokerReservations, useWorkloadInfos, useWorkplanInfos } from '@polkadot/react-hooks'; +import { useBrokerContext } from '../BrokerContext.js'; import { createTaskMap, getOccupancyType } from '../utils.js'; import CoresTable from './CoresTables.js'; import Filters from './Filters.js'; @@ -53,10 +54,10 @@ const formatData = (coreCount: number, workplan: CoreWorkplan[], workload: CoreW function Overview ({ className }: Props): React.ReactElement { const { api, apiEndpoint, isApiReady } = useApi(); - const [data, setData] = useState([]); + const { currentRegion, status } = useBrokerContext(); + const [data, setData] = useState([]); const [filtered, setFiltered] = useState(); - const status = useBrokerStatus(api, isApiReady); const workloadInfos: CoreWorkload[] | undefined = useWorkloadInfos(api, isApiReady); const workplanInfos: CoreWorkplan[] | undefined = useWorkplanInfos(api, isApiReady); @@ -73,11 +74,12 @@ function Overview ({ className }: Props): React.ReactElement { return (
- + {!!currentRegion.beginDate && !!currentRegion.endDate && + } {!!data?.length && (<> { const { t } = useTranslation(); const itemsRef = useRef(createItemsRef(t)); + const { api, isApiReady } = useApi(); return (
- - + + + +
); } diff --git a/packages/page-broker/src/types.ts b/packages/page-broker/src/types.ts index ec65fee655..d5872df835 100644 --- a/packages/page-broker/src/types.ts +++ b/packages/page-broker/src/types.ts @@ -10,6 +10,7 @@ export interface InfoRow { core: number mask?: string start?: string | null, + startTimeslice?: number | null end?: string | null owner?: string leaseLength?: number @@ -38,3 +39,10 @@ export interface CoreWorkloadType extends CoreWorkload { lastBlock: number, type: CoreTimeTypes } + +export interface CurrentRegion { + begin: number | null, + beginDate: string | null, + end: number | null, + endDate: string | null +} diff --git a/packages/page-broker/src/utils.ts b/packages/page-broker/src/utils.ts index cb3fe37818..d3422bc52f 100644 --- a/packages/page-broker/src/utils.ts +++ b/packages/page-broker/src/utils.ts @@ -16,9 +16,11 @@ function formatDate (date: Date) { } /** + * Calculation on the Relay chain + * * blockTime = 6000 ms * BlocksPerTimeslice = 80 - * Default Regoin = 5040 timeslices + * Default Region = 5040 timeslices * TargetBlock = TargetTimeslice * BlocksPerTimeslice * Block Time Difference = |TargetBlock - latest Block| * blockTime * @@ -31,26 +33,29 @@ function formatDate (date: Date) { export const estimateTime = ( targetTimeslice: string | number, latestBlock: number, - { blocksPerTimeslice: blocksPerTs, blocktimeMs }: ChainBlockConstants = { blocksPerTimeslice: 0, blocktimeMs: 0 } -): string | null => { + { blocksPerTimeslice: blocksPerTs, blocktimeMs }: ChainBlockConstants = { blocksPerTimeslice: 80, blocktimeMs: 6000 } +): { timestamp: number, formattedDate: string } | null => { if (!latestBlock || !targetTimeslice) { console.error('Invalid input: one or more inputs are missing'); return null; } - const now = new Date().getTime(); + const now = new BN(Date.now()); try { const blockTime = new BN(blocktimeMs); // Average block time in milliseconds (6 seconds) - const timeSlice = new BN(blocksPerTs); + const blocksPerTimeslice = new BN(blocksPerTs); + const targetBlock = new BN(Number(targetTimeslice)).mul(blocksPerTimeslice); const latestBlockBN = new BN(latestBlock); - const timestampBN = new BN(now); - const targetBlockBN = new BN(targetTimeslice).mul(timeSlice); - const blockTimeDifference = targetBlockBN.sub(latestBlockBN).mul(blockTime); - const estTimestamp = timestampBN.add(blockTimeDifference); + const blockDifference = targetBlock.sub(latestBlockBN); + const timeDifference = blockDifference.mul(blockTime); + const estTimestamp = now.add(timeDifference); - return formatDate(new Date(estTimestamp.toNumber())); + return { + formattedDate: formatDate(new Date(estTimestamp.toNumber())), + timestamp: estTimestamp.toNumber() + }; } catch (error) { console.error('Error in calculation:', error); @@ -70,36 +75,38 @@ export const estimateTime = ( export function formatRowInfo ( data: CoreWorkloadType[] | CoreWorkplanType[], core: number, - currentRegion: RegionInfo | undefined, + regionOwnerInfo: RegionInfo | undefined, currentTimeSlice: number, - { regionBegin, regionEnd }: { regionBegin: number, regionEnd: number }, + currentRegion: { begin: number, beginDate: string, end: number, endDate: string }, regionLength: number, coretimeRelayConstants: ChainBlockConstants = { blocksPerTimeslice: 0, blocktimeMs: 0 } ): InfoRow[] { + const blockNumberNow = currentTimeSlice * coretimeRelayConstants.blocksPerTimeslice; + return data.map((one: CoreWorkloadType | CoreWorkplanType) => { - const item: InfoRow = { core, maskBits: one?.info?.maskBits, task: one?.info?.task, type: one?.type }; - const blockNumberNow = currentTimeSlice * 80; + const item: InfoRow = { + core, + maskBits: one?.info?.maskBits, + task: one?.info?.task, + type: one?.type + }; - item.type = one.type; - let end; let start = null; - - if (one.type === CoreTimeTypes.Lease) { + // For region-based types, use the provided dates + if ([CoreTimeTypes['Bulk Coretime'], CoreTimeTypes.Reservation, CoreTimeTypes['On Demand']].includes(one.type)) { + item.start = currentRegion.beginDate; + item.end = currentRegion.endDate; + item.startTimeslice = currentRegion.begin; + item.endBlock = currentRegion.end * coretimeRelayConstants.blocksPerTimeslice; + } else if (one.type === CoreTimeTypes.Lease) { // For lease type, calculate the end const period = Math.floor(one.lastBlock / regionLength); + const endTs = period * regionLength; + const endEstimate = estimateTime(endTs, blockNumberNow, coretimeRelayConstants); - end = period * regionLength; - } else { - start = currentRegion?.start?.toString() ?? regionBegin; - end = currentRegion?.end?.toString() ?? regionEnd; + item.end = endEstimate?.formattedDate ?? null; + item.endBlock = endTs * coretimeRelayConstants.blocksPerTimeslice; } - item.owner = currentRegion?.owner.toString(); - item.start = start ? estimateTime(start, blockNumberNow, coretimeRelayConstants) : null; - item.end = end ? estimateTime(end, blockNumberNow, coretimeRelayConstants) : null; - item.endBlock = end ? Number(end) * 80 : 0; - - if ('timeslice' in one && !start) { - start = estimateTime(one.timeslice, blockNumberNow, coretimeRelayConstants) ?? null; - } + item.owner = regionOwnerInfo?.owner.toString(); return item; }); diff --git a/packages/page-coretime/src/CoretimeContext.tsx b/packages/page-coretime/src/CoretimeContext.tsx index 5ca71de1f0..3dac1a84e3 100644 --- a/packages/page-coretime/src/CoretimeContext.tsx +++ b/packages/page-coretime/src/CoretimeContext.tsx @@ -9,17 +9,27 @@ import React, { createContext, useContext, useMemo } from 'react'; import { useCoretimeInformation } from '@polkadot/react-hooks'; -import { createGet } from './utils/index.js'; +import { createGet, estimateTime } from './utils/index.js'; interface CoretimeContextProps { coretimeInfo: CoretimeInformation | null; get: ReturnType | null; + saleStartDate: string | null + saleEndDate: string | null + currentRegionEnd: number | null + currentRegionStart: number | null } -const CoretimeContext = createContext({ +const initState = { coretimeInfo: null, - get: null -}); + currentRegionEnd: null, + currentRegionStart: null, + get: null, + saleEndDate: null, + saleStartDate: null +}; + +const CoretimeContext = createContext(initState); export const CoretimeProvider = ({ api, children, @@ -37,7 +47,26 @@ export const CoretimeProvider = ({ api, return null; }, [coretimeInfo?.constants]); - const value = useMemo(() => ({ coretimeInfo: coretimeInfo ?? null, get }), [coretimeInfo, get]); + const currentRegionEnd = useMemo(() => coretimeInfo ? coretimeInfo?.salesInfo.regionEnd - coretimeInfo?.config.regionLength : 0, [coretimeInfo]); + const currentRegionStart = useMemo(() => coretimeInfo ? coretimeInfo.salesInfo.regionEnd - coretimeInfo?.config.regionLength * 2 : 0, [coretimeInfo]); + + const saleStartDate = useMemo(() => get && coretimeInfo && estimateTime(currentRegionStart, get.blocks.relay(coretimeInfo?.status?.lastTimeslice), coretimeInfo.constants.relay)?.formattedDate, [currentRegionStart, coretimeInfo, get]); + const saleEndDate = useMemo(() => get && coretimeInfo && estimateTime(currentRegionEnd, get.blocks.relay(coretimeInfo?.status?.lastTimeslice), coretimeInfo.constants.relay)?.formattedDate, [currentRegionEnd, coretimeInfo, get]); + + const value = useMemo(() => { + if (!coretimeInfo || !currentRegionEnd || !currentRegionStart || !get || !saleEndDate || !saleStartDate) { + return initState; + } + + return { + coretimeInfo: coretimeInfo ?? null, + currentRegionEnd, + currentRegionStart, + get, + saleEndDate, + saleStartDate + }; + }, [coretimeInfo, currentRegionEnd, currentRegionStart, get, saleEndDate, saleStartDate]); return ( diff --git a/packages/page-coretime/src/CoretimePage.tsx b/packages/page-coretime/src/CoretimePage.tsx index 959f941b0c..1c12eaa9a8 100644 --- a/packages/page-coretime/src/CoretimePage.tsx +++ b/packages/page-coretime/src/CoretimePage.tsx @@ -3,7 +3,7 @@ import type { ApiPromise } from '@polkadot/api'; import type { TabItem } from '@polkadot/react-components/types'; -import type { ChainName } from './types.js'; +import type { RelayName } from './types.js'; import React, { useRef } from 'react'; import { Route, Routes } from 'react-router-dom'; @@ -39,7 +39,7 @@ function createItemsRef (t: (key: string, options?: { replace: Record { const { t } = useTranslation(); const itemsRef = useRef(createItemsRef(t)); - const chainName = useCall(isApiReady && api?.rpc.system.chain)?.toString().toLowerCase() as ChainName; + const relayName = useCall(isApiReady && api?.rpc.system.chain)?.toString().toLowerCase() as RelayName; return (
@@ -47,16 +47,16 @@ function CoretimePage ({ api, basePath, className, isApiReady }: Props): React.R basePath={basePath} items={itemsRef.current} /> - {!chainName + {!relayName ?
: } + element={} index /> } + element={} path='sale' /> diff --git a/packages/page-coretime/src/Overview/Summary.tsx b/packages/page-coretime/src/Overview/Summary.tsx index cd733b6fa7..05dff9c06c 100644 --- a/packages/page-coretime/src/Overview/Summary.tsx +++ b/packages/page-coretime/src/Overview/Summary.tsx @@ -1,8 +1,8 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { BrokerStatus, ChainConstants, CoreDescription, PalletBrokerConfigRecord, PalletBrokerSaleInfoRecord, RegionInfo } from '@polkadot/react-hooks/types'; -import type { ChainName } from '../types.js'; +import type { BrokerStatus, CoreDescription, PalletBrokerConfigRecord, PalletBrokerSaleInfoRecord, RegionInfo } from '@polkadot/react-hooks/types'; +import type { RelayName } from '../types.js'; import React, { useMemo } from 'react'; @@ -11,7 +11,7 @@ import { BN } from '@polkadot/util'; import { useCoretimeContext } from '../CoretimeContext.js'; import { useTranslation } from '../translate.js'; -import { estimateTime, FirstCycleStart } from '../utils/index.js'; +import { FirstCycleStart } from '../utils/index.js'; interface Props { coreDscriptors?: CoreDescription[]; @@ -20,25 +20,24 @@ interface Props { region: RegionInfo[], status: BrokerStatus, parachainCount: number - chainName: ChainName, - constants: ChainConstants + relayName: RelayName, } -function Summary ({ chainName, config, constants, parachainCount, saleInfo, status }: Props): React.ReactElement { +function Summary ({ config, parachainCount, relayName, status }: Props): React.ReactElement { const { t } = useTranslation(); - const currentRegionEnd = saleInfo.regionEnd - config.regionLength; - const currentRegionStart = saleInfo.regionEnd - config.regionLength * 2; - const { get } = useCoretimeContext(); + const { coretimeInfo, currentRegionEnd, currentRegionStart, saleEndDate, saleStartDate } = useCoretimeContext(); const saleNumber = useMemo(() => { - if (chainName && currentRegionEnd) { + if (relayName && currentRegionEnd) { return Math.floor( - (currentRegionEnd - FirstCycleStart.timeslice.coretime[chainName]) / config.regionLength + (currentRegionEnd - FirstCycleStart.timeslice.coretime[relayName]) / config.regionLength ); } return undefined; - }, [currentRegionEnd, chainName, config]); + }, [currentRegionEnd, relayName, config]); + + const timeslicesSinceCycleStart = useMemo(() => currentRegionEnd && new BN(config?.regionLength).sub((new BN(currentRegionEnd)).sub(new BN(status.lastTimeslice))), [status, config, currentRegionEnd]); return ( @@ -56,36 +55,46 @@ function Summary ({ chainName, config, constants, parachainCount, saleInfo, stat {parachainCount && parachainCount} - {config && status && - + {config && status && currentRegionEnd && saleEndDate && saleStartDate && timeslicesSinceCycleStart && coretimeInfo?.constants && + <> + + + + }
- {status && - ( -
-
{get && estimateTime(currentRegionStart, get.blocks.relay(status?.lastTimeslice), constants.relay)}
-
{get && estimateTime(currentRegionEnd, get.blocks.relay(status?.lastTimeslice), constants.relay)}
-
-
) - } - {status && - -
-
{currentRegionStart}
-
{currentRegionEnd}
-
-
- } + +
+
{saleStartDate}
+
{saleEndDate}
+
+
+ +
+
{currentRegionStart}
+
{currentRegionEnd}
+
+
+
); diff --git a/packages/page-coretime/src/Overview/index.tsx b/packages/page-coretime/src/Overview/index.tsx index abf18de638..51f0945aa6 100644 --- a/packages/page-coretime/src/Overview/index.tsx +++ b/packages/page-coretime/src/Overview/index.tsx @@ -1,7 +1,7 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { ChainName } from '../types.js'; +import type { RelayName } from '../types.js'; import React from 'react'; @@ -11,21 +11,20 @@ import Summary from './Summary.js'; interface Props { className?: string; - chainName: ChainName + relayName: RelayName } -function Overview ({ chainName, className }: Props): React.ReactElement { +function Overview ({ className, relayName }: Props): React.ReactElement { const { coretimeInfo } = useCoretimeContext(); return (
{coretimeInfo && ( @@ -33,6 +32,7 @@ function Overview ({ chainName, className }: Props): React.ReactElement { {!!coretimeInfo && }
diff --git a/packages/page-coretime/src/ParachainTableRow.tsx b/packages/page-coretime/src/ParachainTableRow.tsx index d1d4c75a55..7550c3b7f0 100644 --- a/packages/page-coretime/src/ParachainTableRow.tsx +++ b/packages/page-coretime/src/ParachainTableRow.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { ChainInformation, ChainWorkTaskInformation } from '@polkadot/react-hooks/types'; +import type { RelayName } from './types.js'; import React from 'react'; @@ -15,9 +16,10 @@ interface Props { regionEnd: number regionBegin: number lastCommittedTimeslice: number + relayName: RelayName } -function ParachainTableRow ({ chain, lastCommittedTimeslice, regionBegin, regionEnd }: Props): React.ReactElement { +function ParachainTableRow ({ chain, lastCommittedTimeslice, regionBegin, regionEnd, relayName }: Props): React.ReactElement { const [isExpanded, toggleIsExpanded] = useToggle(false); const info = chain.workTaskInfo; const firstRecord = chain.workTaskInfo[0]; @@ -38,6 +40,7 @@ function ParachainTableRow ({ chain, lastCommittedTimeslice, regionBegin, region lease={chain.lease} regionBegin={regionBegin} regionEnd={regionEnd} + relayName={relayName} /> {idx === 0 && diff --git a/packages/page-coretime/src/ParachainsTable.tsx b/packages/page-coretime/src/ParachainsTable.tsx index 1524eb9061..21ca902a72 100644 --- a/packages/page-coretime/src/ParachainsTable.tsx +++ b/packages/page-coretime/src/ParachainsTable.tsx @@ -1,6 +1,8 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { RelayName } from './types.js'; + import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Table } from '@polkadot/react-components'; @@ -12,9 +14,10 @@ import { useTranslation } from './translate.js'; interface Props { coretimeInfo: CoretimeInformation + relayName: RelayName } -function ParachainsTable ({ coretimeInfo }: Props): React.ReactElement { +function ParachainsTable ({ coretimeInfo, relayName }: Props): React.ReactElement { const { t } = useTranslation(); const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([ [t('parachains'), 'start'], @@ -22,7 +25,7 @@ function ParachainsTable ({ coretimeInfo }: Props): React.ReactElement { [t('core number'), 'start'], [t('type'), 'start'], [t('last block'), 'start media--800'], - [t('end'), 'start media--1000'], + [t('end date (approx)'), 'start media--1000'], [t('renewal'), 'start media--1200'], [t('renewal price'), 'start media--1200'], [t('links'), 'start media--800'], @@ -67,6 +70,7 @@ function ParachainsTable ({ coretimeInfo }: Props): React.ReactElement { lastCommittedTimeslice={coretimeInfo.status.lastCommittedTimeslice} regionBegin={coretimeInfo.salesInfo.regionBegin} regionEnd={coretimeInfo.salesInfo.regionEnd} + relayName={relayName} /> ); })} diff --git a/packages/page-coretime/src/Row.tsx b/packages/page-coretime/src/Row.tsx index 21f4eb14b0..621093b36e 100644 --- a/packages/page-coretime/src/Row.tsx +++ b/packages/page-coretime/src/Row.tsx @@ -3,6 +3,7 @@ import type { FlagColor } from '@polkadot/react-components/types'; import type { ChainWorkTaskInformation, LegacyLease } from '@polkadot/react-hooks/types'; +import type { RelayName } from './types.js'; import React from 'react'; @@ -22,6 +23,7 @@ interface Props { lastCommittedTimeslice: number lease: LegacyLease | undefined highlight?: boolean + relayName: RelayName } interface StyledCellProps { @@ -50,16 +52,34 @@ const StyledMarkWarning = styled(MarkWarning)` const EXPIRES_IN_DAYS = 7; -function Row ({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease, regionBegin, regionEnd }: Props): React.ReactElement { - const chainRegionEnd = (chainRecord.renewalStatus === ChainRenewalStatus.Renewed ? regionEnd : regionBegin); +function Row ({ chainRecord, highlight = false, id, lastCommittedTimeslice, lease, regionBegin, regionEnd, relayName }: Props): React.ReactElement { + // Group status checks + const { renewalStatus } = chainRecord; + const isRenewed = renewalStatus === ChainRenewalStatus.Renewed; + const isEligible = renewalStatus === ChainRenewalStatus.Eligible; + const chainRegionEnd = isRenewed ? regionEnd : regionBegin; + + const renewalLink = isEligible && ( + + Renew on RegionX + + ); + + const renewalValue = isRenewed + ? chainRecord.renewalStatusMessage + : (isEligible ? renewalLink : '-'); + const targetTimeslice = lease?.until || chainRegionEnd; const showEstimates = !!targetTimeslice && Object.values(CoreTimeTypes)[chainRecord.type] !== CoreTimeTypes.Reservation; const { coretimeInfo, get } = useCoretimeContext(); - const estimatedTime = showEstimates && get && coretimeInfo && - estimateTime(targetTimeslice, get.blocks.relay(lastCommittedTimeslice), coretimeInfo?.constants?.relay); + const estimatedTime = showEstimates && get && coretimeInfo && estimateTime(targetTimeslice, get.blocks.relay(lastCommittedTimeslice), coretimeInfo?.constants?.relay); - const isWithinWeek = estimatedTime && new Date(estimatedTime).getTime() - Date.now() < EXPIRES_IN_DAYS * 24 * 60 * 60 * 1000; + const isWithinWeek = !!estimatedTime && new Date(estimatedTime.timestamp).getTime() - Date.now() < EXPIRES_IN_DAYS * 24 * 60 * 60 * 1000; const isReservation = chainRecord.type === CoreTimeTypes.Reservation; return ( @@ -83,9 +103,9 @@ function Row ({ chainRecord, highlight = false, id, lastCommittedTimeslice, leas {showEstimates && chainRecord?.lastBlock && + >{showEstimates && chainRecord?.lastBlock && relayName && {formatNumber(chainRecord?.lastBlock)} @@ -97,7 +117,7 @@ function Row ({ chainRecord, highlight = false, id, lastCommittedTimeslice, leas style={{ whiteSpace: 'nowrap' }} > - {estimatedTime} + {estimatedTime && estimatedTime?.formattedDate} {!!isWithinWeek && !isReservation && chainRecord?.renewalStatus !== ChainRenewalStatus.Renewed && ( {chainRecord?.renewalStatus === ChainRenewalStatus.Renewed ? chainRecord.renewalStatusMessage : chainRecord.renewalStatus} + >{renewalValue} { +const SaleDetailsView = ({ chosenSaleNumber, relayName, saleParams }: { saleParams: SaleParameters, chosenSaleNumber: number, relayName: RelayName }) => { const { t } = useTranslation(); const subscanPriceGraphUrl = useMemo(() => - `https://coretime-${chainName}.subscan.io/coretime_dashboard` - , [chainName]); + `https://coretime-${relayName}.subscan.io/coretime_dashboard` + , [relayName]); if (chosenSaleNumber === -1 || !saleParams) { return null; @@ -127,14 +127,14 @@ const SaleDetailsView = ({ chainName, chosenSaleNumber, saleParams }: { salePara target='_blank' >Sale Purchase Graph ( diff --git a/packages/page-coretime/src/Sale/Summary.tsx b/packages/page-coretime/src/Sale/Summary.tsx index f03dd450b9..c0c04e3491 100644 --- a/packages/page-coretime/src/Sale/Summary.tsx +++ b/packages/page-coretime/src/Sale/Summary.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { ApiPromise } from '@polkadot/api'; -import type { BrokerStatus, ChainConstants, CoreDescription, PalletBrokerConfigRecord, PalletBrokerSaleInfoRecord, RegionInfo } from '@polkadot/react-hooks/types'; +import type { BrokerStatus, CoreDescription, PalletBrokerConfigRecord, PalletBrokerSaleInfoRecord, RegionInfo } from '@polkadot/react-hooks/types'; import React from 'react'; @@ -11,7 +11,7 @@ import { BN, formatNumber } from '@polkadot/util'; import { useCoretimeContext } from '../CoretimeContext.js'; import { useTranslation } from '../translate.js'; -import { estimateTime, getCurrentRegionStartEndTs } from '../utils/index.js'; +import { getCurrentRegionStartEndTs } from '../utils/index.js'; interface Props { api: ApiPromise | null, @@ -21,15 +21,12 @@ interface Props { region: RegionInfo[], status: BrokerStatus, saleNumber: number, - constants: ChainConstants } -function Summary ({ config, constants, saleInfo, saleNumber, status }: Props): React.ReactElement { +function Summary ({ config, saleInfo, saleNumber, status }: Props): React.ReactElement { const { t } = useTranslation(); const { currentRegionEndTs, currentRegionStartTs } = getCurrentRegionStartEndTs(saleInfo, config.regionLength); - const { get } = useCoretimeContext(); - const cycleEnd = get && estimateTime(currentRegionEndTs, get.blocks.relay(status?.lastTimeslice), constants.relay); - const cycleStart = get && estimateTime(currentRegionStartTs, get.blocks.relay(status?.lastTimeslice), constants.relay); + const { get, saleEndDate, saleStartDate } = useCoretimeContext(); return ( @@ -45,7 +42,7 @@ function Summary ({ config, constants, saleInfo, saleNumber, status }: Props): R {`${saleInfo?.coresSold} / ${saleInfo?.coresOffered}`} -
{cycleEnd}
+
{saleEndDate}
{get && formatNumber(get.blocks.relay(currentRegionEndTs))}
@@ -70,8 +67,8 @@ function Summary ({ config, constants, saleInfo, saleNumber, status }: Props): R {status && (
-
{cycleStart}
-
{cycleEnd}
+
{saleStartDate}
+
{saleEndDate}
) } diff --git a/packages/page-coretime/src/Sale/boxes/Cores.tsx b/packages/page-coretime/src/Sale/boxes/Cores.tsx index 67dbdf73d1..bb1cf01e99 100644 --- a/packages/page-coretime/src/Sale/boxes/Cores.tsx +++ b/packages/page-coretime/src/Sale/boxes/Cores.tsx @@ -19,8 +19,7 @@ export const Cores = ({ color, phaseName, salesInfo }: { phaseName: string, sale const { t } = useTranslation(); const { apiCoretime } = useApi(); const bestNumberFinalized = useCall(apiCoretime?.derive.chain.bestNumberFinalized); - const soldOut = useMemo(() => salesInfo.coresOffered === salesInfo.coresSold, [salesInfo.coresOffered, salesInfo.coresSold]); - const coretimePrice = useMemo(() => bestNumberFinalized && getCorePriceAt(bestNumberFinalized.toNumber(), salesInfo), [salesInfo, bestNumberFinalized]); + const coretimePrice = useMemo(() => bestNumberFinalized && salesInfo && getCorePriceAt(bestNumberFinalized.toNumber(), salesInfo), [salesInfo, bestNumberFinalized]); const CoresWrapper = styled(WhiteBox)` justify-self: flex-end; @@ -39,10 +38,7 @@ export const Cores = ({ color, phaseName, salesInfo }: { phaseName: string, sale ) : (
- {soldOut && ( -

{t('All cores are sold out')}

- )} - {!soldOut && ( + {

{t('current price')}

@@ -53,7 +49,7 @@ export const Cores = ({ color, phaseName, salesInfo }: { phaseName: string, sale

{salesInfo.coresOffered - salesInfo.coresSold}

- )} + }
)} diff --git a/packages/page-coretime/src/Sale/boxes/Timeline.tsx b/packages/page-coretime/src/Sale/boxes/Timeline.tsx index cf79088218..6073f637eb 100644 --- a/packages/page-coretime/src/Sale/boxes/Timeline.tsx +++ b/packages/page-coretime/src/Sale/boxes/Timeline.tsx @@ -11,7 +11,7 @@ import { CardSummary, ProgressBar, styled, SummaryBox } from '@polkadot/react-co import { formatBalance, formatNumber } from '@polkadot/util'; import { useTranslation } from '../../translate.js'; -import { getSaleProgress } from '../../utils/sale.js'; +import { getCorePriceAt, getSaleProgress } from '../../utils/sale.js'; import { WhiteBox } from '../../WhiteBox.js'; const TimelineWrapper = styled(WhiteBox)` @@ -43,6 +43,12 @@ export const Timeline = ({ color, coretimeInfo: { salesInfo, status }, phaseName salesInfo.regionBegin), [saleParams, status.lastTimeslice, salesInfo.regionBegin]); + const coretimePriceStart = useMemo(() => salesInfo && getCorePriceAt(salesInfo.saleStart, salesInfo), [salesInfo]); + + const endPrice = useMemo(() => salesInfo.endPrice.toNumber() < 10 ** formatBalance.getDefaults().decimals + ? `${salesInfo.endPrice.toNumber() / 10 ** formatBalance.getDefaults().decimals} ${formatBalance.getDefaults().unit}` + : formatBalance(salesInfo.endPrice), [salesInfo.endPrice]); + return (

{t('Sale timeline')}

@@ -53,7 +59,8 @@ export const Timeline = ({ color, coretimeInfo: { salesInfo, status }, phaseName {saleParams?.phaseConfig?.config[phaseName as keyof typeof saleParams.phaseConfig.config].end.date} {formatNumber(saleParams?.phaseConfig?.config[phaseName as keyof typeof saleParams.phaseConfig.config].end.blocks.relay)} } - {formatBalance(salesInfo.endPrice)} + {formatBalance(coretimePriceStart)} + {endPrice}
diff --git a/packages/page-coretime/src/Sale/index.tsx b/packages/page-coretime/src/Sale/index.tsx index 48061ae8f3..4ae0443553 100644 --- a/packages/page-coretime/src/Sale/index.tsx +++ b/packages/page-coretime/src/Sale/index.tsx @@ -1,7 +1,7 @@ // Copyright 2017-2025 @polkadot/app-coretime authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { ChainName, SaleParameters } from '../types.js'; +import type { RelayName, SaleParameters } from '../types.js'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; @@ -19,7 +19,7 @@ import SaleDetailsView from './SaleDetailsView.js'; import Summary from './Summary.js'; interface Props { - chainName: ChainName + relayName: RelayName } const ResponsiveGrid = styled.div` @@ -46,7 +46,7 @@ const ResponsiveGrid = styled.div` } `; -function Sale ({ chainName }: Props): React.ReactElement { +function Sale ({ relayName }: Props): React.ReactElement { const { coretimeInfo } = useCoretimeContext(); const { api, apiEndpoint, isApiReady } = useApi(); const { t } = useTranslation(); @@ -71,11 +71,11 @@ function Sale ({ chainName }: Props): React.ReactElement { if (coretimeInfo && !saleParams) { setSaleParams(getSaleParameters( coretimeInfo, - chainName, + relayName, lastCommittedTimeslice ?? 0 )); } - }, [coretimeInfo, saleParams, lastCommittedTimeslice, chainName]); + }, [coretimeInfo, saleParams, lastCommittedTimeslice, relayName]); const phaseName = useMemo(() => saleParams?.phaseConfig?.currentPhaseName, [saleParams]); @@ -87,9 +87,9 @@ function Sale ({ chainName }: Props): React.ReactElement { return; } - setSelectedSaleParams(getSaleParameters(coretimeInfo, chainName, lastCommittedTimeslice ?? 0, value)); + setSelectedSaleParams(getSaleParameters(coretimeInfo, relayName, lastCommittedTimeslice ?? 0, value)); } - }, [coretimeInfo, chainName, lastCommittedTimeslice]); + }, [coretimeInfo, relayName, lastCommittedTimeslice]); return (
@@ -97,7 +97,6 @@ function Sale ({ chainName }: Props): React.ReactElement { {
{saleParams && selectedSaleParams && } diff --git a/packages/page-coretime/src/types.ts b/packages/page-coretime/src/types.ts index 13ee82c063..8a28308d17 100644 --- a/packages/page-coretime/src/types.ts +++ b/packages/page-coretime/src/types.ts @@ -83,7 +83,7 @@ export interface RegionInfo { regionEnd: number; } -export type ChainName = 'kusama' | 'polkadot' | 'paseo testnet' | 'westend' +export type RelayName = 'kusama' | 'polkadot' | 'paseo testnet' | 'westend' export interface GetResponse { blocks: { diff --git a/packages/page-coretime/src/utils/index.ts b/packages/page-coretime/src/utils/index.ts index 9220830a23..7276e01f20 100644 --- a/packages/page-coretime/src/utils/index.ts +++ b/packages/page-coretime/src/utils/index.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { ChainBlockConstants, ChainConstants, CoretimeInformation } from '@polkadot/react-hooks/types'; -import type { ChainName, GetResponse, RegionInfo } from '../types.js'; +import type { GetResponse, RegionInfo, RelayName } from '../types.js'; import { CoreTimeTypes } from '@polkadot/react-hooks/constants'; import { BN } from '@polkadot/util'; @@ -11,7 +11,7 @@ type FirstCycleStartType = Record< 'block' | 'timeslice', Record< 'coretime', -Record +Record > >; @@ -41,11 +41,18 @@ export const coretimeTypeColours: Record = { [CoreTimeTypes['Bulk Coretime']]: 'pink' }; -export function formatDate (date: Date) { +export function formatDate (date: Date, time = false) { const day = date.getDate(); const month = date.toLocaleString('default', { month: 'short' }); const year = date.getFullYear(); + if (time) { + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + + return `${day} ${month} ${year}, ${hours}:${minutes}`; + } + return `${day} ${month} ${year}`; } @@ -71,7 +78,7 @@ export const estimateTime = ( targetTimeslice: string | number, latestBlock: number, { blocksPerTimeslice: blocksPerTs, blocktimeMs }: ChainBlockConstants -): string | null => { +): { timestamp: number, formattedDate: string } | null => { if (!latestBlock || !targetTimeslice) { console.error('Invalid input: one or more inputs are missing'); @@ -88,7 +95,10 @@ export const estimateTime = ( const timeDifference = blockDifference.mul(blockTime); const estTimestamp = now.add(timeDifference); - return formatDate(new Date(estTimestamp.toNumber())); + return { + formattedDate: formatDate(new Date(estTimestamp.toNumber())), + timestamp: estTimestamp.toNumber() + }; } catch (error) { console.error('Error in calculation:', error); diff --git a/packages/page-coretime/src/utils/sale.ts b/packages/page-coretime/src/utils/sale.ts index cb5cf7f1ec..34df2c20fd 100644 --- a/packages/page-coretime/src/utils/sale.ts +++ b/packages/page-coretime/src/utils/sale.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { ChainConstants, PalletBrokerConfigRecord, PalletBrokerSaleInfoRecord } from '@polkadot/react-hooks/types'; -import type { ChainName, GetResponse, PhaseConfig, RegionInfo, SaleParameters } from '../types.js'; +import type { GetResponse, PhaseConfig, RegionInfo, RelayName, SaleParameters } from '../types.js'; import { type ProgressBarSection } from '@polkadot/react-components/types'; import { BN } from '@polkadot/util'; @@ -69,14 +69,14 @@ export const getCorePriceAt = (blockNow: number | null, saleInfo: PalletBrokerSa */ export const getCurrentSaleNumber = ( currentRegionEnd: number, - chainName: ChainName, + relayName: RelayName, config: Pick ): number => { - if (!chainName || !currentRegionEnd) { + if (!relayName || !currentRegionEnd) { return -1; } - return Math.ceil((currentRegionEnd - FirstCycleStart.timeslice.coretime[chainName]) / config.regionLength); + return Math.ceil((currentRegionEnd - FirstCycleStart.timeslice.coretime[relayName]) / config.regionLength); }; export const determinePhaseName = ( @@ -164,11 +164,11 @@ const getPhaseConfiguration = ( const fixedPriceLengthTs = regionLength - interludeLengthTs - leadInLengthTs; const fixedPriceEndTs = priceDiscoveryEndTs + fixedPriceLengthTs; const get = createGet(constants); - const getDate = (ts: number) => estimateTime(ts, get.blocks.relay(lastCommittedTimeslice) ?? 0, constants.relay); + const getDate = (ts: number) => estimateTime(ts, get.blocks.relay(lastCommittedTimeslice), constants.relay)?.formattedDate ?? null; return { config: { - [PhaseName.FixedPrice]: makeConfig(fixedPriceEndTs, fixedPriceEndTs, get, getDate, PhaseName.FixedPrice), + [PhaseName.FixedPrice]: makeConfig(priceDiscoveryEndTs, fixedPriceEndTs, get, getDate, PhaseName.FixedPrice), [PhaseName.PriceDiscovery]: makeConfig(renewalsEndTs, priceDiscoveryEndTs, get, getDate, PhaseName.PriceDiscovery), [PhaseName.Renewals]: makeConfig(currentRegionStart, renewalsEndTs, get, getDate, PhaseName.Renewals) }, @@ -178,7 +178,7 @@ const getPhaseConfiguration = ( export const getSaleParameters = ( { config, constants, salesInfo }: {salesInfo: RegionInfo, config: Pick, constants: ChainConstants}, - chainName: ChainName, + relayName: RelayName, lastCommittedTimeslice: number, chosenSaleNumber = -1 ): SaleParameters => { @@ -186,30 +186,30 @@ export const getSaleParameters = ( const interludeLengthTs = get.timeslices.coretime(config.interludeLength); const leadInLengthTs = get.timeslices.coretime(config.leadinLength); let { currentRegionEndTs, currentRegionStartTs } = getCurrentRegionStartEndTs(salesInfo, config.regionLength); - const getDate = (ts: number) => estimateTime(ts, get.blocks.relay(lastCommittedTimeslice), constants.relay) || ''; - const saleNumber = getCurrentSaleNumber(currentRegionEndTs, chainName, config); + const getDate = (ts: number) => estimateTime(ts, get.blocks.relay(lastCommittedTimeslice), constants.relay)?.formattedDate ?? null; + const saleNumber = getCurrentSaleNumber(currentRegionEndTs, relayName, config); let currentRegionInfo: SaleParameters['currentRegion']; if (chosenSaleNumber !== -1) { // A hack for Kusama as one of the sales had an unusual length // checked against Subscan historical sales - if (chainName === 'kusama') { + if (relayName === 'kusama') { const irregularRegionLength = 848; if (chosenSaleNumber === 0) { - currentRegionStartTs = FirstCycleStart.timeslice.coretime[chainName]; + currentRegionStartTs = FirstCycleStart.timeslice.coretime[relayName]; currentRegionEndTs = currentRegionStartTs + config.regionLength; } else if (chosenSaleNumber === 1) { - currentRegionStartTs = FirstCycleStart.timeslice.coretime[chainName] + config.regionLength; + currentRegionStartTs = FirstCycleStart.timeslice.coretime[relayName] + config.regionLength; // that particular sale #2 was only 848 blocks long currentRegionEndTs = currentRegionStartTs + irregularRegionLength; } else { - currentRegionStartTs = FirstCycleStart.timeslice.coretime[chainName] + config.regionLength * (chosenSaleNumber - 1) + irregularRegionLength; + currentRegionStartTs = FirstCycleStart.timeslice.coretime[relayName] + config.regionLength * (chosenSaleNumber - 1) + irregularRegionLength; currentRegionEndTs = currentRegionStartTs + config.regionLength; } } else { - currentRegionStartTs = FirstCycleStart.timeslice.coretime[chainName] + config.regionLength * chosenSaleNumber; + currentRegionStartTs = FirstCycleStart.timeslice.coretime[relayName] + config.regionLength * chosenSaleNumber; currentRegionEndTs = currentRegionStartTs + config.regionLength; } @@ -220,7 +220,7 @@ export const getSaleParameters = ( coretime: 0, relay: get.blocks.relay(currentRegionEndTs) }, - date: getDate(currentRegionEndTs), + date: getDate(currentRegionEndTs) ?? '', ts: currentRegionEndTs }, start: { @@ -229,7 +229,7 @@ export const getSaleParameters = ( coretime: 0, relay: get.blocks.relay(currentRegionStartTs) }, - date: getDate(currentRegionStartTs), + date: getDate(currentRegionStartTs) ?? '', ts: currentRegionStartTs } }; @@ -267,7 +267,7 @@ export const getSaleParameters = ( coretime: 0, relay: currentRegionInfo.end.blocks.relay + get.blocks.relay(config.regionLength) }, - date: estimateTime(currentRegionInfo.end.ts + config.regionLength, get.blocks.relay(lastCommittedTimeslice), constants.relay), + date: estimateTime(currentRegionInfo.end.ts + config.regionLength, get.blocks.relay(lastCommittedTimeslice), constants.relay)?.formattedDate ?? null, ts: currentRegionInfo.end.ts + config.regionLength }, start: { diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index 332fbebed8..929ff384d1 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -35,6 +35,7 @@ export { useCallMulti } from './useCallMulti.js'; export { useCollectiveInstance } from './useCollectiveInstance.js'; export { useCollectiveMembers } from './useCollectiveMembers.js'; export { useCoreDescriptor } from './useCoreDescriptor.js'; +export { useCoretimeConsts } from './useCoretimeConsts.js'; export { useCoretimeEndpoint } from './useCoretimeEndpoint.js'; export { useCoretimeInformation } from './useCoretimeInformation.js'; export { useDebounce } from './useDebounce.js';