Skip to content

Commit 6f5982d

Browse files
pamelachiajoshenlimalaister
authored
Chore/update churn survey (supabase#36722)
* update cancellation reasons * update texts * randomise options and only single select reasons * label overrides and reason readability * minor fixes * fix reasons sending * Fixes * Adjust tooltip side * improve header description * add a label for radio options * wrap shuffledReasons in useState so it doesn't randomly shuffle --------- Co-authored-by: Joshen Lim <[email protected]> Co-authored-by: Alaister Young <[email protected]>
1 parent 699d53e commit 6f5982d

File tree

8 files changed

+296
-270
lines changed

8 files changed

+296
-270
lines changed
Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,44 @@
11
export const USAGE_APPROACHING_THRESHOLD = 0.8
22

33
export const CANCELLATION_REASONS = [
4-
'Pricing',
5-
"My project isn't getting traction",
6-
'Poor customer service',
7-
'Missing feature',
8-
"I didn't see the value",
9-
"Supabase didn't meet my needs",
10-
'Dashboard is too complicated',
11-
'Postgres is too complicated',
12-
'Problem not solved',
13-
'Too many bugs/issues',
14-
'I decided to use something else',
15-
'My work has finished/discontinued',
16-
'I’m migrating to/starting a new project',
17-
'None of the above',
4+
{
5+
value: 'I was just exploring, or it was a hobby/student project.',
6+
},
7+
{
8+
value: 'I was not satisfied with the customer support I received.',
9+
label: 'Could you tell us more about your experience with our support team?',
10+
},
11+
{
12+
value: 'Supabase is missing a specific feature I need.',
13+
label: 'What specific feature(s) are we missing?',
14+
},
15+
{
16+
value: 'I found it difficult to use or build with.',
17+
label: 'What specific parts of Supabase did you find difficult or frustrating?',
18+
},
19+
{
20+
value: 'Performance or reliability insufficient.',
21+
label:
22+
'Could you tell us more about the specific issues you encountered (e.g., UI bugs, API latency, downtime)?',
23+
},
24+
{
25+
value: 'My project was cancelled or put on hold.',
26+
},
27+
{
28+
value: 'Too expensive',
29+
label: 'We appreciate your perspective on our pricing, what aspects of the cost felt too high?',
30+
},
31+
{
32+
value: 'The pricing is unpredictable and hard to budget for.',
33+
label:
34+
'Which aspects of our pricing model made it difficult for you to predict your monthly costs?',
35+
},
36+
{
37+
value: 'My company went out of business or was acquired.',
38+
},
39+
{
40+
value: 'I lost trust in the company or its future direction.',
41+
label:
42+
'Building and maintaining your trust is our highest priority, could you please share the specific event or reason that led to this loss of trust?',
43+
},
1844
]

apps/studio/components/interfaces/Organization/BillingSettings/BillingSettings.constants.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 102 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import { includes, without } from 'lodash'
2-
import { useReducer, useState } from 'react'
1+
import { useState } from 'react'
32
import { toast } from 'sonner'
43

54
import { useParams } from 'common'
5+
import { CANCELLATION_REASONS } from 'components/interfaces/Billing/Billing.constants'
66
import { useSendDowngradeFeedbackMutation } from 'data/feedback/exit-survey-send'
7+
import { ProjectInfo } from 'data/projects/projects-query'
78
import { useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation'
89
import { useFlag } from 'hooks/ui/useFlag'
9-
import { Alert, Button, Input, Modal } from 'ui'
10-
import type { ProjectInfo } from '../../../../../data/projects/projects-query'
11-
import { CANCELLATION_REASONS } from '../BillingSettings.constants'
10+
import { Alert, Button, cn, Input, Modal } from 'ui'
1211
import ProjectUpdateDisabledTooltip from '../ProjectUpdateDisabledTooltip'
1312

1413
export interface ExitSurveyModalProps {
@@ -18,11 +17,11 @@ export interface ExitSurveyModalProps {
1817
}
1918

2019
// [Joshen] For context - Exit survey is only when going to Free Plan from a paid plan
21-
const ExitSurveyModal = ({ visible, projects, onClose }: ExitSurveyModalProps) => {
20+
export const ExitSurveyModal = ({ visible, projects, onClose }: ExitSurveyModalProps) => {
2221
const { slug } = useParams()
2322

2423
const [message, setMessage] = useState('')
25-
const [selectedReasons, dispatchSelectedReasons] = useReducer(reducer, [])
24+
const [selectedReason, setSelectedReason] = useState<string[]>([])
2625

2726
const subscriptionUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
2827
const { mutate: updateOrgSubscription, isLoading: isUpdating } = useOrgSubscriptionUpdateMutation(
@@ -42,17 +41,26 @@ const ExitSurveyModal = ({ visible, projects, onClose }: ExitSurveyModalProps) =
4241

4342
const hasProjectsWithComputeDowngrade = projectsWithComputeDowngrade.length > 0
4443

45-
function reducer(state: any, action: any) {
46-
if (includes(state, action.target.value)) {
47-
return without(state, action.target.value)
48-
} else {
49-
return [...state, action.target.value]
50-
}
44+
const [shuffledReasons] = useState(() => [
45+
...CANCELLATION_REASONS.sort(() => Math.random() - 0.5),
46+
{ value: 'None of the above' },
47+
])
48+
49+
const onSelectCancellationReason = (reason: string) => {
50+
setSelectedReason([reason])
51+
}
52+
53+
// Helper to get label for selected reason
54+
const getReasonLabel = (reason: string | undefined) => {
55+
const found = CANCELLATION_REASONS.find((r) => r.value === reason)
56+
return found?.label || 'What can we improve on?'
5157
}
5258

59+
const textareaLabel = getReasonLabel(selectedReason[0])
60+
5361
const onSubmit = async () => {
54-
if (selectedReasons.length === 0) {
55-
return toast.error('Please select at least one reason for canceling your subscription')
62+
if (selectedReason.length === 0) {
63+
return toast.error('Please select a reason for canceling your subscription')
5664
}
5765

5866
await downgradeOrganization()
@@ -70,7 +78,7 @@ const ExitSurveyModal = ({ visible, projects, onClose }: ExitSurveyModalProps) =
7078
try {
7179
await sendExitSurvey({
7280
orgSlug: slug,
73-
reasons: selectedReasons.reduce((a, b) => `${a}- ${b}\n`, ''),
81+
reasons: selectedReason.reduce((a, b) => `${a}- ${b}\n`, ''),
7482
message,
7583
exitAction: 'downgrade',
7684
})
@@ -92,99 +100,87 @@ const ExitSurveyModal = ({ visible, projects, onClose }: ExitSurveyModalProps) =
92100
}
93101

94102
return (
95-
<>
96-
<Modal
97-
hideFooter
98-
size="xlarge"
99-
visible={visible}
100-
onCancel={onClose}
101-
header="Help us improve."
102-
>
103-
<Modal.Content>
104-
<div className="space-y-4">
105-
<p className="text-sm text-foreground-light">
106-
We always strive to improve Supabase as much as we can. Please let us know the reasons
107-
you are canceling your subscription so that we can improve in the future.
108-
</p>
109-
<div className="space-y-8 mt-6">
110-
<div className="flex flex-wrap gap-2" data-toggle="buttons">
111-
{CANCELLATION_REASONS.map((option) => {
112-
const active = selectedReasons.find((x) => x === option)
113-
return (
114-
<label
115-
key={option}
116-
className={`
117-
flex cursor-pointer items-center space-x-2 rounded-md py-1
118-
pl-2 pr-3 text-center text-sm
119-
shadow-sm transition-all duration-100
120-
${
121-
active
122-
? ` bg-foreground text-background opacity-100 hover:bg-opacity-75`
123-
: ` bg-border-strong text-foreground opacity-25 hover:opacity-50`
124-
}
125-
`}
126-
>
127-
<input
128-
type="checkbox"
129-
name="options"
130-
value={option}
131-
className="hidden"
132-
onClick={dispatchSelectedReasons}
133-
/>
134-
<div>{option}</div>
135-
</label>
136-
)
137-
})}
138-
</div>
139-
<div className="text-area-text-sm">
140-
<Input.TextArea
141-
id="message"
142-
name="message"
143-
value={message}
144-
onChange={(event: any) => setMessage(event.target.value)}
145-
label="Anything else that we can improve on?"
146-
/>
147-
</div>
103+
<Modal hideFooter size="xlarge" visible={visible} onCancel={onClose} header="Help us improve">
104+
<Modal.Content>
105+
<div className="space-y-4">
106+
<p className="text-sm text-foreground-light">
107+
Share with us why you're downgrading your plan.
108+
</p>
109+
<div className="space-y-8 mt-6">
110+
<div className="flex flex-wrap gap-2" data-toggle="buttons">
111+
{shuffledReasons.map((option) => {
112+
const active = selectedReason[0] === option.value
113+
return (
114+
<label
115+
key={option.value}
116+
className={cn(
117+
'flex cursor-pointer items-center space-x-2 rounded-md py-1',
118+
'pl-2 pr-3 text-center text-sm',
119+
'shadow-sm transition-all duration-100',
120+
active
121+
? `bg-foreground text-background opacity-100 hover:bg-opacity-75`
122+
: `bg-border-strong text-foreground opacity-75 hover:opacity-100`
123+
)}
124+
>
125+
<input
126+
type="radio"
127+
name="options"
128+
value={option.value}
129+
className="hidden"
130+
checked={active}
131+
onChange={() => onSelectCancellationReason(option.value)}
132+
/>
133+
<div>{option.value}</div>
134+
</label>
135+
)
136+
})}
137+
</div>
138+
<div className="text-area-text-sm flex flex-col gap-y-2">
139+
<label className="text-sm whitespace-pre-line break-words">{textareaLabel}</label>
140+
<Input.TextArea
141+
id="message"
142+
name="message"
143+
value={message}
144+
onChange={(event: any) => setMessage(event.target.value)}
145+
rows={3}
146+
/>
148147
</div>
149-
{hasProjectsWithComputeDowngrade && (
150-
<Alert
151-
withIcon
152-
variant="warning"
153-
title={`${projectsWithComputeDowngrade.length} of your projects will be restarted upon clicking confirm,`}
154-
>
155-
This is due to changes in compute instances from the downgrade. Affected projects
156-
include {projectsWithComputeDowngrade.map((project) => project.name).join(', ')}.
157-
</Alert>
158-
)}
159148
</div>
160-
</Modal.Content>
161-
162-
<div className="flex items-center justify-between border-t px-4 py-4">
163-
<p className="text-xs text-foreground-lighter">
164-
The unused amount for the remaining time of your billing cycle will be refunded as
165-
credits
166-
</p>
167-
168-
<div className="flex items-center space-x-2">
169-
<Button type="default" onClick={() => onClose()}>
170-
Cancel
149+
{hasProjectsWithComputeDowngrade && (
150+
<Alert
151+
withIcon
152+
variant="warning"
153+
title={`${projectsWithComputeDowngrade.length} of your projects will be restarted upon clicking confirm,`}
154+
>
155+
This is due to changes in compute instances from the downgrade. Affected projects
156+
include {projectsWithComputeDowngrade.map((project) => project.name).join(', ')}.
157+
</Alert>
158+
)}
159+
</div>
160+
</Modal.Content>
161+
162+
<div className="flex items-center justify-between border-t px-4 py-4">
163+
<p className="text-xs text-foreground-lighter">
164+
The unused amount for the remaining time of your billing cycle will be refunded as credits
165+
</p>
166+
167+
<div className="flex items-center space-x-2">
168+
<Button type="default" onClick={() => onClose()}>
169+
Cancel
170+
</Button>
171+
<ProjectUpdateDisabledTooltip projectUpdateDisabled={subscriptionUpdateDisabled}>
172+
<Button
173+
type="danger"
174+
className="pointer-events-auto"
175+
loading={isSubmitting}
176+
disabled={subscriptionUpdateDisabled || isSubmitting}
177+
onClick={onSubmit}
178+
>
179+
Confirm downgrade
171180
</Button>
172-
<ProjectUpdateDisabledTooltip projectUpdateDisabled={subscriptionUpdateDisabled}>
173-
<Button
174-
type="danger"
175-
className="pointer-events-auto"
176-
loading={isSubmitting}
177-
disabled={subscriptionUpdateDisabled || isSubmitting}
178-
onClick={onSubmit}
179-
>
180-
Confirm downgrade
181-
</Button>
182-
</ProjectUpdateDisabledTooltip>
183-
</div>
181+
</ProjectUpdateDisabledTooltip>
184182
</div>
185-
</Modal>
186-
</>
183+
</div>
184+
</Modal>
187185
)
188186
}
189-
190-
export default ExitSurveyModal

apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { useRouter } from 'next/router'
55
import { useEffect, useRef, useState } from 'react'
66

77
import { StudioPricingSidePanelOpenedEvent } from 'common/telemetry-constants'
8+
import { getPlanChangeType } from 'components/interfaces/Billing/Subscription/Subscription.utils'
89
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
10+
import PartnerManagedResource from 'components/ui/PartnerManagedResource'
911
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
1012
import { useFreeProjectLimitCheckQuery } from 'data/organizations/free-project-limit-check-query'
1113
import { useOrganizationBillingSubscriptionPreview } from 'data/organizations/organization-billing-subscription-preview'
@@ -23,12 +25,10 @@ import { useOrgSettingsPageStateSnapshot } from 'state/organization-settings'
2325
import { Button, SidePanel, cn } from 'ui'
2426
import DowngradeModal from './DowngradeModal'
2527
import { EnterpriseCard } from './EnterpriseCard'
26-
import ExitSurveyModal from './ExitSurveyModal'
28+
import { ExitSurveyModal } from './ExitSurveyModal'
2729
import MembersExceedLimitModal from './MembersExceedLimitModal'
2830
import { SubscriptionPlanUpdateDialog } from './SubscriptionPlanUpdateDialog'
2931
import UpgradeSurveyModal from './UpgradeModal'
30-
import PartnerManagedResource from 'components/ui/PartnerManagedResource'
31-
import { getPlanChangeType } from 'components/interfaces/Billing/Subscription/Subscription.utils'
3232

3333
const PlanUpdateSidePanel = () => {
3434
const router = useRouter()

apps/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useState } from 'react'
44
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
55
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
66
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
7-
import DeleteProjectModal from './DeleteProjectModal'
7+
import { DeleteProjectModal } from './DeleteProjectModal'
88

99
export interface DeleteProjectButtonProps {
1010
type?: 'danger' | 'default'

0 commit comments

Comments
 (0)