/**
* 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 ;
}
}