diff --git a/packages/common/src/models/Collection.ts b/packages/common/src/models/Collection.ts index 8935cf56151..1fd77ac497c 100644 --- a/packages/common/src/models/Collection.ts +++ b/packages/common/src/models/Collection.ts @@ -63,7 +63,8 @@ export type CollectionMetadata = { playlist_image_sizes_multihash?: string offline?: OfflineCollectionMetadata local?: boolean - release_date?: string + release_date: Nullable + is_scheduled_release: boolean ddex_app?: string | null is_stream_gated: boolean stream_conditions: Nullable diff --git a/packages/common/src/utils/formatUtil.ts b/packages/common/src/utils/formatUtil.ts index d1f0e87a967..a457c6da3bc 100644 --- a/packages/common/src/utils/formatUtil.ts +++ b/packages/common/src/utils/formatUtil.ts @@ -242,12 +242,20 @@ export const formatNumberString = ( return options?.excludeCommas ? res : formatNumberCommas(res) } +/** Capitalizes the given input string */ export const formatCapitalizeString = (word: string) => { const lowerCase = word.toLowerCase() const firstChar = word.charAt(0).toUpperCase() return firstChar + lowerCase.slice(1) } +/** + * Formats a given date string into a human-readable format based on its proximity to the current date. + * + * - If the date is before the current week, it returns the date formatted as "M/D/YY h:mm A". + * - If the date is before today but within the current week, it returns the date formatted as "dddd h:mm A". + * - If the date is today, it returns the time formatted as "h:mm A". + */ export const formatMessageDate = (date: string) => { const d = dayjs(date) const today = dayjs() @@ -256,6 +264,38 @@ export const formatMessageDate = (date: string) => { return d.format('h:mm A') } +/* + * Formats a given date string into a human-readable format based on its proximity to the current date. + * + * - If the release date is within the next week, it returns the day of the week. + * - If the release date is further out, it returns the full date formatted as "M/D/YY". + * - If the `withHour` flag is set to true, it also includes the time formatted as "h:mm A". + */ +export const formatReleaseDate = ({ + date, + withHour +}: { + date: string + withHour?: boolean +}) => { + const releaseDate = dayjs(date) + const now = dayjs() + + const daysDifference = releaseDate.diff(now, 'days') + + if (daysDifference >= 0 && daysDifference < 7) { + return ( + `${releaseDate.format('dddd')}` + + (withHour ? ` @ ${releaseDate.format('h A')}` : '') + ) + } else { + return ( + `${releaseDate.format('M/D/YY')}` + + (withHour ? ` @ ${releaseDate.format('h A')}` : '') + ) + } +} + /** * Generate a short base36 hash for a given string. * Used to generate short hashes for for queries and urls. diff --git a/packages/mobile/src/components/collection-list/CollectionCard.tsx b/packages/mobile/src/components/collection-list/CollectionCard.tsx index b18cfed473c..366f6528e46 100644 --- a/packages/mobile/src/components/collection-list/CollectionCard.tsx +++ b/packages/mobile/src/components/collection-list/CollectionCard.tsx @@ -10,7 +10,7 @@ import { accountSelectors, cacheCollectionsSelectors } from '@audius/common/store' -import { formatCount } from '@audius/common/utils' +import { formatCount, formatReleaseDate } from '@audius/common/utils' import type { GestureResponderEvent } from 'react-native' import { useSelector } from 'react-redux' @@ -34,7 +34,9 @@ const { getUserId } = accountSelectors const messages = { repost: 'Reposts', favorites: 'Favorites', - hidden: 'Hidden' + hidden: 'Hidden', + releases: (releaseDate: string) => + `Releases ${formatReleaseDate({ date: releaseDate })}` } type CollectionCardProps = { @@ -72,7 +74,9 @@ export const CollectionCard = (props: CollectionCardProps) => { save_count, is_private, access, - stream_conditions + stream_conditions, + release_date: releaseDate, + is_scheduled_release: isScheduledRelease } = collection const isOwner = accountId === playlist_owner_id @@ -113,7 +117,9 @@ export const CollectionCard = (props: CollectionCardProps) => { // Ensures footer height is not affected style={{ lineHeight: 16 }} > - {messages.hidden} + {isScheduledRelease && releaseDate + ? messages.releases(releaseDate) + : messages.hidden} ) : ( <> diff --git a/packages/web/src/components/collection/CollectionCard.tsx b/packages/web/src/components/collection/CollectionCard.tsx index 1791d04d6d2..cdc7cc74652 100644 --- a/packages/web/src/components/collection/CollectionCard.tsx +++ b/packages/web/src/components/collection/CollectionCard.tsx @@ -10,7 +10,7 @@ import { accountSelectors, cacheCollectionsSelectors } from '@audius/common/store' -import { formatCount } from '@audius/common/utils' +import { formatCount, formatReleaseDate } from '@audius/common/utils' import { Flex, Skeleton, Text } from '@audius/harmony' import IconHeart from '@audius/harmony/src/assets/icons/Heart.svg' import IconRepost from '@audius/harmony/src/assets/icons/Repost.svg' @@ -30,7 +30,9 @@ const { getUserId } = accountSelectors const messages = { repost: 'Reposts', favorites: 'Favorites', - hidden: 'Hidden' + hidden: 'Hidden', + releases: (releaseDate: string) => + `Releases ${formatReleaseDate({ date: releaseDate })}` } type CollectionCardProps = Omit & { @@ -89,9 +91,11 @@ export const CollectionCard = forwardRef( playlist_owner_id, repost_count, save_count, - is_private, + is_private: isPrivate, access, - stream_conditions + stream_conditions, + is_scheduled_release: isScheduledRelease, + release_date: releaseDate } = collection const isOwner = accountId === playlist_owner_id @@ -128,7 +132,7 @@ export const CollectionCard = forwardRef( - {is_private ? ( + {isPrivate ? ( ({ lineHeight: theme.typography.lineHeight.s })} > - {messages.hidden} + {isScheduledRelease && releaseDate + ? messages.releases(releaseDate) + : messages.hidden} ) : ( <> diff --git a/packages/web/src/components/track/GiantTrackTile.tsx b/packages/web/src/components/track/GiantTrackTile.tsx index e3171010f8e..e3e88d3248e 100644 --- a/packages/web/src/components/track/GiantTrackTile.tsx +++ b/packages/web/src/components/track/GiantTrackTile.tsx @@ -22,7 +22,8 @@ import { formatSeconds, formatDate, getDogEarType, - Nullable + Nullable, + formatReleaseDate } from '@audius/common/utils' import { Text, @@ -42,7 +43,7 @@ import IconTrending from '@audius/harmony/src/assets/icons/Trending.svg' import IconVisibilityHidden from '@audius/harmony/src/assets/icons/VisibilityHidden.svg' import { Mood } from '@audius/sdk' import cn from 'classnames' -import moment from 'moment' +import dayjs from 'dayjs' import { useDispatch, shallowEqual, useSelector } from 'react-redux' import { TextLink, UserLink } from 'components/link' @@ -57,7 +58,6 @@ import { ComponentPlacement } from 'components/types' import { UserGeneratedText } from 'components/user-generated-text' import { getFeatureEnabled } from 'services/remote-config/featureFlagHelpers' import { moodMap } from 'utils/Moods' -import { getLocalTimezone } from 'utils/dateUtils' import { trpc } from 'utils/trpcClientWeb' import { AiTrackSection } from './AiTrackSection' @@ -101,9 +101,7 @@ const messages = { actionGroupLabel: 'track actions', hidden: 'hidden', releases: (releaseDate: string) => - `Releases ${moment(releaseDate).format( - 'M/D/YY [@] h:mm A' - )} ${getLocalTimezone()}` + `Releases ${formatReleaseDate({ date: releaseDate, withHour: true })}` } export type GiantTrackTileProps = { @@ -229,7 +227,7 @@ export const GiantTrackTile = ({ // Play button is conditionally hidden for USDC-gated tracks when the user does not have access const showPlay = isUSDCPurchaseGated ? hasStreamAccess : true const isPlaylistAddable = useIsGatedContentPlaylistAddable(track) - const shouldShowScheduledRelease = moment(releaseDate).isAfter(moment()) + const shouldShowScheduledRelease = dayjs(releaseDate).isAfter(dayjs()) const { data: albumInfo } = trpc.tracks.getAlbumBacklink.useQuery( { trackId }, { enabled: !!trackId } diff --git a/packages/web/src/pages/track-page/components/mobile/TrackHeader.tsx b/packages/web/src/pages/track-page/components/mobile/TrackHeader.tsx index cfe943cf533..207ccbff76c 100644 --- a/packages/web/src/pages/track-page/components/mobile/TrackHeader.tsx +++ b/packages/web/src/pages/track-page/components/mobile/TrackHeader.tsx @@ -23,7 +23,8 @@ import { formatSeconds, formatDate, getDogEarType, - Nullable + Nullable, + formatReleaseDate } from '@audius/common/utils' import { Flex, @@ -56,7 +57,6 @@ import { GatedContentSection } from 'components/track/GatedContentSection' import { UserGeneratedText } from 'components/user-generated-text' import { useTrackCoverArt } from 'hooks/useTrackCoverArt' import { moodMap } from 'utils/Moods' -import { getLocalTimezone } from 'utils/dateUtils' import { isDarkMode } from 'utils/theme/theme' import { trpc } from 'utils/trpcClientWeb' @@ -77,9 +77,7 @@ const messages = { artworkAltText: 'Track Artwork', hidden: 'Hidden', releases: (releaseDate: string) => - `Releases ${moment(releaseDate).format( - 'M/D/YY [@] h:mm A' - )} ${getLocalTimezone()}` + `Releases ${formatReleaseDate({ date: releaseDate, withHour: true })}` } type PlayButtonProps = {