import IVSpinner from '~/components/IVSpinner' import { ActionMode } from '~/utils/types' import { UI_STATE } from '../../useTransaction' import { useEffect, useReducer, useRef, useState } from 'react' import classNames from 'classnames' import { DisclosureContent, useDisclosureState } from 'reakit' import { PageUIState } from '~/components/PageUI/usePage' type ErrorStateProps = { mode: ActionMode state: UI_STATE | PageUIState onRefresh: () => void } export const ERROR_STATE_MESSAGES: Partial< Record > = { HOST_NOT_FOUND: (

Unable to connect to host. Check that your SDK server is running and try again.

), HOST_DROPPED:

The connection to this action was lost.

, USURPED: (

This transaction was opened in another client, like another browser window. This client has been disconnected.

), SERVER_DROPPED:

The connection to Interval was lost.

, INVALID_MESSAGE: ( <>

Received invalid message from action. See the Logs pane below for more information.

Please review your action definition and try again.

), DROPPED:

The connection to this page was lost.

, } function ErrorMessage({ mode, state }: ErrorStateProps) { if ( mode === 'console' && (state === 'HOST_DROPPED' || state === 'HOST_NOT_FOUND' || state === 'DROPPED') ) { return
Waiting for your dev host to reconnect...
} if ( state === 'HOST_DROPPED' || state === 'HOST_NOT_FOUND' || state === 'INVALID_MESSAGE' || state === 'SERVER_DROPPED' || state === 'USURPED' || state === 'DROPPED' ) { return
{ERROR_STATE_MESSAGES[state] ?? null}
} if (state === 'IN_PROGRESS' || state === 'CONNECTED') { return
Connected!
} return null } interface AlertState { isVisible: boolean didDisconnect: boolean lastAction: 'DISCONNECT' | 'CONNECT' | 'RESET' | null } function alertStateReducer( prevState: AlertState, action: AlertState['lastAction'] ): AlertState { // don't handle the same action twice; this will mess with prevState.didDisconnect if (action === prevState.lastAction) { return prevState } switch (action) { case 'DISCONNECT': return { isVisible: true, didDisconnect: true, lastAction: action, } case 'CONNECT': // only show the 'connected' alert upon reconnecting return { isVisible: prevState.didDisconnect, didDisconnect: false, lastAction: action, } case 'RESET': default: return { isVisible: false, didDisconnect: false, lastAction: action, } } } function useAlertState(state: ErrorStateProps['state']) { const hideTimeout = useRef() const [lastState, setLastState] = useState(state) const [alertState, setAlertState] = useReducer(alertStateReducer, { isVisible: false, didDisconnect: false, lastAction: null, }) useEffect(() => { // 'CONNECTING' is a transient state and we don't want it to affect the alert display if (state === 'CONNECTING') return setLastState(state) }, [state]) useEffect(() => { if ( state === 'HOST_DROPPED' || state === 'HOST_NOT_FOUND' || state === 'SERVER_DROPPED' || state === 'USURPED' || state === 'INVALID_MESSAGE' || state === 'DROPPED' ) { setAlertState('DISCONNECT') } else if (state === 'IN_PROGRESS' || state === 'CONNECTED') { setAlertState('CONNECT') } }, [state]) useEffect(() => { // clear previous timeout whenever alertState changes clearTimeout(hideTimeout.current) if (alertState.isVisible && !alertState.didDisconnect) { hideTimeout.current = setTimeout(() => { setAlertState('RESET') }, 1000) } return () => clearTimeout(hideTimeout.current) }, [alertState]) return { ...alertState, state: lastState } } export default function ErrorState(props: ErrorStateProps) { const { state, isVisible } = useAlertState(props.state) const disclosure = useDisclosureState({ animated: 200 }) { const { show, hide } = disclosure useEffect(() => { if (isVisible) { show() } else { hide() } }, [show, hide, isVisible]) } const isConnected = state === 'IN_PROGRESS' || state === 'CONNECTED' return (
{!isConnected && (
)}
{(state === 'USURPED' || state === 'INVALID_MESSAGE') && ( )}
) }