@@ -5,7 +5,7 @@ import CloseButton from '../common/close-button';
55import BackButton from '../common/back-button' ;
66import { MultipleProductsPlansSection } from '../common/plans-section' ;
77import { getDateString } from '../../utils/date-time' ;
8- import { formatNumber , getAvailablePrices , getCurrencySymbol , getFilteredPrices , getMemberActivePrice , getMemberActiveProduct , getMemberSubscription , getPriceFromSubscription , getProductFromPrice , getSubscriptionFromId , getUpgradeProducts , hasMultipleProductsFeature , isComplimentaryMember , isPaidMember } from '../../utils/helpers' ;
8+ import { formatNumber , getAvailablePrices , getCurrencySymbol , getFilteredPrices , getMemberActivePrice , getMemberActiveProduct , getMemberSubscription , getPriceFromSubscription , getProductFromId , getProductFromPrice , getSubscriptionFromId , getUpdatedOfferPrice , getUpgradeProducts , hasMultipleProductsFeature , isComplimentaryMember , isPaidMember } from '../../utils/helpers' ;
99import Interpolate from '@doist/react-interpolate' ;
1010import { t } from '../../utils/i18n' ;
1111
@@ -39,35 +39,15 @@ export const AccountPlanPageStyles = `
3939 padding: 6px 12px;
4040 }
4141
42- .gh-portal-retention-offer {
43- text-align: center;
42+ .gh-portal-retention-offer-price {
43+ display: flex;
44+ align-items: center;
45+ gap: 6px;
46+ margin-top: 20px;
4447 }
4548
46- .gh-portal-retention-offer-message {
47- font-size: 1.5rem;
48- color: var(--grey4);
49- margin: 0 0 24px;
50- line-height: 1.5;
51- }
52-
53- .gh-portal-retention-offer-card {
54- background: var(--grey14);
55- border-radius: 8px;
56- padding: 32px 24px;
57- margin-bottom: 24px;
58- }
59-
60- .gh-portal-retention-offer-discount {
61- font-size: 2.4rem;
62- font-weight: 700;
63- color: var(--grey1);
64- line-height: 1.1;
65- }
66-
67- .gh-portal-retention-offer-duration {
68- font-size: 1.5rem;
69- color: var(--grey5);
70- margin-top: 4px;
49+ .gh-portal-retention-offer-price .gh-portal-offer-oldprice {
50+ margin: 4px 0 0;
7151 }
7252` ;
7353
@@ -295,47 +275,79 @@ function formatOfferDuration(offer) {
295275 return '' ;
296276}
297277
298- const RetentionOfferSection = ( { offer, onAcceptOffer, onDeclineOffer} ) => {
278+ const RetentionOfferSection = ( { offer, product , price , onAcceptOffer, onDeclineOffer} ) => {
299279 const { brandColor, action} = useContext ( AppContext ) ;
300280 const isAcceptingOffer = action === 'applyOffer:running' ;
301281
282+ const originalPrice = formatNumber ( price . amount / 100 ) ;
283+ const discountedPrice = formatNumber ( getUpdatedOfferPrice ( { offer, price} ) ) ;
284+ const currencySymbol = getCurrencySymbol ( price . currency ) ;
285+
302286 const discountText = formatOfferDiscount ( offer ) ;
303287 const durationText = formatOfferDuration ( offer ) ;
288+ const intervalLabel = offer . cadence === 'month' ? 'month' : 'year' ;
289+
290+ let offerMessage = `${ discountText } ${ durationText } .` ;
291+
292+ if ( offer . duration !== 'forever' ) {
293+ offerMessage += ` Renews at ${ currencySymbol } ${ originalPrice } /${ intervalLabel } .` ;
294+ }
304295
305296 // TODO: Add i18n once copy is finalized
306297 return (
307- < div className = "gh-portal-logged-out-form-container gh-portal-retention-offer" >
308- < p className = "gh-portal-retention-offer-message " >
298+ < div className = "gh-portal-logged-out-form-container gh-portal-offer gh-portal- retention-offer" >
299+ < p className = "gh-portal-text-center " >
309300 { 'We\'d hate to see you go! How about a special offer to stay?' }
310301 </ p >
311- < div className = "gh-portal-retention-offer-card" >
312- < div className = "gh-portal-retention-offer-discount" > { discountText } </ div >
313- < div className = "gh-portal-retention-offer-duration" > { durationText } </ div >
302+
303+ < div className = "gh-portal-offer-bar" >
304+ < div className = "gh-portal-offer-title" >
305+ < h4 > { product . name } - { offer . cadence === 'month' ? 'Monthly' : 'Yearly' } </ h4 >
306+ < h5 className = "gh-portal-discount-label" > { discountText } </ h5 >
307+ </ div >
308+
309+ < div className = "gh-portal-offer-details" >
310+ < div className = "gh-portal-retention-offer-price" >
311+ < div className = "gh-portal-product-price" >
312+ < span className = "currency-sign" > { currencySymbol } </ span >
313+ < span className = "amount" > { discountedPrice } </ span >
314+ </ div >
315+ < div className = "gh-portal-offer-oldprice" >
316+ { currencySymbol } { originalPrice }
317+ </ div >
318+ </ div >
319+ < p className = "footnote" >
320+ { offerMessage }
321+ </ p >
322+ </ div >
323+
324+ < ActionButton
325+ dataTestId = { 'accept-retention-offer' }
326+ onClick = { onAcceptOffer }
327+ isRunning = { isAcceptingOffer }
328+ disabled = { isAcceptingOffer }
329+ isPrimary = { true }
330+ brandColor = { brandColor }
331+ label = "Accept offer"
332+ style = { {
333+ width : '100%' ,
334+ height : '40px' ,
335+ marginTop : '28px'
336+ } }
337+ />
314338 </ div >
315- < ActionButton
316- dataTestId = { 'accept-retention-offer' }
317- onClick = { onAcceptOffer }
318- isRunning = { isAcceptingOffer }
319- disabled = { isAcceptingOffer }
320- isPrimary = { true }
321- brandColor = { brandColor }
322- label = "Accept offer"
323- style = { {
324- width : '100%' ,
325- height : '40px'
326- } }
327- />
339+
328340 < ActionButton
329341 dataTestId = { 'decline-retention-offer' }
330342 onClick = { onDeclineOffer }
331343 isPrimary = { false }
332344 isDestructive = { true }
333345 classes = { 'gh-portal-btn-text' }
334346 brandColor = { brandColor }
335- label = "Continue to cancellation "
347+ label = "No thanks, I want to cancel "
336348 style = { {
337349 width : '100%' ,
338- marginTop : '24px ' ,
350+ marginTop : '32px ' ,
339351 marginBottom : '24px'
340352 } }
341353 />
@@ -382,7 +394,7 @@ const PlansContainer = ({
382394 pendingOffer, onPlanSelect, onPlanCheckout, onConfirm, onCancelSubscription,
383395 onAcceptRetentionOffer, onDeclineRetentionOffer
384396} ) => {
385- const { member} = useContext ( AppContext ) ;
397+ const { member, site } = useContext ( AppContext ) ;
386398 // Plan upgrade flow for free member or complimentary member
387399 if ( ! isPaidMember ( { member} ) || isComplimentaryMember ( { member} ) ) {
388400 return (
@@ -404,13 +416,21 @@ const PlansContainer = ({
404416
405417 // Retention offer flow - shown before cancellation confirmation
406418 if ( confirmationType === 'offerRetention' && pendingOffer ) {
407- return (
408- < RetentionOfferSection
409- offer = { pendingOffer }
410- onAcceptOffer = { onAcceptRetentionOffer }
411- onDeclineOffer = { onDeclineRetentionOffer }
412- />
413- ) ;
419+ const offerProduct = getProductFromId ( { site, productId : pendingOffer . tier . id } ) ;
420+ const offerPrice = pendingOffer . cadence === 'month' ? offerProduct ?. monthlyPrice : offerProduct ?. yearlyPrice ;
421+
422+ // Skip retention offer if product or price data is invalid
423+ if ( offerProduct && offerPrice ) {
424+ return (
425+ < RetentionOfferSection
426+ offer = { pendingOffer }
427+ product = { offerProduct }
428+ price = { offerPrice }
429+ onAcceptOffer = { onAcceptRetentionOffer }
430+ onDeclineOffer = { onDeclineRetentionOffer }
431+ />
432+ ) ;
433+ }
414434 }
415435
416436 // Plan confirmation flow for cancel/update flows
0 commit comments