Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { useQuery } from '@tanstack/react-query';
import { ExternalLink } from '@wordpress/components';
import { createInterpolateElement } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { isToday } from 'date-fns';
import SiteIcon from '../../components/site-icon';
import { formatDate } from '../../utils/datetime';
import { Text } from '../../components/text';
import { formatDate, getRelativeTimeString, parseDateAsUTC } from '../../utils/datetime';

export const MonetizeSubscriptionTerms = ( {
subscription,
Expand All @@ -20,19 +22,36 @@ export const MonetizeSubscriptionTerms = ( {
return <>{ __( 'Never expires' ) }</>;
}

// Check if end_date is in the past
const endDate = parseDateAsUTC( subscription.end_date );
const isExpired = endDate < new Date();

// Show "Expired" for past dates
if ( isExpired ) {
const isExpiredToday = isToday( endDate );
const expiredTodayText = __( 'Expired today' );
// translators: timeSinceExpiry is of the form "[number] [time-period] ago" i.e. "3 days ago"
const expiredFromNowText = sprintf( __( 'Expired %(timeSinceExpiry)s' ), {
timeSinceExpiry: getRelativeTimeString( endDate ),
} );

return <Text intent="error">{ isExpiredToday ? expiredTodayText : expiredFromNowText }</Text>;
}

// Show renewal or expiry for future dates
return (
<>
{ subscription.renew_interval === null
? // translators: %(date)s is the date the subscription expires. Format is LL (e.g. January 1, 2020).
sprintf( __( 'Expires on %(date)s' ), {
date: formatDate( new Date( Date.parse( subscription?.end_date ?? '' ) ), locale, {
date: formatDate( endDate, locale, {
dateStyle: 'long',
} ),
} )
: // translators: %(siteUrl)s is the URL of the site. %(date)s is the date the subscription renews. . Format is LL (e.g. January 1, 2020).
: // translators: %(amount)s is the renewal price, %(date)s is the date the subscription renews. Format is LL (e.g. January 1, 2020).
sprintf( __( 'Renews at %(amount)s on %(date)s' ), {
amount: formatCurrency( Number( subscription.renewal_price ), subscription.currency ),
date: formatDate( new Date( Date.parse( subscription?.end_date ?? '' ) ), locale, {
date: formatDate( endDate, locale, {
dateStyle: 'long',
} ),
} ) }
Expand Down
14 changes: 14 additions & 0 deletions client/dashboard/utils/datetime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,17 @@ export function formatSiteYmd( date: Date ) {
const day = String( date.getDate() ).padStart( 2, '0' );
return `${ year }-${ month }-${ day }`;
}

/**
* Parse a date string as UTC, handling MySQL and ISO8601 formats.
*/
export function parseDateAsUTC( dateString: string ): Date {
// Handle datetime without timezone: "YYYY-MM-DD HH:MM:SS" or "YYYY-MM-DDTHH:MM:SS" (optional microseconds)
const match = /^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}:\d{2}(?:\.\d+)?)$/.exec( dateString.trim() );
if ( match ) {
return new Date( `${ match[ 1 ] }T${ match[ 2 ] }Z` );
}

// Everything else - pass through unchanged
return new Date( dateString );
}
26 changes: 24 additions & 2 deletions client/me/purchases/membership-item/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,40 @@ export const MembershipTerms = ( { subscription }: { subscription: MembershipSub
return <>{ translate( 'Never expires' ) }</>;
}

// Check if expired (parse as UTC since end_date is stored in UTC, then convert to local for display)
const endDate = moment.utc( subscription.end_date ).local();
const isExpired = endDate.isBefore( moment() );

if ( isExpired ) {
const isExpiredToday = moment().isSame( endDate, 'day' );

return (
<span className="purchase-item__is-error">
{ isExpiredToday
? translate( 'Expired today' )
: translate( 'Expired %(timeSinceExpiry)s', {
args: {
timeSinceExpiry: endDate.fromNow(),
},
context:
'timeSinceExpiry is of the form "[number] [time-period] ago" i.e. "3 days ago"',
} ) }
</span>
);
}

return (
<>
{ subscription.renew_interval === null
? translate( 'Expires on %(date)s', {
args: {
date: moment( subscription.end_date ).format( 'LL' ),
date: endDate.format( 'LL' ),
},
} )
: translate( 'Renews at %(amount)s on %(date)s', {
args: {
amount: formatCurrency( Number( subscription.renewal_price ), subscription.currency ),
date: moment( subscription.end_date ).format( 'LL' ),
date: endDate.format( 'LL' ),
},
} ) }
</>
Expand Down