import { LOCAL_STORAGE_KEYS } from 'common' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import { toast } from 'sonner' import { TextArea } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { CANCELLATION_REASONS } from '@/components/interfaces/Billing/Billing.constants' import { LogicalBackupCliInstructions } from '@/components/layouts/ProjectLayout/LogicalBackupCliInstructions' import { TextConfirmModal } from '@/components/ui/TextConfirmModalWrapper' import { useSendDowngradeFeedbackMutation } from '@/data/feedback/exit-survey-send' import type { OrgProject } from '@/data/projects/org-projects-infinite-query' import { useProjectDeleteMutation } from '@/data/projects/project-delete-mutation' import { useOrgSubscriptionQuery } from '@/data/subscriptions/org-subscription-query' import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import type { Organization } from '@/types' export const DeleteProjectModal = ({ visible, onClose, project: projectProp, organization: organizationProp, }: { visible: boolean onClose: () => void project?: OrgProject organization?: Organization }) => { const router = useRouter() const { data: projectFromQuery } = useSelectedProjectQuery() const { data: organizationFromQuery } = useSelectedOrganizationQuery() // Use props if provided, otherwise fall back to hooks const project = projectProp || projectFromQuery const organization = organizationProp || organizationFromQuery const [lastVisitedOrganization] = useLocalStorageQuery( LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION, '' ) const projectRef = project?.ref const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const projectPlan = subscription?.plan?.id ?? 'free' const isFree = projectPlan === 'free' const [message, setMessage] = useState('') const [selectedReason, setSelectedReason] = useState([]) // Single select for cancellation reason 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 [shuffledReasons] = useState(() => [ ...CANCELLATION_REASONS.sort(() => Math.random() - 0.5), { value: 'None of the above' }, ]) const { mutate: deleteProject, isPending: isDeleting } = useProjectDeleteMutation({ onSuccess: async () => { if (!isFree) { try { await sendExitSurvey({ orgSlug: organization?.slug, projectRef, message, reasons: selectedReason.reduce((a, b) => `${a}- ${b}\n`, ''), exitAction: 'delete', }) } 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 } } toast.success(`Successfully deleted ${project?.name}`) // Only redirect if still viewing the deleted project if (router.asPath.startsWith(`/project/${projectRef}`)) { if (lastVisitedOrganization) { router.push(`/org/${lastVisitedOrganization}`) } else { router.push('/organizations') } } }, }) const { mutateAsync: sendExitSurvey, isPending: isSending } = useSendDowngradeFeedbackMutation() const isSubmitting = isDeleting || isSending async function handleDeleteProject() { if (project === undefined) return if (!isFree && selectedReason.length === 0) { return toast.error('Please select a reason for deleting your project') } deleteProject({ projectRef: project.ref, organizationSlug: organization?.slug }) } useEffect(() => { if (visible) { setSelectedReason([]) setMessage('') } }, [visible]) return ( { if (!isSubmitting) onClose() }} >
{/* [Joshen] This is basically ExitSurvey.tsx, ideally we have one shared component but the one in ExitSurvey has a Form wrapped around it already. Will probably need some effort to refactor but leaving that for the future. */} {!isFree && (
{shuffledReasons.map((option) => { const active = selectedReason[0] === option.value return ( ) })}