import { useFlag, useParams } from 'common' import { useState } from 'react' import { toast } from 'sonner' import { Button, cn, Modal, TextArea } from 'ui' import { Admonition } from 'ui-patterns/admonition' import { ProjectUpdateDisabledTooltip } from '../ProjectUpdateDisabledTooltip' import { CANCELLATION_REASONS } from '@/components/interfaces/Billing/Billing.constants' import { useSendDowngradeFeedbackMutation } from '@/data/feedback/exit-survey-send' import { getComputeSize, OrgProject } from '@/data/projects/org-projects-infinite-query' import { useOrgSubscriptionUpdateMutation } from '@/data/subscriptions/org-subscription-update-mutation' export interface ExitSurveyModalProps { visible: boolean projects: OrgProject[] onClose: (success?: boolean) => void } // [Joshen] For context - Exit survey is only when going to Free Plan from a paid plan export const ExitSurveyModal = ({ visible, projects, onClose }: ExitSurveyModalProps) => { const { slug } = useParams() const [message, setMessage] = useState('') const [selectedReason, setSelectedReason] = useState([]) const subscriptionUpdateDisabled = useFlag('disableProjectCreationAndUpdate') const { mutate: updateOrgSubscription, isPending: isUpdating } = useOrgSubscriptionUpdateMutation( { onError: (error) => { toast.error(`Failed to downgrade project: ${error.message}`) }, } ) const { mutateAsync: sendExitSurvey, isPending: isSubmittingFeedback } = useSendDowngradeFeedbackMutation() const isSubmitting = isUpdating || isSubmittingFeedback const projectsWithComputeDowngrade = projects.filter((project) => { const computeSize = getComputeSize(project) return computeSize !== 'nano' }) const hasProjectsWithComputeDowngrade = projectsWithComputeDowngrade.length > 0 const [shuffledReasons] = useState(() => [ ...CANCELLATION_REASONS.sort(() => Math.random() - 0.5), { value: 'None of the above' }, ]) const onSelectCancellationReason = (reason: string) => { setSelectedReason([reason]) } // Helper to get label for selected reason const getReasonLabel = (reason: string | undefined) => { const found = CANCELLATION_REASONS.find((r) => r.value === reason) return found?.label || 'What can we improve on?' } const textareaLabel = getReasonLabel(selectedReason[0]) const onSubmit = async () => { if (selectedReason.length === 0) { return toast.error('Please select a reason for canceling your subscription') } await downgradeOrganization() } const downgradeOrganization = async () => { // Update the subscription first, followed by posting the exit survey if successful // If compute instance is present within the existing subscription, then a restart will be triggered if (!slug) return console.error('Slug is required') updateOrgSubscription( { slug, tier: 'tier_free' }, { onSuccess: async () => { try { await sendExitSurvey({ orgSlug: slug, reasons: selectedReason.reduce((a, b) => `${a}- ${b}\n`, ''), message, exitAction: 'downgrade', }) } catch (error) { // [Joshen] In this case we don't raise any errors if the exit survey fails to send since it shouldn't block the user } finally { toast.success( hasProjectsWithComputeDowngrade ? 'Successfully downgraded organization to the Free Plan. Your projects are currently restarting to update their compute instances.' : 'Successfully downgraded organization to the Free Plan', { duration: hasProjectsWithComputeDowngrade ? 8000 : 4000 } ) onClose(true) window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }) } }, } ) } return (

What made you decide to downgrade your plan?

{shuffledReasons.map((option) => { const active = selectedReason[0] === option.value return ( ) })}