DowngradeModal.tsx161 lines · main
1import { MinusCircle, PauseCircle } from 'lucide-react'
2import { useMemo } from 'react'
3import { plans as subscriptionsPlans } from 'shared-data/plans'
4import { Modal } from 'ui'
5import { Admonition } from 'ui-patterns'
6
7import { getComputeSize, OrgProject } from '@/data/projects/org-projects-infinite-query'
8import type { OrgSubscription, ProjectAddon } from '@/data/subscriptions/types'
9
10export interface DowngradeModalProps {
11 visible: boolean
12 subscription?: OrgSubscription
13 onClose: () => void
14 onConfirm: () => void
15 projects: OrgProject[]
16}
17
18const ProjectDowngradeListItem = ({ projectAddon }: { projectAddon: ProjectAddon }) => {
19 const needsRestart = projectAddon.addons.find((addon) => addon.type === 'compute_instance')
20
21 /**
22 * We do not include Log Drains and Advanced MFA Phone for the following reasons:
23 * 1. These addons are not removed automatically. Instead, users have to remove the respective configuration themselves
24 * 2. It's not obvious to users that Log Drains and MFA Phone are addons
25 */
26 const relevantAddonsToList = projectAddon.addons.filter(
27 (addon) => !['log_drain', 'auth_mfa_phone'].includes(addon.type)
28 )
29
30 const addonNames = relevantAddonsToList.map((addon) => {
31 if (addon.type === 'compute_instance') return `${addon.variant.name} Compute Instance`
32 return addon.variant.name
33 })
34
35 return (
36 <li className="list-disc ml-6">
37 {projectAddon.name}: {addonNames.join(', ')} will be removed.
38 {needsRestart ? (
39 <>
40 {' '}
41 Project will also <span className="font-bold">need to be restarted</span> due to change in
42 compute instance
43 </>
44 ) : (
45 ''
46 )}
47 </li>
48 )
49}
50
51const DowngradeModal = ({
52 visible,
53 subscription,
54 onClose,
55 onConfirm,
56 projects,
57}: DowngradeModalProps) => {
58 const selectedPlan = useMemo(() => subscriptionsPlans.find((tier) => tier.id === 'tier_free'), [])
59
60 // Filter out the micro addon as we're dealing with that separately
61 const previousProjectAddons =
62 subscription?.project_addons.flatMap((projectAddons) => {
63 const addons = projectAddons.addons.filter((it) => it.variant.identifier !== 'ci_micro')
64 if (!addons.length) {
65 return []
66 } else {
67 return {
68 ...projectAddons,
69 // Overwrite addons, filtered out the micro addon
70 addons,
71 }
72 }
73 }) || []
74
75 const hasInstancesOnMicro = projects.some((project) => {
76 const computeSize = getComputeSize(project)
77 return computeSize === 'micro'
78 })
79
80 return (
81 <Modal
82 size="large"
83 alignFooter="right"
84 variant="warning"
85 visible={visible}
86 onCancel={onClose}
87 onConfirm={onConfirm}
88 header={`Confirm to downgrade to ${selectedPlan?.name} plan`}
89 >
90 <Modal.Content>
91 <div className="space-y-2">
92 <Admonition
93 type="warning"
94 title="Downgrading to the Free Plan will lead to reductions in your organization's quota"
95 description="If you're already past the limits of the Free Plan, your projects could become
96 unresponsive or enter read only mode."
97 />
98
99 {((previousProjectAddons.length ?? 0) > 0 || hasInstancesOnMicro) && (
100 <Admonition type="warning" title="Projects affected by the downgrade">
101 <ul className="space-y-1 max-h-[100px] overflow-y-auto">
102 {previousProjectAddons.map((project) => (
103 <ProjectDowngradeListItem key={project.ref} projectAddon={project} />
104 ))}
105
106 {projects
107 .filter((it) => {
108 const computeSize = getComputeSize(it)
109 return computeSize === 'micro'
110 })
111 .map((project) => (
112 <li className="list-disc ml-6" key={project.ref}>
113 {project.name}: Compute will be downgraded. Project will also{' '}
114 <span className="font-bold">need to be restarted</span>.
115 </li>
116 ))}
117 </ul>
118 </Admonition>
119 )}
120 </div>
121
122 <ul className="mt-4 space-y-5 text-sm">
123 <li className="flex items-center gap-3">
124 <PauseCircle size={18} />
125 <span>Projects will be paused after a week of inactivity</span>
126 </li>
127
128 <li className="flex items-center gap-3 mb-2">
129 <MinusCircle size={18} />
130 <span>Add ons from all projects under this organization will be removed.</span>
131 </li>
132
133 <li className="flex gap-3">
134 <div>
135 <strong>Before you downgrade to the {selectedPlan?.name} plan, consider:</strong>
136 <ul className="space-y-2 mt-2">
137 <li className="list-disc ml-6 text-foreground-light">
138 Your projects no longer require their respective add-ons.
139 </li>
140 <li className="list-disc ml-6 text-foreground-light">
141 Your resource consumption are well within the {selectedPlan?.name} plan's quota.
142 </li>
143 <li className="list-disc ml-6 text-foreground-light">
144 Alternatively, you may also transfer projects across organizations.
145 </li>
146 </ul>
147 </div>
148 </li>
149 </ul>
150
151 {subscription?.billing_via_partner === true && subscription.billing_partner === 'fly' && (
152 <p className="mt-4 text-sm">
153 Your organization will be downgraded at the end of your current billing cycle.
154 </p>
155 )}
156 </Modal.Content>
157 </Modal>
158 )
159}
160
161export default DowngradeModal