All recipes
Recipe

Contact — support team card

The default ContactOffer is a single button that opens a mailto, tel, or chat URL. This recipe overrides it to show an avatar and an SLA reassurance block above the button, so 'talk to support' has the visual weight of a discount or pause offer. Useful when your support team is a real save channel.

custom recipe

Talk to us first?

Our team would love to help resolve any issues.

Support team
Average reply: under 2 hours

The full source

Copy this file into your project. It's the same file the showcase preview above renders from.

components/ContactWithSupportCard.tsxtsx
/**
 * Contact offer with a support-team avatar block. Adds a face/SLA card
 * above the action button — useful when "talk to support" is the offer
 * and you want the accept button to compete visually with save offers.
 *
 * Wire it as a per-type override:
 *
 *   <CancelFlow
 *     ...
 *     components={{ ContactOffer: ContactWithSupportCard }}
 *   />
 *
 * Edit the SUPPORT constant for your own team's info — name, SLA copy,
 * avatar character/icon.
 */
import type { OfferStepProps } from '@churnkey/react/core'

const SUPPORT = {
  name: 'Support team',
  slaCopy: 'Average reply: under 2 hours',
  avatar: '💬', // emoji or swap for an <img src=... /> with your team's photo
}

export function ContactWithSupportCard({
  title,
  description,
  offer,
  onAccept,
  onDecline,
  isProcessing,
}: OfferStepProps) {
  const headline = title ?? offer.copy.headline
  const body = description ?? offer.copy.body

  return (
    <div className="ck-step ck-step-offer">
      {headline && <h2 className="ck-step-title">{headline}</h2>}
      {body && <p className="ck-step-description">{body}</p>}

      <div className="ck-offer-card">
        <div
          style={{
            display: 'flex',
            alignItems: 'center',
            gap: 12,
            padding: 16,
            background: 'var(--ck-color-primary-soft)',
            borderRadius: 'var(--ck-radius-md)',
            marginBottom: 20,
          }}
        >
          <div
            style={{
              width: 40,
              height: 40,
              borderRadius: 999,
              background: 'var(--ck-color-surface)',
              color: 'var(--ck-color-primary)',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              fontSize: 20,
              flexShrink: 0,
            }}
            aria-hidden
          >
            {SUPPORT.avatar}
          </div>
          <div>
            <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ck-color-text)' }}>{SUPPORT.name}</div>
            <div style={{ fontSize: 12, color: 'var(--ck-color-text-secondary)', marginTop: 1 }}>{SUPPORT.slaCopy}</div>
          </div>
        </div>

        <button
          type="button"
          className="ck-button ck-button-primary"
          onClick={() => onAccept()}
          disabled={isProcessing}
        >
          {isProcessing ? 'Processing...' : offer.copy.cta}
        </button>
        <button type="button" className="ck-button-link" 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
import { CancelFlow } from '@churnkey/react'
import { ContactWithSupportCard } from './components/ContactWithSupportCard'

<CancelFlow
  steps={[
    {
      type: 'survey',
      reasons: [{
        id: 'need-help',
        label: 'I need help with something',
        offer: {
          type: 'contact',
          url: 'mailto:support@example.com',
          label: 'Email support',
        },
      }],
    },
    { type: 'confirm' },
  ]}
  components={{ ContactOffer: ContactWithSupportCard }}
  handleCancel={async () => myBilling.cancel()}
/>