diff --git a/studio/components/interfaces/BillingV2/ProjectUpdateDisabledTooltip.tsx b/studio/components/interfaces/BillingV2/ProjectUpdateDisabledTooltip.tsx deleted file mode 100644 index 873a8144f68..00000000000 --- a/studio/components/interfaces/BillingV2/ProjectUpdateDisabledTooltip.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import * as Tooltip from '@radix-ui/react-tooltip' -import { PropsWithChildren } from 'react' - -export interface ProjectUpdateDisabledTooltipProps { - projectUpdateDisabled: boolean -} - -const ProjectUpdateDisabledTooltip = ({ - projectUpdateDisabled, - children, -}: PropsWithChildren) => { - return ( - - {children} - {projectUpdateDisabled ? ( - - - -
- - Subscription changes are currently disabled. Our engineers are working on a fix. - -
-
-
- ) : null} -
- ) -} - -export default ProjectUpdateDisabledTooltip diff --git a/studio/components/interfaces/BillingV2/Subscription/AddOns/AddOns.tsx b/studio/components/interfaces/BillingV2/Subscription/AddOns/AddOns.tsx index 8f8f19036db..3d7c6d34e0d 100644 --- a/studio/components/interfaces/BillingV2/Subscription/AddOns/AddOns.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/AddOns/AddOns.tsx @@ -15,8 +15,11 @@ import { CustomDomainSidePanel, PITRSidePanel, } from 'components/interfaces/Settings/Addons' -import ProjectUpdateDisabledTooltip from '../../ProjectUpdateDisabledTooltip' -import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import ProjectUpdateDisabledTooltip from 'components/interfaces/Organization/BillingSettings/ProjectUpdateDisabledTooltip' +import { + useIsProjectActive, + useProjectContext, +} from 'components/layouts/ProjectLayout/ProjectContext' import { getCloudProviderArchitecture } from 'lib/cloudprovider-utils' export interface AddOnsProps {} @@ -28,6 +31,7 @@ const AddOns = ({}: AddOnsProps) => { const { isDarkMode } = useTheme() const { project: selectedProject } = useProjectContext() + const isProjectActive = useIsProjectActive() const cpuArchitecture = getCloudProviderArchitecture(selectedProject?.cloud_provider) // [Joshen] We could possibly look into reducing the interval to be more "realtime" @@ -136,12 +140,15 @@ const AddOns = ({}: AddOnsProps) => {

Optimized compute

{computeInstance?.variant.name ?? 'Micro'}

- + @@ -303,12 +310,15 @@ const AddOns = ({}: AddOnsProps) => { ? `Point in time recovery of ${pitr.variant.meta?.backup_duration_days} days is enabled` : 'Point in time recovery is not enabled'}

- + @@ -341,12 +351,15 @@ const AddOns = ({}: AddOnsProps) => { ? 'Custom domain is enabled' : 'Custom domain is not enabled'}

- + diff --git a/studio/components/interfaces/BillingV2/Subscription/CostControl/CostControl.tsx b/studio/components/interfaces/BillingV2/Subscription/CostControl/CostControl.tsx index 34ce550a355..0d5ef46b6ff 100644 --- a/studio/components/interfaces/BillingV2/Subscription/CostControl/CostControl.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/CostControl/CostControl.tsx @@ -6,7 +6,7 @@ import { useSubscriptionPageStateSnapshot } from 'state/subscription-page' import { Alert, Button, IconExternalLink } from 'ui' import SpendCapSidePanel from './SpendCapSidePanel' import Link from 'next/link' -import ProjectUpdateDisabledTooltip from '../../ProjectUpdateDisabledTooltip' +import ProjectUpdateDisabledTooltip from 'components/interfaces/Organization/BillingSettings/ProjectUpdateDisabledTooltip' import { useFlag } from 'hooks' export interface CostControlProps {} @@ -129,7 +129,7 @@ const CostControl = ({}: CostControlProps) => {
diff --git a/studio/components/interfaces/Organization/BillingSettingsV2/CostControl/CostControl.tsx b/studio/components/interfaces/Organization/BillingSettingsV2/CostControl/CostControl.tsx index a0530cd4ea5..68ce2b7bd22 100644 --- a/studio/components/interfaces/Organization/BillingSettingsV2/CostControl/CostControl.tsx +++ b/studio/components/interfaces/Organization/BillingSettingsV2/CostControl/CostControl.tsx @@ -147,7 +147,7 @@ const CostControl = ({}: CostControlProps) => { @@ -342,12 +348,15 @@ const Addons = () => { ? `Point in time recovery of ${pitr.variant.meta?.backup_duration_days} days is enabled` : 'Point in time recovery is not enabled'}

- + @@ -403,12 +412,15 @@ const Addons = () => { ? 'Custom domain is enabled' : 'Custom domain is not enabled'}

- + diff --git a/studio/components/interfaces/Settings/Addons/ComputeInstanceSidePanel.tsx b/studio/components/interfaces/Settings/Addons/ComputeInstanceSidePanel.tsx index c8c0a009d21..228708f7769 100644 --- a/studio/components/interfaces/Settings/Addons/ComputeInstanceSidePanel.tsx +++ b/studio/components/interfaces/Settings/Addons/ComputeInstanceSidePanel.tsx @@ -13,9 +13,9 @@ import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon- import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-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 { getCloudProviderArchitecture } from 'lib/cloudprovider-utils' import { useSubscriptionPageStateSnapshot } from 'state/subscription-page' import { Alert, Button, IconExternalLink, IconInfo, Modal, Radio, SidePanel } from 'ui' @@ -356,8 +356,8 @@ const ComputeInstanceSidePanel = () => { {selectedCategory === 'micro' && (

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

)} diff --git a/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx b/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx index f88e0a9704f..c3088075d1c 100644 --- a/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx +++ b/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx @@ -4,16 +4,17 @@ import { Input, Tabs } from 'ui' import { pluckObjectFields } from 'lib/helpers' import { useProjectSettingsQuery } from 'data/config/project-settings-query' -import { useTelemetryProps } from 'common' +import { useParams, useTelemetryProps } from 'common' import Telemetry from 'lib/telemetry' import Panel from 'components/ui/Panel' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import ResetDbPassword from './ResetDbPassword' -const DatabaseSettings: FC = ({ projectRef }) => { - const { data, isLoading, isError } = useProjectSettingsQuery({ projectRef }) +const DatabaseSettings = () => { const router = useRouter() + const { ref: projectRef } = useParams() const telemetryProps = useTelemetryProps() + const { data, isLoading, isError } = useProjectSettingsQuery({ projectRef }) if (isError) { return ( @@ -50,7 +51,7 @@ const DatabaseSettings: FC = ({ projectRef }) => { - +
= ({ disabled = false }) => { - const { ui, app, meta } = useStore() +const ResetDbPassword = ({ disabled = false }) => { const { ref } = useParams() + const { ui, meta } = useStore() + const isProjectActive = useIsProjectActive() const canResetDbPassword = useCheckPermissions(PermissionAction.UPDATE, 'projects') const [showResetDbPass, setShowResetDbPass] = useState(false) @@ -109,13 +111,13 @@ const ResetDbPassword: FC = ({ disabled = false }) => { - {!canResetDbPassword && ( + {(!canResetDbPassword || !isProjectActive) && ( @@ -126,7 +128,11 @@ const ResetDbPassword: FC = ({ disabled = false }) => { ].join(' ')} > - You need additional permissions to reset the database password + {!canResetDbPassword + ? 'You need additional permissions to reset the database password' + : !isProjectActive + ? 'Unable to reset database password as project is not active' + : ''} diff --git a/studio/components/interfaces/Settings/General/General.tsx b/studio/components/interfaces/Settings/General/General.tsx index 853edb560aa..a4f2ac6b4dd 100644 --- a/studio/components/interfaces/Settings/General/General.tsx +++ b/studio/components/interfaces/Settings/General/General.tsx @@ -17,7 +17,6 @@ import { useProjectUpdateMutation } from 'data/projects/project-update-mutation' import { useCheckPermissions, useSelectedOrganization, useStore } from 'hooks' import PauseProjectButton from './Infrastructure/PauseProjectButton' import RestartServerButton from './Infrastructure/RestartServerButton' -import { Project } from 'types' const General = () => { const { ui } = useStore() diff --git a/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx b/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx index 151e8b0492b..f8c4a5d7d98 100644 --- a/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx +++ b/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx @@ -4,7 +4,10 @@ import { useQueryClient } from '@tanstack/react-query' import { useRouter } from 'next/router' import { useState } from 'react' -import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { + useIsProjectActive, + useProjectContext, +} from 'components/layouts/ProjectLayout/ProjectContext' import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog' import { setProjectStatus } from 'data/projects/projects-query' import { useCheckPermissions, useStore } from 'hooks' @@ -19,6 +22,7 @@ const PauseProjectButton = () => { const { ui } = useStore() const { project } = useProjectContext() const router = useRouter() + const isProjectActive = useIsProjectActive() const [loading, setLoading] = useState(false) const [isModalOpen, setIsModalOpen] = useState(false) @@ -68,26 +72,12 @@ const PauseProjectButton = () => { icon={} onClick={openModal} loading={loading} - disabled={isPaused || !canPauseProject} + disabled={isPaused || !canPauseProject || !isProjectActive} > Pause Project - {isPaused ? ( - - - -
- Your project is already paused -
-
-
- ) : !canPauseProject ? ( + {isPaused || !canPauseProject || !isProjectActive ? ( @@ -98,14 +88,18 @@ const PauseProjectButton = () => { ].join(' ')} > - You need additional permissions to pause this project + {isPaused + ? 'Your project is already paused' + : !canPauseProject + ? 'You need additional permissions to pause this project' + : !isProjectActive + ? 'Unable to pause project as project is not active' + : ''} - ) : ( - <> - )} + ) : null} { const queryClient = useQueryClient() const { project } = useProjectContext() const { ui } = useStore() const router = useRouter() + const isProjectActive = useIsProjectActive() const [loading, setLoading] = useState(false) const [serviceToRestart, setServiceToRestart] = useState<'project' | 'database'>() @@ -82,13 +86,13 @@ const RestartServerButton = () => {
- {canRestartProject && ( + {canRestartProject && isProjectActive && ( { )}
- {!canRestartProject && ( + {(!canRestartProject || !isProjectActive) && ( @@ -131,7 +135,11 @@ const RestartServerButton = () => { ].join(' ')} > - You need additional permissions to restart this project + {!canRestartProject + ? 'You need additional permissions to restart this project' + : !isProjectActive + ? 'Unable to restart project as project is not active' + : ''} diff --git a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx index e2dae22e23c..8d20baea54e 100644 --- a/studio/components/layouts/ProjectLayout/ProjectLayout.tsx +++ b/studio/components/layouts/ProjectLayout/ProjectLayout.tsx @@ -17,6 +17,26 @@ import ProjectPausedState from './ProjectPausedState' import RestoringState from './RestoringState' import UpgradingState from './UpgradingState' +// [Joshen] This is temporary while we unblock users from managing their project +// if their project is not responding well for any reason. Eventually needs a bit of an overhaul +const routesToIgnoreProjectDetailsRequest = [ + '/project/[ref]/settings/general', + '/project/[ref]/settings/database', + '/project/[ref]/settings/storage', + '/project/[ref]/settings/billing/subscription', + '/project/[ref]/settings/billing/usage', + '/project/[ref]/settings/billing/invoices', +] + +const routesToIgnorePostgrestConnection = [ + '/project/[ref]/reports', + '/project/[ref]/settings/general', + '/project/[ref]/settings/database', + '/project/[ref]/settings/billing/subscription', + '/project/[ref]/settings/billing/usage', + '/project/[ref]/settings/billing/invoices', +] + export interface ProjectLayoutProps { title?: string isLoading?: boolean @@ -108,8 +128,14 @@ interface MenuBarWrapperProps { } const MenuBarWrapper = ({ isLoading, productMenu, children }: MenuBarWrapperProps) => { + const router = useRouter() const selectedProject = useSelectedProject() - return <>{!isLoading && productMenu && selectedProject !== undefined ? children : null} + const requiresProjectDetails = !routesToIgnoreProjectDetailsRequest.includes(router.pathname) + + const showMenuBar = + !requiresProjectDetails || (requiresProjectDetails && selectedProject !== undefined) + + return <>{!isLoading && productMenu && showMenuBar ? children : null} } interface ContentWrapperProps { @@ -133,17 +159,11 @@ const ContentWrapper = ({ isLoading, children }: ContentWrapperProps) => { const selectedProject = useSelectedProject() const router = useRouter() - const routesToIgnorePostgrestConnection = [ - '/project/[ref]/reports', - '/project/[ref]/settings/general', - '/project/[ref]/settings/database', - '/project/[ref]/settings/billing/subscription', - '/project/[ref]/settings/billing/usage', - '/project/[ref]/settings/billing/invoices', - ] - - const requiresDbConnection: boolean = router.pathname !== '/project/[ref]/settings/general' + const requiresDbConnection: boolean = + !router.pathname.includes('/project/[ref]/settings') || + router.pathname.includes('/project/[ref]/settings/vault') const requiresPostgrestConnection = !routesToIgnorePostgrestConnection.includes(router.pathname) + const requiresProjectDetails = !routesToIgnoreProjectDetailsRequest.includes(router.pathname) const isProjectUpgrading = selectedProject?.status === PROJECT_STATUS.UPGRADING const isProjectRestoring = selectedProject?.status === PROJECT_STATUS.RESTORING @@ -155,7 +175,7 @@ const ContentWrapper = ({ isLoading, children }: ContentWrapperProps) => { return ( <> - {isLoading || selectedProject === undefined ? ( + {isLoading || (requiresProjectDetails && selectedProject === undefined) ? ( ) : isProjectUpgrading ? ( @@ -168,7 +188,7 @@ const ContentWrapper = ({ isLoading, children }: ContentWrapperProps) => { ) : requiresDbConnection && isProjectBuilding ? ( ) : ( - {children} + {children} )} ) diff --git a/studio/components/layouts/SettingsLayout/SettingsLayout.tsx b/studio/components/layouts/SettingsLayout/SettingsLayout.tsx index fa1edfb8c91..61c49d15af0 100644 --- a/studio/components/layouts/SettingsLayout/SettingsLayout.tsx +++ b/studio/components/layouts/SettingsLayout/SettingsLayout.tsx @@ -1,18 +1,19 @@ -import { FC, ReactNode, useEffect } from 'react' +import { useFlag, useSelectedOrganization, useSelectedProject, useStore, withAuth } from 'hooks' import { observer } from 'mobx-react-lite' import { useRouter } from 'next/router' -import { useFlag, useSelectedOrganization, useSelectedProject, useStore, withAuth } from 'hooks' +import { PropsWithChildren, useEffect } from 'react' import { generateSettingsMenu } from './SettingsMenu.utils' -import ProjectLayout from '../' +import { useParams } from 'common' import ProductMenu from 'components/ui/ProductMenu' +import ProjectLayout from '../' -interface Props { +interface SettingsLayoutProps { title?: string - children: ReactNode } -const SettingsLayout: FC = ({ title, children }) => { +const SettingsLayout = ({ title, children }: PropsWithChildren) => { + const { ref } = useParams() const { ui, meta } = useStore() const project = useSelectedProject() const organization = useSelectedOrganization() @@ -26,12 +27,7 @@ const SettingsLayout: FC = ({ title, children }) => { : router.pathname.split('/')[4] const isVaultEnabled = useFlag('vaultExtension') - const menuRoutes = generateSettingsMenu( - project?.ref as string, - project, - isVaultEnabled, - isOrgBilling - ) + const menuRoutes = generateSettingsMenu(ref, project, isVaultEnabled, isOrgBilling) useEffect(() => { if (ui.selectedProjectRef) { diff --git a/studio/components/layouts/SettingsLayout/SettingsMenu.utils.ts b/studio/components/layouts/SettingsLayout/SettingsMenu.utils.ts index b4abdacdbc8..bf9ebf7cfaf 100644 --- a/studio/components/layouts/SettingsLayout/SettingsMenu.utils.ts +++ b/studio/components/layouts/SettingsLayout/SettingsMenu.utils.ts @@ -3,7 +3,7 @@ import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' import { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types' export const generateSettingsMenu = ( - ref: string, + ref?: string, project?: ProjectBase, isVaultEnabled: boolean = false, isOrgBilling: boolean = false diff --git a/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx index d79d2746bfa..640d1a18aa6 100644 --- a/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx @@ -9,6 +9,7 @@ import { useCheckPermissions, useStore } from 'hooks' import { IS_PLATFORM } from 'lib/constants' import { STORAGE_FILE_SIZE_LIMIT_MAX_BYTES, StorageSizeUnits } from './StorageSettings.constants' import { convertFromBytes, convertToBytes } from './StorageSettings.utils' +import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' export type StorageSettingsProps = { projectRef: string | undefined @@ -27,8 +28,8 @@ const StorageSettings = ({ projectRef }: StorageSettingsProps) => { if (!data) { return ( -
-

Loading...

+
+
) } diff --git a/studio/components/ui/ProjectUpgradeFailedBanner.tsx b/studio/components/ui/ProjectUpgradeFailedBanner.tsx index 3e96af454a5..6b109898b0d 100644 --- a/studio/components/ui/ProjectUpgradeFailedBanner.tsx +++ b/studio/components/ui/ProjectUpgradeFailedBanner.tsx @@ -15,7 +15,8 @@ const ProjectUpgradeFailedBanner = () => { const { target_version, status, initiated_at, error } = data?.databaseUpgradeStatus ?? {} const key = `supabase-upgrade-${ref}-${initiated_at}` - const isAcknowledged = localStorage?.getItem(key) === 'true' ?? false + const isAcknowledged = + typeof window !== 'undefined' ? localStorage?.getItem(key) === 'true' ?? false : false const [showMessage, setShowMessage] = useState(!isAcknowledged) const isFailed = status === DatabaseUpgradeStatus.Failed diff --git a/studio/data/projects/project-detail-query.ts b/studio/data/projects/project-detail-query.ts index 79dd3d9ffba..a64643541cf 100644 --- a/studio/data/projects/project-detail-query.ts +++ b/studio/data/projects/project-detail-query.ts @@ -8,16 +8,10 @@ import { projectKeys } from './keys' export type ProjectDetailVariables = { ref?: string } export async function getProjectDetail({ ref }: ProjectDetailVariables, signal?: AbortSignal) { - if (!ref) { - throw new Error('Project ref is required') - } + if (!ref) throw new Error('Project ref is required') const data = await get(`${API_URL}/projects/${ref}`, { signal }) - - if (!isResponseOk(data)) { - throw data.error - } - + if (!isResponseOk(data)) throw data.error return data } diff --git a/studio/pages/project/[ref]/settings/database.tsx b/studio/pages/project/[ref]/settings/database.tsx index f4ce38c156c..e9d6e352999 100644 --- a/studio/pages/project/[ref]/settings/database.tsx +++ b/studio/pages/project/[ref]/settings/database.tsx @@ -13,13 +13,12 @@ import SSLConfiguration from 'components/interfaces/Settings/Database/SSLConfigu import DiskSizeConfiguration from 'components/interfaces/Settings/Database/DiskSizeConfiguration' const ProjectSettings: NextPageWithLayout = () => { - const { ref: projectRef } = useParams() const networkRestrictions = useFlag('networkRestrictions') return (
- + diff --git a/studio/pages/project/[ref]/settings/storage.tsx b/studio/pages/project/[ref]/settings/storage.tsx index 8086374e287..09dd18e9582 100644 --- a/studio/pages/project/[ref]/settings/storage.tsx +++ b/studio/pages/project/[ref]/settings/storage.tsx @@ -8,9 +8,6 @@ import { post } from 'lib/common/fetch' import { API_URL, PROJECT_STATUS } from 'lib/constants' import { NextPageWithLayout } from 'types' -/** - * PageLayout is used to setup layout - as usual it will requires inject global store - */ const PageLayout: NextPageWithLayout = () => { const { ref: projectRef } = useParams() const { project } = useProjectContext() @@ -21,8 +18,6 @@ const PageLayout: NextPageWithLayout = () => { } }, [project]) - if (!project) return
- return (