mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 14:13:43 +01:00
[web] Remove SubscriptionFormatters, format subscription dates in the frontend (#36409)
* [web] Remove SubscriptionFormatters, format subscription dates in the frontend Replace the server-formatted nextPaymentDueAt/nextPaymentDueDate/ trialEndsAtFormatted fields with the raw periodEnd date and format in the React dashboard (same strings as before, English + UTC). Also consolidate hand-rolled trial checks onto isInFreeTrial / SubscriptionHelper.isInTrial. * [web] Remove the now-unused dateformat dependency * [web] Address review: payment-dates unit test, UTC dates in acceptance test Add a dedicated unit test asserting the exact formatted strings from formatPaymentDate/formatPaymentDateTime, and use explicit UTC timestamps in the SubscriptionDashboard acceptance fixtures instead of local-timezone Date constructors. GitOrigin-RevId: 16ac4f1098b14ef9f1478d22cf39c1357ead7dea
This commit is contained in:
@@ -17,6 +17,7 @@ import * as eventTracking from '../../../../infrastructure/event-tracking'
|
||||
import PauseDuck from '../../images/pause-duck.svg'
|
||||
import GenericErrorAlert from './generic-error-alert'
|
||||
import { PaidSubscription } from '../../../../../../types/subscription/dashboard/subscription'
|
||||
import isInFreeTrial from '../../util/is-in-free-trial'
|
||||
|
||||
const pauseMonthDurationOptions = [1, 2, 3]
|
||||
|
||||
@@ -39,10 +40,7 @@ export default function PauseSubscriptionModal() {
|
||||
const subscription = personalSubscription as PaidSubscription
|
||||
eventTracking.sendMB('subscription-page-cancel-button-click', {
|
||||
plan_code: subscription?.planCode,
|
||||
is_trial:
|
||||
subscription?.payment.trialEndsAtFormatted &&
|
||||
subscription?.payment.trialEndsAt &&
|
||||
new Date(subscription.payment.trialEndsAt).getTime() > Date.now(),
|
||||
is_trial: isInFreeTrial(subscription?.payment.trialEndsAt),
|
||||
})
|
||||
setShowCancellation(true)
|
||||
}
|
||||
|
||||
+14
-9
@@ -30,6 +30,10 @@ import { useLocation } from '@/shared/hooks/use-location'
|
||||
import { FlashMessage } from '@/features/subscription/components/dashboard/states/active/flash-message'
|
||||
import Notification from '@/shared/components/notification'
|
||||
import { PendingPlanChange } from './pending-plan-change'
|
||||
import {
|
||||
formatPaymentDate,
|
||||
formatPaymentDateTime,
|
||||
} from '../../../../util/payment-dates'
|
||||
|
||||
export function ActiveSubscription({
|
||||
subscription,
|
||||
@@ -138,7 +142,7 @@ export function ActiveSubscription({
|
||||
<p className="mb-1" data-testid="renews-on">
|
||||
<Trans
|
||||
i18nKey="renews_on"
|
||||
values={{ date: subscription.payment.nextPaymentDueDate }}
|
||||
values={{ date: formatPaymentDate(subscription.payment.periodEnd) }}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[<strong />]} // eslint-disable-line react/jsx-key
|
||||
@@ -198,13 +202,12 @@ export function ActiveSubscription({
|
||||
<hr />
|
||||
<h2 className="h3 fw-bold">{t('plan')}</h2>
|
||||
<h3 className="h5 mt-0 mb-1 fw-bold">{planName}</h3>
|
||||
{isInFreeTrial(subscription.payment.trialEndsAt) &&
|
||||
subscription.payment.trialEndsAtFormatted && (
|
||||
<TrialEnding
|
||||
trialEndsAtFormatted={subscription.payment.trialEndsAtFormatted}
|
||||
className="mb-1"
|
||||
/>
|
||||
)}
|
||||
{isInFreeTrial(subscription.payment.trialEndsAt) && (
|
||||
<TrialEnding
|
||||
trialEndsAt={subscription.payment.trialEndsAt}
|
||||
className="mb-1"
|
||||
/>
|
||||
)}
|
||||
{subscription.payment.totalLicenses > 0 && (
|
||||
<p className="mb-1" data-testid="plan-licenses">
|
||||
{isLegacyPlan &&
|
||||
@@ -245,7 +248,9 @@ export function ActiveSubscription({
|
||||
i18nKey="your_subscription_will_pause_on"
|
||||
values={{
|
||||
planName: subscription.plan.name,
|
||||
pauseDate: subscription.payment.nextPaymentDueAt,
|
||||
pauseDate: formatPaymentDateTime(
|
||||
subscription.payment.periodEnd
|
||||
),
|
||||
reactivationDate: getFormattedRenewalDate(),
|
||||
}}
|
||||
shouldUnescape
|
||||
|
||||
+2
-1
@@ -22,6 +22,7 @@ import sparkle from '@/shared/svgs/sparkle.svg'
|
||||
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
|
||||
import { LICENSE_ADD_ON } from '@/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details'
|
||||
import WritefullManagedBundleAddOn from './change-plan/modals/writefull-bundle-management-modal'
|
||||
import { formatPaymentDate } from '../../../../util/payment-dates'
|
||||
|
||||
type AddOnsProps = {
|
||||
subscription: PaidSubscription
|
||||
@@ -189,7 +190,7 @@ function AddOns({
|
||||
)
|
||||
}
|
||||
displayPrice={addOnsDisplayPrices[addOn.addOnCode]}
|
||||
nextBillingDate={subscription.payment.nextPaymentDueDate}
|
||||
nextBillingDate={formatPaymentDate(subscription.payment.periodEnd)!}
|
||||
/>
|
||||
))}
|
||||
{hasAiAssistViaWritefull && <WritefullManagedBundleAddOn />}
|
||||
|
||||
+2
-4
@@ -6,6 +6,7 @@ import { PaidSubscription } from '../../../../../../../../types/subscription/das
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
import { useLocation } from '@/shared/hooks/use-location'
|
||||
import { stripHasSubscription } from '../../../../data/subscription-url'
|
||||
import isInFreeTrial from '../../../../util/is-in-free-trial'
|
||||
|
||||
export function CancelSubscriptionButton() {
|
||||
const { t } = useTranslation()
|
||||
@@ -18,10 +19,7 @@ export function CancelSubscriptionButton() {
|
||||
} = useSubscriptionDashboardContext()
|
||||
|
||||
const subscription = personalSubscription as PaidSubscription
|
||||
const isInTrial =
|
||||
subscription?.payment.trialEndsAtFormatted &&
|
||||
subscription?.payment.trialEndsAt &&
|
||||
new Date(subscription.payment.trialEndsAt).getTime() > Date.now()
|
||||
const isInTrial = isInFreeTrial(subscription?.payment.trialEndsAt)
|
||||
const hasPendingOrActivePause =
|
||||
subscription.payment.state === 'paused' ||
|
||||
(subscription.payment.state === 'active' &&
|
||||
|
||||
+4
-1
@@ -4,6 +4,7 @@ import { Trans, useTranslation } from 'react-i18next'
|
||||
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useLocation } from '@/shared/hooks/use-location'
|
||||
import { formatPaymentDateTime } from '../../../../util/payment-dates'
|
||||
|
||||
export type FlashMessageName = 'paused' | 'unpaused' | 'error'
|
||||
|
||||
@@ -36,7 +37,9 @@ export function FlashMessage() {
|
||||
<Trans
|
||||
i18nKey="your_subscription_will_pause_on_short"
|
||||
values={{
|
||||
pauseDate: subscription.payment.nextPaymentDueAt,
|
||||
pauseDate: formatPaymentDateTime(
|
||||
subscription.payment.periodEnd
|
||||
),
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
|
||||
+8
-6
@@ -1,5 +1,10 @@
|
||||
import { Trans } from 'react-i18next'
|
||||
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
|
||||
import isInFreeTrial from '../../../../util/is-in-free-trial'
|
||||
import {
|
||||
formatPaymentDate,
|
||||
formatPaymentDateTime,
|
||||
} from '../../../../util/payment-dates'
|
||||
|
||||
type SubscriptionRemainderProps = {
|
||||
subscription: PaidSubscription
|
||||
@@ -10,14 +15,11 @@ function SubscriptionRemainder({
|
||||
subscription,
|
||||
hideTime,
|
||||
}: SubscriptionRemainderProps) {
|
||||
const stillInATrial =
|
||||
subscription.payment.trialEndsAtFormatted &&
|
||||
subscription.payment.trialEndsAt &&
|
||||
new Date(subscription.payment.trialEndsAt).getTime() > Date.now()
|
||||
const stillInATrial = isInFreeTrial(subscription.payment.trialEndsAt)
|
||||
|
||||
const terminationDate = hideTime
|
||||
? subscription.payment.nextPaymentDueDate
|
||||
: subscription.payment.nextPaymentDueAt
|
||||
? formatPaymentDate(subscription.payment.periodEnd)
|
||||
: formatPaymentDateTime(subscription.payment.periodEnd)
|
||||
return stillInATrial ? (
|
||||
<Trans
|
||||
i18nKey="subscription_will_remain_active_until_end_of_trial_period_x"
|
||||
|
||||
+4
-6
@@ -1,19 +1,17 @@
|
||||
import { Trans } from 'react-i18next'
|
||||
import { formatPaymentDateTime } from '../../../../util/payment-dates'
|
||||
|
||||
type TrialEndingProps = {
|
||||
trialEndsAtFormatted: string
|
||||
trialEndsAt: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function TrialEnding({
|
||||
trialEndsAtFormatted,
|
||||
className,
|
||||
}: TrialEndingProps) {
|
||||
export function TrialEnding({ trialEndsAt, className }: TrialEndingProps) {
|
||||
return (
|
||||
<p className={className} data-testid="trial-ending">
|
||||
<Trans
|
||||
i18nKey="youre_on_free_trial_which_ends_on"
|
||||
values={{ date: trialEndsAtFormatted }}
|
||||
values={{ date: formatPaymentDateTime(trialEndsAt) }}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
|
||||
+4
-1
@@ -6,6 +6,7 @@ import {
|
||||
} from '../../../data/add-on-codes'
|
||||
import ReactivateSubscription from '../reactivate-subscription'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import { formatPaymentDateTime } from '../../../util/payment-dates'
|
||||
|
||||
export function CanceledSubscription({
|
||||
subscription,
|
||||
@@ -48,7 +49,9 @@ export function CanceledSubscription({
|
||||
<Trans
|
||||
i18nKey="subscription_canceled_and_terminate_on_x"
|
||||
values={{
|
||||
terminateDate: subscription.payment.nextPaymentDueAt,
|
||||
terminateDate: formatPaymentDateTime(
|
||||
subscription.payment.periodEnd
|
||||
),
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
|
||||
+2
-1
@@ -17,6 +17,7 @@ import Notification from '@/shared/components/notification'
|
||||
import WritefullManagedBundleAddOn from './states/active/change-plan/modals/writefull-bundle-management-modal'
|
||||
import RedirectAlerts from './redirect-alerts'
|
||||
import { PaidSubscription } from '@ol-types/subscription/dashboard/subscription'
|
||||
import { formatPaymentDate } from '../../util/payment-dates'
|
||||
|
||||
function SubscriptionDashboard() {
|
||||
const { t } = useTranslation()
|
||||
@@ -42,7 +43,7 @@ function SubscriptionDashboard() {
|
||||
subscription &&
|
||||
subscription.pendingPlan &&
|
||||
subscription.pendingPlan.name !== subscription.plan.name
|
||||
const nextPaymentDueDate = subscription?.payment?.nextPaymentDueDate
|
||||
const nextPaymentDueDate = formatPaymentDate(subscription?.payment?.periodEnd)
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
|
||||
+7
-3
@@ -15,6 +15,7 @@ import {
|
||||
import { PaidSubscription } from '../../../../../../types/subscription/dashboard/subscription'
|
||||
import { useBroadcastUser } from '@/shared/hooks/user-channel/use-broadcast-user'
|
||||
import { getUpgradePlanDisplayName } from '../../util/plan-display-names'
|
||||
import { formatPaymentDateTime } from '../../util/payment-dates'
|
||||
|
||||
function SuccessfulSubscription() {
|
||||
const { t } = useTranslation()
|
||||
@@ -56,8 +57,9 @@ function SuccessfulSubscription() {
|
||||
i18nKey="next_payment_of_x_collectected_on_y"
|
||||
values={{
|
||||
paymentAmmount: subscription.payment.displayPrice,
|
||||
collectionDate:
|
||||
subscription.payment.nextPaymentDueAt,
|
||||
collectionDate: formatPaymentDateTime(
|
||||
subscription.payment.periodEnd
|
||||
),
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
@@ -175,7 +177,9 @@ function UpgradeSuccess({
|
||||
i18nKey="next_payment_of_x_collectected_on_y"
|
||||
values={{
|
||||
paymentAmmount: subscription.payment.displayPrice,
|
||||
collectionDate: subscription.payment.nextPaymentDueAt,
|
||||
collectionDate: formatPaymentDateTime(
|
||||
subscription.payment.periodEnd
|
||||
),
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
|
||||
Reference in New Issue
Block a user