import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { useTheme } from 'next-themes' import { useEffect, useState } from 'react' import { toast } from 'sonner' import { Alert, AlertDescription, AlertTitle, Button, cn, CriticalIcon, RadioGroupCard, RadioGroupCardItem, SidePanel, } from 'ui' import { subscriptionHasHipaaAddon } from '@/components/interfaces/Billing/Subscription/Subscription.utils' import { TaxDisclaimer } from '@/components/interfaces/Billing/TaxDisclaimer' import { SupportLink } from '@/components/interfaces/Support/SupportLink' import { DocsButton } from '@/components/ui/DocsButton' import { UpgradeToPro } from '@/components/ui/UpgradeToPro' import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query' import { useOrgSubscriptionQuery } from '@/data/subscriptions/org-subscription-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 type { AddonVariantId } from '@/data/subscriptions/types' import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { BASE_PATH, DOCS_URL } from '@/lib/constants' import { formatCurrency } from '@/lib/helpers' import { useAddonsPagePanel } from '@/state/addons-page' const PITR_CATEGORY_OPTIONS: { id: 'off' | 'on' name: string imageUrl: string imageUrlLight: string }[] = [ { id: 'off', name: 'Disable PITR', imageUrl: `${BASE_PATH}/img/pitr-off.svg?v=2`, imageUrlLight: `${BASE_PATH}/img/pitr-off--light.svg?v=2`, }, { id: 'on', name: 'Enable PITR', imageUrl: `${BASE_PATH}/img/pitr-on.svg?v=2`, imageUrlLight: `${BASE_PATH}/img/pitr-on--light.svg?v=2`, }, ] const PITRSidePanel = () => { const { ref: projectRef } = useParams() const { resolvedTheme } = useTheme() const { data: project } = useSelectedProjectQuery() const { data: organization } = useSelectedOrganizationQuery() const { data: projectSettings } = useProjectSettingsV2Query({ projectRef }) const [selectedCategory, setSelectedCategory] = useState<'on' | 'off'>('off') const [selectedOption, setSelectedOption] = useState('pitr_0') const { can: canUpdatePitr } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.subscriptions' ) const isBranchingEnabled = project?.is_branch_enabled === true || project?.parent_project_ref !== undefined const { panel, closePanel } = useAddonsPagePanel() const visible = panel === 'pitr' const { data: addons, isPending: isLoading } = useProjectAddonsQuery({ projectRef }) const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const hasHipaaAddon = subscriptionHasHipaaAddon(subscription) && projectSettings?.is_sensitive const { mutate: updateAddon, isPending: isUpdating } = useProjectAddonUpdateMutation({ onSuccess: () => { toast.success(`Successfully updated point in time recovery duration`) closePanel() }, onError: (error) => { toast.error(`Unable to update PITR: ${error.message}`) }, }) const { mutate: removeAddon, isPending: isRemoving } = useProjectAddonRemoveMutation({ onSuccess: () => { toast.success(`Successfully disabled point in time recovery`) closePanel() }, onError: (error) => { toast.error(`Unable to disable PITR: ${error.message}`) }, }) const isSubmitting = isUpdating || isRemoving const selectedAddons = addons?.selected_addons ?? [] const availableAddons = addons?.available_addons ?? [] const subscriptionCompute = selectedAddons.find((addon) => addon.type === 'compute_instance') const subscriptionPitr = selectedAddons.find((addon) => addon.type === 'pitr') const availableOptions = availableAddons.find((addon) => addon.type === 'pitr')?.variants ?? [] const hasChanges = selectedOption !== (subscriptionPitr?.variant.identifier ?? 'pitr_0') const { hasAccess: hasAccessToPitrVariants } = useCheckEntitlements('pitr.available_variants') const selectedPitr = availableOptions.find((option) => option.identifier === selectedOption) const hasSufficientCompute = !!subscriptionCompute && subscriptionCompute.variant.identifier !== 'ci_micro' // These are illegal states. If they are true, we should block the user from saving them. const blockDowngradeDueToHipaa = hasHipaaAddon && (selectedCategory !== 'on' || // If the project is HIPAA, we don't allow the user to downgrade below 28 days selectedPitr?.identifier !== 'pitr_28') const onConfirm = async () => { if (!projectRef) return console.error('Project ref is required') if (selectedOption === 'pitr_0' && subscriptionPitr !== undefined) { removeAddon({ projectRef, variant: subscriptionPitr.variant.identifier }) } else { updateAddon({ projectRef, type: 'pitr', variant: selectedOption as AddonVariantId }) } } useEffect(() => { if (visible) { if (subscriptionPitr !== undefined) { setSelectedCategory('on') setSelectedOption(subscriptionPitr.variant.identifier) } else { setSelectedCategory('off') setSelectedOption('pitr_0') } } }, [visible, isLoading]) return (

Point in Time Recovery

} >

Point-in-Time Recovery (PITR) allows a project to be backed up at much shorter intervals. This provides users an option to restore to any chosen point of up to seconds in granularity.

{PITR_CATEGORY_OPTIONS.map((option) => { const isSelected = selectedCategory === option.id return (
{ setSelectedCategory(option.id) if (option.id === 'off') { setSelectedOption('pitr_0') } else if (subscriptionPitr?.variant.identifier !== undefined) { setSelectedOption(subscriptionPitr.variant.identifier) } else { if (hasHipaaAddon) { setSelectedOption('pitr_28') } else { setSelectedOption('pitr_7') } } }} > Point-In-Time-Recovery

{option.name}

) })}
{selectedCategory === 'off' && subscriptionPitr !== undefined && isBranchingEnabled && ( Are you sure you want to disable this while using Branching? Without PITR, you might not be able to recover lost data if you accidentally merge a branch that deletes a column or user data. We don't recommend this. )} {blockDowngradeDueToHipaa ? ( PITR cannot be disabled on HIPAA projects PITR is enabled by default for all HIPAA projects and cannot be turned off. Contact support for further assistance.
) : null} {selectedCategory === 'on' && (
{!hasAccessToPitrVariants ? ( ) : !hasSufficientCompute ? ( ) : null} setSelectedOption(value)} disabled={!hasAccessToPitrVariants || subscriptionCompute === undefined} > {availableOptions.map((option) => (

{option.name}

Allow database restorations to any time up to{' '} {option.identifier.split('_')[1]} days ago

{formatCurrency(option.price)}

/ month

} showIndicator={false} /> ))}
)} {hasChanges && selectedOption !== 'pitr_0' && (

There are no immediate charges. The add-on is billed at the end of your billing cycle based on your usage and prorated to the hour.

)}
) } export default PITRSidePanel