PITRSidePanel() — supabase Function Reference
Architecture documentation for the PITRSidePanel() function in PITRSidePanel.tsx from the supabase codebase.
Entity Profile
Relationship Graph
Source Code
apps/studio/components/interfaces/Settings/Addons/PITRSidePanel.tsx lines 56–341
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<string>('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 (
<SidePanel
size="xlarge"
visible={visible}
onCancel={closePanel}
onConfirm={onConfirm}
loading={isLoading || isSubmitting}
disabled={
!hasAccessToPitrVariants ||
isLoading ||
!hasChanges ||
isSubmitting ||
!canUpdatePitr ||
(!!selectedPitr && !hasSufficientCompute) ||
blockDowngradeDueToHipaa
}
tooltip={
blockDowngradeDueToHipaa
? 'Unable to disable PITR with HIPAA add-on'
: !hasAccessToPitrVariants
? 'Unable to enable point in time recovery on your Plan'
: !canUpdatePitr
? 'You do not have permission to update PITR'
: undefined
}
header={
<div className="flex items-center justify-between">
<h4>Point in Time Recovery</h4>
<Button asChild type="default" icon={<ExternalLink strokeWidth={1.5} />}>
<Link
href={`${DOCS_URL}/guides/platform/backups#point-in-time-recovery`}
target="_blank"
rel="noreferrer"
>
About point in time recovery
</Link>
</Button>
</div>
}
>
<SidePanel.Content>
<div className="py-6 space-y-4">
<p className="text-sm">
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.
</p>
<div className="!mt-8 pb-4">
<div className="flex gap-3">
{PITR_CATEGORY_OPTIONS.map((option) => {
const isSelected = selectedCategory === option.id
return (
<div
key={option.id}
className={cn(
'col-span-3 group space-y-1',
!hasAccessToPitrVariants && 'opacity-75'
)}
onClick={() => {
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')
}
}
}}
>
<img
alt="Point-In-Time-Recovery"
className={cn(
'relative rounded-xl transition border bg-no-repeat bg-center bg-cover cursor-pointer w-[160px] h-[96px]',
isSelected
? 'border-foreground'
: 'border-foreground-muted opacity-50 group-hover:border-foreground-lighter group-hover:opacity-100'
)}
width={160}
height={96}
src={resolvedTheme?.includes('dark') ? option.imageUrl : option.imageUrlLight}
/>
<p
className={cn(
'text-sm transition',
isSelected ? 'text-foreground' : 'text-foreground-light'
)}
>
{option.name}
</p>
</div>
)
})}
</div>
</div>
{selectedCategory === 'off' && subscriptionPitr !== undefined && isBranchingEnabled && (
<Alert_Shadcn_ variant="warning">
<CriticalIcon />
<AlertTitle_Shadcn_>
Are you sure you want to disable this while using Branching?
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
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.
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{blockDowngradeDueToHipaa ? (
<Alert_Shadcn_>
<AlertTitle_Shadcn_>PITR cannot be disabled on HIPAA projects</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
PITR is enabled by default for all HIPAA projects and cannot be turned off. Contact
support for further assistance.
</AlertDescription_Shadcn_>
<div className="mt-4">
<Button type="default" asChild>
<SupportLink>Contact support</SupportLink>
</Button>
</div>
</Alert_Shadcn_>
) : null}
{selectedCategory === 'on' && (
<div className="!mt-8 pb-4">
{!hasAccessToPitrVariants ? (
<UpgradeToPro
className="mb-4"
addon="pitr"
primaryText="Changing your Point-In-Time-Recovery is only available on the Pro Plan"
secondaryText="Upgrade your plan to change PITR for your project."
featureProposition="enable PITR"
/>
) : !hasSufficientCompute ? (
<UpgradeToPro
className="mb-4"
addon="computeSize"
primaryText="Project needs to be at least on a Small compute size to enable PITR"
secondaryText="This ensures enough resources to execute PITR successfully."
featureProposition="enable PITR"
/>
) : null}
<Radio.Group
type="large-cards"
size="tiny"
id="pitr"
label={<p className="text-sm">Choose the duration of recovery</p>}
onChange={(event: any) => setSelectedOption(event.target.value)}
>
{availableOptions.map((option) => (
<Radio
name="pitr"
disabled={!hasAccessToPitrVariants || subscriptionCompute === undefined}
className="col-span-4 !p-0"
key={option.identifier}
checked={selectedOption === option.identifier}
label={<span className="text-sm">{option.name}</span>}
value={option.identifier}
>
<div className="w-full group">
<div className="border-b border-default px-4 py-2">
<p className="text-sm">{option.name}</p>
</div>
<div className="px-4 py-2">
<p className="text-foreground-light">
Allow database restorations to any time up to{' '}
{option.identifier.split('_')[1]} days ago
</p>
<div className="flex items-center space-x-1 mt-2">
<p className="text-foreground text-sm" translate="no">
{formatCurrency(option.price)}
</p>
<p className="text-foreground-light translate-y-[1px]"> / month</p>
</div>
</div>
</div>
</Radio>
))}
</Radio.Group>
</div>
)}
{hasChanges && selectedOption !== 'pitr_0' && (
<p className="text-sm text-foreground-light">
There are no immediate charges. The addon is billed at the end of your billing cycle
based on your usage and prorated to the hour.
</p>
)}
</div>
</SidePanel.Content>
</SidePanel>
)
}
Domain
Subdomains
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free