import Head from 'next/head' import { ReactNode, Fragment } from 'react' import { observer } from 'mobx-react-lite' import { useRouter } from 'next/router' import { useStore, withAuth, useFlag } from 'hooks' import { useParams } from 'common/hooks' import { PROJECT_STATUS } from 'lib/constants' import Connecting from 'components/ui/Loading' import NavigationBar from './NavigationBar/NavigationBar' import ProductMenuBar from './ProductMenuBar' import LayoutHeader from './LayoutHeader' import ConnectingState from './ConnectingState' import PausingState from './PausingState' import BuildingState from './BuildingState' import { ProjectContextProvider } from './ProjectContext' import RestoringState from './RestoringState' import UpgradingState from './UpgradingState' import ProjectPausedState from './ProjectPausedState' export interface ProjectLayoutProps { title?: string isLoading?: boolean product?: string productMenu?: ReactNode hideHeader?: boolean hideIconBar?: boolean children: ReactNode selectedTable?: string } const ProjectLayout = ({ title, isLoading = false, product = '', productMenu, children, hideHeader = false, hideIconBar = false, selectedTable, }: ProjectLayoutProps) => { const { ui } = useStore() const router = useRouter() const { ref: projectRef } = useParams() const ongoingIncident = useFlag('ongoingIncident') const projectName = ui.selectedProject?.name const organizationName = ui.selectedOrganization?.name const isPaused = ui.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} )} {!hideHeader && } {showPausedState ? ( ) : ( {children} )} ) } export const ProjectLayoutWithAuth = withAuth(observer(ProjectLayout)) export default observer(ProjectLayout) interface MenuBarWrapperProps { isLoading: boolean productMenu?: ReactNode children: ReactNode } const MenuBarWrapper = observer(({ isLoading, productMenu, children }: MenuBarWrapperProps) => { const { ui } = useStore() return <>{!isLoading && productMenu && ui.selectedProject !== undefined ? 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 = observer(({ isLoading, children }: ContentWrapperProps) => { const { ui } = useStore() 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', '/project/[ref]/settings/billing/update', '/project/[ref]/settings/billing/update/free', '/project/[ref]/settings/billing/update/pro', ] const requiresDbConnection: boolean = router.pathname !== '/project/[ref]/settings/general' const requiresPostgrestConnection = !routesToIgnorePostgrestConnection.includes(router.pathname) const isProjectUpgrading = ui.selectedProject?.status === PROJECT_STATUS.UPGRADING const isProjectRestoring = ui.selectedProject?.status === PROJECT_STATUS.RESTORING const isProjectBuilding = ui.selectedProject?.status === PROJECT_STATUS.COMING_UP const isProjectPausing = ui.selectedProject?.status === PROJECT_STATUS.GOING_DOWN const isProjectOffline = ui.selectedProject?.postgrestStatus === 'OFFLINE' return ( <> {isLoading || ui.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 = observer( ({ title, product = '', productMenu, children, hideHeader = false, hideIconBar = false, }: ProjectLayoutProps) => { const { ui } = useStore() const router = useRouter() const { ref: projectRef } = useParams() const ongoingIncident = useFlag('ongoingIncident') const isPaused = ui.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` : 'Supabase'} {/* Left-most navigation side bar to access products */} {!hideIconBar && } {/* Product menu bar */} {productMenu && !showPausedState && ( {productMenu} )} {!hideHeader && } {showPausedState ? ( ) : ( children )} ) } )