All recipes
Default
Default trial extension offer
DefaultTrialExtensionOffer renders any offer of type 'trial_extension'. It shows a +N days badge and the new computed trial end date — minimal by design, since trial extensions are usually a single-click decision. Fork it to add a 'what you can do with the extra time' feature list, or replace the badge with a calendar visualization.
default component
Have more time on us
Your trial is extended by 14 days.
+14
days
New end date
Jun 2
The full source
Copy this file into your project. It's the same file the showcase preview above renders from.
components/MyTrialExtensionOffer.tsxtsx
import { formatMonthDay } from '../../../core/format'
import type { OfferDecision, OfferStepProps } from '../../../core/types'
import { cn } from '../../../core/utils'
import { RichText } from '../../rich-text'
export function DefaultTrialExtensionOffer({
title,
description,
offer,
onAccept,
onDecline,
isProcessing,
classNames,
}: OfferStepProps) {
const o = offer as OfferDecision & { days: number }
const headline = title ?? offer.copy.headline
const body = description ?? offer.copy.body
const end = new Date()
end.setDate(end.getDate() + o.days)
const newEnd = formatMonthDay(end)
return (
<div className={cn('ck-step ck-step-offer', classNames?.root)}>
{headline && <h2 className={cn('ck-step-title', classNames?.title)}>{headline}</h2>}
{body && <RichText html={body} className={cn('ck-step-description', classNames?.description)} />}
<div className={cn('ck-offer-card', classNames?.card)}>
<div className="ck-offer-details ck-trial-block">
<div className="ck-trial-badge">
<div className="ck-trial-days">+{o.days}</div>
<div className="ck-trial-unit">{o.days === 1 ? 'day' : 'days'}</div>
</div>
<div>
<div className="ck-trial-end-label">New end date</div>
<div className="ck-trial-end-date">{newEnd}</div>
</div>
</div>
<button
type="button"
className={cn('ck-button ck-button-primary', classNames?.acceptButton)}
onClick={() => onAccept()}
disabled={isProcessing}
>
{isProcessing ? 'Processing...' : offer.copy.cta}
</button>
<button type="button" className={cn('ck-button-link', classNames?.declineButton)} onClick={onDecline}>
{offer.copy.declineCta}
</button>
</div>
</div>
)
}
Wire it into a flow
Drop the file into your codebase and reference it from the appropriate prop on <CancelFlow>.
CancelButton.tsxtsx
// The SDK uses DefaultTrialExtensionOffer when an offer of type
// 'trial_extension' renders. Copy the source to fork from this baseline.
import { CancelFlow } from '@churnkey/react'
import { MyTrialExtensionOffer } from './components/MyTrialExtensionOffer'
<CancelFlow
steps={[
{
type: 'survey',
reasons: [{
id: 'evaluating',
label: 'Still evaluating',
offer: { type: 'trial_extension', days: 14 },
}],
},
{ type: 'confirm' },
]}
components={{ TrialExtensionOffer: MyTrialExtensionOffer }}
handleTrialExtension={async (offer) => myBilling.extendTrial(offer.days)}
handleCancel={async () => myBilling.cancel()}
/>