All recipes
Default
Default redirect offer
DefaultRedirectOffer renders any offer of type 'redirect'. It's a link to an external URL — useful for sending the customer to onboarding, a recent product update, or a help article. The accept action is a regular anchor with target="_blank", so no handle<Type> callback runs. Fork to add a destination preview, SSO/auth parameters in the URL, or click analytics.
default component
Have you seen the new dashboard?
The redesign launched last week. Take a quick tour before you go.
The full source
Copy this file into your project. It's the same file the showcase preview above renders from.
components/MyRedirectOffer.tsxtsx
import type { OfferDecision, OfferStepProps } from '../../../core/types'
import { cn } from '../../../core/utils'
import { RichText } from '../../rich-text'
export function DefaultRedirectOffer({ title, description, offer, onDecline, classNames }: OfferStepProps) {
const url = (offer as OfferDecision & { url: string }).url
const headline = title ?? offer.copy.headline
const body = description ?? offer.copy.body
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">
<a href={url} target="_blank" rel="noopener noreferrer" className="ck-redirect-link">
<span>{offer.copy.cta}</span>
<ExternalIcon />
</a>
</div>
<button type="button" className={cn('ck-button-link', classNames?.declineButton)} onClick={onDecline}>
{offer.copy.declineCta}
</button>
</div>
</div>
)
}
function ExternalIcon() {
return (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="ck-redirect-icon"
aria-hidden="true"
>
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
<polyline points="15 3 21 3 21 9" />
<line x1="10" y1="14" x2="21" y2="3" />
</svg>
)
}
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 DefaultRedirectOffer when an offer of type 'redirect' renders.
// Copy the source to fork from this baseline.
import { CancelFlow } from '@churnkey/react'
import { MyRedirectOffer } from './components/MyRedirectOffer'
<CancelFlow
steps={[
{
type: 'survey',
reasons: [{
id: 'missing-features',
label: 'Missing features',
offer: {
type: 'redirect',
url: 'https://example.com/whats-new',
label: 'See the latest updates',
},
}],
},
{ type: 'confirm' },
]}
components={{ RedirectOffer: MyRedirectOffer }}
handleCancel={async () => myBilling.cancel()}
/>