diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx index 76c075fbf7e22..f105c7d6e6d15 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx @@ -3,7 +3,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { AnimatePresence, motion } from 'framer-motion' import { ChevronRight } from 'lucide-react' -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { useForm } from 'react-hook-form' import { CloudProvider } from 'shared-data' import { toast } from 'sonner' @@ -13,6 +13,7 @@ import { Collapsible_Shadcn_, CollapsibleContent_Shadcn_, CollapsibleTrigger_Shadcn_, + DialogSectionSeparator, Form, Separator, } from 'ui' @@ -77,6 +78,8 @@ export function DiskManagementForm() { const { data: org } = useSelectedOrganizationQuery() const { setProjectStatus } = useSetProjectStatus() + const advancedSettingsRef = useRef(null) + const isSpendCapEnabled = org?.plan.id !== 'free' && !org?.usage_billing_enabled && project?.cloud_provider !== 'FLY' @@ -163,43 +166,8 @@ export function DiskManagementForm() { reValidateMode: 'onChange', }) - useEffect(() => { - if (!isDiskAttributesSuccess) return - // @ts-ignore - const { type, iops, throughput_mbps, size_gb } = data?.attributes ?? { size_gb: 0 } - const formValues = { - storageType: type, - provisionedIOPS: iops, - throughput: throughput_mbps, - totalSize: size_gb, - computeSize: form.getValues('computeSize'), - } - - if (!('requested_modification' in data)) { - if (refetchInterval !== false) { - form.reset(formValues) - setRefetchInterval(false) - toast.success('Disk configuration changes have been successfully applied!') - } - } else { - setRefetchInterval(2000) - } - }, [data, isDiskAttributesSuccess, form, refetchInterval]) - const { computeSize: modifiedComputeSize } = form.watch() - // We only support disk configurations for >=Large instances - // If a customer downgrades back to { - if (modifiedComputeSize && project?.infra_compute_size && isDialogOpen) { - if (RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(modifiedComputeSize)) { - form.setValue('storageType', DiskType.GP3) - form.setValue('throughput', DISK_LIMITS['gp3'].minThroughput) - form.setValue('provisionedIOPS', DISK_LIMITS['gp3'].minIops) - } - } - }, [modifiedComputeSize, isDialogOpen, project]) - const isSuccess = isAddonsSuccess && isDiskAttributesSuccess && @@ -213,6 +181,7 @@ export function DiskManagementForm() { const isPlanUpgradeRequired = !hasAccess const { formState } = form + const errors = formState.errors const usedSize = Math.round(((diskUtil?.metrics.fs_used_bytes ?? 0) / GB) * 100) / 100 const totalSize = formState.defaultValues?.totalSize || 0 const usedPercentage = (usedSize / totalSize) * 100 @@ -328,6 +297,41 @@ export function DiskManagementForm() { } } + useEffect(() => { + if (!isDiskAttributesSuccess) return + // @ts-ignore + const { type, iops, throughput_mbps, size_gb } = data?.attributes ?? { size_gb: 0 } + const formValues = { + storageType: type, + provisionedIOPS: iops, + throughput: throughput_mbps, + totalSize: size_gb, + computeSize: form.getValues('computeSize'), + } + + if (!('requested_modification' in data)) { + if (refetchInterval !== false) { + form.reset(formValues) + setRefetchInterval(false) + toast.success('Disk configuration changes have been successfully applied!') + } + } else { + setRefetchInterval(2000) + } + }, [data, isDiskAttributesSuccess, form, refetchInterval]) + + // We only support disk configurations for >=Large instances + // If a customer downgrades back to { + if (modifiedComputeSize && project?.infra_compute_size && isDialogOpen) { + if (RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(modifiedComputeSize)) { + form.setValue('storageType', DiskType.GP3) + form.setValue('throughput', DISK_LIMITS['gp3'].minThroughput) + form.setValue('provisionedIOPS', DISK_LIMITS['gp3'].minIops) + } + } + }, [modifiedComputeSize, isDialogOpen, project]) + useEffect(() => { // Initialize field values properly when data has been loaded, preserving any user changes if (isDiskAttributesSuccess || isSuccess) { @@ -336,6 +340,26 @@ export function DiskManagementForm() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSuccess, isDiskAttributesSuccess]) + useEffect(() => { + const fieldErrors = Object.keys(errors) + if (fieldErrors.length > 0) { + if ( + fieldErrors.includes('throughput') || + fieldErrors.includes('provisionedIOPS') || + fieldErrors.includes('maxSizeGb') + ) { + setAdvancedSettingsOpenState(true) + + // [Joshen] The timeout is to let the collapsible open prior to scrolling + const timeoutId = setTimeout(() => { + advancedSettingsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }) + }, 100) + + return () => clearTimeout(timeoutId) + } + } + }, [errors]) + return ( <> @@ -447,8 +471,6 @@ export function DiskManagementForm() { setAdvancedSettingsOpenState((prev) => !prev)} > @@ -467,16 +489,17 @@ export function DiskManagementForm() { />
- +
{ - const API_URL = process.env.NEXT_PUBLIC_MARKETPLACE_API_URL || '' - return `${API_URL}${imagePath}` -} - /** * [Joshen] Returns a combination of * - Marketplace integrations retrieved remotely (Only if feature flag enabled) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/CostControl.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/CostControl.tsx index 13bf71bbfec63..be588279c7062 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/CostControl.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/CostControl.tsx @@ -4,7 +4,8 @@ import { ExternalLink } from 'lucide-react' import { useTheme } from 'next-themes' import Image from 'next/image' import Link from 'next/link' -import { Alert, Alert_Shadcn_, AlertTitle_Shadcn_, Button } from 'ui' +import { Alert_Shadcn_, AlertTitle_Shadcn_, Button } from 'ui' +import { Admonition } from 'ui-patterns/admonition' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { ProjectUpdateDisabledTooltip } from '../ProjectUpdateDisabledTooltip' @@ -118,23 +119,28 @@ const CostControl = ({}: CostControlProps) => { {isSuccess && !costControlDisabled && (
{['team', 'enterprise', 'platform'].includes(currentPlan?.id || '') ? ( - - {currentPlan?.name || ''} plan requires you to have spend cap off at all - times. Your projects will never become unresponsive. Only when your{' '} - - included usage - {' '} - is exceeded will you be charged for any additional usage. - + <> + + {currentPlan?.name || ''} plan requires you to have spend cap off at all + times. Your projects will never become unresponsive. Only when your{' '} + + included usage + {' '} + is exceeded will you be charged for any additional usage. + + } + /> + ) : (

If you need to go beyond the included quota, simply switch off your spend cap diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/ExitSurveyModal.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/ExitSurveyModal.tsx index 2c52ed8cc0b62..2b8c6a6823942 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/ExitSurveyModal.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/ExitSurveyModal.tsx @@ -151,8 +151,8 @@ export const ExitSurveyModal = ({ visible, projects, onClose }: ExitSurveyModalP

{hasProjectsWithComputeDowngrade && ( diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx index c24380673ed44..30817aa61a73e 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx @@ -1,8 +1,8 @@ import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants' import { useFlag, useParams } from 'common' import Link from 'next/link' -import { Alert, Button } from 'ui' -import { Admonition } from 'ui-patterns' +import { Button } from 'ui' +import { Admonition } from 'ui-patterns/admonition' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { ProjectUpdateDisabledTooltip } from '../ProjectUpdateDisabledTooltip' @@ -95,22 +95,20 @@ const Subscription = () => { ) : projectUpdateDisabled ? ( - - We have temporarily disabled project and subscription changes - our - engineers are working on a fix. - + description="We have temporarily disabled project and subscription changes - our + engineers are working on a fix." + /> ) : ( - { > Contact support - , - ]} - > - Please contact us if you'd like to change your plan. - + + } + /> )}
diff --git a/apps/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx b/apps/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx index 3c79ccdba7138..1a78a50ab2586 100644 --- a/apps/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx +++ b/apps/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx @@ -5,7 +5,6 @@ import { Download, Loader2 } from 'lucide-react' import { useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' import { - Alert, AlertDialog, AlertDialogAction, AlertDialogCancel, @@ -23,14 +22,15 @@ import { TooltipContent, TooltipTrigger, } from 'ui' +import { Admonition } from 'ui-patterns/admonition' +import { FormLayout } from 'ui-patterns/form/Layout/FormLayout' import { PageSection, PageSectionContent, PageSectionMeta, PageSectionSummary, PageSectionTitle, -} from 'ui-patterns' -import { FormLayout } from 'ui-patterns/form/Layout/FormLayout' +} from 'ui-patterns/PageSection' import { SupportLink } from '@/components/interfaces/Support/SupportLink' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' @@ -186,15 +186,18 @@ export const SSLConfiguration = () => { {isSuccess && !sslEnforcementConfiguration?.appliedSuccessfully && ( - - Please try updating again, or contact{' '} - support if this error - persists - + description={ + <> + Please try updating again, or contact{' '} + support if this error + persists + + } + /> )} diff --git a/apps/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectModal.tsx b/apps/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectModal.tsx index a7a9edafbaa22..2f05a0c7c9e18 100644 --- a/apps/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectModal.tsx +++ b/apps/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectModal.tsx @@ -3,6 +3,7 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import { toast } from 'sonner' import { TextArea_Shadcn_ as 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' @@ -141,17 +142,18 @@ export const DeleteProjectModal = ({ >
+ {/* [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 && ( - <> -
-

What made you decide to delete your project?

-
-
+
+
{shuffledReasons.map((option) => { const active = selectedReason[0] === option.value @@ -181,19 +183,17 @@ export const DeleteProjectModal = ({ ) })}
-
- -