import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import { atom, useRecoilState } from 'recoil' import { inferQueryOutput, trpc, useQueryClient } from '~/utils/trpc' import IVSpinner from '~/components/IVSpinner' import ActionsList from '~/components/ActionsList' import ConsoleOnboarding from '~/components/ConsoleOnboarding' import { PendingConnectionIndicator } from '~/components/TransactionUI/_presentation/TransactionLayout' import { UI_STATE } from '~/components/TransactionUI/useTransaction' import { hasRecentlyConnectedToConsole } from '~/pages/dashboard/[orgSlug]/develop' import EmptyState from '~/components/EmptyState' import IconCode from '~/icons/compiled/Code' import { useHasPermission } from '~/components/DashboardContext' import { useOrgParams } from '~/utils/organization' import IVAlert from '~/components/IVAlert' import IconInfo from '~/icons/compiled/Info' import { ActionGroup } from '@prisma/client' import PageHeading from '../PageHeading' import ApiKeyButton from '../APIKeyButton' import PageUI from '../PageUI' import QueuedActionsList from '../QueuedActionsList' function hasVisibleActions(data?: inferQueryOutput<'action.console.index'>) { return !!data?.actions.length || !!data?.groups.length || !!data?.currentPage } export const consoleUIState = atom({ key: 'consoleUIState', default: 'IDLE', }) function ConsoleExplanation() { const { orgSlug } = useOrgParams() return ( Actions you've defined using{' '} your personal development key {' '} will appear here in the Console when your SDK is connected. For help developing actions, check out our{' '} gallery of pre-built example tools . ) } export default function ConsoleIndex({ mode, slugPrefix, pageTitle, breadcrumbs, canRunActions, }: { canRunActions: boolean mode: 'console' | 'anon-console' slugPrefix?: string pageTitle?: string breadcrumbs?: ActionGroup[] }) { const queryClient = useQueryClient() const [hasRecentlyConnected, setHasRecentlyConnected] = useRecoilState( hasRecentlyConnectedToConsole ) // the cached page is shown while the host is reconnecting const [cachedCurrentPage, setCurrentPage] = useState(null) const [hostState, setHostState] = useRecoilState(consoleUIState) useHasPermission('RUN_DEV_ACTIONS', { redirectToDashboardHome: true }) const [ forceShowInstallationInstructions, setForceShowInstallationInstructions, ] = useState(false) useEffect(() => { if (hostState === 'IN_PROGRESS') { setForceShowInstallationInstructions(false) } }, [hostState]) const actions = trpc.useQuery( [ 'action.console.index', { slugPrefix, }, ], { refetchInterval: data => { // refetch every 1s if you don't have any online actions if (!hasVisibleActions(data)) return 1000 // refetch every 3 seconds otherwise return 3000 }, refetchIntervalInBackground: false, // forces all page components to unmount when switching between pages, // also prevents some cached data from displaying before remote data is received. cacheTime: 0, } ) const ctx = trpc.useContext() const { refetchQueries } = ctx const hasCreatedFirstAction = actions.data?.hasAnyActions ?? false // consider "recently connected" to be true if actions have // appeared on screen during this session. // this is also set to true when a user runs an action. useEffect(() => { if (hasVisibleActions(actions.data)) { setHasRecentlyConnected(true) setCurrentPage(actions.data?.currentPage ?? null) } }, [actions.data, setHasRecentlyConnected]) // refetch navigation when actions begins refetching, e.g. when connecting to a restarting host useEffect(() => { if (actions.isFetching) { refetchQueries(['dashboard.structure', { mode: 'console' }], { exact: true, }) } }, [actions.isFetching, refetchQueries]) // "fake" host state based on presence of online or offline actions. // host is considered "reconnecting" if we've connected during this session but no actions are visible. useEffect(() => { setHostState(prev => { if (hasVisibleActions(actions.data)) { return 'IN_PROGRESS' } if (actions.isLoading && prev === 'IN_PROGRESS') { return 'IN_PROGRESS' } if (hasRecentlyConnected) { return 'HOST_DROPPED' } return 'IDLE' }) }, [actions.data, setHostState, hasRecentlyConnected, actions.isLoading]) const onRefresh = () => { actions.refetch() } if (actions.isLoading) { return (
) } const fallback = hasVisibleActions(actions.data) ? (
{ queryClient.setQueryData(['action.console.index', { slugPrefix }], { ...actions.data, queued: actions.data?.queued.filter(q => q.id !== queuedAction.id) ?? [], }) }} />
) : hasRecentlyConnected ? ( ) : hasCreatedFirstAction && !forceShowInstallationInstructions ? ( setForceShowInstallationInstructions(true), theme: 'plain', className: 'text-primary-500 hover:opacity-70 font-medium', }, { label: 'Retry connection', onClick: onRefresh, theme: 'plain', className: 'text-primary-500 hover:opacity-70 font-medium', disabled: actions.isRefetching, }, ]} >

Actions will appear here when your development server comes online.

Use your personal development key to connect:

) : (
) const currentPage = actions.data?.currentPage ?? cachedCurrentPage if (currentPage?.hasHandler) { return ( ) } return fallback }