diff --git a/packages/common/src/utils/timeUtil.test.ts b/packages/common/src/utils/timeUtil.test.ts index 42b333b2cea..410b75b1432 100644 --- a/packages/common/src/utils/timeUtil.test.ts +++ b/packages/common/src/utils/timeUtil.test.ts @@ -8,7 +8,9 @@ import { formatDate, utcToLocalTime, getLocalTimezone, - formatDateWithTimezoneOffset + formatDateWithTimezoneOffset, + formatContestDeadline, + formatContestDeadlineWithStatus } from './timeUtil' describe('formatSeconds', () => { @@ -70,3 +72,50 @@ describe('getLocalTimezone', () => { expect(timezone).toBe(dayjs().format('z')) }) }) + +describe('formatContestDeadline', () => { + test('should format deadline with short format', () => { + const result = formatContestDeadline('2023-12-17T15:30:00Z', 'short') + expect(result).toContain('12/17/23') + expect(result).toContain(getLocalTimezone()) + }) + + test('should format deadline with card format', () => { + const result = formatContestDeadline('2023-12-17T15:30:00Z', 'card') + expect(result).toContain('12/17/23 at 3:30 PM') + expect(result).toContain(getLocalTimezone()) + }) + + test('should format deadline with long format', () => { + const result = formatContestDeadline('2023-12-17T15:30:00Z', 'long') + expect(result).toContain('Sun. Dec 17, 2023 at 3:30 PM') + expect(result).toContain(getLocalTimezone()) + }) + + test('should return empty string for undefined deadline', () => { + expect(formatContestDeadline(undefined)).toBe('') + }) +}) + +describe('formatContestDeadlineWithStatus', () => { + test('should format active deadline', () => { + const result = formatContestDeadlineWithStatus( + '2023-12-17T15:30:00Z', + false + ) + expect(result).toContain('Deadline:') + expect(result).toContain('12/17/23') + expect(result).toContain(getLocalTimezone()) + }) + + test('should format ended deadline', () => { + const result = formatContestDeadlineWithStatus('2023-12-17T15:30:00Z', true) + expect(result).toContain('Ended:') + expect(result).toContain('12/17/23') + expect(result).toContain(getLocalTimezone()) + }) + + test('should return empty string for undefined deadline', () => { + expect(formatContestDeadlineWithStatus(undefined)).toBe('') + }) +}) diff --git a/packages/common/src/utils/timeUtil.ts b/packages/common/src/utils/timeUtil.ts index f4164a16c69..8c0df5c45fd 100644 --- a/packages/common/src/utils/timeUtil.ts +++ b/packages/common/src/utils/timeUtil.ts @@ -104,3 +104,36 @@ export const getDurationFromTimestampMatch = (match: RegExpMatchArray) => { const s = match[3] ? Number(match[3]) : 0 return s + m * 60 + h * 60 * 60 } + +export const formatContestDeadline = ( + deadline?: string, + format: 'short' | 'long' | 'card' = 'short' +): string => { + if (!deadline) return '' + + const date = dayjs(deadline) + const timezone = getLocalTimezone() + + switch (format) { + case 'long': + return `${date.format('ddd. MMM D, YYYY')} at ${date.format('h:mm A')} ${timezone}` + case 'card': + return `${date.format('MM/DD/YY')} at ${date.format('h:mm A')} ${timezone}` + case 'short': + default: + return `${date.format('MM/DD/YY')} ${timezone}` + } +} + +export const formatContestDeadlineWithStatus = ( + deadline?: string, + isEnded: boolean = false +): string => { + if (!deadline) return '' + + const date = dayjs(deadline) + const timezone = getLocalTimezone() + const status = isEnded ? 'Ended' : 'Deadline' + + return `${status}: ${date.format('MM/DD/YY')} ${timezone}` +} diff --git a/packages/mobile/src/components/remix-carousel/RemixContestCard.tsx b/packages/mobile/src/components/remix-carousel/RemixContestCard.tsx index 1b669574700..7b05b51446c 100644 --- a/packages/mobile/src/components/remix-carousel/RemixContestCard.tsx +++ b/packages/mobile/src/components/remix-carousel/RemixContestCard.tsx @@ -3,7 +3,7 @@ import { useCallback } from 'react' import { useRemixContest, useTrack } from '@audius/common/api' import { SquareSizes } from '@audius/common/models' import type { ID } from '@audius/common/models' -import { formatDate } from '@audius/common/utils' +import { formatContestDeadlineWithStatus } from '@audius/common/utils' import { useNavigation } from '@react-navigation/native' import type { NativeStackNavigationProp } from '@react-navigation/native-stack' @@ -24,7 +24,7 @@ const messages = { deadline: (releaseDate?: string) => releaseDate ? new Date(releaseDate) > new Date() - ? `Deadline: ${formatDate(releaseDate)}` + ? formatContestDeadlineWithStatus(releaseDate, false) : 'Ended' : releaseDate } diff --git a/packages/mobile/src/screens/track-screen/RemixContestDetailsTab.tsx b/packages/mobile/src/screens/track-screen/RemixContestDetailsTab.tsx index c5527e9aaee..f4daa349d33 100644 --- a/packages/mobile/src/screens/track-screen/RemixContestDetailsTab.tsx +++ b/packages/mobile/src/screens/track-screen/RemixContestDetailsTab.tsx @@ -2,7 +2,7 @@ import type { RefObject } from 'react' import { useRemixContest } from '@audius/common/api' import type { ID } from '@audius/common/models' -import { dayjs } from '@audius/common/utils' +import { dayjs, formatContestDeadline } from '@audius/common/utils' import type { ScrollView, FlatList } from 'react-native/types' import { Flex, Text } from '@audius/harmony-native' @@ -11,11 +11,7 @@ import { ExpandableContent, UserGeneratedText } from 'app/components/core' const messages = { due: 'Submission Due:', ended: 'Contest Ended:', - deadline: (deadline?: string) => { - if (!deadline) return '' - const date = dayjs(deadline) - return `${date.format('MM/DD/YY')} at ${date.format('h:mm A')}` - }, + deadline: (deadline?: string) => formatContestDeadline(deadline, 'card'), fallbackDescription: 'Enter my remix contest before the deadline for your chance to win!' } diff --git a/packages/mobile/src/screens/track-screen/TrackScreenDetailsTile.tsx b/packages/mobile/src/screens/track-screen/TrackScreenDetailsTile.tsx index 84311b19c31..d7ef15b7260 100644 --- a/packages/mobile/src/screens/track-screen/TrackScreenDetailsTile.tsx +++ b/packages/mobile/src/screens/track-screen/TrackScreenDetailsTile.tsx @@ -47,8 +47,13 @@ import { usePublishConfirmationModal, useEarlyReleaseConfirmationModal } from '@audius/common/store' -import { formatReleaseDate, Genre, removeNullable } from '@audius/common/utils' -import dayjs from 'dayjs' +import { + formatReleaseDate, + Genre, + removeNullable, + dayjs, + formatContestDeadline +} from '@audius/common/utils' import type { FlatList } from 'react-native' import { TouchableOpacity } from 'react-native' import { useDispatch, useSelector } from 'react-redux' @@ -122,8 +127,7 @@ const messages = { remixContest: 'Remix Contest', contestEnded: 'Contest Ended', contestDeadline: 'Contest Deadline', - deadline: (deadline?: string) => - deadline ? `${dayjs(deadline).format('MM/DD/YYYY')}` : '', + deadline: (deadline?: string) => formatContestDeadline(deadline, 'short'), uploadRemixButtonText: 'Upload Your Remix' } diff --git a/packages/web/src/components/remix-contest-card/RemixContestCard.tsx b/packages/web/src/components/remix-contest-card/RemixContestCard.tsx index eb2581a4c30..8ce1e691334 100644 --- a/packages/web/src/components/remix-contest-card/RemixContestCard.tsx +++ b/packages/web/src/components/remix-contest-card/RemixContestCard.tsx @@ -2,7 +2,7 @@ import { MouseEvent, Ref, forwardRef, useCallback } from 'react' import { useRemixContest, useTrack, useUser } from '@audius/common/api' import { ID, SquareSizes } from '@audius/common/models' -import { formatDate } from '@audius/common/utils' +import { formatContestDeadlineWithStatus } from '@audius/common/utils' import { Flex, Skeleton, Text } from '@audius/harmony' import { useLinkClickHandler } from 'react-router-dom-v5-compat' @@ -14,7 +14,7 @@ const messages = { deadline: (releaseDate?: string) => releaseDate ? new Date(releaseDate) > new Date() - ? `Deadline: ${formatDate(releaseDate)}` + ? formatContestDeadlineWithStatus(releaseDate, false) : 'Ended' : releaseDate } diff --git a/packages/web/src/components/track-flair/RemixContestFlair.tsx b/packages/web/src/components/track-flair/RemixContestFlair.tsx index 18f36d17741..ff85eab0030 100644 --- a/packages/web/src/components/track-flair/RemixContestFlair.tsx +++ b/packages/web/src/components/track-flair/RemixContestFlair.tsx @@ -1,4 +1,4 @@ -import { formatDate } from '@audius/common/utils' +import { formatContestDeadline } from '@audius/common/utils' import { Divider, Flex, @@ -58,7 +58,8 @@ const RemixContestFlair = ({ - {messages.submissionDue} {formatDate(endDate)} + {messages.submissionDue}{' '} + {formatContestDeadline(endDate, 'short')} diff --git a/packages/web/src/components/track/GiantTrackTile.tsx b/packages/web/src/components/track/GiantTrackTile.tsx index c0a1c1601ac..243eea465b5 100644 --- a/packages/web/src/components/track/GiantTrackTile.tsx +++ b/packages/web/src/components/track/GiantTrackTile.tsx @@ -20,7 +20,13 @@ import { useEarlyReleaseConfirmationModal, usePublishConfirmationModal } from '@audius/common/store' -import { Genre, Nullable, dayjs, formatReleaseDate } from '@audius/common/utils' +import { + Genre, + Nullable, + dayjs, + formatReleaseDate, + formatContestDeadline +} from '@audius/common/utils' import { Text, Box, @@ -103,11 +109,7 @@ const messages = { contestDeadline: 'Contest Deadline', uploadRemixButtonText: 'Upload Your Remix', contestEnded: 'Contest Ended', - deadline: (deadline?: string) => { - return deadline - ? `${dayjs(deadline).format('MM/DD/YYYY')} at ${dayjs(deadline).format('h:mm A')}` - : '' - }, + deadline: (deadline?: string) => formatContestDeadline(deadline, 'long'), seeMore: 'See More', seeLess: 'See Less' } diff --git a/packages/web/src/pages/explore-page/components/desktop/TrackArtCard.tsx b/packages/web/src/pages/explore-page/components/desktop/TrackArtCard.tsx index d2c175bc20b..ab63d3ea097 100644 --- a/packages/web/src/pages/explore-page/components/desktop/TrackArtCard.tsx +++ b/packages/web/src/pages/explore-page/components/desktop/TrackArtCard.tsx @@ -2,7 +2,7 @@ import { useCallback } from 'react' import { useRemixContest, useTrack } from '@audius/common/api' import { ID, SquareSizes } from '@audius/common/models' -import { dayjs } from '@audius/common/utils' +import { dayjs, formatContestDeadlineWithStatus } from '@audius/common/utils' import { Flex, Skeleton, Text } from '@audius/harmony' import { useNavigate } from 'react-router-dom-v5-compat' @@ -23,7 +23,7 @@ const messages = { if (!endDate) return null const isContestEnded = dayjs(endDate).isBefore(dayjs()) - return `${isContestEnded ? messages.ended : messages.deadline}: ${dayjs(endDate).format('MM/DD/YY')}` + return formatContestDeadlineWithStatus(endDate, isContestEnded) } } diff --git a/packages/web/src/pages/track-page/components/desktop/RemixContestDetailsTab.tsx b/packages/web/src/pages/track-page/components/desktop/RemixContestDetailsTab.tsx index c5413d310ea..f8b5a2100a1 100644 --- a/packages/web/src/pages/track-page/components/desktop/RemixContestDetailsTab.tsx +++ b/packages/web/src/pages/track-page/components/desktop/RemixContestDetailsTab.tsx @@ -1,6 +1,6 @@ import { useRemixContest } from '@audius/common/api' import { ID } from '@audius/common/models' -import { dayjs } from '@audius/common/utils' +import { dayjs, formatContestDeadline } from '@audius/common/utils' import { Flex, spacing, Text } from '@audius/harmony' import { CollapsibleContent } from 'components/collapsible-content' @@ -8,11 +8,7 @@ import { UserGeneratedText } from 'components/user-generated-text' const messages = { due: 'Submission Due:', - deadline: (deadline?: string) => { - if (!deadline) return '' - const date = dayjs(deadline) - return `${date.format('ddd. MMM D, YYYY')} at ${date.format('h:mm A')}` - }, + deadline: (deadline?: string) => formatContestDeadline(deadline, 'long'), ended: 'Contest Ended:', fallbackDescription: 'Enter my remix contest before the deadline for your chance to win!' diff --git a/packages/web/src/pages/track-page/components/mobile/remix-contests/RemixContestDetailsTab.tsx b/packages/web/src/pages/track-page/components/mobile/remix-contests/RemixContestDetailsTab.tsx index 91ed54505a2..d9b968f50bd 100644 --- a/packages/web/src/pages/track-page/components/mobile/remix-contests/RemixContestDetailsTab.tsx +++ b/packages/web/src/pages/track-page/components/mobile/remix-contests/RemixContestDetailsTab.tsx @@ -1,7 +1,7 @@ import { useRemixContest } from '@audius/common/api' import { ID } from '@audius/common/models' import { UPLOAD_PAGE } from '@audius/common/src/utils/route' -import { dayjs } from '@audius/common/utils' +import { dayjs, formatContestDeadline } from '@audius/common/utils' import { Button, Divider, Flex, IconCloudUpload, Text } from '@audius/harmony' import { UserGeneratedText } from 'components/user-generated-text' @@ -10,11 +10,7 @@ import { useRequiresAccountCallback } from 'hooks/useRequiresAccount' const messages = { due: 'Submission Due:', - deadline: (deadline?: string) => { - if (!deadline) return '' - const date = dayjs(deadline) - return `${date.format('MM/DD/YY')} at ${date.format('h:mm A')}` - }, + deadline: (deadline?: string) => formatContestDeadline(deadline, 'card'), ended: 'Contest Ended:', fallbackDescription: 'Enter my remix contest before the deadline for your chance to win!',