/** * The router, and the one place connection state lives (it decides whether the * app shows at all, the shell's notices and the account menu). * * Until the site is connected, every menu item shows the Get started screen. */ import { useCallback, useEffect } from 'react'; import { api } from './lib/api'; import { boot } from './lib/boot'; import { useAsync } from './lib/useAsync'; import { useRoute, type RouteId } from './lib/router'; import { keepMenuInApp, syncSubmenu } from './lib/submenu'; import { Shell } from './shell/Shell'; import { HeaderActionsHost } from './shell/headerActions'; import { ToastProvider } from './components/Toasts'; import { ErrorBoundary } from './components/ErrorBoundary'; import { FirstSync } from './components/FirstSync'; import { notifySynced } from './lib/sync'; import { Connect } from './screens/Connect'; import { Home } from './screens/Home'; import { Team } from './screens/Team'; import { Resources } from './screens/Resources'; import { Placements } from './screens/Placements'; import { Seo } from './screens/Seo'; import { Services } from './screens/Services'; import { Settings, SETTINGS_PANELS } from './screens/Settings'; import type { BreadcrumbItem } from './ds'; import { Support } from './screens/Support'; import { Design } from './screens/Design'; import type { Connection } from './lib/types'; /** * The pane header's breadcrumb: only inside a settings panel, where * "Settings ›" is the way back. Elsewhere it would repeat the page title. */ function crumbsFor( route: RouteId, sub: string | null ): BreadcrumbItem[] { const panel = route === 'settings' && sub ? SETTINGS_PANELS[ sub as keyof typeof SETTINGS_PANELS ] : undefined; return panel ? [ { label: 'Settings', href: '#/settings' }, { label: panel } ] : []; } export function App() { const [ route, go ] = useRoute(); // WordPress highlights the submenu item on the server, where the fragment is unknown. useEffect( () => { syncSubmenu( route.id ); }, [ route.id ] ); // Our menu links switch screens here instead of reloading the page. useEffect( () => { keepMenuInApp(); }, [] ); // First paint uses the connection PHP printed into the page; GET connection // then brings the live status. If that read fails, the printed state stays // and the shell says so. const connection = useAsync< Connection >( () => api.get< Connection >( 'connection' ) ); const current = connection.data ?? boot().connection; const connectionError = connection.error && ! connection.loading ? connection.error : null; const reconnect = useCallback( async () => { try { const { url } = await api.get< { url: string } >( 'connect-url', { intent: 'signin' } ); window.location.href = url; } catch { // Settings → Site key has the paste-a-key fallback. go( 'settings' ); } }, [ go ] ); if ( current.status === 'disconnected' ) { return ( { /* Reload: WordPress renders the menu for the connection state. */ } window.location.reload() } /> ); } // Connected, but the store every screen reads hasn't been filled yet: the // first sync after connecting (or after the plugin was reactivated). const firstSync = current.status === 'connected' && ! current.last_sync; return ( { ( actions ) => ( { firstSync ? ( ) : ( ) } ) } ); } interface ScreenProps { route: RouteId; sub: string | null; connection: Connection; go: ( to: string ) => void; reload: () => void; onReconnect: () => void; } function Screen( { route, sub, connection, go, reload, onReconnect }: ScreenProps ) { switch ( route ) { case 'services': return ; case 'team': return ; case 'resources': return ; case 'placements': return ; case 'design': return ; case 'seo': return ; case 'support': return ; case 'settings': return ( ); case 'home': default: return ; } }