Skip to content

Commit

Permalink
feat(unsub-survey): randomize reasons and require response (#26203)
Browse files Browse the repository at this point in the history
  • Loading branch information
raquelmsmith authored Nov 14, 2024
1 parent d22c08c commit 71f35d9
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 28 deletions.
2 changes: 2 additions & 0 deletions cypress/e2e/billing.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ describe('Billing', () => {
cy.get('[data-attr=more-button]').first().click()
cy.contains('.LemonButton', 'Unsubscribe').click()
cy.get('.LemonModal h3').should('contain', 'Unsubscribe from Product analytics')
cy.get('[data-attr=unsubscribe-reason-too-expensive]').click()

cy.get('[data-attr=unsubscribe-reason-survey-textarea]').type('Product analytics')
cy.contains('.LemonModal .LemonButton', 'Cancel').click()

cy.get('[data-attr=more-button]').eq(1).click()
cy.contains('.LemonButton', 'Unsubscribe').click()
cy.get('.LemonModal h3').should('contain', 'Unsubscribe from Session replay')
cy.get('[data-attr=unsubscribe-reason-too-expensive]').click()
cy.get('[data-attr=unsubscribe-reason-survey-textarea]').type('Session replay')
cy.contains('.LemonModal .LemonButton', 'Cancel').click()

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 33 additions & 28 deletions frontend/src/scenes/billing/UnsubscribeSurveyModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,28 @@ import {
LemonModal,
LemonTextArea,
Link,
Tooltip,
} from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { HeartHog } from 'lib/components/hedgehogs'
import { useHogfetti } from 'lib/components/Hogfetti/Hogfetti'
import { supportLogic } from 'lib/components/Support/supportLogic'
import { useState } from 'react'

import { BillingProductV2AddonType, BillingProductV2Type } from '~/types'

import { billingLogic } from './billingLogic'
import { billingProductLogic } from './billingProductLogic'
import { billingProductLogic, randomizeReasons, UNSUBSCRIBE_REASONS } from './billingProductLogic'
import { ExportsUnsubscribeTable, exportsUnsubscribeTableLogic } from './ExportsUnsubscribeTable'

const UNSUBSCRIBE_REASONS = [
'Too expensive',
'Not getting enough value',
'Not using the product',
'Found a better alternative',
'Poor customer support',
'Too difficult to use',
'Not enough hedgehogs',
'Other (let us know below!)',
]

export const UnsubscribeSurveyModal = ({
product,
}: {
product: BillingProductV2Type | BillingProductV2AddonType
}): JSX.Element | null => {
const { trigger, HogfettiComponent } = useHogfetti()

const { surveyID, surveyResponse, isAddonProduct, unsubscribeModalStep } = useValues(
const { surveyID, surveyResponse, isAddonProduct, unsubscribeModalStep, unsubscribeReasonQuestions } = useValues(
billingProductLogic({ product, hogfettiTrigger: trigger })
)
const {
Expand All @@ -55,6 +46,7 @@ export const UnsubscribeSurveyModal = ({
const { unsubscribeError, billingLoading, billing } = useValues(billingLogic)
const { unsubscribeDisabledReason, itemsToDisable } = useValues(exportsUnsubscribeTableLogic)
const { openSupportForm } = useActions(supportLogic)
const [randomizedReasons] = useState(randomizeReasons(UNSUBSCRIBE_REASONS))

const textAreaNotEmpty = surveyResponse['$survey_response']?.length > 0
const includesPipelinesAddon =
Expand Down Expand Up @@ -152,7 +144,15 @@ export const UnsubscribeSurveyModal = ({
</LemonButton>
<LemonButton
type={textAreaNotEmpty ? 'primary' : 'secondary'}
disabledReason={includesPipelinesAddon && unsubscribeDisabledReason}
disabledReason={
surveyResponse['$survey_response_2'].length === 0
? 'Please select a reason'
: !textAreaNotEmpty
? 'Please share your feedback'
: includesPipelinesAddon
? unsubscribeDisabledReason
: undefined
}
onClick={handleUnsubscribe}
loading={billingLoading}
>
Expand Down Expand Up @@ -192,30 +192,35 @@ export const UnsubscribeSurveyModal = ({
? `Why are you ${actionVerb}?`
: `Why are you ${actionVerb} from ${product.name}?`}{' '}
<i className="text-muted">(you can select multiple)</i>
<Tooltip title="Required">
<span className="text-danger">*</span>
</Tooltip>
</LemonLabel>
<div className="grid grid-cols-2 gap-2">
{UNSUBSCRIBE_REASONS.map((reason) => (
{randomizedReasons.map((reason) => (
<LemonCheckbox
bordered
key={reason}
label={reason}
dataAttr={`unsubscribe-reason-${reason.toLowerCase().replace(' ', '-')}`}
checked={surveyResponse['$survey_response_2'].includes(reason)}
onChange={() => toggleSurveyReason(reason)}
key={reason.reason}
label={reason.reason}
dataAttr={`unsubscribe-reason-${reason.reason.toLowerCase().replace(' ', '-')}`}
checked={surveyResponse['$survey_response_2'].includes(reason.reason)}
onChange={() => toggleSurveyReason(reason.reason)}
className="w-full"
labelClassName="w-full"
/>
))}
</div>

<LemonTextArea
data-attr="unsubscribe-reason-survey-textarea"
placeholder="Share your feedback here so we can improve PostHog!"
value={surveyResponse['$survey_response']}
onChange={(value) => {
setSurveyResponse('$survey_response', value)
}}
/>
{surveyResponse['$survey_response_2'].length > 0 && (
<LemonTextArea
data-attr="unsubscribe-reason-survey-textarea"
placeholder={unsubscribeReasonQuestions}
value={surveyResponse['$survey_response']}
onChange={(value) => {
setSurveyResponse('$survey_response', value)
}}
/>
)}

<LemonBanner type="info">
<p>
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/scenes/billing/billingProductLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,28 @@ import { BillingGaugeItemKind, BillingGaugeItemType } from './types'

const DEFAULT_BILLING_LIMIT: number = 500

type UnsubscribeReason = {
reason: string
question: string
}

export const UNSUBSCRIBE_REASONS: UnsubscribeReason[] = [
{ reason: 'Too expensive', question: 'What will you be using instead?' },
{ reason: 'Not getting enough value', question: 'What prevented you from getting more value out of PostHog?' },
{ reason: 'Not using the product', question: 'Why are you not using the product?' },
{ reason: 'Found a better alternative', question: 'What service will you be moving to?' },
{ reason: 'Poor customer support', question: 'Please provide details on your support experience.' },
{ reason: 'Too difficult to use', question: 'What was difficult to use?' },
{ reason: 'Not enough hedgehogs', question: 'How many hedgehogs do you need? (but really why are you leaving)' },
{ reason: 'Other (let us know below!)', question: 'Why are you leaving?' },
]

export const randomizeReasons = (reasons: UnsubscribeReason[]): UnsubscribeReason[] => {
const shuffledReasons = reasons.slice(0, -1).sort(() => Math.random() - 0.5)
shuffledReasons.push(reasons[reasons.length - 1])
return shuffledReasons
}

export interface BillingProductLogicProps {
product: BillingProductV2Type | BillingProductV2AddonType
productRef?: React.MutableRefObject<HTMLDivElement | null>
Expand Down Expand Up @@ -298,6 +320,18 @@ export const billingProductLogic = kea<billingProductLogicType>([
(billing, product): boolean =>
!!billing?.products?.some((p) => p.addons?.some((addon) => addon.type === product?.type)),
],
unsubscribeReasonQuestions: [
(s) => [s.surveyResponse],
(surveyResponse): string => {
return surveyResponse['$survey_response_2']
.map((reason) => {
const reasonObject = UNSUBSCRIBE_REASONS.find((r) => r.reason === reason)
return reasonObject?.question
})
.join(' ')
.concat(' (required)')
},
],
})),
listeners(({ actions, values, props }) => ({
updateBillingLimitsSuccess: () => {
Expand Down

0 comments on commit 71f35d9

Please sign in to comment.