import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQueryClient } from '@tanstack/react-query' import clsx from 'clsx' import Link from 'next/link' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import { useParams } from 'common' import { useTheme } from 'next-themes' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { setProjectStatus } from 'data/projects/projects-query' import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon-remove-mutation' import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useCheckPermissions, useSelectedOrganization, useStore } from 'hooks' import { getCloudProviderArchitecture } from 'lib/cloudprovider-utils' import { BASE_PATH, PROJECT_STATUS } from 'lib/constants' import Telemetry from 'lib/telemetry' import { useSubscriptionPageStateSnapshot } from 'state/subscription-page' import { Alert, AlertDescription_Shadcn_, Alert_Shadcn_, Button, IconAlertTriangle, IconExternalLink, IconInfo, Modal, Radio, SidePanel, } from 'ui' import * as Tooltip from '@radix-ui/react-tooltip' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' const COMPUTE_CATEGORY_OPTIONS: { id: 'micro' | 'optimized' name: string imageUrl: string imageUrlLight: string }[] = [ { id: 'micro', name: 'Micro Compute', imageUrl: `${BASE_PATH}/img/optimized-compute-off.png`, imageUrlLight: `${BASE_PATH}/img/optimized-compute-off--light.png`, }, { id: 'optimized', name: 'Optimized Compute', imageUrl: `${BASE_PATH}/img/optimized-compute-on.png`, imageUrlLight: `${BASE_PATH}/img/optimized-compute-on--light.png`, }, ] const ComputeInstanceSidePanel = () => { const queryClient = useQueryClient() const { ui } = useStore() const router = useRouter() const { ref: projectRef } = useParams() const { resolvedTheme } = useTheme() const { project: selectedProject } = useProjectContext() const organization = useSelectedOrganization() const [showConfirmationModal, setShowConfirmationModal] = useState(false) const [selectedCategory, setSelectedCategory] = useState<'micro' | 'optimized'>('micro') const [selectedOption, setSelectedOption] = useState('ci_micro') const canUpdateCompute = useCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.subscriptions' ) const snap = useSubscriptionPageStateSnapshot() const visible = snap.panelKey === 'computeInstance' const onClose = () => snap.setPanelKey(undefined) const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef }) const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const { mutate: updateAddon, isLoading: isUpdating } = useProjectAddonUpdateMutation({ onSuccess: () => { ui.setNotification({ duration: 8000, category: 'success', message: `Successfully updated compute instance to ${selectedCompute?.name}. Your project is currently being restarted to update its instance`, }) setProjectStatus(queryClient, projectRef!, PROJECT_STATUS.RESTORING) onClose() router.push(`/project/${projectRef}`) }, onError: (error) => { ui.setNotification({ error, category: 'error', message: `Unable to update compute instance: ${error.message}`, }) }, }) const { mutate: removeAddon, isLoading: isRemoving } = useProjectAddonRemoveMutation({ onSuccess: () => { ui.setNotification({ duration: 8000, category: 'success', message: `Successfully updated compute instance to Micro. Your project is currently being restarted to update its instance`, }) setProjectStatus(queryClient, projectRef!, PROJECT_STATUS.RESTORING) onClose() router.push(`/project/${projectRef}`) }, onError: (error) => { ui.setNotification({ error, category: 'error', message: `Unable to update compute instance: ${error.message}`, }) }, }) const isSubmitting = isUpdating || isRemoving const projectId = selectedProject?.id const cpuArchitecture = getCloudProviderArchitecture(selectedProject?.cloud_provider) const selectedAddons = addons?.selected_addons ?? [] const availableAddons = addons?.available_addons ?? [] const isFreePlan = subscription?.plan?.id === 'free' const subscriptionCompute = selectedAddons.find((addon) => addon.type === 'compute_instance') const pitrAddon = selectedAddons.find((addon) => addon.type === 'pitr') const availableOptions = availableAddons.find((addon) => addon.type === 'compute_instance')?.variants ?? [] const selectedCompute = availableOptions.find((option) => option.identifier === selectedOption) const hasChanges = selectedOption !== (subscriptionCompute?.variant.identifier ?? 'ci_micro') const blockMicroDowngradeDueToPitr = pitrAddon !== undefined && selectedOption === 'ci_micro' && hasChanges useEffect(() => { if (visible) { if (subscriptionCompute !== undefined) { setSelectedCategory('optimized') setSelectedOption(subscriptionCompute.variant.identifier) } else { setSelectedCategory('micro') setSelectedOption('ci_micro') } Telemetry.sendActivity( { activity: 'Side Panel Viewed', source: 'Dashboard', data: { title: 'Change project compute size', section: 'Add ons', }, projectRef, }, router ) } }, [visible, isLoading]) const onConfirmUpdateComputeInstance = async () => { if (!projectRef) return console.error('Project ref is required') if (!projectId) return console.error('Project ID is required') if (selectedOption === 'ci_micro' && subscriptionCompute !== undefined) { removeAddon({ projectRef, variant: subscriptionCompute.variant.identifier }) } else { updateAddon({ projectRef, type: 'compute_instance', variant: selectedOption }) } } return ( <> setShowConfirmationModal(true)} loading={isLoading} disabled={ isFreePlan || isLoading || !hasChanges || blockMicroDowngradeDueToPitr || !canUpdateCompute } tooltip={ isFreePlan ? 'Unable to update compute instance on a free plan' : !canUpdateCompute ? 'You do not have permission to update compute instance' : undefined } header={

Change project compute size

} >

For the database, compute size refers to the amount of CPU and memory allocated to the database instance.

{COMPUTE_CATEGORY_OPTIONS.map((option) => { const isSelected = selectedCategory === option.id return (
{ setSelectedCategory(option.id) if (option.id === 'micro') setSelectedOption('ci_micro') Telemetry.sendActivity( { activity: 'Option Selected', source: 'Dashboard', data: { title: 'Change project compute size', section: 'Add ons', option: option.name, }, projectRef, }, router ) }} > Compute Instance

{option.name}

) })}
{selectedCategory === 'optimized' && (
{isFreePlan && ( View available plans } > Upgrade your plan to change the compute size of your project )} Choose the compute size you want to use

} onChange={(event: any) => setSelectedOption(event.target.value)} > {availableOptions.map((option) => (

{option.name}

{option.meta?.memory_gb ?? 0} GB memory

{option.meta?.cpu_cores ?? 0}-core {cpuArchitecture} CPU ( {option.meta?.cpu_dedicated ? 'Dedicated' : 'Shared'})

${option.price.toLocaleString()} {' '} / {option.price_interval === 'monthly' ? 'month' : 'hour'}
{option.price_interval === 'hourly' && (

${Number(option.price * 672).toFixed(0)} - $ {Number(option.price * 744).toFixed(0)} per month

)}
))}
)} {selectedCategory === 'micro' && (

Your database will use the standard Micro size instance of 2-core {cpuArchitecture}{' '} CPU (Shared) with 1GB of memory.

)} {hasChanges && (selectedCategory !== 'micro' && selectedCompute?.price_interval === 'monthly' ? ( // Monthly payment with project-level subscription

Upon clicking confirm, the amount of{' '} ${selectedCompute?.price.toLocaleString()} {' '} will be added to your monthly invoice. Any previous compute addon is prorated and you're immediately charged for the remaining days of your billing cycle. The addon is prepaid per month and in case of a downgrade, you get credits for the remaining time.

) : selectedCategory !== 'micro' ? ( // Hourly usage-billing with org-based subscription

There are no immediate charges when changing compute. Compute Hours are a usage-based item and you're billed at the end of your billing cycle based on your compute usage. Read more about{' '} Compute Billing .

) : ( <> ))} {hasChanges && !blockMicroDowngradeDueToPitr && ( It will take up to 2 minutes for changes to take place, in which your project will be unavailable during that time. )} {blockMicroDowngradeDueToPitr && ( snap.setPanelKey('pitr')}> Change PITR } >

You currently have PITR enabled. The minimum compute instance size for using PITR is the Small Compute.

You need to disable PITR before downgrading to Micro Compute.

)} {hasChanges && subscription?.billing_via_partner && subscription.scheduled_plan_change?.target_plan !== undefined && ( You have a scheduled subscription change that will be canceled if you change your PITR add on. )}
setShowConfirmationModal(false)} onConfirm={() => onConfirmUpdateComputeInstance()} header="Confirm to upgrade compute instance now" >
It will take up to 2 minutes for changes to take place, in which your project will be unavailable during that time.
) } export default ComputeInstanceSidePanel