import { useParams } from 'common/hooks' import ResourceExhaustionWarningBanner from 'components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner' import { useFlag, useSelectedOrganization, useSelectedProject, withAuth } from 'hooks' import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' import Head from 'next/head' import { useRouter } from 'next/router' import { Fragment, PropsWithChildren, ReactNode } from 'react' import AppLayout from '../AppLayout/AppLayout' import EnableBranchingModal from '../AppLayout/EnableBranchingButton/EnableBranchingModal' import BuildingState from './BuildingState' import ConnectingState from './ConnectingState' import LayoutHeader from './LayoutHeader' import LoadingState from './LoadingState' import NavigationBar from './NavigationBar/NavigationBar' import PausingState from './PausingState' import ProductMenuBar from './ProductMenuBar' import { ProjectContextProvider } from './ProjectContext' 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/infrastructure', '/project/[ref]/settings/billing/subscription', '/project/[ref]/settings/billing/usage', '/project/[ref]/settings/billing/invoices', ] const routesToIgnoreDBConnection = ['/project/[ref]/branches'] const routesToIgnorePostgrestConnection = [ '/project/[ref]/reports', '/project/[ref]/settings/general', '/project/[ref]/settings/database', '/project/[ref]/settings/infrastructure', '/project/[ref]/settings/billing/subscription', '/project/[ref]/settings/billing/usage', '/project/[ref]/settings/billing/invoices', ] export interface ProjectLayoutProps { title?: string isLoading?: boolean product?: string productMenu?: ReactNode hideHeader?: boolean hideIconBar?: boolean selectedTable?: string } const ProjectLayout = ({ title, isLoading = false, product = '', productMenu, children, hideHeader = false, hideIconBar = false, selectedTable, }: PropsWithChildren) => { const router = useRouter() const { ref: projectRef } = useParams() const selectedOrganization = useSelectedOrganization() const selectedProject = useSelectedProject() const projectName = selectedProject?.name const organizationName = selectedOrganization?.name const navLayoutV2 = useFlag('navigationLayoutV2') const showResourceExhaustionWarnings = useFlag('resourceExhaustionWarnings') const isPaused = selectedProject?.status === PROJECT_STATUS.INACTIVE const ignorePausedState = router.pathname === '/project/[ref]' || router.pathname.includes('/project/[ref]/settings') const showPausedState = isPaused && !ignorePausedState return ( {title ? `${title} | Supabase` : selectedTable ? `${selectedTable} | ${projectName} | ${organizationName} | Supabase` : projectName ? `${projectName} | ${organizationName} | Supabase` : organizationName ? `${organizationName} | Supabase` : 'Supabase'}
{/* Left-most navigation side bar to access products */} {!hideIconBar && } {/* Product menu bar */} {!showPausedState && ( {productMenu} )}
{!navLayoutV2 && !hideHeader && IS_PLATFORM && } {showPausedState ? (
) : ( {showResourceExhaustionWarnings && } {children} )}
) } export const ProjectLayoutWithAuth = withAuth(ProjectLayout) export default ProjectLayout interface MenuBarWrapperProps { isLoading: boolean productMenu?: ReactNode children: ReactNode } const MenuBarWrapper = ({ isLoading, productMenu, children }: MenuBarWrapperProps) => { const router = useRouter() const selectedProject = useSelectedProject() const requiresProjectDetails = !routesToIgnoreProjectDetailsRequest.includes(router.pathname) const showMenuBar = !requiresProjectDetails || (requiresProjectDetails && selectedProject !== undefined) return <>{!isLoading && productMenu && showMenuBar ? children : null} } interface ContentWrapperProps { isLoading: boolean children: ReactNode } /** * Check project.status to show building state or error state * * [Joshen] As of 210422: Current testing connection by pinging postgres * Ideally we'd have a more specific monitoring of the project such as during restarts * But that will come later: https://supabase.slack.com/archives/C01D6TWFFFW/p1650427619665549 * * Just note that this logic does not differentiate between a "restarting" state and * a "something is wrong and can't connect to project" state. * * [TODO] Next iteration should scrape long polling and just listen to the project's status */ const ContentWrapper = ({ isLoading, children }: ContentWrapperProps) => { const router = useRouter() const selectedProject = useSelectedProject() const requiresDbConnection: boolean = (!router.pathname.includes('/project/[ref]/settings') && !routesToIgnoreDBConnection.includes(router.pathname)) || router.pathname === '/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 const isProjectBuilding = selectedProject?.status === PROJECT_STATUS.COMING_UP || selectedProject?.status === PROJECT_STATUS.UNKNOWN const isProjectPausing = selectedProject?.status === PROJECT_STATUS.GOING_DOWN || selectedProject?.status === PROJECT_STATUS.PAUSING const isProjectOffline = selectedProject?.postgrestStatus === 'OFFLINE' return ( <> {isLoading || (requiresProjectDetails && selectedProject === undefined) ? ( ) : isProjectUpgrading ? ( ) : isProjectPausing ? ( ) : requiresPostgrestConnection && isProjectOffline ? ( ) : requiresDbConnection && isProjectRestoring ? ( ) : requiresDbConnection && isProjectBuilding ? ( ) : ( {children} )} ) } /** * Shows the children irregardless of whether the selected project has loaded or not * We'll eventually want to use this instead of the current ProjectLayout to prevent * a catch-all spinner on the dashboard */ export const ProjectLayoutNonBlocking = ({ title, product = '', productMenu, children, hideHeader = false, hideIconBar = false, }: PropsWithChildren) => { const selectedProject = useSelectedProject() const router = useRouter() const { ref: projectRef } = useParams() const isPaused = selectedProject?.status === PROJECT_STATUS.INACTIVE const ignorePausedState = router.pathname === '/project/[ref]' || router.pathname.includes('/project/[ref]/settings') const showPausedState = isPaused && !ignorePausedState const navLayoutV2 = useFlag('navigationLayoutV2') const showResourceExhaustionWarnings = useFlag('resourceExhaustionWarnings') return ( {title ? `${title} | Supabase` : 'Supabase'}
{/* Left-most navigation side bar to access products */} {!hideIconBar && } {/* Product menu bar */} {productMenu && !showPausedState && ( {productMenu} )}
{!navLayoutV2 && !hideHeader && IS_PLATFORM && } {showPausedState ? (
) : ( <> {showResourceExhaustionWarnings && } {children} )}
) }