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()}
/>