import * as React from 'react' import * as ReactDOM from 'react-dom' import Button from '../Button/Button' import FormFooter from '../Form/FormFooter' import { SideDrawer } from '../SideDrawer/SideDrawer' import Tabs from '../Tabs/Tabs/Tabs' import Tag from '../Tag/Tag' import { getApiUrlPrefix, getEnvironmentName, } from '../../services/apiEndpointHelpers' import { FrontendOverrides } from './FrontendOverrides' import styles from './_dev-tools.module.scss' import ToggleTable from './ToggleTable' import { ToggleProviderContext } from '../ToggleProvider/ToggleProvider' import { c } from '../../translations/LibraryTranslationService' const { useState, useEffect } = React const { createPortal } = ReactDOM type CalloutType = React.Dispatch>> type DevToolsProps = { backendNames: string[] rerenderCallout: CalloutType } export function DevTools({ backendNames = [], rerenderCallout, }: DevToolsProps): React.JSX.Element | null { const [showDevtools, setShowDevtools] = useState(false) const [showPanel, setShowPanel] = useState(false) const [activeTab, setActiveTab] = useState(0) const [activeSubTab, setActiveSubTab] = useState(0) const [searchTerm, setSearchTerm] = useState('') const { toggles } = React.useContext(ToggleProviderContext) const isClient = typeof window !== 'undefined' useEffect(() => { setShowDevtools(localStorage.getItem('pattern-devtools') === 'true') }, []) const formattedToggleData = (toggles || []) .filter((toggle) => { if (searchTerm) { return toggle.key.includes(searchTerm) } return true }) ?.sort((a, b) => { // sort alphabetically const upperA = a.key.toUpperCase() const upperB = b.key.toUpperCase() if (upperA < upperB) return -1 if (upperA > upperB) return 1 return 0 }) if (!showDevtools) return null return ( <> {isClient ? createPortal( , document.body, ) : null} setShowPanel((prev) => !prev)} size='lg' headerContent={c('patternDevTools')} {...(activeTab === 1 ? { footerContent: ({ close }) => ( { close() }, }} saveButtonProps={{ children: 'Reload App', onClick: () => { isClient && window.location.reload() }, }} /> ), } : {})} > setActiveTab(tabId)} />
{activeTab === 0 && ( )} {activeTab === 1 && ( <> setActiveSubTab(tabId)} subtabs /> <> {activeSubTab === 0 && } {activeSubTab === 1 && ( {backendNames.map((backend) => ( ))}
{c('backendName')} {c('currentEndpoint')} {c('localSettings')}
)} )}
) } type BackendOverrideProps = { name: string } function BackendOverride({ name }: BackendOverrideProps) { const [backendOverride, setBackendOverride] = useState(() => { const currentUrl = getApiUrlPrefix(name) let environment = 'production' if (currentUrl.includes('/staging')) environment = 'development|staging' if (localStorage.getItem(`localBackendOverride:${name}:env`)) { environment = localStorage.getItem(`localBackendOverride:${name}:env`) ?? environment } return { environment, endpoint: currentUrl, } }) useEffect(() => { const localStorageKey = `localBackendOverride:${name}` if (backendOverride.environment.includes(getEnvironmentName())) { localStorage.removeItem(localStorageKey) localStorage.removeItem(`${localStorageKey}:env`) } else { localStorage.setItem(localStorageKey, backendOverride.endpoint) localStorage.setItem( `${localStorageKey}:env`, backendOverride.environment, ) } }, [backendOverride.environment, backendOverride.endpoint, name]) return ( {name} {backendOverride.endpoint}
{c('prod')}
setBackendOverride({ endpoint: `/${name}`, environment: 'production', }) } />
{c('staging')}
setBackendOverride({ endpoint: `/staging-${name}`, environment: 'development|staging', }) } />
{c('custom')}
{ evt.target.select() }} onChange={(evt) => { const fourDigitPortRegex = /^\d{4}$/g const newEndpoint = fourDigitPortRegex.test(evt.target.value) ? `http://localhost:${evt.target.value}` : evt.target.value setBackendOverride({ environment: 'custom', endpoint: newEndpoint, }) }} value={ backendOverride.environment === 'custom' ? backendOverride.endpoint : '' } />
) }