From 5f68d5873091fb812c746d33edb6020a9bf93f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Thu, 26 Feb 2026 01:04:50 +0100 Subject: [PATCH] nicolium: migrate relative timestamp component to functional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- .../src/components/relative-timestamp.tsx | 210 +++++++----------- 1 file changed, 81 insertions(+), 129 deletions(-) diff --git a/packages/pl-fe/src/components/relative-timestamp.tsx b/packages/pl-fe/src/components/relative-timestamp.tsx index 28689cdf3..427b391b0 100644 --- a/packages/pl-fe/src/components/relative-timestamp.tsx +++ b/packages/pl-fe/src/components/relative-timestamp.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { injectIntl, defineMessages, IntlShape, FormatDateOptions } from 'react-intl'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { defineMessages, useIntl, FormatDateOptions } from 'react-intl'; import Text, { IText } from './ui/text'; @@ -78,12 +78,83 @@ const getUnitDelay = (units: string) => { } }; -const timeAgoString = (intl: IntlShape, date: Date, now: number, year: number) => { +interface IRelativeTimestamp extends IText { + timestamp: string; + year?: number; + futureDate?: boolean; +} + +/** Displays a timestamp compared to the current time, eg "1m" for one minute ago. */ +const RelativeTimestamp: React.FC = ({ + timestamp, + year = new Date().getFullYear(), + futureDate, + theme = 'inherit', + ...props +}) => { + const intl = useIntl(); + const [now, setNow] = useState(Date.now); + const timerRef = useRef(undefined); + + const scheduleNextUpdate = useCallback(() => { + if (timerRef.current) { + clearTimeout(timerRef.current); + } + + const delta = new Date(timestamp).getTime() - now; + const unitDelay = getUnitDelay(selectUnits(delta)); + const unitRemainder = Math.abs(delta % unitDelay); + const updateInterval = 1000 * 10; + const delay = + delta < 0 + ? Math.max(updateInterval, unitDelay - unitRemainder) + : Math.max(updateInterval, unitRemainder); + + timerRef.current = setTimeout(() => { + setNow(Date.now()); + }, delay); + }, [timestamp, now]); + + useEffect(() => { + setNow(Date.now()); + }, [timestamp]); + + useEffect(() => { + scheduleNextUpdate(); + return () => { + if (timerRef.current) { + clearTimeout(timerRef.current); + } + }; + }, [scheduleNextUpdate]); + + const date = new Date(timestamp); const delta = now - date.getTime(); - let relativeTime; + let relativeTime: string; + if (futureDate) { + const futureDelta = date.getTime() - now; - if (delta < 10 * SECOND) { + if (futureDelta < 10 * SECOND) { + relativeTime = intl.formatMessage(messages.momentsRemaining); + } else if (futureDelta < MINUTE) { + relativeTime = intl.formatMessage(messages.secondsRemaining, { + number: Math.floor(futureDelta / SECOND), + }); + } else if (futureDelta < HOUR) { + relativeTime = intl.formatMessage(messages.minutesRemaining, { + number: Math.floor(futureDelta / MINUTE), + }); + } else if (futureDelta < DAY) { + relativeTime = intl.formatMessage(messages.hoursRemaining, { + number: Math.floor(futureDelta / HOUR), + }); + } else { + relativeTime = intl.formatMessage(messages.daysRemaining, { + number: Math.floor(futureDelta / DAY), + }); + } + } else if (delta < 10 * SECOND) { relativeTime = intl.formatMessage(messages.justNow); } else if (delta < 7 * DAY) { if (delta < MINUTE) { @@ -101,130 +172,11 @@ const timeAgoString = (intl: IntlShape, date: Date, now: number, year: number) = relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' }); } - return relativeTime; + return ( + + {relativeTime} + + ); }; -const timeRemainingString = (intl: IntlShape, date: Date, now: number) => { - const delta = date.getTime() - now; - - let relativeTime; - - if (delta < 10 * SECOND) { - relativeTime = intl.formatMessage(messages.momentsRemaining); - } else if (delta < MINUTE) { - relativeTime = intl.formatMessage(messages.secondsRemaining, { - number: Math.floor(delta / SECOND), - }); - } else if (delta < HOUR) { - relativeTime = intl.formatMessage(messages.minutesRemaining, { - number: Math.floor(delta / MINUTE), - }); - } else if (delta < DAY) { - relativeTime = intl.formatMessage(messages.hoursRemaining, { - number: Math.floor(delta / HOUR), - }); - } else { - relativeTime = intl.formatMessage(messages.daysRemaining, { number: Math.floor(delta / DAY) }); - } - - return relativeTime; -}; - -interface RelativeTimestampProps extends IText { - intl: IntlShape; - timestamp: string; - year?: number; - futureDate?: boolean; -} - -interface RelativeTimestampState { - now: number; -} - -/** Displays a timestamp compared to the current time, eg "1m" for one minute ago. */ -const RelativeTimestamp = injectIntl( - class RelativeTimestamp extends React.Component { - _timer: NodeJS.Timeout | undefined; - - state = { - now: Date.now(), - }; - - static defaultProps = { - year: new Date().getFullYear(), - theme: 'inherit' as const, - }; - - shouldComponentUpdate(nextProps: RelativeTimestampProps, nextState: RelativeTimestampState) { - // As of right now the locale doesn't change without a new page load, - // but we might as well check in case that ever changes. - return ( - this.props.timestamp !== nextProps.timestamp || - this.props.intl.locale !== nextProps.intl.locale || - this.state.now !== nextState.now - ); - } - - UNSAFE_componentWillReceiveProps(prevProps: RelativeTimestampProps) { - if (this.props.timestamp !== prevProps.timestamp) { - this.setState({ now: Date.now() }); - } - } - - componentDidMount() { - this._scheduleNextUpdate(); - } - - UNSAFE_componentWillUpdate() { - this._scheduleNextUpdate(); - } - - componentWillUnmount() { - if (this._timer) { - clearTimeout(this._timer); - } - } - - _scheduleNextUpdate() { - if (this._timer) { - clearTimeout(this._timer); - } - - const { timestamp } = this.props; - const delta = new Date(timestamp).getTime() - this.state.now; - const unitDelay = getUnitDelay(selectUnits(delta)); - const unitRemainder = Math.abs(delta % unitDelay); - const updateInterval = 1000 * 10; - const delay = - delta < 0 - ? Math.max(updateInterval, unitDelay - unitRemainder) - : Math.max(updateInterval, unitRemainder); - - this._timer = setTimeout(() => { - this.setState({ now: Date.now() }); - }, delay); - } - - render() { - const { timestamp, intl, year, futureDate, theme, ...textProps } = this.props; - - const date = new Date(timestamp); - const relativeTime = futureDate - ? timeRemainingString(intl, date, this.state.now) - : timeAgoString(intl, date, this.state.now, year!); - - return ( - - {relativeTime} - - ); - } - }, -); - export { dateFormatOptions, RelativeTimestamp as default };