date-fns#differenceInSeconds TypeScript Examples
The following examples show how to use
date-fns#differenceInSeconds.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: AuctionProgress.tsx From glide-frontend with GNU General Public License v3.0 | 6 votes |
AuctionProgress: React.FC<{ auction: Auction }> = ({ auction }) => {
const [progress, setProgress] = useState<number>(0)
const { slowRefresh } = useRefresh()
// Note: opted to base it on date rather than block number to reduce the amount of calls and async handling
useEffect(() => {
if (auction.status === AuctionStatus.ToBeAnnounced || auction.status === AuctionStatus.Pending) {
setProgress(0)
} else {
const now = new Date()
const auctionDuration = differenceInSeconds(auction.endDate, auction.startDate)
const secondsPassed = differenceInSeconds(now, auction.startDate)
const percentagePassed = (secondsPassed * 100) / auctionDuration
setProgress(percentagePassed < 100 ? percentagePassed : 100)
}
}, [slowRefresh, auction])
return <Progress variant="flat" primaryStep={progress} />
}
Example #2
Source File: timeParser.ts From tempomat with MIT License | 6 votes |
function intervalToSeconds(startTime: string, endTime: string, referenceDate: Date): number | null {
var start = parseTime(startTime, referenceDate)
var end = parseTime(endTime, referenceDate)
if (!start || !end) return null
const diff = differenceInSeconds(end, start)
if (isAfter(end, start)) {
return diff
} else {
const dayInSeconds = 86400
return dayInSeconds + diff
}
}
Example #3
Source File: AuctionProgress.tsx From vvs-ui with GNU General Public License v3.0 | 6 votes |
AuctionProgress: React.FC<{ auction: Auction }> = ({ auction }) => {
const [progress, setProgress] = useState<number>(0)
const { slowRefresh } = useRefresh()
// Note: opted to base it on date rather than block number to reduce the amount of calls and async handling
useEffect(() => {
if (auction.status === AuctionStatus.ToBeAnnounced || auction.status === AuctionStatus.Pending) {
setProgress(0)
} else {
const now = new Date()
const auctionDuration = differenceInSeconds(auction.endDate, auction.startDate)
const secondsPassed = differenceInSeconds(now, auction.startDate)
const percentagePassed = (secondsPassed * 100) / auctionDuration
setProgress(percentagePassed < 100 ? percentagePassed : 100)
}
}, [slowRefresh, auction])
return <Progress variant="flat" primaryStep={progress} />
}
Example #4
Source File: TransactionDisplay.tsx From anchor-web-app with Apache License 2.0 | 5 votes |
function TransactionDisplayBase(props: TransactionDisplayProps) {
const { className, tx } = props;
const [isCopied, setCopied] = useClipboard(tx.txHash, {
successDuration: 2000,
});
const [countdown, { start, stop }] = useCountdown({
seconds: differenceInSeconds(new Date(), tx.display.timestamp),
interval: 1000,
isIncrement: true,
});
useEffect(() => {
start();
return () => {
stop();
};
//eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className={className} key={tx.txHash}>
<div className="details">
<span className="action">{formatTxKind(tx.display.txKind)}</span>
<div className="amount">{tx.display.amount ?? 'Unknown'}</div>
</div>
<div className="more-details">
<span className="tx-hash" onClick={setCopied}>
<span className="hash">{truncateEvm(tx.txHash)}</span>
{isCopied && (
<IconSpan className="copy">
<Check /> Copied
</IconSpan>
)}
</span>
<div className="timestamp">
{formatEllapsedSimple(countdown * 1000)}
</div>
</div>
<TransactionProgress
stepCount={txStepCount(tx)}
currStep={tx.lastEventKind}
/>
</div>
);
}
Example #5
Source File: deployment.service.ts From amplication with Apache License 2.0 | 5 votes |
/**
* Gets the updated status of running deployments from
* DeployerService, and updates the step and deployment status. This function should
* be called periodically from an external scheduler
*/
async updateRunningDeploymentsStatus(): Promise<void> {
const lastUpdateThreshold = subSeconds(
new Date(),
DEPLOY_STATUS_FETCH_INTERVAL_SEC
);
//find all deployments that are still running
const deployments = await this.findMany({
where: {
statusUpdatedAt: {
lt: lastUpdateThreshold
},
status: {
equals: EnumDeploymentStatus.Waiting
}
},
include: ACTION_INCLUDE
});
await Promise.all(
deployments.map(async deployment => {
const steps = await this.actionService.getSteps(deployment.actionId);
const deployStep = steps.find(step => step.name === DEPLOY_STEP_NAME);
const destroyStep = steps.find(step => step.name === DESTROY_STEP_NAME);
const currentStep = destroyStep || deployStep; //when destroy step exist it is the current one
try {
const result = await this.deployerService.getStatus(
deployment.statusQuery
);
//To avoid too many messages in the log, if the status is still "running" handle the results only if the bigger interval passed
if (
result.status !== EnumDeployStatus.Running ||
differenceInSeconds(new Date(), deployment.statusUpdatedAt) >
DEPLOY_STATUS_UPDATE_INTERVAL_SEC
) {
this.logger.info(
`Deployment ${deployment.id}: current status ${result.status}`
);
const updatedDeployment = await this.handleDeployResult(
deployment,
currentStep,
result
);
return updatedDeployment.status;
} else {
return deployment.status;
}
} catch (error) {
await this.actionService.logInfo(currentStep, error);
await this.actionService.complete(
currentStep,
EnumActionStepStatus.Failed
);
const status = EnumDeploymentStatus.Failed;
await this.updateStatus(deployment.id, status);
}
})
);
}
Example #6
Source File: AuctionTimer.tsx From glide-frontend with GNU General Public License v3.0 | 5 votes |
AuctionTimer: React.FC<{ auction: Auction }> = ({ auction }) => {
const { t } = useTranslation()
if (!auction) {
return (
<Flex justifyContent="center" alignItems="center" mb="48px">
<Skeleton width="256px" height="40px" />
</Flex>
)
}
if (auction.status === AuctionStatus.ToBeAnnounced || auction.status === AuctionStatus.Closed) {
return null
}
if (auction.status === AuctionStatus.Finished) {
return (
<Flex justifyContent="center" alignItems="center" mb="48px">
<Text bold>{t('Closing')}...</Text>
<PocketWatchIcon height="40px" width="40px" />
</Flex>
)
}
const { startDate, endDate } = auction
const timerUntil = isAfter(startDate, new Date()) ? startDate : endDate
const timerTitle = timerUntil === endDate ? t('Ending in') : t('Next auction')
const secondsRemaining = differenceInSeconds(timerUntil, new Date())
const { days, hours, minutes } = getTimePeriods(secondsRemaining)
return (
<Flex justifyContent="center" alignItems="center" mb="48px">
<Text bold>{timerTitle}: </Text>
<AuctionCountDown>
{days !== 0 && (
<>
<Text verticalAlign="baseline" lineHeight="28px" fontSize="24px" bold color="secondary" mr="4px">
{days}
</Text>
<Text verticalAlign="baseline" bold mr="4px">
{t('d')}
</Text>
</>
)}
<Text verticalAlign="baseline" lineHeight="28px" fontSize="24px" bold color="secondary" mr="4px">
{hours}
</Text>
<Text verticalAlign="baseline" bold mr="4px">
{t('h')}
</Text>
<Text verticalAlign="baseline" lineHeight="28px" fontSize="24px" bold color="secondary" mr="4px">
{minutes}
</Text>
<Text verticalAlign="baseline" bold>
{t('m')}
</Text>
</AuctionCountDown>
<PocketWatchIcon height="40px" width="40px" />
</Flex>
)
}
Example #7
Source File: CountdownBar.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 5 votes |
CountdownBar: FC<Props> = ({ className, width = 150, percentage = 0, end, color, tip, textColor }) => {
const [value, setValue] = useState((percentage / 100) * width)
const endDate = new Date(end)
const dateDifference = differenceInSeconds(endDate, new Date())
const timeMultiplier = 60 // minute
const interval = ((((100 - percentage) / 100) * width) / dateDifference) * timeMultiplier
const renderer = ({ days: total, hours, minutes, completed }: CountdownRenderProps): ReactElement => {
const years = Math.floor(total / YEAR)
const months = Math.floor((total % YEAR) / MONTH)
const weeks = Math.floor((total % MONTH) / WEEK)
const days = total % 7
return (
<Time color={textColor}>
{completed
? `Complete`
: `${formatLabel(years, 'y')}
${formatLabel(months, 'm')}
${formatLabel(weeks, 'w')}
${formatLabel(days, 'd')}
${hours}h
${minutes}m`}
</Time>
)
}
useInterval(() => {
setValue(value - interval <= 0 ? 0 : value - interval)
}, 1000 * timeMultiplier)
return (
<Container className={className}>
<Progress style={{ width: `${width}px` }} color={color}>
<div style={{ width: `${value}px` }} />
</Progress>
<Countdown date={end} renderer={renderer} />
{tip && <StyledTooltip tip={tip} />}
</Container>
)
}
Example #8
Source File: AuctionTimer.tsx From vvs-ui with GNU General Public License v3.0 | 5 votes |
AuctionTimer: React.FC<{ auction: Auction }> = ({ auction }) => {
const { t } = useTranslation()
if (!auction) {
return (
<Flex justifyContent="center" alignItems="center" mb="48px">
<Skeleton width="256px" height="40px" />
</Flex>
)
}
if (auction.status === AuctionStatus.ToBeAnnounced || auction.status === AuctionStatus.Closed) {
return null
}
if (auction.status === AuctionStatus.Finished) {
return (
<Flex justifyContent="center" alignItems="center" mb="48px">
<Text bold>{t('Closing')}...</Text>
<PocketWatchIcon height="40px" width="40px" />
</Flex>
)
}
const { startDate, endDate } = auction
const timerUntil = isAfter(startDate, new Date()) ? startDate : endDate
const timerTitle = timerUntil === endDate ? t('Ending in') : t('Next auction')
const secondsRemaining = differenceInSeconds(timerUntil, new Date())
const { days, hours, minutes } = getTimePeriods(secondsRemaining)
return (
<Flex justifyContent="center" alignItems="center" mb="48px">
<Text bold>{timerTitle}: </Text>
<AuctionCountDown>
{days !== 0 && (
<>
<Text verticalAlign="baseline" lineHeight="28px" fontSize="24px" bold color="secondary" mr="4px">
{days}
</Text>
<Text verticalAlign="baseline" bold mr="4px">
{t('d')}
</Text>
</>
)}
<Text verticalAlign="baseline" lineHeight="28px" fontSize="24px" bold color="secondary" mr="4px">
{hours}
</Text>
<Text verticalAlign="baseline" bold mr="4px">
{t('h')}
</Text>
<Text verticalAlign="baseline" lineHeight="28px" fontSize="24px" bold color="secondary" mr="4px">
{minutes}
</Text>
<Text verticalAlign="baseline" bold>
{t('m')}
</Text>
</AuctionCountDown>
<PocketWatchIcon height="40px" width="40px" />
</Flex>
)
}
Example #9
Source File: NftWidget.hooks.ts From atlas with GNU General Public License v3.0 | 4 votes |
useNftWidget = (videoId?: string): UseNftWidgetReturn => {
const { activeMemberId } = useUser()
const { nft, nftStatus } = useNft(videoId ?? '', { pollInterval: POLL_INTERVAL })
const {
isOwner,
englishTimerState,
canWithdrawBid,
needsSettling,
auctionPlannedEndDate,
userBid,
startsAtDate,
isUserTopBidder,
userBidUnlockDate,
saleType,
startsAtBlock,
canChangeBid,
isUserWhitelisted,
plannedEndAtBlock,
hasTimersLoaded,
} = useNftState(nft)
const { bids: userBids } = useBids(
{
where: {
isCanceled_eq: false,
nft: { id_eq: nft?.id },
bidder: { id_eq: activeMemberId },
},
},
{
fetchPolicy: 'cache-and-network',
skip: !nft?.id || !activeMemberId,
onError: (error) =>
SentryLogger.error('Failed to fetch member bids', 'useNftState', error, {
data: {
nft: nft?.id,
member: activeMemberId,
},
}),
}
)
const unwithdrawnUserBids = userBids?.filter(
(bid) =>
bid.auction.auctionType.__typename === 'AuctionTypeOpen' &&
(nftStatus?.status !== 'auction' || bid.auction.id !== nftStatus.auctionId) &&
bid.auction.winningMemberId !== activeMemberId
)
const bidFromPreviousAuction = unwithdrawnUserBids?.[0]
const owner = nft?.ownerMember
const { url: ownerAvatarUri } = useMemberAvatar(owner)
const { url: topBidderAvatarUri } = useMemberAvatar(nftStatus?.status === 'auction' ? nftStatus.topBidder : undefined)
const { entries: nftHistory } = useNftHistoryEntries(videoId || null, { pollInterval: POLL_INTERVAL })
switch (nftStatus?.status) {
case 'auction': {
return {
ownerHandle: owner?.handle,
ownerAvatarUri,
isOwner,
needsSettling,
bidFromPreviousAuction,
nftStatus: {
...nftStatus,
startsAtDate,
canWithdrawBid,
canChangeBid,
englishTimerState,
auctionPlannedEndDate,
topBidderAvatarUri,
isUserTopBidder,
userBidUnlockDate,
startsAtBlock,
plannedEndAtBlock,
hasTimersLoaded,
auctionBeginsInDays: startsAtDate ? differenceInCalendarDays(startsAtDate, new Date()) : 0,
auctionBeginsInSeconds: startsAtDate ? differenceInSeconds(startsAtDate, new Date()) : 0,
topBidderHandle: nftStatus.topBidder?.handle,
userBidAmount: Number(userBid?.amount) || undefined,
isUserWhitelisted,
},
nftHistory,
saleType,
}
}
case 'buy-now':
return {
ownerHandle: owner?.handle,
ownerAvatarUri,
isOwner,
needsSettling,
bidFromPreviousAuction,
nftStatus: {
...nftStatus,
},
nftHistory,
saleType,
}
case 'idle':
return {
ownerHandle: owner?.handle,
ownerAvatarUri,
isOwner,
needsSettling,
bidFromPreviousAuction,
nftStatus: {
...nftStatus,
},
nftHistory,
saleType,
}
}
return null
}
Example #10
Source File: NftWidget.tsx From atlas with GNU General Public License v3.0 | 4 votes |
NftWidget: React.FC<NftWidgetProps> = ({
ownerHandle,
isOwner,
nftStatus,
nftHistory,
needsSettling,
ownerAvatarUri,
onNftPutOnSale,
onNftAcceptBid,
onWithdrawBid,
bidFromPreviousAuction,
onNftCancelSale,
onNftChangePrice,
onNftPurchase,
onNftSettlement,
onNftBuyNow,
}) => {
const timestamp = useMsTimestamp()
const { ref, width = SMALL_VARIANT_MAXIMUM_SIZE + 1 } = useResizeObserver({
box: 'border-box',
})
const size: Size = width > SMALL_VARIANT_MAXIMUM_SIZE ? 'medium' : 'small'
const { convertToUSD, isLoadingPrice } = useTokenPrice()
const content = useDeepMemo(() => {
if (!nftStatus) {
return
}
const contentTextVariant = size === 'small' ? 'h400' : 'h600'
const buttonSize = size === 'small' ? 'medium' : 'large'
const buttonColumnSpan = size === 'small' ? 1 : 2
const timerColumnSpan = size === 'small' ? 1 : 2
const BuyNow = ({ buyNowPrice }: { buyNowPrice?: number }) =>
buyNowPrice ? (
<NftInfoItem
size={size}
label="Buy now"
content={
<>
<JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />
<Text variant={contentTextVariant}>{formatNumberShort(buyNowPrice)}</Text>
</>
}
secondaryText={convertToUSD(buyNowPrice)}
/>
) : null
const InfoBanner = ({ title, description }: { title: string; description: string }) => (
<GridItem colSpan={buttonColumnSpan}>
<Banner id="" dismissable={false} icon={<SvgAlertsInformative24 />} {...{ title, description }} />
</GridItem>
)
const WithdrawBidFromPreviousAuction = ({ secondary }: { secondary?: boolean }) =>
bidFromPreviousAuction ? (
<>
<GridItem colSpan={buttonColumnSpan}>
<Button variant={secondary ? 'secondary' : undefined} fullWidth size={buttonSize} onClick={onWithdrawBid}>
Withdraw last bid
</Button>
<Text as="p" margin={{ top: 2 }} variant="t100" secondary align="center">
You bid {formatNumberShort(Number(bidFromPreviousAuction?.amount))} tJOY on{' '}
{formatDateTime(new Date(bidFromPreviousAuction.createdAt))}
</Text>
</GridItem>
</>
) : null
const BidPlacingInfoText = () => (
<Text as="p" variant="t100" secondary align="center">
Placing a bid will withdraw your last bid
</Text>
)
switch (nftStatus.status) {
case 'idle':
return (
<>
{nftStatus.lastSalePrice ? (
<NftInfoItem
size={size}
label="Last price"
content={
<>
<JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />
<Text variant={contentTextVariant} secondary>
{formatNumberShort(nftStatus.lastSalePrice)}
</Text>
</>
}
secondaryText={nftStatus.lastSaleDate && formatDateTime(nftStatus.lastSaleDate)}
/>
) : (
<NftInfoItem
size={size}
label="status"
content={
<Text variant={contentTextVariant} secondary>
Not for sale
</Text>
}
/>
)}
{bidFromPreviousAuction && (
<>
<InfoBanner
title="Withdraw your bid"
description="You placed a bid in a previous auction that you can now withdraw to claim back your money."
/>
<WithdrawBidFromPreviousAuction />
</>
)}
{isOwner && (
<GridItem colSpan={buttonColumnSpan}>
<Button fullWidth variant="secondary" size={buttonSize} onClick={onNftPutOnSale}>
Start sale of this NFT
</Button>
</GridItem>
)}
</>
)
case 'buy-now':
return (
<>
<BuyNow buyNowPrice={nftStatus.buyNowPrice} />
<GridItem colSpan={buttonColumnSpan}>
<ButtonGrid data-size={size}>
{isOwner ? (
<>
<Button fullWidth variant="secondary" size={buttonSize} onClick={onNftChangePrice}>
Change price
</Button>
<Button fullWidth variant="destructive" size={buttonSize} onClick={onNftCancelSale}>
Remove from sale
</Button>
</>
) : (
<GridItem colSpan={buttonColumnSpan}>
<Button fullWidth size={buttonSize} onClick={onNftPurchase}>
Buy now
</Button>
</GridItem>
)}
{bidFromPreviousAuction && (
<>
<InfoBanner
title="Withdraw your bid"
description="You placed a bid in a previous auction that you can now withdraw to claim back your money."
/>
<WithdrawBidFromPreviousAuction secondary />
</>
)}
</ButtonGrid>
</GridItem>
</>
)
case 'auction': {
const getInfoBannerProps = () => {
const hasBids = !nftStatus.topBid?.isCanceled && nftStatus.topBidAmount
if (nftStatus.type === 'open' && bidFromPreviousAuction) {
return {
title: 'Withdraw your bid to participate',
description:
'You placed a bid in a previous auction that you can now withdraw to be able to participate in this auction.',
}
}
if (nftStatus.englishTimerState === 'expired' && isOwner && !hasBids) {
return {
title: 'Auction ended',
description: 'This auction has ended and no one placed a bid. You can now remove this NFT from sale.',
}
}
if (nftStatus.englishTimerState === 'expired' && !bidFromPreviousAuction && !hasBids && !isOwner) {
return {
title: 'Auction ended',
description:
"This auction has ended and no one placed a bid. We're waiting for the NFT owner to remove this NFT from sale.",
}
}
if (
nftStatus.englishTimerState === 'expired' &&
!bidFromPreviousAuction &&
hasBids &&
!isOwner &&
!nftStatus.isUserTopBidder
) {
return {
title: 'Auction ended',
description:
'We are waiting for this auction to be settled by the auction winner or the current NFT owner.',
}
}
if (nftStatus.englishTimerState === 'expired' && bidFromPreviousAuction) {
return {
title: 'Withdraw your bid',
description: 'You placed a bid in a previous auction that you can now withdraw.',
}
}
if (nftStatus.englishTimerState === 'running' && bidFromPreviousAuction) {
return {
title: 'Withdraw your bid to participate',
description:
'You placed a bid in a previous auction that you can now withdraw to be able to participate in this auction.',
}
}
if (nftStatus.englishTimerState === 'upcoming' && bidFromPreviousAuction) {
return {
title: 'Withdraw your bid to participate',
description:
'You placed a bid in a previous auction that you can now withdraw to be able to participate in this upcoming auction.',
}
}
if (nftStatus.isUserWhitelisted === false) {
return {
title: "You're not on the whitelist",
description: `This sale is available only to members whitelisted by ${ownerHandle}.`,
}
}
return null
}
const infoBannerProps = getInfoBannerProps()
const infoTextNode = !!nftStatus.userBidAmount && nftStatus.userBidUnlockDate && (
<GridItem colSpan={buttonColumnSpan}>
{nftStatus.type === 'english' ? (
<BidPlacingInfoText />
) : (
<Text as="p" variant="t100" secondary align="center">
{nftStatus.canWithdrawBid
? `Your last bid: ${nftStatus.userBidAmount} tJOY`
: `Your last bid (${nftStatus.userBidAmount} tJOY) becomes withdrawable on ${formatDateTime(
nftStatus.userBidUnlockDate
)}`}
</Text>
)}
</GridItem>
)
return (
<>
{nftStatus.topBidAmount && !nftStatus.topBid?.isCanceled ? (
<NftInfoItem
size={size}
label="Top bid"
content={
<>
<TopBidderContainer>
<Avatar assetUrl={nftStatus.topBidderAvatarUri} size="bid" />
<TopBidderTokenContainer data-size={size}>
<JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />
</TopBidderTokenContainer>
</TopBidderContainer>
<Text variant={contentTextVariant}>{formatNumberShort(nftStatus.topBidAmount)}</Text>
</>
}
secondaryText={
!isLoadingPrice && nftStatus.topBidAmount ? (
<>
{convertToUSD(nftStatus.topBidAmount)} from{' '}
<OwnerHandle
to={absoluteRoutes.viewer.member(nftStatus.topBidderHandle)}
variant="secondary"
textOnly
>
<Text variant="t100">{nftStatus.isUserTopBidder ? 'you' : nftStatus.topBidderHandle}</Text>
</OwnerHandle>
</>
) : null
}
/>
) : (
<NftInfoItem
size={size}
label="Starting Price"
content={
<>
<JoyTokenIcon size={size === 'small' ? 16 : 24} variant="silver" />
<Text variant={contentTextVariant}>{formatNumberShort(nftStatus.startingPrice)}</Text>
</>
}
secondaryText={convertToUSD(nftStatus.startingPrice)}
/>
)}
<BuyNow buyNowPrice={nftStatus.buyNowPrice} />
{nftStatus.englishTimerState === 'expired' && (
<GridItem colSpan={timerColumnSpan}>
<NftInfoItem
size={size}
label="Auction ended on"
loading={!nftStatus.auctionPlannedEndDate}
content={
nftStatus.auctionPlannedEndDate && (
<Text variant={contentTextVariant} secondary>
{formatDateTime(nftStatus.auctionPlannedEndDate)}
</Text>
)
}
/>
</GridItem>
)}
{nftStatus.englishTimerState === 'running' && nftStatus?.auctionPlannedEndDate && (
<GridItem colSpan={timerColumnSpan}>
<NftTimerItem size={size} time={nftStatus.auctionPlannedEndDate} />
</GridItem>
)}
{nftStatus.startsAtBlock && nftStatus.auctionBeginsInSeconds >= 0 && (
<GridItem colSpan={timerColumnSpan}>
<NftInfoItem
size={size}
label="Auction begins on"
loading={!nftStatus.startsAtDate}
content={
nftStatus.startsAtDate && (
<Text variant={contentTextVariant} secondary>
{nftStatus.auctionBeginsInDays > 1 && formatDateTime(nftStatus.startsAtDate)}
{nftStatus.auctionBeginsInDays === 1 && `Tomorrow at ${formatTime(nftStatus.startsAtDate)}`}
{nftStatus.auctionBeginsInDays < 1 &&
formatDurationShort(differenceInSeconds(nftStatus.startsAtDate, timestamp))}
</Text>
)
}
/>
</GridItem>
)}
{nftStatus.hasTimersLoaded && infoBannerProps && <InfoBanner {...infoBannerProps} />}
{nftStatus.hasTimersLoaded && needsSettling && (nftStatus.isUserTopBidder || isOwner) && (
<GridItem colSpan={buttonColumnSpan}>
<Button fullWidth size={buttonSize} onClick={onNftSettlement}>
Settle auction
</Button>
</GridItem>
)}
{nftStatus.hasTimersLoaded && bidFromPreviousAuction && <WithdrawBidFromPreviousAuction />}
{nftStatus.hasTimersLoaded &&
!needsSettling &&
!bidFromPreviousAuction &&
(isOwner
? (nftStatus.type === 'open' ||
// english auction with no bids
!nftStatus.topBidAmount ||
nftStatus.topBid?.isCanceled) && (
<GridItem colSpan={buttonColumnSpan}>
<ButtonGrid data-size={size}>
{nftStatus.type === 'open' && nftStatus.topBid && !nftStatus.topBid?.isCanceled && (
<Button fullWidth size={buttonSize} onClick={onNftAcceptBid}>
Review and accept bid
</Button>
)}
<Button
fullWidth
onClick={onNftCancelSale}
variant={
nftStatus.type === 'open' && !nftStatus.topBid?.isCanceled
? 'destructive-secondary'
: 'destructive'
}
size={buttonSize}
>
Remove from sale
</Button>
</ButtonGrid>
</GridItem>
)
: nftStatus.englishTimerState === 'running' &&
nftStatus.isUserWhitelisted !== false &&
(nftStatus.buyNowPrice ? (
<GridItem colSpan={buttonColumnSpan}>
<ButtonGrid data-size={size} data-two-columns={size === 'medium'}>
<Button fullWidth variant="secondary" size={buttonSize} onClick={onNftPurchase}>
{nftStatus.canChangeBid ? 'Change bid' : 'Place bid'}
</Button>
<Button fullWidth size={buttonSize} onClick={onNftBuyNow}>
Buy now
</Button>
{/* second row button */}
{nftStatus.canWithdrawBid && (
<GridItem colSpan={buttonColumnSpan}>
<Button fullWidth size={buttonSize} variant="destructive-secondary" onClick={onWithdrawBid}>
Withdraw bid
</Button>
</GridItem>
)}
{infoTextNode}
</ButtonGrid>
</GridItem>
) : (
<GridItem colSpan={buttonColumnSpan}>
<ButtonGrid data-size={size}>
<GridItem colSpan={buttonColumnSpan}>
<Button fullWidth size={buttonSize} onClick={onNftPurchase}>
{nftStatus.canChangeBid ? 'Change bid' : 'Place bid'}
</Button>
</GridItem>
{nftStatus.canWithdrawBid && (
<GridItem colSpan={buttonColumnSpan}>
<Button fullWidth size={buttonSize} variant="destructive-secondary" onClick={onWithdrawBid}>
Withdraw bid
</Button>
</GridItem>
)}
{infoTextNode}
</ButtonGrid>
</GridItem>
)))}
</>
)
}
}
}, [
nftStatus,
size,
convertToUSD,
bidFromPreviousAuction,
onWithdrawBid,
isOwner,
onNftPutOnSale,
onNftChangePrice,
onNftCancelSale,
onNftPurchase,
isLoadingPrice,
timestamp,
needsSettling,
onNftSettlement,
onNftAcceptBid,
onNftBuyNow,
ownerHandle,
])
if (!nftStatus) return null
return (
<Container ref={ref}>
<NftOwnerContainer data-size={size}>
<OwnerAvatar assetUrl={ownerAvatarUri} size="small" />
<OwnerLabel variant="t100" secondary>
This NFT is owned by
</OwnerLabel>
<OwnerHandle to={ownerHandle && absoluteRoutes.viewer.member(ownerHandle)} variant="secondary" textOnly>
<Text variant="h300">{ownerHandle}</Text>
</OwnerHandle>
</NftOwnerContainer>
<Content data-size={size}>{content}</Content>
<NftHistory size={size} width={width} historyItems={nftHistory} />
</Container>
)
}
Example #11
Source File: useGetNftSlot.tsx From atlas with GNU General Public License v3.0 | 4 votes |
useGetNftSlot = ({
englishTimerState,
timerLoading,
auctionPlannedEndDate,
hasBuyNowPrice,
needsSettling,
startsAtDate,
withNftLabel,
status,
}: UseGetSlotsOpts): SlotsObject['bottomLeft'] => {
const msTimestamp = useMsTimestamp({
shouldStop: timerLoading || englishTimerState === 'expired' || !englishTimerState,
})
const generatePills: () => PillProps[] = useCallback(() => {
const buyNowPill: PillProps = { icon: <SvgActionBuyNow />, variant: 'overlay', title: 'Buy now' }
switch (status) {
case 'idle':
return [{ icon: <SvgActionNotForSale />, variant: 'overlay', title: 'Not for sale' }]
case 'buy-now':
return [buyNowPill]
case 'auction': {
const additionalBuyNowPill = hasBuyNowPrice ? [buyNowPill] : []
if (needsSettling) {
return [
{
icon: <SvgActionAuction />,
label: 'To be settled',
variant: 'overlay',
},
...additionalBuyNowPill,
]
}
if (timerLoading) {
return [
{
icon: <SvgActionAuction />,
label: 'Loading',
variant: 'overlay',
},
...additionalBuyNowPill,
]
}
switch (englishTimerState) {
case 'upcoming': {
const diff = startsAtDate && differenceInSeconds(new Date(), startsAtDate) * -1
const diffTime =
diff && diff < 3600
? `Starts in ${formatDurationShort(diff)}`
: startsAtDate && ` ${format(startsAtDate, 'd MMM')} at ${format(startsAtDate, 'HH:mm')}`
return [
{
icon: <SvgActionClock />,
label: diffTime,
variant: 'overlay',
},
...additionalBuyNowPill,
]
}
case 'running': {
const diff = auctionPlannedEndDate && differenceInSeconds(auctionPlannedEndDate, new Date())
const lessThanMinute = auctionPlannedEndDate && differenceInSeconds(auctionPlannedEndDate, msTimestamp) < 60
const lessThanHour = auctionPlannedEndDate && differenceInHours(auctionPlannedEndDate, msTimestamp) < 1
return [
{
icon: <SvgActionAuction />,
label: diff ? (lessThanMinute ? '< 1 min' : formatDurationShort(diff, true)) : undefined,
variant: lessThanHour ? 'danger' : 'overlay',
},
...additionalBuyNowPill,
]
}
case 'expired':
return [
{
icon: <SvgActionAuction />,
label: 'Auction ended',
variant: 'overlay',
},
...additionalBuyNowPill,
]
default:
return []
}
}
default:
return []
}
}, [
status,
hasBuyNowPrice,
needsSettling,
timerLoading,
englishTimerState,
startsAtDate,
auctionPlannedEndDate,
msTimestamp,
])
const nftPill: PillProps[] = withNftLabel ? [{ label: 'NFT', variant: 'overlay', title: 'NFT' }] : []
return {
element: <PillGroup items={[...nftPill, ...generatePills()]} />,
}
}
Example #12
Source File: NftPurchaseBottomDrawer.tsx From atlas with GNU General Public License v3.0 | 4 votes |
NftPurchaseBottomDrawer: React.FC = () => {
const { displaySnackbar } = useSnackbar()
const [type, setType] = useState<'english_auction' | 'open_auction' | 'buy_now'>('english_auction')
const [showBuyNowInfo, setBuyNowInfo] = useState(false)
const { currentAction, closeNftAction, currentNftId, isBuyNowClicked } = useNftActions()
const { nft, nftStatus, loading, refetch } = useNft(currentNftId || '')
const { userBid, canChangeBid, userBidUnlockDate } = useNftState(nft)
const { isLoadingAsset: thumbnailLoading, url: thumbnailUrl } = useAsset(nft?.video.thumbnailPhoto)
const { url: creatorAvatarUrl } = useAsset(nft?.video.channel.avatarPhoto)
const { url: ownerMemberAvatarUrl } = useMemberAvatar(nft?.ownerMember)
const mdMatch = useMediaMatch('md')
const { convertToUSD } = useTokenPrice()
const accountBalance = useSubscribeAccountBalance()
const timestamp = useMsTimestamp({ shouldStop: !currentAction })
const { convertBlockToMsTimestamp, convertBlocksToDuration } = useBlockTimeEstimation()
const {
joystream,
proxyCallback,
currentBlock,
chainState: { nftPlatformFeePercentage },
} = useJoystream()
const handleTransaction = useTransaction()
const { activeMemberId } = useUser()
const {
watch,
setValue,
handleSubmit: createSubmitHandler,
register,
reset,
formState: { errors },
} = useForm<{ bid: number }>()
const isAuction = nftStatus?.status === 'auction'
const isBuyNow = nftStatus?.status === 'buy-now'
const isEnglishAuction = nftStatus?.status === 'auction' && nftStatus.type === 'english'
const isOpenAuction = nftStatus?.status === 'auction' && nftStatus?.type === 'open'
useEffect(() => {
if (isBuyNow) {
setType('buy_now')
}
if (isEnglishAuction) {
setType('english_auction')
}
if (isOpenAuction) {
setType('open_auction')
}
}, [isBuyNow, isEnglishAuction, isOpenAuction])
const auctionBuyNowPrice = (isAuction && nftStatus.buyNowPrice) || 0
const bidLockingTime = isAuction && nftStatus.bidLockingTime && convertBlocksToDuration(nftStatus.bidLockingTime)
const buyNowPrice = (isBuyNow && nftStatus.buyNowPrice) || 0
const startingPrice = isAuction && nftStatus.startingPrice
const topBidder = isAuction && nftStatus.topBidder ? nftStatus.topBidder : undefined
const topBidAmount = (isAuction && !nftStatus.topBid?.isCanceled && nftStatus.topBidAmount) || 0
const minimalBidStep = (isAuction && nftStatus.minimalBidStep) || 0
const endAtBlock = isAuction && nftStatus.auctionPlannedEndBlock
const minimumBidEnglishAuction = startingPrice > topBidAmount ? startingPrice : topBidAmount + minimalBidStep
const minimumBid = type === 'open_auction' ? startingPrice : minimumBidEnglishAuction
const endTime = endAtBlock && convertBlockToMsTimestamp(endAtBlock)
const timeLeftSeconds = endTime ? Math.trunc((endTime - timestamp) / 1000) : 0
const creatorRoyalty = nft?.creatorRoyalty || 0
const ownerRoyalty = 100 - creatorRoyalty - nftPlatformFeePercentage
// check if input value isn't bigger than fixed price
useEffect(() => {
if (type === 'buy_now' || !auctionBuyNowPrice) {
return
}
const subscription = watch(({ bid }) => {
if (bid >= auctionBuyNowPrice) {
setBuyNowInfo(true)
} else {
setBuyNowInfo(false)
}
if (bid > auctionBuyNowPrice) {
setValue('bid', auctionBuyNowPrice)
}
})
return () => subscription.unsubscribe()
}, [auctionBuyNowPrice, setValue, type, watch])
const handleBuyNow = useCallback(async () => {
if (!joystream || !currentNftId || !activeMemberId) return
const completed = await handleTransaction({
onError: () => refetch(),
txFactory: async (updateStatus) => {
if (!isAuction) {
return (await joystream.extrinsics).buyNftNow(
currentNftId,
activeMemberId,
buyNowPrice,
proxyCallback(updateStatus)
)
} else {
return (await joystream.extrinsics).makeNftBid(
currentNftId,
activeMemberId,
Number(auctionBuyNowPrice),
isEnglishAuction ? 'english' : 'open',
proxyCallback(updateStatus)
)
}
},
onTxSync: async (_) => refetch(),
})
if (completed) {
closeNftAction()
displaySnackbar({
title: 'You have successfully bought NFT.',
iconType: 'success',
})
}
}, [
activeMemberId,
auctionBuyNowPrice,
buyNowPrice,
closeNftAction,
currentNftId,
displaySnackbar,
handleTransaction,
isAuction,
joystream,
isEnglishAuction,
proxyCallback,
refetch,
])
const handleBidOnAuction = useCallback(() => {
const submit = createSubmitHandler(async (data) => {
if (!joystream || !currentNftId || !activeMemberId) return
const completed = await handleTransaction({
txFactory: async (updateStatus) =>
(
await joystream.extrinsics
).makeNftBid(
currentNftId,
activeMemberId,
Number(data.bid),
isEnglishAuction ? 'english' : 'open',
proxyCallback(updateStatus)
),
onTxSync: (_) => refetch(),
})
if (completed) {
if (Number(data.bid) === auctionBuyNowPrice) {
displaySnackbar({
title: 'You have bought this NFT successfully',
iconType: 'success',
})
} else {
displaySnackbar({
title: 'Your bid has been placed',
description: 'We will notify you about any changes.',
iconType: 'success',
})
}
closeNftAction()
}
})
submit()
}, [
activeMemberId,
auctionBuyNowPrice,
closeNftAction,
createSubmitHandler,
currentNftId,
displaySnackbar,
handleTransaction,
isEnglishAuction,
joystream,
proxyCallback,
refetch,
])
const isBuyNowAffordable = (buyNowPrice || auctionBuyNowPrice) + TRANSACTION_FEE < (accountBalance || 0)
const bid = watch('bid')
const timeLeftUnderMinute = !!timeLeftSeconds && timeLeftSeconds < 60
const auctionEnded = type === 'english_auction' && timeLeftSeconds <= 0
const insufficientFoundsError = errors.bid && errors.bid.type === 'bidTooHigh'
const primaryButtonText =
type === 'buy_now' || (auctionBuyNowPrice && auctionBuyNowPrice <= bid) || isBuyNowClicked
? 'Buy NFT'
: canChangeBid
? 'Change bid'
: 'Place bid'
const blocksLeft = endAtBlock && endAtBlock - currentBlock
const isOpen = currentAction === 'purchase'
useEffect(() => {
if (!isOpen) {
setValue('bid', NaN)
}
}, [isOpen, setValue])
const hasErrors = !!Object.keys(errors).length
const { isLoadingAsset: userBidAvatarLoading, url: userBidAvatarUrl } = useMemberAvatar(userBid?.bidder)
const { isLoadingAsset: topBidderAvatarLoading, url: topBidderAvatarUrl } = useMemberAvatar(topBidder)
const timeToUnlockSeconds = userBidUnlockDate ? differenceInSeconds(userBidUnlockDate, new Date()) : 0
return (
<BottomDrawer
isOpen={isOpen}
onClose={() => {
reset()
closeNftAction()
}}
actionBar={{
primaryButton: {
text: primaryButtonText,
disabled: isBuyNowClicked || type === 'buy_now' ? !isBuyNowAffordable : hasErrors,
onClick: () => (isBuyNowClicked || type === 'buy_now' ? handleBuyNow() : handleBidOnAuction()),
},
}}
>
<Content>
<NftPreview>
<NftCard
title={nft?.video.title}
thumbnail={{
loading: thumbnailLoading || loading || !nft,
thumbnailUrl: thumbnailUrl,
}}
creator={{ name: nft?.video.channel.title, assetUrl: creatorAvatarUrl }}
owner={{ name: nft?.ownerMember?.handle, assetUrl: ownerMemberAvatarUrl }}
loading={loading}
fullWidth={!mdMatch}
/>
</NftPreview>
<PlaceBidWrapper>
<InnerContainer>
<Header>
<Text variant="h600">
{type !== 'buy_now' && !isBuyNowClicked ? (canChangeBid ? 'Change bid' : 'Place bid') : 'Buy NFT'}
</Text>
{type === 'english_auction' && (
<FlexWrapper>
<Text variant="h200" secondary>
Ending in:
</Text>
<Text
variant="h200"
margin={{ left: 2, right: 2 }}
color={
auctionEnded
? cVar('colorTextMuted', true)
: timeLeftUnderMinute
? cVar('colorTextError')
: undefined
}
>
{!auctionEnded
? !timeLeftUnderMinute
? formatDurationShort(timeLeftSeconds, true)
: 'Under 1 min'
: 'Auction ended'}
<Text variant="h200" as="span" secondary>
{' '}
/ {pluralizeNoun(blocksLeft && blocksLeft > 0 ? blocksLeft : 0, 'block')}
</Text>
</Text>
<FlexWrapper>
{endAtBlock && (
<Information
text={`On blockchain, duration is expressed in number of blocks. This auction ends at block ${endAtBlock}.`}
placement="top"
/>
)}
</FlexWrapper>
</FlexWrapper>
)}
</Header>
{type !== 'buy_now' && !isBuyNowClicked ? (
<>
<CurrentBidWrapper>
{topBidder && !!topBidAmount ? (
<ActiveBidWrapper>
<ActionBarCell>
<Text variant="h100" secondary margin={{ bottom: 2 }}>
Top bid
</Text>
<FlexWrapper>
<Avatar size="bid" assetUrl={topBidderAvatarUrl} loading={topBidderAvatarLoading} />
<TokenWrapper>
<StyledJoyTokenIcon variant="gray" size={24} />
</TokenWrapper>
<BidAmount variant="h400">{formatNumberShort(topBidAmount)}</BidAmount>
</FlexWrapper>
<Text variant="t100" secondary margin={{ top: 1 }}>
{topBidder.handle === userBid?.bidder.handle ? 'You' : topBidder.handle}
</Text>
</ActionBarCell>
{userBid && (
<ActionBarCell>
<Text variant="h100" secondary margin={{ bottom: 2 }}>
Your Bid
</Text>
<FlexWrapper>
<Avatar size="bid" assetUrl={userBidAvatarUrl} loading={userBidAvatarLoading} />
<TokenWrapper>
<StyledJoyTokenIcon variant="gray" size={24} />
</TokenWrapper>
<BidAmount variant="h400">{formatNumberShort(Number(userBid.amount))}</BidAmount>
</FlexWrapper>
<Text variant="t100" secondary margin={{ top: 1 }}>
You
</Text>
</ActionBarCell>
)}
</ActiveBidWrapper>
) : (
<ActiveBidWrapper>
<ActionBarCell>
<Text variant="h100" secondary margin={{ bottom: 2 }}>
Top bid
</Text>
<Text variant="h400">No bids yet</Text>
</ActionBarCell>
</ActiveBidWrapper>
)}
</CurrentBidWrapper>
{!auctionEnded && (
<MinimumBidWrapper>
<MinimumBid>
<Text variant="h300" secondary>
Minimum bid
</Text>
<JoyTokenIcon variant="gray" size={24} /> <Text variant="h400">{minimumBid}</Text>
</MinimumBid>
{auctionBuyNowPrice > 0 && (
<Text variant="t100" secondary>
Buy now: {formatTokens(auctionBuyNowPrice, true)}
</Text>
)}
</MinimumBidWrapper>
)}
<TextField
{...register('bid', {
valueAsNumber: true,
validate: {
bidLocked: (value) => {
if (isOpenAuction && value < Number(userBid?.amount) && timeToUnlockSeconds > 0) {
return `You will be able to change your bid to a lower one after ${
userBidUnlockDate && formatDateTime(userBidUnlockDate)
}`
}
return true
},
bidTooLow: (value) =>
Number(value) >= minimumBid ? true : 'Your bid must be higher than the minimum bid',
bidTooHigh: (value) => {
return Number(value) + TRANSACTION_FEE > (accountBalance || 0)
? 'You do not have enough funds to place this bid'
: true
},
},
})}
disabled={auctionEnded}
placeholder={auctionEnded ? 'Auction ended' : 'Enter your bid'}
nodeStart={<JoyTokenIcon variant="gray" size={24} />}
nodeEnd={!!bid && <Pill variant="default" label={`${convertToUSD(bid)}`} />}
type="number"
error={!!errors.bid}
helperText={errors.bid && errors.bid.message}
onBlur={(event) => {
const { target } = event
if (Number(target.value) % 1 !== 0) {
setValue('bid', Math.floor(Number(event.target.value)))
}
}}
/>
{showBuyNowInfo && (
<BuyNowInfo variant="t100" margin={{ top: 2 }}>
Max bid cannot be more than buy now price. Bidding for amount higher than Buy now will automatically
end the auction and make you an owner of that NFT.
</BuyNowInfo>
)}
</>
) : (
<MinimumBidWrapper>
<MinimumBid>
<Text variant="h300" secondary>
Price:
</Text>
<JoyTokenIcon variant="silver" size={24} />{' '}
<Text variant="h400">{buyNowPrice || auctionBuyNowPrice}</Text>
</MinimumBid>
</MinimumBidWrapper>
)}
<Divider />
<FlexWrapper>
<Text variant="h400">Revenue split</Text>
<Information
placement="top"
text="Revenue split shows the proceedings from this sale based on royalties set up by the creator"
/>
</FlexWrapper>
<PaymentSplitWrapper>
<div>
<Text variant="h300" secondary>
Owner
</Text>
<PaymentSplitValues>
<Avatar size="bid" assetUrl={ownerMemberAvatarUrl} />
<Text variant="h400" secondary margin={{ left: 2 }}>
{ownerRoyalty}%
</Text>
</PaymentSplitValues>
</div>
<div>
<Text variant="h300" secondary>
Creator
</Text>
<PaymentSplitValues>
<Avatar size="bid" assetUrl={creatorAvatarUrl} />
<Text variant="h400" secondary margin={{ left: 2 }}>
{creatorRoyalty}%
</Text>
</PaymentSplitValues>
</div>
<div>
<Text variant="h300" secondary>
Platform
</Text>
<PaymentSplitValues>
<SvgJoystreamLogoShort height={24} viewBox="0 0 26 32" />
<Text variant="h400" secondary margin={{ left: 2 }}>
{nftPlatformFeePercentage}%
</Text>
</PaymentSplitValues>
</div>
</PaymentSplitWrapper>
<Divider />
<Text variant="h400" margin={{ bottom: 4 }}>
Price breakdown
</Text>
<Row>
<Text variant="t100" secondary color={insufficientFoundsError ? cVar('colorTextError') : undefined}>
Your balance
</Text>
{accountBalance != null ? (
<Text variant="t100" secondary color={insufficientFoundsError ? cVar('colorTextError') : undefined}>
{formatTokens(accountBalance, true)}
</Text>
) : (
<SkeletonLoader width={82} height={16} />
)}
</Row>
<Row>
<Text variant="t100" secondary>
{type === 'buy_now' || isBuyNowClicked ? 'Price' : bid ? 'Your bid' : ''}
</Text>
{(bid > 0 || isBuyNowClicked || type === 'buy_now') && (
<Text variant="t100" secondary>
{formatTokens(type !== 'buy_now' ? (isBuyNowClicked ? auctionBuyNowPrice : bid) : buyNowPrice, true)}
</Text>
)}
</Row>
{(bid || type === 'buy_now') && (
<>
<Row>
<Text variant="t100" secondary>
Transaction fee
</Text>
<Text variant="t100" secondary>
{formatTokens(TRANSACTION_FEE, true)}
</Text>
</Row>
<Row>
<Text variant="h500" secondary>
You will pay
</Text>
<Text variant="h500">
{formatTokens((type === 'buy_now' ? buyNowPrice : Number(bid) || 0) + TRANSACTION_FEE, true)}
</Text>
</Row>
</>
)}
{type === 'open_auction' && bidLockingTime && (
<Messages>
<SvgAlertsWarning24 />
<Text variant="t200" secondary margin={{ left: 2 }}>
Your bid can be withdrawn if it’s not accepted by the owner within{' '}
{formatDuration(intervalToDuration({ start: 0, end: bidLockingTime }))} from placing it.
</Text>
</Messages>
)}
{type === 'english_auction' && !isBuyNowClicked && (
<Messages>
<SvgAlertsWarning24 />
<Text variant="t200" secondary margin={{ left: 2 }}>
After placing your bid, you will not be able to withdraw it. If someone places higher bid, your bid
will be returned automatically.
</Text>
</Messages>
)}
</InnerContainer>
</PlaceBidWrapper>
</Content>
</BottomDrawer>
)
}
Example #13
Source File: ActionLog.tsx From amplication with Apache License 2.0 | 4 votes |
ActionLog = ({ action, title, versionNumber }: Props) => {
const logData = useMemo(() => {
if (!action?.steps) return [];
return action?.steps.map((step) => {
let duration = "";
if (step.completedAt) {
const seconds = differenceInSeconds(
new Date(step.completedAt),
new Date(step.createdAt)
);
duration = seconds.toString().concat(SECOND_STRING);
}
return {
...step,
duration: duration,
messages: step.logs
?.map((log) => {
return chalk`{${LOG_LEVEL_TO_CHALK[log.level]} ${
log.createdAt
} {gray (${log.level})} ${log.message} }`;
})
.join("\n"),
};
});
}, [action]);
const actionStatus = useMemo(() => {
if (
logData.find(
(step) =>
step.status === models.EnumActionStepStatus.Waiting ||
step.status === models.EnumActionStepStatus.Running
)
)
return models.EnumActionStepStatus.Running;
if (
logData.find((step) => step.status === models.EnumActionStepStatus.Failed)
)
return models.EnumActionStepStatus.Failed;
return models.EnumActionStepStatus.Success;
}, [logData]);
const lastStepCompletedAt = useMemo(() => {
if (actionStatus === models.EnumActionStepStatus.Running) return null;
return last(logData)?.completedAt;
}, [logData, actionStatus]);
return (
<div className={`${CLASS_NAME}`}>
<div className={`${CLASS_NAME}__header`}>
<Icon icon="option_set" />
{!action ? (
<h3>Action Log</h3>
) : (
<>
<h3>
{title} <span>{versionNumber}</span>
</h3>
<div className={`${CLASS_NAME}__header__info__status`}>
<CircleIcon
size={EnumCircleIconSize.Small}
{...STEP_STATUS_TO_STYLE[actionStatus]}
/>
{actionStatus}
</div>
<div className="spacer" />
<div className={`${CLASS_NAME}__header__info__time`}>
Total duration{" "}
<Timer
startTime={action.createdAt}
runTimer
endTime={lastStepCompletedAt}
/>
</div>
</>
)}
</div>
<div className={`${CLASS_NAME}__body`}>
{logData.map((stepData) => (
<div className={`${CLASS_NAME}__step`} key={stepData.id}>
<div className={`${CLASS_NAME}__step__row`}>
<span
className={`${CLASS_NAME}__step__status ${CLASS_NAME}__step__status--${stepData.status.toLowerCase()}`}
>
{stepData.status === models.EnumActionStepStatus.Running ? (
<CircularProgress size={16} />
) : (
<Icon icon={STEP_STATUS_TO_ICON[stepData.status]} />
)}
</span>
<span className={`${CLASS_NAME}__step__message`}>
{stepData.message}
</span>
<span className={`${CLASS_NAME}__step__duration`}>
{stepData.duration}
</span>
</div>
{!isEmpty(stepData.messages) && (
<div className={`${CLASS_NAME}__step__log`}>
<LazyLog
rowHeight={LOG_ROW_HEIGHT}
lineClassName={`${CLASS_NAME}__line`}
extraLines={0}
enableSearch={false}
text={stepData.messages}
height={10} //we use a random value in order to disable the auto-sizing, and use "height:auto !important" in CSS
/>
</div>
)}
</div>
))}
{isEmpty(logData) && (
<div className={`${CLASS_NAME}__empty-state`}>
<img src={logsImage} alt="log is empty" />
<div className={`${CLASS_NAME}__empty-state__title`}>
Create or select an action to view the log
</div>
</div>
)}
</div>
</div>
);
}