[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/utils/reactIntl.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import PropTypes from 'prop-types'
import { FormattedRelativeTime, injectIntl } from 'react-intl'
import React, { FC } from 'react'
import hoistNonReactStatics from 'hoist-non-react-statics'

function getDisplayName(Component: React.ComponentType<any>) {
  return Component.displayName || Component.name || 'Component'
}

// We do our best to maintain compatibility with the prvious API but this is not perfect
const getCorrectUnit = (timeDiff: number) => {
  const years = timeDiff / (millisecondsInDay * 365)
  if (isUnitValid(years)) {
    return {
      value: Math.round(years),
      unit: 'year',
    }
  }
  const months = timeDiff / (millisecondsInDay * 30)
  if (isUnitValid(months)) {
    return {
      value: Math.round(months),
      unit: 'month',
    }
  }

  const days = timeDiff / millisecondsInDay
  if (isUnitValid(days)) {
    return {
      value: Math.round(days),
      unit: 'day',
    }
  }
  const hours = timeDiff / millisecondsInHour
  if (isUnitValid(hours)) {
    return {
      value: Math.round(hours),
      unit: 'hour',
    }
  }
  const minutes = (timeDiff / 60) * 1000
  if (isUnitValid(minutes)) {
    return {
      value: Math.round(minutes),
      unit: 'minute',
    }
  }
  return {
    value: Math.round(timeDiff / 1000),
    unit: 'second',
  }
}

const FormattedRelative: FC<any> = props => {
  console.warn(
    'FormattedRelative was removed in react-intl@3.x, you are using an approximation. Plase start using FormattedRelativeTime.'
  )
  const { value } = props
  let timeDiff
  if (value instanceof Date) {
    timeDiff = value.getTime() - Date.now()
  }
  if (typeof value === 'string') {
    timeDiff = new Date(value).getTime() - Date.now()
  }
  if (!timeDiff) {
    return null
  }
  const { value: newValue, unit } = getCorrectUnit(timeDiff)
  return <FormattedRelativeTime value={newValue} unit={unit as any} />
}

const addLocaleData = () => {
  console.warn(
    'addLocaleData was removed in react-intl@3.x and is not needed anymore. Please read: https://github.com/formatjs/react-intl/blob/master/docs/Upgrade-Guide.md#formattedrelativetime'
  )
}

const millisecondsInHour = 60 * 60 * 1000
const millisecondsInDay = 24 * millisecondsInHour

const isUnitValid = (value: number) => Math.abs(value) >= 1

const renderFormatRelative = (intl: any) => (value: any) => {
  console.warn(
    'formatRelative was removed in react-intl@3.x, please start using formatRelativeTime.'
  )
  let timeDiff
  if (value instanceof Date) {
    timeDiff = value.getTime() - Date.now()
  }
  if (typeof value === 'string') {
    timeDiff = new Date(value).getTime() - Date.now()
  }
  if (!timeDiff) {
    return null
  }

  const { value: newValue, unit } = getCorrectUnit(timeDiff)

  return intl.formatRelativeTime(newValue, unit)
}

const renderRuntimeInjectIntl: FC<any> = (
  WrappedComponent: any,
  options?: any
) => {
  const intlPropName = (options && options.intlPropName) || 'intl'
  const forwardRef = (options && options.forwardRef) || false
  const WithRenderRuntimeIntl = injectIntl(props => {
    const intlObj = props[intlPropName] as any
    if (intlObj) {
      intlObj.formatRelative = renderFormatRelative(intlObj)
    }
    return (
      <WrappedComponent
        {...props}
        ref={forwardRef ? props.forwardedRef : null}
      />
    )
  }, options)

  WithRenderRuntimeIntl.displayName = `renderRuntimeInjectIntl(${getDisplayName(
    WrappedComponent
  )}`
  WithRenderRuntimeIntl.WrappedComponent = WrappedComponent

  if (forwardRef) {
    return hoistNonReactStatics(
      // eslint-disable-next-line react/display-name
      React.forwardRef((props, ref) => (
        <WithRenderRuntimeIntl {...(props as any)} forwardedRef={ref} />
      )),
      WrappedComponent
    ) as any
  }
  return hoistNonReactStatics(WithRenderRuntimeIntl, WrappedComponent) as any
}

export const createReactIntl = () => {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const { injectIntl, ...rest } = window.ReactIntl
  if (!window.ReactIntlLocaleData) {
    window.ReactIntlLocaleData = {}
  }

  return {
    ...rest,
    injectIntl: renderRuntimeInjectIntl,
    intlShape: PropTypes.any,
    FormattedRelative,
    addLocaleData,
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/utils/fetch.ts from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```typescript
const delay = (ms: number): Promise<void> => {
  return new Promise(resolve => setTimeout(resolve, ms))
}

const RETRY_STATUSES = [
  0,
  408,
  425,
  429,
  500,
  501,
  502,
  503,
  504,
  505,
  506,
  507,
  508,
  510,
  511,
]

const canRetry = (status: number) => RETRY_STATUSES.includes(status)

const ok = (status: number) => 200 <= status && status < 300

export const fetchWithRetry = (
  url: string,
  init: any,
  fetcher: any['fetch'],
  maxRetries = 3
) => {
  let status = 500
  const callFetch = (
    attempt = 0
  ): Promise<{ response: Response; error: any }> =>
    fetcher(url, init)
      .then(response => {
        status = response.status
        return ok(status)
          ? { response, error: null }
          : response
              .json()
              .then(error => ({ response, error }))
              .catch(() => ({
                response,
                error: { message: 'Unable to parse JSON' },
              }))
      })
      .then(({ response, error }) => {
        if (error) {
          throw new Error(error.message || 'Unknown error')
        }
        return { response, error: null }
      })
      .catch(error => {
        console.error(error)

        if (attempt >= maxRetries || !canRetry(status)) {
          throw error
        }

        const ms = 2 ** attempt * 500
        return delay(ms).then(() => callFetch(++attempt))
      })

  return callFetch()
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/utils/dom.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { canUseDOM } from 'exenv'
import React, { Fragment, ReactElement, ReactPortal } from 'react'
import { createPortal as reactCreatePortal } from 'react-dom'

const portalPattern = /START_SERVER_PORTAL_([^!]+)!((\n|.)+)END_SERVER_PORTAL_\1!/g

// Map `placeholder/with/slashes` to `render-placeholder-with-slashes`.
const hyphenate = (name: string) => name.replace(/[^\w]/g, '-')

const portalWrapperId = (name: string) => `render-portal-ssr-${hyphenate(name)}`

export const RENDER_CONTAINER_CLASS = 'render-container'

export const ROUTE_CLASS_PREFIX = 'render-route-'

export const routeClass = (name: string) =>
  `${ROUTE_CLASS_PREFIX}${hyphenate(name)}`

const renderContainer = (name: string, markup: string) =>
  `<div class="${RENDER_CONTAINER_CLASS} ${routeClass(name)}">${markup}</div>`

const portalWrapper = (name: string, markup: string) =>
  `<span id="${portalWrapperId(name)}">${markup}</span>`

export const createPortal = (
  children: ReactElement<any>,
  name: string,
  hydrate: boolean
) => {
  window.__hasPortals__ = true

  if (!hydrate) {
    return canUseDOM ? null : (
      <Fragment>
        {`START_SERVER_PORTAL_${name}!`}
        {children}
        {`END_SERVER_PORTAL_${name}!`}
      </Fragment>
    )
  }

  const ssrPortalContainer = document.getElementById(portalWrapperId(name))
  if (ssrPortalContainer) {
    ssrPortalContainer.remove()
  }

  const container = document.getElementsByClassName(routeClass(name))[0]
  if (!container) {
    console.warn(`Missing React Portal container div.${routeClass(name)}`)
    return null
  }

  return reactCreatePortal(children, container) as ReactPortal
}

export const getMarkups = (
  pageName: string,
  pageMarkup: string
): any[] => {
  const markups: any[] = []

  let matches = window.__hasPortals__ && portalPattern.exec(pageMarkup)
  let strippedMarkup = pageMarkup
  while (matches) {
    const [matched, name, markup] = matches
    strippedMarkup = strippedMarkup.replace(matched, '')
    markups.push({
      markup: renderContainer(name, portalWrapper(name, markup)),
      name,
    })
    matches = portalPattern.exec(pageMarkup)
  }

  markups.unshift({
    markup: renderContainer(pageName, strippedMarkup),
    name: pageName,
  })

  return markups
}

export const isSiteEditorIframe =
  canUseDOM &&
  window.top !== window.self &&
  window.top.__provideRuntime &&
  !!window.top.__provideRuntime &&
  window.__RUNTIME__.route.domain === 'store'

export const getContainer = () => {
  return canUseDOM
    ? document.getElementsByClassName(RENDER_CONTAINER_CLASS)[0]
    : null
}

export const ensureContainer = (name: string) => {
  if (!canUseDOM) {
    return false
  }

  const existingContainer = document.getElementsByClassName(
    RENDER_CONTAINER_CLASS
  )[0]
  if (existingContainer) {
    return false
  }

  const containerDiv = document.createElement('div')
  containerDiv.className = `render-container ${routeClass(name)}`
  containerDiv.style.display = 'none'
  document.body.appendChild(containerDiv)
  return true
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/LoadingBar.css from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```css
@keyframes loading-bar-animation {
  0% {
    transform: scale(0, 1);
  }
  10% {
    transform: scale(0.5, 1);
  }
  20% {
    transform: scale(0.75, 1);
  }
  100% {
    transform: scale(0.95, 1);
  }
}

.loadingBarAnimation {
  transform-origin: 0 0;
  animation: loading-bar-animation 6s ease-out;
  transform: scale(0.95, 1);
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/ChildBlock.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { useExtension } from '../hooks/extension'

export function useChildBlock(childBlock: ChildBlock): Block | null {
  if (typeof childBlock === 'string') {
    throw new Error(
      `You are passing a string as a parameter to useChildBlock ("${childBlock}"). You should pass an object like {id: "${childBlock}"}.`
    )
  }

  const { id } = childBlock

  if (!id) {
    throw new Error('The id you are sending to useChildBlock is empty')
  }

  const extension = useExtension({ children: id })

  // We are explicitly not exposing the private API here
  return extension
    ? {
        props: extension.props,
      }
    : null
}

export function ChildBlock({ id, children }: ChildBlockProps) {
  const block = useChildBlock({ id })

  return children(block)
}

interface ChildBlock {
  id: string
}

/** Placeholder for possible block data in the future */
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface Block {}

interface ChildBlockProps extends ChildBlock {
  children(block: Block | null): React.ReactNode
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/Link.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { MouseEvent, useCallback } from 'react'
import { NavigateOptions, pathFromPageName } from '../utils/pages'
import { useRuntime } from './RenderContext'

const isLeftClickEvent = (event: MouseEvent<HTMLAnchorElement>) =>
  event.button === 0

const isModifiedEvent = (event: MouseEvent<HTMLAnchorElement>) =>
  !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey)

const absoluteRegex = /^https?:\/\/|^\/\//i
const telephoneRegex = /^tel:/i
const mailToRegex = /^mailto:/i

const isAbsoluteUrl = (url: string) => absoluteRegex.test(url)
const isTelephoneUrl = (url: string) => telephoneRegex.test(url)
const isMailToUrl = (url: string) => mailToRegex.test(url)

interface Props extends NavigateOptions {
  onClick?: (event: React.MouseEvent) => void
  className?: string
  target?: string
}

const Link: React.FunctionComponent<Props> = ({
  page,
  onClick = () => {},
  params,
  to,
  scrollOptions,
  query,
  children,
  modifiers,
  replace,
  modifiersOptions,
  target,
  ...linkProps
}) => {
  const {
    pages,
    navigate,
    rootPath = '',
    route: { domain },
  } = useRuntime()

  const handleClick = useCallback(
    (event: MouseEvent<HTMLAnchorElement>) => {
      if (
        isModifiedEvent(event) ||
        !isLeftClickEvent(event) ||
        (to && (isAbsoluteUrl(to) || isTelephoneUrl(to) || isMailToUrl(to)))
      ) {
        return
      }

      onClick(event)

      const options: NavigateOptions = {
        fallbackToWindowLocation: false,
        page,
        params,
        query,
        rootPath,
        scrollOptions,
        to,
        modifiers,
        replace,
        modifiersOptions,
      }

      // If you pass a target different from "_self" the component
      // will behave just like a normal anchor element
      if ((target === '_self' || !target) && navigate(options)) {
        event.preventDefault()
      }
    },
    [
      to,
      onClick,
      page,
      params,
      query,
      rootPath,
      scrollOptions,
      modifiers,
      navigate,
      replace,
      modifiersOptions,
      target,
    ]
  )

  const getHref = () => {
    if (to) {
      // Prefix any non-absolute paths (e.g. http:// or https://) and non-special links (e.g. mailto: or tel:)
      // with runtime.rootPath
      if (
        rootPath &&
        !to.startsWith('http') &&
        !to.startsWith(rootPath) &&
        !isTelephoneUrl(to) &&
        !isMailToUrl(to)
      ) {
        return rootPath + to
      }
      return to
    }
    if (page) {
      const path = pathFromPageName(page, pages, params)
      const qs = query ? `?${query}` : ''
      if (path) {
        return rootPath + path + qs
      }
    }
    return '#'
  }

  const href = getHref()
  // Href inside admin iframe should omit the `/app/` path
  const hrefWithoutIframePrefix =
    domain && domain === 'admin' && href.startsWith('/admin/app/')
      ? href.replace('/admin/app/', '/admin/')
      : href

  return (
    <a
      target={target}
      href={hrefWithoutIframePrefix}
      {...linkProps}
      onClick={handleClick}
    >
      {children}
    </a>
  )
}

export default Link

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/ExtensionPointError.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import PropTypes from 'prop-types'
import React, { ErrorInfo, PureComponent } from 'react'

interface Props {
  treePath?: string
  error?: Error
  errorInfo?: ErrorInfo | null
  operationIds?: string[]
}

interface State {
  errorDetails?: boolean
}

class ExtensionPointError extends PureComponent<Props, State> {
  public static propTypes = {
    error: PropTypes.object,
    errorInfo: PropTypes.object,
    treePath: PropTypes.string,
  }

  public constructor(props: any) {
    super(props)
    this.state = {}
  }

  public handleToggleErrorDetails = () => {
    this.setState({
      errorDetails: !this.state.errorDetails,
    })
  }

  public render() {
    const { treePath, error, errorInfo, operationIds } = this.props
    const { errorDetails } = this.state

    const componentStack = errorInfo && errorInfo.componentStack

    return (
      <div className="bg-washed-red pa6 f5 serious-black br3 pre">
        <span>
          Error rendering extension point <strong>{treePath}</strong>
        </span>
        <button
          type="button"
          className="red ph0 ma0 mh3 bg-transparent bn pointer link"
          onClick={this.handleToggleErrorDetails}
        >
          ({errorDetails ? 'hide' : 'show'} details)
        </button>
        {errorDetails && error && (
          <>
            <ul className="f6 list pl0">
              {operationIds &&
                operationIds.map(operationId => (
                  <li key={operationId}>
                    <span>Operation ID:</span>{' '}
                    <span className="i">{operationId}</span>
                  </li>
                ))}
            </ul>
            <pre>
              <code className="f6">{error.stack}</code>
            </pre>
          </>
        )}
        {errorDetails && componentStack && (
          <pre>
            <code className="f6">{componentStack}</code>
          </pre>
        )}
      </div>
    )
  }
}

export default ExtensionPointError

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/ExtensionPoint.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { mergeDeepRight, reduce } from 'ramda'
import React, { FC, Fragment, Suspense } from 'react'

import ExtensionPointComponent from './ExtensionPointComponent'
import Loading from './Loading'
import { useRuntime } from './RenderContext'
import { useTreePath } from '../utils/treePath'
import NoSSR from './NoSSR'
import { withErrorBoundary } from './ErrorBoundary'
import GenericPreview from './Preview/GenericPreview'
import LoadingBar from './LoadingBar'

// TODO: Export components separately on @vtex/blocks-inspector, so this import can be simplified
const InspectBlockWrapper = React.lazy(
  () =>
    new Promise<{ default: any }>(resolve => {

    })
)

interface Props {
  id: string
  key?: string
  params?: any
  query?: any
  preview?: boolean
  treePath?: string
  blockProps?: object
}

function mountTreePath(currentId: string, parentTreePath: string) {
  if (parentTreePath === currentId) {
    return parentTreePath
  }
  return [parentTreePath, currentId].filter(id => !!id).join('/')
}

function getChildExtensions(runtime: any, treePath: string) {
  const extension = runtime.extensions && runtime.extensions[treePath]

  if (!extension || !extension.blocks) {
    return
  }

  // This filter condition is for backwards compatibility
  return extension.blocks
    .filter(block => block.children === undefined || block.children === true)
    .map((child, i) => {
      const childTreePath = mountTreePath(child.extensionPointId, treePath)

      const childExtension =
        runtime.extensions && runtime.extensions[childTreePath]
      const childProps = childExtension ? childExtension.props : {}

      return (
        <ExtensionPoint
          key={`around-${treePath}-${i}`}
          id={child.extensionPointId}
          blockProps={childProps}
          treePath={treePath}
        />
      )
    })
}

function withOuterExtensions(
  after: string[],
  around: string[],
  before: string[],
  treePath: string,
  props: any,
  element: JSX.Element
) {
  const beforeElements = before.map(beforeId => (
    <ExtensionPoint
      id={beforeId}
      key={beforeId}
      treePath={treePath}
      params={props.params}
      query={props.query}
    />
  ))

  const afterElements = after.map(afterId => (
    <ExtensionPoint
      id={afterId}
      key={afterId}
      treePath={treePath}
      params={props.params}
      query={props.query}
    />
  ))

  const isRootTreePath = treePath.indexOf('/') === -1

  const wrapped = (
    <Fragment key={`wrapped-${treePath}`}>
      {beforeElements}
      {element}
      {isRootTreePath && <div className="flex flex-grow-1" />}
      {afterElements}
    </Fragment>
  )

  return around.reduce((acc, aroundId) => {
    return (
      <ExtensionPoint
        {...props}
        id={aroundId}
        key={aroundId}
        treePath={treePath}
        beforeElements={beforeElements}
        afterElements={afterElements}
      >
        {acc}
      </ExtensionPoint>
    )
  }, wrapped)
}

const ExtensionPoint: FC<Props> = props => {
  const runtime = useRuntime()

  const { inspect } = runtime

  const treePathFromHook = useTreePath()

  const { children, params, query, id, blockProps, ...parentProps } = props

  const newTreePath = React.useMemo(
    () => mountTreePath(id, props.treePath || treePathFromHook.treePath),
    [id, props.treePath, treePathFromHook.treePath]
  )

  const extension = React.useMemo(() => {
    return runtime.extensions && runtime.extensions[newTreePath]
  }, [newTreePath, runtime.extensions])

  const {
    component = null,
    after = [],
    around = [],
    before = [],
    content = {},
    render: renderStrategy = null,
    props: extensionProps = {},
  } = extension || {}

  const mergedProps = React.useMemo(() => {
    return reduce(mergeDeepRight, {} as any, [
      /** Extra props passed to the ExtensionPoint component
       * e.g. <ExtensionPoint foo="bar" />
       */
      parentProps,
      /** Props that are read from runtime.extensions, that come from the blocks files
       */
      extensionProps,
      /** Props from the blockProps prop, used when the user wants to prevent overriding
       * the native ExtensionPoint props (such as `id`)
       */
      blockProps || {},
      content,
      { params, query },
    ])
  }, [parentProps, extensionProps, blockProps, content, params, query])

  if (
    /* Stops rendering if the extension is not found. Useful for optional ExtensionPoints */
    !extension
  ) {
    return null
  }

  const isCompositionChildren =
    extension && extension.composition === 'children'

  const componentChildren =
    isCompositionChildren && extension.blocks
      ? getChildExtensions(runtime, newTreePath)
      : children

  const isRootTreePath = newTreePath.indexOf('/') === -1

  const extensionPointComponent = withOuterExtensions(
    after,
    around,
    before,
    newTreePath,
    mergedProps,

    <ExtensionPointComponent
      component={component}
      props={mergedProps}
      runtime={runtime}
      treePath={newTreePath}
    >
      {component ? (
        componentChildren
      ) : isRootTreePath ? (
        <GenericPreview />
      ) : (
        <Loading />
      )}
    </ExtensionPointComponent>
  )

  // "client" component assets are sent to server side rendering,
  // but they should display a loading animation.
  //
  // "lazy" components might never be used, so they don't necessarily
  // need a loading animation.
  const maybeClientExtension = (
    <Fragment>
      {runtime.preview && isRootTreePath && <LoadingBar />}
      {renderStrategy === 'client' && !runtime.amp ? (
        <NoSSR onSSR={<Loading />}>{extensionPointComponent}</NoSSR>
      ) : (
        extensionPointComponent
      )}
    </Fragment>
  )

  /** If it's on inspect mode (?__inspect on querystring) wraps the block
   * on a block-inspector wrapper */
  if (inspect) {
    return (
      <Suspense fallback={maybeClientExtension}>
        <InspectBlockWrapper extension={extension} treePath={newTreePath}>
          {maybeClientExtension}
        </InspectBlockWrapper>
      </Suspense>
    )
  }

  return maybeClientExtension
}

ExtensionPoint.defaultProps = {
  blockProps: {},
  treePath: '',
}

export default withErrorBoundary(ExtensionPoint) as any

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/MaybeContext.test.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { cleanup, render } from '@vtex/test-tools/react'
import 'jest-dom/extend-expect'
import MaybeContext from './MaybeContext'

afterEach(cleanup)

const TEST_STRING = 'This is category '

beforeAll(() => {
  window.__RENDER_8_COMPONENTS__ = {
    product: function MockProduct(props: any) {
      return (
        <div>
          <h1>{props.title}</h1>
          <p>{TEST_STRING + props.categoryId}!</p>
        </div>
      )
    } as any,
  }
})

test(`it should pass props defined in extensions[path]context.props to the underlying component`, () => {
  const PAGE = 'store.product'
  const mockContextProps = { categoryId: 1, title: 'Product!' }
  const mockExtensions: Record<string, Partial<Extension>> = {
    [PAGE]: {
      context: {
        props: mockContextProps,
        component: 'product',
      },
    },
  }

  const mockRuntime: any = { extensions: mockExtensions }

  const { getByText } = render(
    <MaybeContext nestedPage={PAGE} runtime={mockRuntime} />
  )
  expect(getByText(new RegExp(mockContextProps.title, 'i'))).toBeInTheDocument()
  expect(
    getByText(new RegExp(TEST_STRING + mockContextProps.categoryId, 'i'))
  ).toBeInTheDocument()
})

afterAll(() => {
  delete window.__RENDER_8_COMPONENTS__
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/LayoutContainer.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { useTreePath } from '../utils/treePath'
import ExtensionPoint from './ExtensionPoint'
import { useRuntime } from './RenderContext'
import { LoadingWrapper } from './LoadingContext'

type Element = string | ElementArray
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ElementArray extends Array<Element> {}

interface LayoutContainerProps {
  aboveTheFold?: number
  elements: Element
}

interface ContainerProps {
  aboveTheFold?: number
  elements: Element
  isRow: boolean
  isMobile?: boolean
  preview?: boolean
}

interface ContainerState {
  shouldRenderBelowTheFold: boolean
}

const elementPropType = PropTypes.oneOfType([PropTypes.string, PropTypes.array])
  .isRequired

class Container extends Component<ContainerProps, ContainerState> {
  public static propTypes = {
    aboveTheFold: PropTypes.number,
    elements: elementPropType,
    isRow: PropTypes.bool,
    isMobile: PropTypes.bool,
    preview: PropTypes.bool,
  }

  public state = {
    shouldRenderBelowTheFold: false,
  }

  private handleScroll = () => {
    this.setState({
      shouldRenderBelowTheFold: true,
    })

    window.document.removeEventListener('scroll', this.handleScroll)
  }

  public componentDidMount() {
    window &&
      window.document &&
      window.document.addEventListener('scroll', this.handleScroll)
  }

  public componentWillUnmount() {
    window &&
      window.document &&
      window.document.removeEventListener('scroll', this.handleScroll)
  }

  public render() {
    const { isRow, isMobile, elements, children, ...props } = this.props

    const { shouldRenderBelowTheFold } = this.state

    const className = `flex flex-grow-1 w-100 ${
      isRow ? 'flex-row' : 'flex-column'
    }`
    if (typeof elements === 'string') {
      if (elements === '__children__') {
        return children
      }
      return (
        <div className={isRow ? '' : className}>
          <ExtensionPoint id={elements} {...props} />
        </div>
      )
    }

    let elementsToRender = this.props.elements.length
    if (this.props.preview && this.props.aboveTheFold != null) {
      elementsToRender = this.props.aboveTheFold
    }

    let foldIndex = elements.indexOf('__fold__')
    if (foldIndex === -1) {
      foldIndex = elements.indexOf(
        `__fold__.${isMobile ? 'mobile' : 'desktop'}`
      )
    }

    const hasFold = foldIndex > -1

    if (hasFold && !shouldRenderBelowTheFold) {
      elementsToRender = foldIndex + 1
    }

    const returnValue: JSX.Element[] = elements
      .slice(0, elementsToRender)
      .map((element: Element) => {
        return (
          <Container
            key={element.toString()}
            elements={element}
            isMobile={isMobile}
            isRow={!isRow}
            {...props}
          >
            {children}
          </Container>
        )
      })

    return (
      <div
        className={className}
        style={{
          // Forces scrolling if there is below-the-fold content to be rendered
          minHeight: hasFold && !shouldRenderBelowTheFold ? '101vh' : 'auto',
        }}
      >
        {returnValue}
      </div>
    )
  }
}

const LayoutContainer: React.FunctionComponent<LayoutContainerProps> = props => {
  const { extensions, preview, hints } = useRuntime()
  const { treePath } = useTreePath()

  const extension = extensions[treePath]

  const elements =
    (extension &&
      extension.blocks &&
      extension.blocks.map(insertion => insertion.extensionPointId)) ||
    []
  const containerProps = { ...props, elements }

  const container = (
    <Container
      {...containerProps}
      preview={preview}
      isRow={false}
      isMobile={hints.mobile}
    />
  )

  const isRootTreePath = treePath.indexOf('/') === -1

  if (extension.preview && isRootTreePath) {
    /** TODO: LoadingWrapper is in the end a makeshift Suspense.
     * Should probably be replaced in the future. */
    return <LoadingWrapper>{container}</LoadingWrapper>
  }

  return container
}

export default LayoutContainer

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/ExtensionContainer.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FC, Fragment } from 'react'

import { getDirectChildren, useTreePath } from '../utils/treePath'
import ExtensionPoint from './ExtensionPoint'
import { useRuntime } from './RenderContext'

const join = (p: string | null, c: string | null): string =>
  [p, c].filter(id => !!id).join('/')

interface Props {
  id: string | null
}

const ExtensionContainer: FC<Props> = props => {
  const { id } = props
  const { extensions } = useRuntime()
  const { treePath } = useTreePath()
  const containerTreePath = join(treePath, id)
  return (
    <Fragment>
      {getDirectChildren(extensions, containerTreePath).map(cid => {
        const childTreePath = join(id, cid)
        return (
          <ExtensionPoint {...props} key={childTreePath} id={childTreePath} />
        )
      })}
    </Fragment>
  )
}

export default ExtensionContainer

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/ExtensionPortal.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import PropTypes from 'prop-types'
import React, { Component, ReactPortal } from 'react'
import { createPortal } from 'react-dom'

import { PortalRenderingRequest } from './ExtensionManager'
import ExtensionPoint from './ExtensionPoint'

interface Props {
  extension: PortalRenderingRequest
}

class ExtensionPortal extends Component<Props> {
  public static propTypes = {
    extension: PropTypes.object,
  }

  public constructor(props: Props) {
    super(props)
  }

  public render() {
    const { extensionName, destination, props } = this.props.extension
    return createPortal(
      <ExtensionPoint id={extensionName} {...props} />,
      destination
    ) as ReactPortal
  }
}

export default ExtensionPortal

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/ExtensionManager.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import PropTypes from 'prop-types'
import { any, map, append } from 'ramda'
import React, { Component } from 'react'

import ExtensionPortal from './ExtensionPortal'

export interface PortalRenderingRequest {
  extensionName: string
  destination: HTMLElement
  props: any
}

interface Props {
  runtime: any
}

interface State {
  extensionsToRender: PortalRenderingRequest[]
}

class ExtensionManager extends Component<Props, State> {
  public static propTypes = {
    runtime: PropTypes.object,
  }

  public constructor(props: Props) {
    super(props)
    this.state = {
      extensionsToRender: [],
    }
  }

  public componentDidMount() {
    const {
      runtime: { emitter },
    } = this.props
    emitter.addListener(
      'renderExtensionLoader.addOrUpdateExtension',
      this.updateExtensions
    )
  }

  public componentWillUnmount() {
    const {
      runtime: { emitter },
    } = this.props
    emitter.removeListener(
      'renderExtensionLoader.addOrUpdateExtension',
      this.updateExtensions
    )
  }

  public updateExtensions = (extension: PortalRenderingRequest) => {
    this.setState({
      extensionsToRender: this.addOrUpdateExtension(
        this.state.extensionsToRender,
        extension
      ),
    })
  }

  public render() {
    return this.state.extensionsToRender.map(el => {
      return <ExtensionPortal key={el.extensionName} extension={el} />
    })
  }

  private addOrUpdateExtension = (
    extensionsList: PortalRenderingRequest[],
    newExtension: PortalRenderingRequest
  ): PortalRenderingRequest[] => {
    const exists = any((el: PortalRenderingRequest) => {
      return el.extensionName === newExtension.extensionName
    })(extensionsList)
    const newExtensionsList = exists
      ? map((el: PortalRenderingRequest) => {
          return el.extensionName === newExtension.extensionName
            ? newExtension
            : el
        }, extensionsList)
      : append(newExtension, extensionsList)
    return newExtensionsList
  }
}

export default ExtensionManager

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/Session.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FunctionComponent, Fragment, useEffect, useState } from 'react'
import Loading from './Loading'
import { useRuntime } from './RenderContext'

interface Props {
  renderWhileLoading?: boolean
}

const Session: FunctionComponent<Props> = ({
  renderWhileLoading,
  children,
}) => {
  const [ensured, setEnsured] = useState(false)
  const [error, setError] = useState(null)
  const { ensureSession } = useRuntime()

  useEffect(() => {
    let isCurrent = true
    if (ensured || error) {
      return
    }

    ensureSession?.()
      .then(() => {
        if (isCurrent) {
          setEnsured(true)
        }
      })
      .catch((err: any) => {
        if (isCurrent) {
          setError(err)
        }
      })

    return () => {
      isCurrent = false
    }
  }, [ensureSession, ensured, error])

  if (ensured || renderWhileLoading) {
    return <Fragment>{children}</Fragment>
  }

  if (error) {
    return (
      <div className="bg-washed-red pa6 f5 serious-black br3 pre">
        <span>Error initializing session</span>
        <pre>
          <code className="f6">{error}</code>
        </pre>
      </div>
    )
  }

  return <Loading />
}

export default Session

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/LoadingBar.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import styles from './LoadingBar.css'

const LoadingBar = () => (
  <div
    className={`${styles.loadingBarAnimation} fixed top-0 left-0 right-0 z-max bg-action-primary`}
    style={{ height: 4 }}
  >
    <div
      style={{
        background:
          'linear-gradient(90deg, rgba(255, 255, 255, 0.1) 80%, rgba(255, 255, 255, 0.5) 90%, rgba(255, 255, 255, 0.8))',
        width: '100%',
        height: '100%',
      }}
    />
  </div>
)

export default LoadingBar

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/RenderProvider.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { NormalizedCacheObject } from 'apollo-cache-inmemory'
import ApolloClient from 'apollo-client'
import { ApolloLink, NextLink, Operation } from 'apollo-link'
import debounce from 'debounce'
import { canUseDOM } from 'exenv'
import { History, UnregisterCallback } from 'history'
import PropTypes from 'prop-types'
import { forEach, merge, mergeWith, equals } from 'ramda'
import React, { Component, Fragment, ReactElement, Suspense } from 'react'
import { ApolloProvider } from 'react-apollo'
import { Helmet } from 'react-helmet'
import { IntlProvider } from 'react-intl'
import gql from 'graphql-tag'

import {
  fetchAssets,
  getLoadedImplementation,
  hotReloadOverrides,
  hotReloadTachyons,
  prefetchAssets,
  hasComponentImplementation,
} from '../utils/assets'
import PageCacheControl from '../utils/cacheControl'
import { getClient } from '../utils/client'
import { OperationContext } from '../utils/client/links/uriSwitchLink'
import {
  traverseComponent,
  traverseListOfComponents,
} from '../utils/components'
import {
  isSiteEditorIframe,
  RENDER_CONTAINER_CLASS,
  ROUTE_CLASS_PREFIX,
  routeClass,
} from '../utils/dom'
import { isEnabled } from '../utils/flags'
import {
  goBack as pageGoBack,
  mapToQueryString,
  navigate as pageNavigate,
  NavigateOptions,
  queryStringToMap,
  scrollTo as pageScrollTo,
  NavigationRouteModifier,
  NavigationRouteChange,
} from '../utils/pages'
import {
  fetchDefaultPages,
  fetchNavigationPage,
  fetchServerPage,
} from '../utils/routes'
import { TreePathContextProvider } from '../utils/treePath'
import BuildStatus from './BuildStatus'
import ExtensionManager from './ExtensionManager'
import ExtensionPoint from './ExtensionPoint'
import { RenderContextProvider } from './RenderContext'
import RenderPage from './RenderPage'
import { appendLocationSearch } from '../utils/location'
import { setCookie } from '../utils/cookie'

// TODO: Export components separately on @vtex/blocks-inspector, so this import can be simplified
const InspectorPopover = React.lazy(
  () =>
    new Promise<{ default: any }>(resolve => {

    })
)

interface Props {
  children: ReactElement<any> | null
  history: History | null
  cacheControl?: PageCacheControl
  baseURI: string
  root: string
  runtime: any
}

export interface RenderProviderState {
  appsEtag: any['appsEtag']
  cacheHints: any['cacheHints']
  components: any['components']
  culture: any['culture']
  defaultExtensions: any['defaultExtensions']
  device: any
  extensions: any['extensions']
  inspect: any['inspect']
  messages: any['messages']
  page: any['page']
  pages: any['pages']
  preview: any['preview']
  production: any['production']
  query: any['query']
  settings: any['settings']
  route: any['route']
  loadedPages: Set<string>
  blocksTree?: any['blocksTree']
  blocks?: any['blocks']
  contentMap?: any['contentMap']
}

const SEND_INFO_DEBOUNCE_MS = 100
const DISABLE_PREFETCH_PAGES = '__disablePrefetchPages'

const noop = () => {}

const areOptionsDifferent = (a: NavigateOptions, b: NavigateOptions) => {
  return (
    a.page !== b.page ||
    a.query !== b.query ||
    a.to !== b.to ||
    a.rootPath !== b.rootPath ||
    !equals(a.params, b.params)
  )
}

interface NavigationState {
  isNavigating: boolean
  lastOptions?: NavigateOptions
}

class RenderProvider extends Component<Props, RenderProviderState> {
  navigationState: NavigationState = { isNavigating: false }
  public static childContextTypes = {
    account: PropTypes.string,
    addMessages: PropTypes.func,
    amp: PropTypes.bool,
    binding: PropTypes.object,
    blocks: PropTypes.object,
    blocksTree: PropTypes.object,
    contentMap: PropTypes.object,
    components: PropTypes.object,
    culture: PropTypes.object,
    defaultExtensions: PropTypes.object,
    device: PropTypes.string,
    emitter: PropTypes.object,
    ensureSession: PropTypes.func,
    extensions: PropTypes.object,
    fetchComponent: PropTypes.func,
    fetchComponents: PropTypes.func,
    getSettings: PropTypes.func,
    goBack: PropTypes.func,
    hints: PropTypes.object,
    history: PropTypes.object,
    inspect: PropTypes.bool,
    messages: PropTypes.object,
    navigate: PropTypes.func,
    onPageChanged: PropTypes.func,
    page: PropTypes.string,
    pages: PropTypes.object,
    patchSession: PropTypes.func,
    platform: PropTypes.string,
    prefetchDefaultPages: PropTypes.func,
    addNavigationRouteModifier: PropTypes.func,
    prefetchPage: PropTypes.func,
    preview: PropTypes.bool,
    production: PropTypes.bool,
    publicEndpoint: PropTypes.string,
    query: PropTypes.object,
    renderMajor: PropTypes.number,
    rootPath: PropTypes.string,
    route: PropTypes.object,
    setDevice: PropTypes.func,
    setQuery: PropTypes.func,
    updateComponentAssets: PropTypes.func,
    updateExtension: PropTypes.func,
    updateRuntime: PropTypes.func,
    workspace: PropTypes.string,
  }

  public static propTypes = {
    children: PropTypes.element,
    history: PropTypes.object,
    root: PropTypes.string,
    runtime: PropTypes.object,
  }

  public sendInfoFromIframe = debounce(
    (params?: { shouldUpdateRuntime?: boolean }) => {
      if (!isSiteEditorIframe) {
        return undefined
      }

      return (window as any).top.__provideRuntime(
        this.getChildContext(),
        this.state.messages,
        (params && params.shouldUpdateRuntime) || false,
        // Deprecated
        this.updateMessages
      )
    },
    SEND_INFO_DEBOUNCE_MS
  )

  private rendered!: boolean
  private sessionPromise: Promise<void>
  private unlisten!: UnregisterCallback | null
  private apolloClient: ApolloClient<NormalizedCacheObject>
  private prefetchRoutes: Set<string>
  private navigationRouteModifiers: Set<NavigationRouteModifier>
  private navigationModifierOptions: Record<string, NavigationRouteChange>
  private fetcher: any['fetch']

  public constructor(props: Props) {
    super(props)
    const {
      appsEtag,
      binding,
      blocks,
      blocksTree,
      cacheHints,
      contentMap,
      culture,
      messages,
      components,
      exposeBindingAddress,
      extensions,
      pages,
      page,
      query,
      production,
      rootPath = '',
      route,
      settings,
      queryData,
    } = props.runtime
    const { history, baseURI, cacheControl } = props
    const ignoreCanonicalReplacement = query && query.map
    this.fetcher = fetch

    if (binding && canUseDOM) {
      setCookie('vtex_binding_address', binding.canonicalBaseAddress)
    }

    if (history) {
      const renderLocation: any = {
        ...history.location,
        search:
          exposeBindingAddress && binding
            ? appendLocationSearch(history.location.search, {
                __bindingAddress: binding.canonicalBaseAddress,
              })
            : history.location.search,
        pathname:
          ignoreCanonicalReplacement || !route.canonicalPath
            ? history.location.pathname
            : rootPath + route.canonicalPath,
        state: {
          navigationRoute: {
            id: route.id,
            params: route.params,
            path: history.location.pathname,
          },
          renderRouting: true,
        },
      }
      // TODO: use something with better performance than equals
      if (!equals(history.location as any, renderLocation)) {
        history.replace(renderLocation)
      }
      // backwards compatibility
      window.browserHistory = (global as any).browserHistory = history
    }

    // todo: reload window if client-side created a segment different from server-side
    this.sessionPromise = canUseDOM
      ? window.__RENDER_8_SESSION__.sessionPromise
      : Promise.resolve()
    const runtimeContextLink = this.createRuntimeContextLink()
    this.apolloClient = getClient(
      props.runtime,
      baseURI,
      runtimeContextLink,
      this.sessionPromise,
      this.fetcher,
      cacheControl
    )

    if (queryData) {
      this.hydrateApolloCache(queryData)
    }

    this.state = {
      appsEtag,
      blocks,
      blocksTree,
      cacheHints,
      contentMap,
      components,
      culture,
      defaultExtensions: {},
      device: 'any',
      extensions,
      loadedPages: new Set([page]),
      messages,
      page,
      pages,
      preview: false,
      production,
      query,
      route,
      settings: settings || {},
      inspect: false,
    }

    this.prefetchRoutes = new Set<string>()
    this.navigationRouteModifiers = new Set()
    this.navigationModifierOptions = {}
  }

  public componentDidMount() {
    this.rendered = true
    const { history, runtime } = this.props
    const { production, emitter, publicEndpoint } = runtime

    this.unlisten = history && history.listen(this.onPageChanged)
    emitter.addListener('localesChanged', this.onLocaleSelected)

    if (!production) {
      emitter.addListener('extensionsUpdated', this.updateRuntime)
      emitter.addListener('blocksUpdated', this.updateRuntime)
      emitter.addListener('styleOverrides', hotReloadOverrides)
      emitter.addListener('styleTachyonsUpdate', hotReloadTachyons)
    }

    this.sendInfoFromIframe()
    this.prefetchPages()

    if (
      publicEndpoint === 'myvtex.com' &&
      !production &&
      '__inspect' in (this.state.query || {})
    ) {
      this.setState({ inspect: true })
    }
  }

  public UNSAFE_componentWillReceiveProps(nextProps: Props) {
    // If RenderProvider is being re-rendered, the global runtime might have changed
    // so we must update all extensions.
    if (this.rendered) {
      const {
        runtime: { extensions },
      } = nextProps
      this.setState({ extensions })
    }
  }

  public componentWillUnmount() {
    const { runtime } = this.props
    const { production, emitter } = runtime
    if (this.unlisten) {
      this.unlisten()
    }
    emitter.removeListener('localesChanged', this.onLocaleSelected)

    if (!production) {
      emitter.removeListener('extensionsUpdated', this.updateRuntime)
      emitter.removeListener('blocksUpdated', this.updateRuntime)
      emitter.removeListener('styleOverrides', hotReloadOverrides)
      emitter.removeListener('styleTachyonsUpdate', hotReloadTachyons)
    }
  }

  public getChildContext(): any {
    const { history, runtime } = this.props
    const {
      components,
      contentMap,
      extensions,
      inspect,
      messages,
      page,
      pages,
      preview,
      culture,
      device,
      route,
      query,
      defaultExtensions,
    } = this.state

    const {
      account,
      amp,
      binding,
      emitter,
      hints,
      platform,
      production,
      publicEndpoint,
      renderMajor,
      rootPath,
      workspace,
    } = runtime

    return {
      account,
      addMessages: this.addMessages,
      amp,
      binding,
      components,
      contentMap,
      culture,
      defaultExtensions,
      device,
      emitter,
      ensureSession: this.ensureSession,
      extensions,
      fetchComponent: this.fetchComponent,
      fetchComponents: this.fetchComponents,
      getSettings: this.getSettings,
      goBack: this.goBack,
      hints,
      history,
      inspect,
      messages,
      navigate: this.navigate,
      onPageChanged: this.onPageChanged,
      page,
      pages,
      patchSession: this.patchSession,
      platform,
      prefetchDefaultPages: this.prefetchDefaultPages,
      addNavigationRouteModifier: this.addNavigationRouteModifier,
      prefetchPage: this.prefetchPage,
      preview,
      production,
      publicEndpoint,
      query,
      renderMajor,
      rootPath,
      route,
      setDevice: this.handleSetDevice,
      setQuery: this.setQuery,
      updateComponentAssets: this.updateComponentAssets,
      updateExtension: this.updateExtension,
      updateRuntime: this.updateRuntime,
      workspace,
    }
  }

  public getSettings = (app: string) => {
    const { settings } = this.state
    return settings[app]
  }

  public ensureSession = () => {
    return this.sessionPromise
  }

  public patchSession = (data?: any) => {
    return this.sessionPromise.then(() =>
      canUseDOM ? window.__RENDER_8_SESSION__.patchSession(data) : undefined
    )
  }

  public getCustomMessages = (locale: string) => {
    const { components } = this.state
    const componentsArray = Object.keys(components)

    const customMessages = componentsArray
      .map(getLoadedImplementation)
      .filter(
        component =>
          component &&
          (component.getCustomMessages || component.WrappedComponent)
      )
      .map(component => {
        const getCustomMessages =
          component.getCustomMessages ||
          (component.WrappedComponent &&
            component.WrappedComponent.getCustomMessages) ||
          noop
        return getCustomMessages(locale)
      })
      .reduce((acc, strings) => ({ ...acc, ...strings }), {})

    return customMessages
  }

  public goBack = () => {
    const { history } = this.props
    return pageGoBack(history)
  }

  public setQuery = (
    query: Record<string, any> = {},
    {
      merge = true,
      replace = false,
      scrollOptions = false,
    }: any = {}
  ): boolean => {
    const {
      history,
      runtime: { rootPath },
    } = this.props
    const {
      pages,
      page,
      route: { params },
    } = this.state
    if (!history) {
      return false
    }
    const {
      location: { search },
    } = history

    const current = queryStringToMap(search)
    const nextQuery = mapToQueryString(merge ? { ...current, ...query } : query)

    return pageNavigate(history, pages, {
      fetchPage: false,
      page,
      params,
      query: nextQuery,
      replace,
      scrollOptions,
      rootPath,
      modifiers: this.navigationRouteModifiers,
    })
  }

  public navigate = (options: NavigateOptions) => {
    const {
      history,
      runtime: { rootPath },
    } = this.props
    const { pages } = this.state
    options.rootPath = rootPath
    options.modifiers = this.navigationRouteModifiers

    this.navigationModifierOptions = {
      ...this.navigationModifierOptions,
      ...options.modifiersOptions,
    }
    options.modifiersOptions = this.navigationModifierOptions

    if (this.navigationState.isNavigating) {
      const lastOptions = this.navigationState.lastOptions!
      if (!areOptionsDifferent(lastOptions, options)) {
        return false
      }
    }

    this.navigationState = {
      isNavigating: true,
      lastOptions: options,
    }

    return pageNavigate(history, pages, options)
  }

  public addNavigationRouteModifier = (modifier: NavigationRouteModifier) => {
    this.navigationRouteModifiers.add(modifier)
  }

  public replaceRouteClass = (route: string) => {
    try {
      const containers = document.getElementsByClassName(RENDER_CONTAINER_CLASS)
      const currentRouteClass = containers[0].className
        .split(' ')
        .find(c => c.startsWith(ROUTE_CLASS_PREFIX))
      const newRouteClass = routeClass(route)

      Array.prototype.forEach.call(containers, (e: Element) => {
        if (currentRouteClass) {
          e.classList.remove(currentRouteClass)
        }
        e.classList.add(newRouteClass)
      })
    } catch (e) {
      console.error('Failed to set route class', routeClass(route))
    }
  }

  public scrollTo = (scrollOptions?: any) => {
    try {
      if (scrollOptions === false) {
        return
      }

      const options = scrollOptions || { top: 0, left: 0 }
      window.setTimeout(() => pageScrollTo(options), 0)
    } catch (e) {
      console.warn('Failed to scroll after page navigation.')
    }
  }

  public afterPageChanged = (
    route: string,
    scrollOptions?: any
  ) => {
    this.navigationState = { isNavigating: false }
    this.replaceRouteClass(route)
    this.scrollTo(scrollOptions)
    this.sendInfoFromIframe()
  }

  public onPageChanged = (location: any) => {
    const {
      runtime: { renderMajor },
    } = this.props
    const {
      culture: { locale },
      pages: pagesState,
      production,
      route,
      loadedPages,
    } = this.state
    const { state } = location

    // Make sure this is our navigation
    if (!state || !state.renderRouting) {
      return
    }

    const { navigationRoute, fetchPage } = state
    const { id: maybePage, params } = navigationRoute
    const transientRoute = { ...route, ...navigationRoute }

    // We always have to navigate to a page. If none was found, we
    // navigate to the current page with preview
    const allowConditions =
      pagesState[maybePage] && pagesState[maybePage].allowConditions
    const declarer = pagesState[maybePage] && pagesState[maybePage].declarer
    const shouldSkipFetchNavigationData =
      (!allowConditions && loadedPages.has(maybePage)) || fetchPage === false
    const query = queryStringToMap(location.search) as any['query']
    const page = maybePage || route.id

    if (shouldSkipFetchNavigationData) {
      return this.setState(
        {
          page,
          query,
          route: transientRoute,
        },
        () => this.afterPageChanged(page, state.scrollOptions)
      )
    }

    // Sets the preloading state, which currently displays
    // a loading bar at the top of the page
    this.setState({
      preview: true,
    })

    const paramsJSON = JSON.stringify(params)
    const apolloClient = this.apolloClient
    const routeId = page
    // Retrieve the adequate assets for the new page. Naming will
    // probably change (query will return something like routes) as
    // well as the fields that need to be retrieved, but the logic
    // that the new state (extensions and assets) will be derived from
    // the results of this query will probably remain the same.
    const navigationPromise = isEnabled('RENDER_NAVIGATION')
      ? fetchServerPage({
          fetcher: this.fetcher,
          path: navigationRoute.path,
          query,
        }).then(
          async ({
            appsEtag,
            components,
            extensions,
            matchingPage,
            messages,
            pages,
            settings,
            queryData,
          }: any) => {
            if (queryData) {
              this.hydrateApolloCache(queryData)
            }

            await this.fetchComponents(components, extensions)

            this.setState(
              state => ({
                ...state,
                appsEtag,
                components: { ...state.components, ...components },
                extensions: { ...state.extensions, ...extensions },
                loadedPages: loadedPages.add(matchingPage.routeId),
                messages: { ...state.messages, ...messages },
                page: matchingPage.routeId,
                pages,
                preview: false,
                query,
                route: matchingPage,
                settings,
              }),
              () => {
                this.navigationState = { isNavigating: false }
                this.replaceRouteClass(matchingPage.routeId)
                this.sendInfoFromIframe()
                this.scrollTo(state.scrollOptions)
              }
            )
          }
        )
      : fetchNavigationPage({
          apolloClient,
          declarer,
          locale,
          paramsJSON,
          production,
          query: JSON.stringify(query),
          renderMajor,
          routeId,
          skipCache: false,
        }).then(
          async ({
            appsEtag,
            cacheHints,
            components,
            extensions,
            matchingPage,
            messages,
            pages,
            settings,
          }: any) => {
            const updatedRoute = { ...transientRoute, ...matchingPage }
            await this.fetchComponents(components, extensions)

            this.setState(
              {
                appsEtag,
                cacheHints: mergeWith(merge, this.state.cacheHints, cacheHints),
                components: { ...this.state.components, ...components },
                extensions: { ...this.state.extensions, ...extensions },
                loadedPages: loadedPages.add(page),
                messages: { ...this.state.messages, ...messages },
                page,
                pages,
                preview: false,
                query,
                route: updatedRoute,
                settings,
              },
              () => {
                this.navigationState = { isNavigating: false }
                this.replaceRouteClass(page)
                this.sendInfoFromIframe()
                this.scrollTo(state.scrollOptions)
              }
            )
          }
        )
    navigationPromise.finally(() => {
      if (this.navigationState.isNavigating) {
        this.navigationState = { isNavigating: false }
      }
    })
    return navigationPromise
  }

  public prefetchPage = (pageName: string) => {
    const { extensions } = this.state
    const component = extensions[pageName] && extensions[pageName].component
    if (component) {
      const { runtime } = this.props
      const { components } = this.state
      const componentsAssetsMap = traverseComponent(components, component)
      return prefetchAssets(runtime, componentsAssetsMap)
    }
  }

  public prefetchDefaultPages = async (routeIds: string[]) => {
    const {
      runtime: { query },
    } = this.props

    const disablePrefetch =
      query &&
      DISABLE_PREFETCH_PAGES in query &&
      query[DISABLE_PREFETCH_PAGES] !== 'false'
    if (!disablePrefetch) {
      if (this.rendered) {
        console.warn(
          "prefetchDefaultPages should only be called before RenderProvider's render."
        )
        return
      }
      routeIds.forEach(routeId => this.prefetchRoutes.add(routeId))
    }
  }

  public updateComponentAssets = (availableComponents: any) => {
    this.setState({
      components: {
        ...this.state.components,
        ...availableComponents,
      },
    })
  }

  public fetchComponents = async (
    components: any['components'],
    extensions?: any['extensions']
  ) => {
    const { runtime } = this.props
    // In order for only fetching `components`, we create corresponding extensions
    // for them if they weren't passed
    if (!extensions) {
      const componentsNames = Object.keys(components)
      extensions = componentsNames.reduce((acc, component) => {
        acc[component] = { component }
        return acc
      }, {} as any['extensions'])
    }
    const componentsToDownload = Object.values(extensions).reduce<string[]>(
      (acc, extension: any) => {
        if (extension.render === 'lazy') {
          return acc
        }
        if (!hasComponentImplementation(extension.component)) {
          acc.push(extension.component)
        }
        const context = extension?.context?.component
        if (context && !hasComponentImplementation(context)) {
          acc.push(context)
        }
        return acc
      },
      []
    )

    if (componentsToDownload.length === 0) {
      return
    }

    const allAssets = traverseListOfComponents(components, componentsToDownload)
    await fetchAssets(runtime, allAssets)
    this.sendInfoFromIframe({ shouldUpdateRuntime: true })
  }

  public fetchComponent = (component: string) => {
    if (!canUseDOM) {
      throw new Error('Cannot fetch components during server side rendering.')
    }

    const { runtime } = this.props
    const { components } = this.state
    const componentsAssetsMap = traverseComponent(components, component)

    const assetsPromise = fetchAssets(runtime, componentsAssetsMap)
    assetsPromise.then(() => {
      this.sendInfoFromIframe({ shouldUpdateRuntime: true })
    })

    return assetsPromise
  }

  public onLocaleSelected = (locale: string, domain?: string) => {
    if (locale !== this.state.culture.locale) {
      const sessionData = { public: {} }
      if (domain && domain === 'admin') {
        sessionData.public = {
          admin_cultureInfo: {
            value: locale,
          },
        }
      } else {
        sessionData.public = {
          cultureInfo: {
            value: locale,
          },
        }
      }
      this.patchSession(sessionData)
        .then(() => window.location.reload())
        .catch(e => {
          console.log('Failed to fetch new locale file.')
          console.error(e)
        })
    }
  }

  public updateRuntime = async (options?: any) => {
    const {
      runtime: { renderMajor },
    } = this.props
    const {
      page,
      pages: pagesState,
      production,
      culture: { locale },
      route,
      query,
    } = this.state
    const declarer = pagesState[page] && pagesState[page].declarer
    const { pathname } = window.location
    const paramsJSON = JSON.stringify(route.params || {})

    const {
      appsEtag,
      cacheHints,
      components,
      extensions,
      messages,
      pages,
      settings,
    } = isEnabled('RENDER_NAVIGATION')
      ? await fetchServerPage({
          path: route.path,
          query,
          fetcher: this.fetcher,
        })
      : await fetchNavigationPage({
          apolloClient: this.apolloClient,
          declarer,
          locale,
          paramsJSON,
          path: pathname,
          production,
          query: '',
          renderMajor,
          routeId: page,
          skipCache: true,
          ...options,
        })

    await this.fetchComponents(components, extensions)

    await new Promise<void>(resolve => {
      this.setState(
        state => ({
          appsEtag,
          cacheHints: isEnabled('RENDER_NAVIGATION')
            ? state.cacheHints
            : cacheHints,
          components,
          extensions: {
            ...state.extensions,
            ...extensions,
          },
          messages: {
            ...state.messages,
            ...messages,
          },
          page,
          pages,
          route,
          settings,
        }),
        resolve
      )
    })

    await this.sendInfoFromIframe()
  }

  public createRuntimeContextLink() {
    return new ApolloLink((operation: Operation, forward?: NextLink) => {
      const {
        appsEtag,
        cacheHints,
        components,
        culture,
        extensions,
        messages,
        pages,
      } = this.state
      operation.setContext(
        (currentContext: OperationContext): OperationContext => {
          return {
            ...currentContext,
            runtime: {
              appsEtag,
              cacheHints,
              components,
              culture,
              extensions,
              messages,
              pages,
            },
          }
        }
      )
      return forward ? forward(operation) : null
    })
  }

  public updateExtension = async (name: string, extension: any) => {
    const { extensions } = this.state

    await new Promise<void>(resolve => {
      this.setState(
        {
          extensions: {
            ...extensions,
            [name]: extension,
          },
        },
        resolve
      )
    })

    if (name !== 'store/__overlay') {
      await this.sendInfoFromIframe()
    }
  }

  public handleSetDevice = (device: any) => {
    this.setState({ device })
  }

  public addMessages = async (newMessages: any['messages']) => {
    await new Promise<void>(resolve => {
      this.setState(
        state => ({
          ...state,
          messages: {
            ...state.messages,
            ...newMessages,
          },
        }),
        resolve
      )
    })

    await this.sendInfoFromIframe()
  }

  public render() {
    const { children } = this.props
    const {
      culture: { locale },
      messages,
      pages,
      page,
      query,
      production,
      inspect,
    } = this.state
    const customMessages = this.getCustomMessages(locale)
    const mergedMessages = {
      ...messages,
      ...customMessages,
    }

    const component = children ? (
      React.cloneElement(children as ReactElement<any>, { query })
    ) : (
      <div className="render-provider">
        <Helmet title={pages[page] && pages[page].title} />
        <RenderPage page={page} query={query} />
      </div>
    )

    const context = this.getChildContext()

    return (
      <RenderContextProvider runtime={context}>
        <TreePathContextProvider treePath="">
          <ApolloProvider client={this.apolloClient}>
            <IntlProvider
              locale={locale}
              messages={mergedMessages}
              textComponent={Fragment}
            >
              <Fragment>
                <ExtensionManager runtime={this.props.runtime} />
                {!production && !isSiteEditorIframe && <BuildStatus />}
                {component}
                {isSiteEditorIframe ? (
                  <ExtensionPoint id="store/__overlay" />
                ) : null}
                {inspect && (
                  <Suspense fallback={null}>
                    <InspectorPopover />
                  </Suspense>
                )}
              </Fragment>
            </IntlProvider>
          </ApolloProvider>
        </TreePathContextProvider>
      </RenderContextProvider>
    )
  }

  private hydrateApolloCache = (
    queryData: Array<{
      data: string
      query: string
      variables: Record<string, any>
    }>
  ) => {
    forEach(({ data, query, variables }) => {
      try {
        this.apolloClient.writeQuery({
          query: gql`
            ${query}
          `,
          data: JSON.parse(data),
          variables,
        })
      } catch (error) {
        console.warn(
          `Error writing query from render-server in Apollo's cache`,
          error
        )
      }
    }, queryData)
  }

  // Deprecated
  private updateMessages = (newMessages: RenderProviderState['messages']) => {
    this.setState(
      prevState => ({
        ...prevState,
        messages: { ...prevState.messages, ...newMessages },
      }),
      () => {
        this.sendInfoFromIframe()
      }
    )
  }

  private prefetchPages = () => {
    if (this.prefetchRoutes.size > 0) {
      setTimeout(this.execPrefetchPages, 6 * 1000)
    }
  }

  private execPrefetchPages = async () => {
    const {
      runtime,
      runtime: { renderMajor },
    } = this.props

    const {
      pages,
      culture: { locale },
    } = this.state

    const { components: defaultComponents } = await fetchDefaultPages({
      apolloClient: this.apolloClient,
      locale,
      pages,
      renderMajor,
      routeIds: Array.from(this.prefetchRoutes),
    })

    const allAssets = traverseListOfComponents(
      defaultComponents,
      Object.keys(defaultComponents)
    )

    await prefetchAssets(runtime, allAssets)
  }
}

export default RenderProvider

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/RenderContext.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import hoistNonReactStatics from 'hoist-non-react-statics'
import React, { ComponentType, useContext } from 'react'

export interface RenderContextProps {
  runtime: any
}

export interface EmitterProps {
  __emitter: any
}

export const RenderContext = React.createContext<any>({} as any)
RenderContext.displayName = 'RenderContext'

export const RenderContextProvider: React.FC<RenderContextProps> = ({
  children,
  runtime,
}) => (
  <RenderContext.Provider value={runtime}>{children}</RenderContext.Provider>
)
RenderContextProvider.displayName = 'RenderContextProvider'

export const useRuntime = () => {
  return useContext(RenderContext)
}

export const withRuntimeContext = <TOriginalProps extends {} = {}>(
  Component: ComponentType<TOriginalProps & RenderContextProps>
): ComponentType<TOriginalProps> => {
  const WithRuntimeContext = (props: TOriginalProps) => {
    const runtime = useRuntime()
    return <Component {...props} runtime={runtime} />
  }
  WithRuntimeContext.displayName = `withRuntimeContext(${Component.displayName ||
    Component.name ||
    'Component'})`
  WithRuntimeContext.WrappedComponent = Component
  return hoistNonReactStatics<any, any>(
    WithRuntimeContext,
    Component
  )
}

export const withEmitter = <TOriginalProps extends {} = {}>(
  Component: ComponentType<TOriginalProps & EmitterProps>
): ComponentType<TOriginalProps> => {
  const WithEmitter = (props: TOriginalProps) => {
    const { emitter } = useRuntime()
    return <Component {...props} __emitter={emitter} />
  }
  WithEmitter.displayName = `withEmitter(${Component.displayName ||
    Component.name ||
    'Component'})`
  WithEmitter.WrappedComponent = Component
  return hoistNonReactStatics<any, any>(
    WithEmitter,
    Component
  )
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/ExtensionPoint.test.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { cleanup, render } from '@vtex/test-tools/react'
import 'jest-dom/extend-expect'
import ExtensionPoint from './ExtensionPoint'
import { TreePathContextProvider } from '../utils/treePath'
import { RenderContextProvider } from './RenderContext'
import { TEST_ID as PreviewTestId } from './Preview'

afterEach(cleanup)

test(`it shouldn't show Preview when there is no extension for current tree path`, () => {
  const mockExtensions: Record<string, Partial<Extension>> = {
    'store.search#category/search-result#category': {
      component: '',
      preview: {
        height: {
          desktop: { defaultValue: 500 },
          mobile: { defaultValue: 500 },
        },
        width: {
          desktop: { defaultValue: 500 },
          mobile: { defaultValue: 500 },
        },
        type: 'box',
      },
    },
    // The extension below is what the ExtensionPoint should use.
    // 'store.search#category/search-result#category/search-title': undefined,
  }

  const { queryByTestId } = render(
    <RenderContextProvider runtime={{ extensions: mockExtensions } as any}>
      <TreePathContextProvider treePath="store.search#category/search-result#category">
        <ExtensionPoint treePath="" id="search-title" />
      </TreePathContextProvider>
    </RenderContextProvider>
  )

  expect(queryByTestId(PreviewTestId)).toBeNull()
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/RenderPage.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FC } from 'react'
import ExtensionPoint from './ExtensionPoint'
import MaybeContext from './MaybeContext'
import { useRuntime } from './RenderContext'

interface Props {
  page: string
  query?: Record<string, string>
}

const RenderPage: FC<Props> = props => {
  const runtime = useRuntime()
  const { page, query } = props
  const {
    route: { params },
  } = runtime

  /** Will use the params of the page as a key for the component, so React will
   * unmount and remount it on page change. This is used to reset components
   * to their initial state on page change, to better simulate how regular
   * web pages work. */
  let paramsString = ''

  /** TODO: This is a quick fix--the admin, which runs on an iframe, doesn't play
   * nice with unmounting/remounting. Should perhaps try and find a better solution
   * for this issue. */
  if (!page?.startsWith('admin.')) {
    try {
      paramsString = JSON.stringify(params)
    } catch (e) {
      console.warn(
        "Unable to stringify params for page. This shouldn't be much of a problem, but might prevent components from being reset on page change. The params object is as follows:",
        params
      )
    }
  }

  return (
    <MaybeContext
      nestedPage={page}
      query={query}
      params={params}
      runtime={runtime}
    >
      <ExtensionPoint
        // Key used to unmount/remount the component on page change.
        key={`${page}/${paramsString}`}
        id={page}
        query={query}
        params={params}
        {...props}
      />
    </MaybeContext>
  )
}

export default RenderPage

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/ExtensionPointComponent.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import ReactDOM from 'react-dom'
import React, { ErrorInfo, PureComponent } from 'react'

import { getImplementation } from '../utils/assets'
import graphQLErrorsStore from '../utils/graphQLErrorsStore'

import ExtensionPointError from './ExtensionPointError'
import GenericPreview from './Preview/GenericPreview'
import { withLoading } from './LoadingContext'
import { RenderContextProps } from './RenderContext'
import { TreePathContextProvider } from '../utils/treePath'
import { isSiteEditorIframe } from '../utils/dom'
import { Loading } from '../index'

interface Props {
  component: string | null
  props: any
  treePath: string
  setLoading?: (treePath: string, value: boolean) => void
}

interface State {
  error?: Error | null
  errorInfo?: ErrorInfo | null
  operationIds: string[]
  lastUpdate?: number
}

const componentPromiseMap: any = {}
const componentPromiseResolvedMap: any = {}

class ExtensionPointComponent extends PureComponent<
  Props & RenderContextProps,
  State
> {
  private _isMounted!: boolean
  private mountedError!: boolean

  public constructor(props: Props & RenderContextProps) {
    super(props)

    this.state = {
      operationIds: [],
    }
    this.mountedError = false
  }

  public updateComponentsWithEvent = (component: string) => {
    if (!this._isMounted) {
      return false
    }

    this.setState({ error: null, errorInfo: null, lastUpdate: Date.now() })
    const { component: mounted, treePath } = this.props

    console.log(
      `[render] Component updated. treePath=${treePath} ${
        mounted !== component ? `mounted=${mounted} ` : ''
      }updated=${component}`
    )

    return true
  }

  public fetchAndRerender = () => {
    const {
      component,
      runtime: { fetchComponent },
    } = this.props
    const Component = component && getImplementation(component)

    // Let's fetch the assets and re-render.
    if (component && !Component) {
      if (!(component in componentPromiseMap)) {
        componentPromiseMap[component] = fetchComponent(component)
      } else if (componentPromiseResolvedMap[component]) {
        this.stopLoading()
        throw new Error(`Unable to fetch component ${component}`)
      }

      componentPromiseMap[component]
        .then(() => {
          componentPromiseResolvedMap[component] = true
          this.updateComponentsWithEvent(component)
          this.stopLoading()
        })
        .catch(() => {
          componentPromiseResolvedMap[component] = true
          this.stopLoading()
        })
    } else {
      this.stopLoading()
    }
  }

  private stopLoading = () => {
    if (this.props.setLoading) {
      this.props.setLoading(this.props.treePath, false)
    }
  }

  private startLoading = () => {
    if (this.props.setLoading) {
      this.props.setLoading(this.props.treePath, true)
    }
  }

  public clearError = () => {
    this.setState({
      error: null,
      errorInfo: null,
      operationIds: [],
    })
  }

  public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    const { component, treePath: path } = this.props

    console.error('Failed to render extension point', path, component)
    const operationIds = graphQLErrorsStore.getOperationIds()

    this.setState({
      error,
      errorInfo,
      operationIds,
    })
  }

  public componentDidMount() {
    this._isMounted = true
    this.startLoading()
    this.fetchAndRerender()
    this.addDataToElementIfEditable()
  }

  public componentDidUpdate() {
    this.fetchAndRerender()
    if (this.state.error) {
      if (this.mountedError) {
        this.clearError()
      } else {
        this.mountedError = true
      }
    }
    this.addDataToElementIfEditable()
  }

  public componentWillUnmount() {
    this.removeDataFromElement()
    this._isMounted = false
  }

  public render() {
    const {
      component,
      props,
      children,
      treePath,
      runtime: { production, page },
      props: componentProps,
    } = this.props
    const { error, errorInfo, operationIds } = this.state
    const Component = component && getImplementation(component)

    // A children of this extension point throwed an uncaught error
    // Only show errors in production if the entire page explodes. (Ignore nested extension points)
    if (error) {
      if (production && !page.startsWith('admin/')) {
        return null
      }

      const errorInstance = (
        <ExtensionPointError
          error={error}
          errorInfo={errorInfo}
          operationIds={operationIds}
          treePath={treePath}
        />
      )
      props.__errorInstance = errorInstance
      props.__clearError = this.clearError

      if (!Component || !(Component as any).hotReload) {
        return errorInstance
      }
    } else {
      delete props.__errorInstance
      delete props.__clearError
    }

    const isRootTreePath = treePath.indexOf('/') === -1
    const isAround = treePath.indexOf('$around') !== -1

    return (
      <TreePathContextProvider treePath={treePath}>
        {Component ? (
          <Component {...props}>{children}</Component>
        ) : isRootTreePath || isAround ? (
          /* Adds header/footer before and after the preview during loading,
           * if the component being loaded is a root component--e.g. context
           * wrappers, `around` wrappers */
          <>
            {componentProps.beforeElements}
            <GenericPreview />
            {componentProps.afterElements}
          </>
        ) : (
          <Loading />
        )}
      </TreePathContextProvider>
    )
  }

  private addDataToElementIfEditable = () => {
    if (!isSiteEditorIframe) {
      return
    }
    const ComponentImpl =
      this.props.component && getImplementation(this.props.component)
    const isEditable =
      ComponentImpl &&
      (ComponentImpl.hasOwnProperty('schema') ||
        ComponentImpl.hasOwnProperty('getSchema'))

    if (!isEditable) {
      return
    }

    // eslint-disable-next-line react/no-find-dom-node
    const element = ReactDOM.findDOMNode(this) as Element

    if (element && element.setAttribute) {
      element.setAttribute('data-extension-point', this.props.treePath)
    }
  }

  private removeDataFromElement = () => {
    if (!isSiteEditorIframe) {
      return
    }
    // eslint-disable-next-line react/no-find-dom-node
    const element = ReactDOM.findDOMNode(this) as Element

    if (element && element.removeAttribute) {
      element.removeAttribute('data-extension-point')
    }
  }
}

/** TODO: withLoading is in the end a makeshift Suspense
 * wrapper. Should probably be replaced in the future. */
export default withLoading(ExtensionPointComponent)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/LegacyExtensionContainer.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { ReactPortal, FC, useState, useEffect, Fragment } from 'react'

import { createPortal } from '../utils/dom'
import { getDirectChildren, useTreePath } from '../utils/treePath'
import ExtensionPoint from './ExtensionPoint'
import { useRuntime } from './RenderContext'

interface Props {
  query: any
  params: any
}

const LegacyExtensionContainer: FC<Props> = ({ query, params }) => {
  const [hydrate, setHydrate] = useState(false)
  useEffect(() => {
    setHydrate(true)
  }, [])
  const { extensions } = useRuntime()
  const { treePath } = useTreePath()
  return (
    <Fragment>
      {getDirectChildren(extensions, treePath).map(
        id =>
          createPortal(
            <ExtensionPoint id={id} query={query} params={params} />,
            `${treePath}/${id}`,
            hydrate
          ) as ReactPortal
      )}
    </Fragment>
  )
}

export default LegacyExtensionContainer

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/Loading.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'

import Preview from './Preview'

import { useExtension } from '../hooks/extension'

const Loading = () => {
  const extension = useExtension()

  if (!extension) {
    return null
  }

  const { preview } = extension

  if (!preview) {
    return null
  }

  return <Preview extension={extension} />
}

export default Loading

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/TrackEventsWrapper.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { forEach } from 'ramda'
import { PureComponent } from 'react'
import ReactDOM from 'react-dom'

interface Props {
  events?: string[]
  id?: string
}

const sendEvent = (id: string, event: string) => {
  window.postMessage(
    {
      pageComponentInteraction: {
        blockId: id,
        interactionType: event,
        namespace: 'renderRuntime',
      },
    },
    '*'
  )
}

class TrackEventsWrapper extends PureComponent<Props> {
  public eventsHandler: Record<string, () => void> = {}

  public componentDidMount() {
    const { id, events } = this.props
    if (events && events.length && id) {
      // eslint-disable-next-line react/no-find-dom-node
      const element = ReactDOM.findDOMNode(this)
      if (element && element.addEventListener) {
        forEach(event => {
          this.eventsHandler[event] = () => sendEvent(id, event)
          element.addEventListener(event, this.eventsHandler[event])
        }, events)
      }
    }
  }

  public componentWillUnmount() {
    const { id, events } = this.props
    if (events && events.length && id) {
      // eslint-disable-next-line react/no-find-dom-node
      const element = ReactDOM.findDOMNode(this)
      if (element && element.addEventListener) {
        forEach(
          event =>
            element.removeEventListener(event, this.eventsHandler[event]),
          events
        )
      }
    }
  }

  public render() {
    const { children } = this.props
    return children
  }
}

export default TrackEventsWrapper

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/MaybeContext.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useMemo, Fragment, FC } from 'react'
import ExtensionPointComponent from '../components/ExtensionPointComponent'
import { RenderContextProps } from './RenderContext'
import { ExtensionPoint } from '../index'

interface Props extends RenderContextProps {
  nestedPage: string
  params?: Record<string, any>
  query?: Record<string, string>
}

const useContextComponent = ({
  nestedPage,
  query,
  params,
  runtime,
}: Props & RenderContextProps) => {
  const { extensions } = runtime

  const { context, before, after, props: pageProps } = extensions[nestedPage]
  const pageContextProps = pageProps && pageProps.context

  /** Allows rendering header/footer if the Context component is loading,
   * by exposing them to ExtensionPointComponent via props.
   * TODO:This is a bit of a hack. The header/footer should probably be decoupled
   * from the context, but doing so introduces bugs on some stores. */
  const beforeElements =
    before &&
    before.map(beforeId => (
      <ExtensionPoint id={beforeId} key={beforeId} treePath={nestedPage} />
    ))

  const afterElements =
    after &&
    after.map(afterId => (
      <ExtensionPoint id={afterId} key={afterId} treePath={nestedPage} />
    ))

  const contextProps = useMemo(() => {
    if (!context) {
      return undefined
    }
    return {
      ...pageContextProps,
      beforeElements,
      afterElements,
      nextTreePath: nestedPage,
      params,
      query,
      ...context.props,
    }
  }, [
    context,
    pageContextProps,
    beforeElements,
    afterElements,
    nestedPage,
    params,
    query,
  ])
  const contextComponent = context ? context.component : undefined
  return [contextProps, contextComponent]
}

const MaybeContext: FC<Props> = ({
  children,
  nestedPage,
  query,
  params,
  runtime,
}) => {
  const [contextProps, contextComponent] = useContextComponent({
    nestedPage,
    query,
    params,
    runtime,
  })

  return contextComponent ? (
    <ExtensionPointComponent
      component={contextComponent}
      props={contextProps}
      runtime={runtime}
      treePath={nestedPage}
    >
      {children}
    </ExtensionPointComponent>
  ) : (
    <Fragment>{children}</Fragment>
  )
}

export default MaybeContext

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/NoSSR.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, {
  useState,
  useEffect,
  useLayoutEffect,
  FunctionComponent,
} from 'react'

const useEnhancedEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect

const useSSR = () => {
  const [isCSRAvailable, setCSR] = useState(false)

  useEnhancedEffect(() => {
    setCSR(true)
  }, [])

  return !isCSRAvailable
}

interface Props {
  onSSR?: React.ReactNode
}

const NoSSR: FunctionComponent<Props> = ({ children, onSSR }) => {
  const isSSR = useSSR()

  return <>{isSSR ? onSSR : children}</>
}

export default NoSSR

export { useSSR }

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/BuildStatus.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { Component } from 'react'
import { RenderContextProps, withRuntimeContext } from './RenderContext'

interface State {
  animateOut: boolean
  status: string | null
  anchor: 'left' | 'right'
  interrupted: boolean | null
}

class BuildStatus extends Component<RenderContextProps, State> {
  private animateOutHandle!: number
  private hideHandle!: number

  public constructor(props: RenderContextProps) {
    super(props)

    this.state = {
      animateOut: false,
      status: null,
      interrupted: null,
      anchor: 'left',
    }
  }

  public clearTimeouts = () => {
    clearTimeout(this.animateOutHandle)
    clearTimeout(this.hideHandle)
  }

  public hideWithDelay = (delayMillis: number) => {
    this.animateOutHandle = window.setTimeout(
      () => this.setState({ animateOut: true }),
      delayMillis
    )
    this.hideHandle = window.setTimeout(
      () =>
        this.setState({ status: null, interrupted: null, animateOut: false }),
      delayMillis + 300
    )
  }

  private handleMouseOver = () => {
    this.setState(state => ({
      anchor: state.anchor === 'left' ? 'right' : 'left',
    }))
  }

  public updateStatus = (status: string) => {
    // After we started a reload, the UI shouldn't move.
    if (this.state.status === 'reload') {
      return
    }

    this.setState({ status, animateOut: false })
    this.clearTimeouts()

    if (status === 'success' || status === 'hmr:success') {
      const delay = status === 'success' ? 2000 : 0
      this.hideWithDelay(delay)
    }
  }

  public updateInterrupted = () => {
    this.clearTimeouts()
    this.setState({ interrupted: true, animateOut: false })
    const delay = 2000
    this.hideWithDelay(delay)
  }

  public subscribeToStatus = () => {
    const { emitter } = this.props.runtime
    emitter.addListener('build.status', this.updateStatus)
  }

  public unsubscribeToStatus = () => {
    const { emitter } = this.props.runtime
    emitter.removeListener('build.status', this.updateStatus)
  }

  public subscribeToLinkInterrupted = () => {
    const { emitter } = this.props.runtime
    emitter.addListener('link_interrupted', this.updateInterrupted)
  }

  public unsubscribeToLinkInterrupted = () => {
    const { emitter } = this.props.runtime
    emitter.removeListener('link_interrupted', this.updateInterrupted)
  }

  public componentDidMount() {
    this.subscribeToStatus()
    this.subscribeToLinkInterrupted()
  }

  public componentWillUnmount() {
    this.unsubscribeToStatus()
    this.unsubscribeToLinkInterrupted()
    this.clearTimeouts()
  }

  private renderLoading = () => (
    <svg
      width="26px"
      height="26px"
      viewBox="0 0 100 100"
      preserveAspectRatio="xMidYMid"
    >
      <style>{`
        @keyframes build-status-rotate {
            0% {
              transform: rotate(0);
            }
            100% {
              transform: rotate(360deg);
            }
          }

          .build-status-rotate {
            transform-origin: 50px 50px;
            animation: build-status-rotate 0.7s infinite linear;
          }

        `}</style>
      <g>
        <circle
          cx="50"
          opacity="0.4"
          cy="50"
          fill="none"
          stroke="#F71963"
          r="40"
          strokeWidth="14"
        />
        <circle
          cx="50"
          cy="50"
          fill="none"
          stroke="#F71963"
          r="40"
          strokeDasharray="60 900"
          strokeWidth="12"
          strokeLinecap="round"
          className="build-status-rotate"
        />
      </g>
    </svg>
  )

  public render() {
    const { status, animateOut, anchor, interrupted } = this.state

    if (status === null && interrupted === null) {
      return null
    }

    const className = `build-status ${status} z-max fixed animated pa3 bg-near-white br3 rebel-pink flex items-center shadow-4 ${
      animateOut ? 'fadeOut' : 'fadeIn'
    }`

    const message = interrupted
      ? 'Link interrupted'
      : status === 'fail'
      ? 'Oops! Build failed. Check your terminal for more information'
      : status === 'reload'
      ? 'Performing full reload'
      : null

    return (
      // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events
      <div
        aria-hidden
        className={className}
        style={{
          top: '12px',
          [anchor]: '12px',
          animationDuration: '0.2s',
          opacity: 0.8,
        }}
        onMouseOver={this.handleMouseOver}
      >
        {message ? <p className="ma2">{message}</p> : this.renderLoading()}
      </div>
    )
  }
}

export default withRuntimeContext<{}>(BuildStatus)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of store-on-localhost/src/vtex.render-runtime/components/ErrorBoundary.tsx from the store-on-localhost VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { ErrorInfo, FunctionComponent } from 'react'
import ExtensionPointError from './ExtensionPointError'
import { useRuntime } from './RenderContext'

interface Props {
  runtime: any
}

class ErrorBoundary extends React.Component<Props> {
  public state = {
    error: undefined,
    errorInfo: undefined,
  }

  public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    this.setState({
      error,
      errorInfo,
    })
  }

  public render() {
    const { production } = this.props.runtime

    const { error, errorInfo } = this.state
    if (error) {
      if (!production) {
        return <ExtensionPointError error={error} errorInfo={errorInfo} />
      }
      return null
    }

    return this.props.children
  }
}

const ErrorBoundaryWithContext: FunctionComponent = ({ children }) => {
  const runtime = useRuntime()

  return <ErrorBoundary runtime={runtime}>{children}</ErrorBoundary>
}

export default ErrorBoundaryWithContext

export const withErrorBoundary = <P extends object>(
  Component: React.ComponentType<P>
) => {
  const WithErrorBoundary = (props: P) => {
    const runtime = useRuntime()

    return (
      <ErrorBoundary runtime={runtime}>
        <Component {...props} />
      </ErrorBoundary>
    )
  }

  const displayName = Component.displayName || Component.name

  if (displayName) {
    WithErrorBoundary.displayName = `withErrorBoundary(${displayName})`
  }

  return WithErrorBoundary
}

```