[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 b2b-organizations/react/components/OrganizationsAutocomplete.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useEffect, useState } from 'react'
import { useQuery } from 'react-apollo'
import { AutocompleteInput, Input } from 'vtex.styleguide'
import { useIntl } from 'react-intl'

import GET_ORGANIZATIONS from '../graphql/getOrganizations.graphql'
import GET_ORGANIZATION_BY_ID from '../graphql/getOrganization.graphql'
import { organizationMessages as adminMessages } from '../admin/utils/messages'
import { organizationMessages as storeMessages } from './utils/messages'

const initialState = {
  status: ['active', 'on-hold', 'inactive'],
  search: '',
  page: 1,
  pageSize: 25,
  sortOrder: 'ASC',
  sortedBy: 'name',
}

interface Props {
  onChange: (value: { value: string | null; label: string }) => void
  organizationId: string
  isAdmin: boolean
  disabled?: boolean
}

const OrganizationsAutocomplete = ({
  onChange,
  organizationId,
  isAdmin,
  disabled,
}: Props) => {
  const [term, setTerm] = useState('')
  const [values, setValues] = useState([] as any)
  const { data, loading, refetch } = useQuery(GET_ORGANIZATIONS, {
    variables: initialState,
    ssr: false,
    notifyOnNetworkStatusChange: true,
  })

  const { data: organization } = useQuery(GET_ORGANIZATION_BY_ID, {
    variables: { id: organizationId },
    ssr: false,
    fetchPolicy: 'network-only',
    notifyOnNetworkStatusChange: true,
  })

  const { formatMessage } = useIntl()

  const options = {
    onSelect: (value: any) => onChange(value),
    loading,
    value: values,
  }

  const onClear = () => {
    setTerm('')
    onChange({ value: null, label: '' })
  }

  useEffect(() => {
    if (!organization) {
      return
    }

    const { name, id } = organization.getOrganizationById

    setTerm(name)
    onChange({ value: id, label: name })
  }, [organization])

  useEffect(() => {
    if (data?.getOrganizations?.data) {
      setValues(
        data.getOrganizations.data.map((item: any) => {
          return {
            value: item.id,
            label: item.name,
          }
        })
      )
    }
  }, [data])

  useEffect(() => {
    if (term && term.length > 2) {
      refetch({
        ...initialState,
        search: term,
      })
    } else if (term === '') {
      onClear()
    }
  }, [term])

  const input = {
    onChange: (_term: string) => {
      setTerm(_term)
    },
    onClear,
    placeholder: `${formatMessage(
      isAdmin
        ? adminMessages.autocompleteSearching
        : storeMessages.autocompleteSearching
    )}...`,
    value: term,
  }

  return disabled ? (
    <Input disabled={disabled} value={term} />
  ) : (
    <AutocompleteInput input={input} options={options} />
  )
}

export default OrganizationsAutocomplete

```


[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 b2b-organizations/react/components/OrganizationDetails.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useState, useContext, useEffect, useCallback } from 'react'
import type { FunctionComponent, ChangeEvent } from 'react'
import { useIntl, FormattedMessage } from 'react-intl'
import {
  Layout,
  PageHeader,
  PageBlock,
  Spinner,
  Table,
  ToastContext,
} from 'vtex.styleguide'
import { useQuery, useMutation } from 'react-apollo'
import { useCssHandles } from 'vtex.css-handles'

import { organizationMessages as messages } from './utils/messages'
import storageFactory from '../utils/storage'
import { useSessionResponse } from '../modules/session'
import NewCostCenterModal from './NewCostCenterModal'
import OrganizationUsersTable from './OrganizationUsersTable'
import GET_ORGANIZATION from '../graphql/getOrganizationStorefront.graphql'
import GET_COST_CENTERS from '../graphql/getCostCentersByOrganizationIdStorefront.graphql'
import CREATE_COST_CENTER from '../graphql/createCostCenter.graphql'
import GET_PERMISSIONS from '../graphql/getPermissions.graphql'
import OrganizationsWithoutSalesManager from './OrganizationsWithoutSalesManager'

interface RouterProps {
  match: Match
  history: any
}

interface Match {
  isExact: boolean
  params: any
  path: string
  url: string
}

interface CellRendererProps {
  rowData: CostCenterSimple
  updateCellMeasurements: () => void
}

interface CostCenterSimple {
  id: string
  name: string
  addresses: Address[]
}

interface Role {
  id: string
  name: string
  slug: string
}

const CSS_HANDLES = ['createCostCenter'] as const

const localStore = storageFactory(() => localStorage)
let isAuthenticated =
  JSON.parse(String(localStore.getItem('b2b-organizations_isAuthenticated'))) ??
  false

const OrganizationDetails: FunctionComponent<RouterProps> = ({
  match: { params },
  history,
}) => {
  const sessionResponse: any = useSessionResponse()
  const handles = useCssHandles(CSS_HANDLES)

  if (sessionResponse) {
    isAuthenticated =
      sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true'

    localStore.setItem(
      'b2b-organizations_isAuthenticated',
      JSON.stringify(isAuthenticated)
    )
  }

  const { formatMessage } = useIntl()
  const { showToast } = useContext(ToastContext)

  const toastMessage = (message: MessageDescriptor) => {
    const translatedMessage = formatMessage(message)

    const action = undefined

    showToast({ message: translatedMessage, duration: 5000, action })
  }

  const [costCenterPaginationState, setCostCenterPaginationState] = useState({
    page: 1,
    pageSize: 25,
  })

  const [permissionsState, setPermissionsState] = useState([] as string[])
  const [roleState, setRoleState] = useState(null as Role | null)

  const isSalesAdmin = useCallback((): boolean => {
    return roleState !== null && new RegExp(/sales-admin/g).test(roleState.slug)
  }, [roleState])

  const isSales = useCallback((): boolean => {
    return roleState !== null && new RegExp(/sales-/g).test(roleState.slug)
  }, [roleState])

  const [loadingState, setLoadingState] = useState(false)
  const [newCostCenterModalState, setNewCostCenterModalState] = useState(false)

  const { data, loading } = useQuery(GET_ORGANIZATION, {
    variables: { id: params?.id },
    ssr: false,
  })

  const {
    data: costCentersData,
    loading: costCentersLoading,
    refetch: refetchCostCenters,
  } = useQuery(GET_COST_CENTERS, {
    variables: { ...costCenterPaginationState },
    fetchPolicy: 'network-only',
    ssr: false,
  })

  const {
    data: permissionsData,
    // loading: permissionsLoading,
  } = useQuery(GET_PERMISSIONS, { ssr: false })

  const [createCostCenter] = useMutation(CREATE_COST_CENTER)

  useEffect(() => {
    if (!permissionsData) return

    const { permissions = [], role } = permissionsData.checkUserPermission ?? {}

    if (permissions.length) {
      setPermissionsState(permissions)
    }

    if (role) {
      setRoleState(role)
    }
  }, [permissionsData])

  const handleCostCentersPrevClick = () => {
    if (costCenterPaginationState.page === 1) return

    const newPage = costCenterPaginationState.page - 1

    setCostCenterPaginationState({
      ...costCenterPaginationState,
      page: newPage,
    })

    refetchCostCenters({
      ...costCenterPaginationState,
      page: newPage,
    })
  }

  const handleCostCentersNextClick = () => {
    const newPage = costCenterPaginationState.page + 1

    setCostCenterPaginationState({
      ...costCenterPaginationState,
      page: newPage,
    })

    refetchCostCenters({
      ...costCenterPaginationState,
      page: newPage,
    })
  }

  const handleCostCentersRowsChange = (e: ChangeEvent<HTMLInputElement>) => {
    const {
      target: { value },
    } = e

    setCostCenterPaginationState({
      page: 1,
      pageSize: +value,
    })

    refetchCostCenters({
      page: 1,
      pageSize: +value,
    })
  }

  const handleAddNewCostCenter = ({
    name,
    address,
    phoneNumber,
    businessDocument,
    customFields,
  }: {
    name: string
    address: AddressFormFields
    phoneNumber: string
    businessDocument: string
    customFields: CustomField[]
  }) => {
    setLoadingState(true)
    const newAddress = {
      addressId: address.addressId.value,
      addressType: address.addressType.value,
      city: address.city.value,
      complement: address.complement.value,
      country: address.country.value,
      receiverName: address.receiverName.value,
      geoCoordinates: address.geoCoordinates.value,
      neighborhood: address.neighborhood.value,
      number: address.number.value,
      postalCode: address.postalCode.value,
      reference: address.reference.value,
      state: address.state.value,
      street: address.street.value,
      addressQuery: address.addressQuery.value,
    }

    const variables = {
      input: {
        name,
        addresses: [newAddress],
        phoneNumber,
        businessDocument,
        customFields,
      },
    }

    createCostCenter({ variables })
      .then(() => {
        setNewCostCenterModalState(false)
        setTimeout(() => {
          setLoadingState(false)
          toastMessage(messages.toastAddCostCenterSuccess)
          refetchCostCenters({ ...costCenterPaginationState })
        }, 500)
      })
      .catch(error => {
        setNewCostCenterModalState(false)
        setLoadingState(false)
        console.error(error)
        toastMessage(messages.toastAddCostCenterFailure)
      })
  }

  const handleCloseModal = () => {
    setNewCostCenterModalState(false)
  }

  const getCostCenterSchema = () => ({
    properties: {
      name: {
        title: formatMessage(messages.columnName),
      },
      addresses: {
        title: formatMessage(messages.columnAddresses),
        cellRenderer: ({ rowData: { addresses } }: CellRendererProps) => (
          <span>{addresses.length}</span>
        ),
      },
    },
  })

  if (!isAuthenticated) {
    return (
      <Layout
        fullWidth
        pageHeader={<PageHeader title={formatMessage(messages.pageTitle)} />}
      >
        <PageBlock>
          <FormattedMessage id="store/b2b-organizations.not-authenticated" />
        </PageBlock>
      </Layout>
    )
  }

  if (!data) {
    return (
      <Layout
        fullWidth
        pageHeader={<PageHeader title={formatMessage(messages.pageTitle)} />}
      >
        <PageBlock>
          {loading ? (
            <Spinner />
          ) : (
            <FormattedMessage id="store/b2b-organizations.organization-details.empty-state" />
          )}
        </PageBlock>
      </Layout>
    )
  }

  return (
    <Layout
      fullWidth
      pageHeader={
        <PageHeader
          title={
            roleState && !isSalesAdmin()
              ? data.getOrganizationByIdStorefront?.name
              : formatMessage(messages.salesAdminTitle)
          }
          linkLabel={formatMessage(messages.back)}
          onLinkClick={() => {
            history.push(`/`)
          }}
        />
      }
    >
      {roleState && !isSales() && (
        <div className={`${handles.createCostCenter}`}>
          <PageBlock title={formatMessage(messages.costCenters)}>
            <Table
              fullWidth
              schema={getCostCenterSchema()}
              items={
                costCentersData?.getCostCentersByOrganizationIdStorefront?.data
              }
              loading={costCentersLoading}
              onRowClick={({ rowData: { id } }: CellRendererProps) => {
                if (!id) return

                history.push(`/cost-center/${id}`)
              }}
              pagination={{
                onNextClick: handleCostCentersNextClick,
                onPrevClick: handleCostCentersPrevClick,
                onRowsChange: handleCostCentersRowsChange,
                currentItemFrom:
                  (costCenterPaginationState.page - 1) *
                    costCenterPaginationState.pageSize +
                  1,
                currentItemTo:
                  costCentersData?.getCostCentersByOrganizationIdStorefront
                    ?.pagination?.total <
                  costCenterPaginationState.page *
                    costCenterPaginationState.pageSize
                    ? costCentersData?.getCostCentersByOrganizationIdStorefront
                        ?.pagination?.total
                    : costCenterPaginationState.page *
                      costCenterPaginationState.pageSize,
                textShowRows: formatMessage(messages.showRows),
                textOf: formatMessage(messages.of),
                totalItems:
                  costCentersData?.getCostCentersByOrganizationIdStorefront
                    ?.pagination?.total ?? 0,
                rowsOptions: [25, 50, 100],
              }}
              toolbar={{
                newLine: {
                  label: formatMessage(messages.new),
                  handleCallback: () => setNewCostCenterModalState(true),
                  disabled: !permissionsState.includes(
                    'create-cost-center-organization'
                  ),
                },
              }}
            />
          </PageBlock>
        </div>
      )}

      {roleState && isSalesAdmin() && <OrganizationsWithoutSalesManager />}

      <PageBlock title={formatMessage(messages.users)}>
        {roleState && (
          <OrganizationUsersTable
            organizationId={data.getOrganizationByIdStorefront?.id}
            permissions={permissionsState}
            refetchCostCenters={loadingState}
            isSalesAdmin={isSalesAdmin()}
          />
        )}
      </PageBlock>
      <NewCostCenterModal
        isOpen={newCostCenterModalState}
        loading={loadingState}
        handleAddNewCostCenter={handleAddNewCostCenter}
        handleCloseModal={handleCloseModal}
      />
    </Layout>
  )
}

export default OrganizationDetails

```


[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 b2b-organizations/react/components/NewUserModal.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useState, useEffect } from 'react'
import type { FunctionComponent } from 'react'
import { Modal, Button, Input, Dropdown } from 'vtex.styleguide'
import { useIntl } from 'react-intl'
import { useQuery } from 'react-apollo'

import { organizationMessages as storeMessages } from './utils/messages'
import { organizationMessages as adminMessages } from '../admin/utils/messages'
import { validateEmail } from '../modules/formValidators'
import GET_ROLES from '../graphql/getRoles.graphql'
import GET_COST_CENTERS from '../graphql/getCostCentersByOrganizationIdStorefront.graphql'
import GET_COST_CENTERS_ADMIN from '../graphql/getCostCentersByOrganizationId.graphql'
import OrganizationsAutocomplete from './OrganizationsAutocomplete'

interface Props {
  loading: boolean
  isOpen: boolean
  handleAddNewUser: (user: UserInput) => void
  handleCloseModal: () => void
  organizationId: string
  isAdmin?: boolean
  canEdit?: boolean
  canEditSales?: boolean
  isSalesAdmin?: boolean
  canManageOrg: boolean
}

interface DropdownOption {
  value: string
  label: string
}

const NewUserModal: FunctionComponent<Props> = ({
  loading,
  isOpen,
  handleAddNewUser,
  handleCloseModal,
  organizationId,
  isAdmin = false,
  canEdit,
  canEditSales,
  isSalesAdmin,
  canManageOrg,
}) => {
  const { formatMessage } = useIntl()
  const [userState, setUserState] = useState({
    name: '',
    email: '',
    orgId: organizationId,
    costId: '',
    roleId: '',
  } as any)

  const [costCenterOptions, setCostCenterOptions] = useState(
    [] as DropdownOption[]
  )

  const [organizationState, setOrganizationState] = useState(organizationId)
  const [roleOptions, setRoleOptions] = useState([] as DropdownOption[])

  const { data: rolesData } = useQuery(GET_ROLES, {
    ssr: false,
  })

  const {
    data: costCentersData,
    refetch,
    loading: costCenterLoading,
  } = useQuery(isAdmin ? GET_COST_CENTERS_ADMIN : GET_COST_CENTERS, {
    variables: {
      id: organizationId,
      pageSize: 100,
    },
    fetchPolicy: 'network-only',
    ssr: false,
  })

  useEffect(() => {
    if (!organizationState) {
      return
    }

    setUserState({ ...userState, orgId: organizationState })
    refetch({
      id: organizationState,
      pageSize: 100,
    })
  }, [organizationState])

  useEffect(() => {
    if (
      !costCentersData?.getCostCentersByOrganizationIdStorefront?.data
        ?.length &&
      !costCentersData?.getCostCentersByOrganizationId?.data?.length
    ) {
      return
    }

    const data = isAdmin
      ? costCentersData.getCostCentersByOrganizationId.data
      : costCentersData.getCostCentersByOrganizationIdStorefront.data

    const options = data.map((costCenter: any) => {
      return { label: costCenter.name, value: costCenter.id }
    })

    setCostCenterOptions([...options])
    setUserState({
      ...userState,
      costId: options[0].value,
    })
  }, [costCentersData])

  useEffect(() => {
    if (!rolesData?.listRoles?.length) {
      return
    }

    const filteredArray = rolesData.listRoles.filter((role: any) => {
      if (isAdmin) return true

      if (canManageOrg) return true

      if (role.slug.includes('customer') && canEdit) {
        return true
      }

      if (role.slug.includes('sales') && canEditSales) {
        return !(role.slug.includes('sales-admin') && !isSalesAdmin)
      }

      return false
    })

    const options = filteredArray.map((role: any) => {
      return { label: role.name, value: role.id }
    })

    setRoleOptions(options)
  }, [rolesData])

  useEffect(() => {
    // if modal is not open, reset user state
    if (isOpen) return

    setUserState({
      name: '',
      email: '',
      orgId: organizationId,
      costId: costCenterOptions.length ? costCenterOptions[0].value : '',
      roleId: '',
    })
  }, [isOpen])

  if (!costCenterOptions?.length || !roleOptions?.length) return null

  return (
    <Modal
      centered
      bottomBar={
        <div className="nowrap">
          <span className="mr4">
            <Button
              variation="tertiary"
              onClick={() => handleCloseModal()}
              disabled={loading}
            >
              {formatMessage(
                isAdmin ? adminMessages.cancel : storeMessages.cancel
              )}
            </Button>
          </span>
          <span>
            <Button
              variation="primary"
              onClick={() => handleAddNewUser(userState)}
              isLoading={loading}
              disabled={
                !userState.name ||
                !validateEmail(userState.email) ||
                !userState.orgId ||
                !userState.costId ||
                !userState.roleId
              }
            >
              {formatMessage(isAdmin ? adminMessages.add : storeMessages.add)}
            </Button>
          </span>
        </div>
      }
      isOpen={isOpen}
      onClose={() => handleCloseModal()}
      closeOnOverlayClick={false}
    >
      <p className="f3 f1-ns fw3 gray">
        {formatMessage(isAdmin ? adminMessages.addUser : storeMessages.addUser)}
      </p>
      <p>
        {formatMessage(
          isAdmin ? adminMessages.addUserHelp : storeMessages.addUserHelp
        )}
      </p>
      <div className="w-100 mv6">
        <Input
          size="large"
          label={formatMessage(
            isAdmin ? adminMessages.name : storeMessages.name
          )}
          value={userState.name}
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            setUserState({ ...userState, name: e.target.value })
          }}
          required
        />
      </div>
      <div className="w-100 mv6">
        <Input
          size="large"
          label={formatMessage(
            isAdmin ? adminMessages.email : storeMessages.email
          )}
          value={userState.email}
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            setUserState({ ...userState, email: e.target.value })
          }}
          required
        />
      </div>
      {isOpen && canEditSales && (
        <div className="w-100 mv6">
          <p className="mb3">
            {formatMessage(
              isAdmin
                ? adminMessages.userOrganization
                : storeMessages.userOrganization
            )}
          </p>

          <OrganizationsAutocomplete
            disabled={!isAdmin && !isSalesAdmin}
            isAdmin={isAdmin}
            organizationId={organizationId}
            onChange={event => setOrganizationState(event.value as string)}
          />
        </div>
      )}
      <div className="w-100 mv6">
        <Dropdown
          label={
            <h4 className="t-heading-5 mb0 pt3">
              {formatMessage(
                isAdmin
                  ? adminMessages.userCostCenter
                  : storeMessages.userCostCenter
              )}
            </h4>
          }
          placeholder={formatMessage(
            isAdmin ? adminMessages.costCenter : storeMessages.costCenter
          )}
          disabled={costCenterLoading}
          options={costCenterOptions}
          value={userState.costId}
          onChange={(_: any, v: string) =>
            setUserState({ ...userState, costId: v })
          }
        />
      </div>
      <div className="w-100 mv6">
        <Dropdown
          label={
            <h4 className="t-heading-5 mb0 pt3">
              {formatMessage(
                isAdmin ? adminMessages.userRole : storeMessages.userRole
              )}
            </h4>
          }
          placeholder={formatMessage(
            isAdmin ? adminMessages.role : storeMessages.role
          )}
          disabled={costCenterLoading}
          options={roleOptions}
          value={userState.roleId}
          onChange={(_: any, v: string) =>
            setUserState({ ...userState, roleId: v })
          }
        />
      </div>
    </Modal>
  )
}

export default NewUserModal

```


[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 b2b-organizations/react/components/CheckCustomerSchema.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useState, useEffect, Fragment } from 'react'
import { useQuery } from 'react-apollo'
import { Alert } from 'vtex.styleguide'
import { useIntl } from 'react-intl'

import CHECK_CUSTOMER_SCHEMA from '../graphql/checkCustomerSchema.graphql'
import { organizationMessages as storeMessages } from './utils/messages'
import { organizationMessages as adminMessages } from '../admin/utils/messages'

const CheckCustomerSchema = ({ isAdmin }: { isAdmin: boolean }) => {
  const { data } = useQuery(CHECK_CUSTOMER_SCHEMA)
  const { formatMessage } = useIntl()
  const [isInvalidSchema, setIsInvalidSchema] = useState(false)

  useEffect(() => {
    if (!data) {
      return
    }

    setIsInvalidSchema(!data.checkCustomerSchema)
  }, [data])

  return (
    <Fragment>
      {isInvalidSchema && (
        <div className="mb5">
          <Alert
            type="error"
            action={{
              label: formatMessage(
                isAdmin ? adminMessages.checkItOut : storeMessages.checkItOut
              ),
              onClick: () =>
                window.open(
                  'https://developers.vtex.com/vtex-developer-docs/docs/vtex-b2b-suite',
                  '_blank'
                ),
            }}
          >
            {formatMessage(
              isAdmin
                ? adminMessages.invalidSchema
                : storeMessages.invalidSchema
            )}
          </Alert>
        </div>
      )}
    </Fragment>
  )
}

export default CheckCustomerSchema

```


[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 b2b-organizations/react/components/RequestOrganizationForm.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FC } from 'react'
import React, { useState, useContext, useEffect, Fragment } from 'react'
import {
  Input,
  Button,
  ToastContext,
  Layout,
  PageHeader,
  PageBlock,
  Alert,
  Spinner,
} from 'vtex.styleguide'
import {
  AddressRules,
  AddressContainer,
  PostalCodeGetter,
  CountrySelector,
  AddressForm,
} from 'vtex.address-form'
import { StyleguideInput } from 'vtex.address-form/inputs'
import { addValidation } from 'vtex.address-form/helpers'
import { useCssHandles } from 'vtex.css-handles'
import { useQuery, useMutation } from 'react-apollo'
import { useIntl, FormattedMessage } from 'react-intl'
import { useRuntime } from 'vtex.render-runtime'
import 'vtex.country-codes/locales'

import { organizationRequestMessages as messages } from './utils/messages'
import storageFactory from '../utils/storage'
import { getSession } from '../modules/session'
import { validateEmail, validatePhoneNumber } from '../modules/formValidators'
import { getEmptyAddress, isValidAddress } from '../utils/addresses'
import CREATE_ORGANIZATION_REQUEST from '../graphql/createOrganizationRequest.graphql'
import GET_ORGANIZATION_REQUEST from '../graphql/getOrganizationRequest.graphql'
import GET_LOGISTICS from '../graphql/getLogistics.graphql'
import GET_B2B_CUSTOM_FIELDS from '../graphql/getB2BCustomFields.graphql'
import IMPERSONATE_USER from '../graphql/impersonateUser.graphql'
import CustomFieldInputList from './CustomFieldInputList/CustomFieldInputList'

const localStore = storageFactory(() => localStorage)
let requestId = localStore.getItem('b2b-organizations_orgRequestId') ?? ''

const useSessionResponse = () => {
  const [session, setSession] = useState<any>()
  const sessionPromise = getSession()

  useEffect(() => {
    if (!sessionPromise) {
      return
    }

    sessionPromise.then(sessionResponse => {
      const { response } = sessionResponse

      setSession(response)
    })
  }, [sessionPromise])

  return session
}

const isAuthenticated = true

const CSS_HANDLES = [
  'newOrganizationContainer',
  'newOrganizationInput',
  'newOrganizationAddressForm',
  'newOrganizationButtonsContainer',
  'newOrganizationButtonSubmit',
] as const

const CreateNewOrganizationRequest = (props: any) => {
  return (
    <Fragment>

        <Button variation="primary" {...props}>
          <FormattedMessage id="store/b2b-organizations.request-new-organization.submit-button.create-new-request" />
        </Button>

    </Fragment>
  )
}

const RequestOrganizationForm: FC = () => {
  const { formatMessage, formatDate } = useIntl()
  const {
    culture: { country },
  } = useRuntime()

  const { showToast } = useContext(ToastContext)
  const sessionResponse: any = useSessionResponse()
  const handles = useCssHandles(CSS_HANDLES)
  const { data } = useQuery(GET_LOGISTICS, { ssr: false })
  const { data: existingRequestData, refetch, loading } = useQuery(
    GET_ORGANIZATION_REQUEST,
    {
      variables: { id: requestId },
      skip: !requestId,
    }
  )

  const [createOrganizationRequest] = useMutation(CREATE_ORGANIZATION_REQUEST)

  const [addressState, setAddressState] = useState(() =>
    addValidation(getEmptyAddress(country))
  )

  const formStateModel = {
    organizationName: '',
    tradeName: '',
    firstName: '',
    lastName: '',
    email: '',
    defaultCostCenterName: '',
    phoneNumber: '',
    businessDocument: '',
    stateRegistration: '',
    isSubmitting: false,
    submitted: true,
  }

  const [formState, setFormState] = useState(formStateModel)

  const [hasProfile, setHasProfile] = useState(false)

  const [impersonateUser] = useMutation(IMPERSONATE_USER)

  useEffect(() => {
    if (!sessionResponse || hasProfile) return

    if (sessionResponse.namespaces?.profile?.isAuthenticated?.value) {
      setFormState({
        ...formState,
        firstName: sessionResponse.namespaces.profile.firstName?.value,
        lastName: sessionResponse.namespaces.profile.lastName?.value,
        email: sessionResponse.namespaces.profile.email?.value,
      })
      setHasProfile(true)
    }
  }, [sessionResponse])

  const translateMessage = (message: MessageDescriptor) => {
    return formatMessage(message)
  }

  const toastMessage = (message: MessageDescriptor) => {
    const translatedMessage = translateMessage(message)
    const action = undefined

    showToast({ message: translatedMessage, action })
  }

  const translateCountries = () => {
    const { shipsTo = [] } = data?.logistics ?? []

    return shipsTo.map((code: string) => ({
      label: formatMessage({ id: `country.${code}` }),
      value: code,
    }))
  }

  const handleAddressChange = (changedAddress: AddressFormFields) => {
    const curAddress = addressState

    const newAddress = { ...curAddress, ...changedAddress }

    setAddressState(newAddress)
  }

  const handleNewOrganizationRequest = () => {
    localStore.removeItem('b2b-organizations_orgRequestId')
    setFormState({
      ...formStateModel,
      submitted: false,
    })
    setAddressState(() => addValidation(getEmptyAddress(country)))
  }

  //! CUSTOM FIELDS
  const {
    data: defaultCustomFieldsData,
    loading: defaultCustomFieldsDataLoading,
  } = useQuery(GET_B2B_CUSTOM_FIELDS, {
    ssr: false,
  })

  const [orgCustomFieldsState, setOrgCustomFieldsState] = useState<
    CustomField[]
  >([])

  const [
    costCenterCustomFieldsState,
    setCostCenterCustomFieldsState,
  ] = useState<CustomField[]>([])

  useEffect(() => {
    if (defaultCustomFieldsDataLoading) return

    const organizationFieldsToDisplay = defaultCustomFieldsData?.getB2BSettings.organizationCustomFields.filter(
      (item: CustomField) => item.useOnRegistration
    )

    const costCenterFieldsToDisplay = defaultCustomFieldsData?.getB2BSettings.costCenterCustomFields.filter(
      (item: CustomField) => item.useOnRegistration
    )

    setOrgCustomFieldsState(organizationFieldsToDisplay)
    setCostCenterCustomFieldsState(costCenterFieldsToDisplay)
  }, [defaultCustomFieldsData])

  //! CUSTOM FIELDS

  const handleImpersonation = () => {
    impersonateUser({
      variables: { userId: '' },
    })
      .then(result => {
        if (result?.data?.impersonateUser?.status === 'error') {
          console.error(
            'Impersonation error:',
            result.data.impersonateUser.message
          )
          toastMessage(messages.toastFailure)
        } else {
          window.location.reload()
        }
      })
      .catch(error => {
        console.error(error)
      })
  }

  const handleSubmit = () => {
    setFormState({
      ...formState,
      isSubmitting: true,
      submitted: true,
    })

    const organizationRequest = {
      name: formState.organizationName,
      tradeName: formState.tradeName,
      b2bCustomerAdmin: {
        firstName: formState.firstName,
        lastName: formState.lastName,
        email: formState.email,
      },
      defaultCostCenter: {
        name: formState.defaultCostCenterName,
        address: {
          addressId: addressState.addressId.value,
          addressType: addressState.addressType.value,
          city: addressState.city.value,
          complement: addressState.complement.value,
          country: addressState.country.value,
          receiverName: addressState.receiverName.value,
          geoCoordinates: addressState.geoCoordinates.value,
          neighborhood: addressState.neighborhood.value,
          number: addressState.number.value,
          postalCode: addressState.postalCode.value,
          reference: addressState.reference.value,
          state: addressState.state.value,
          street: addressState.street.value,
          addressQuery: addressState.addressQuery.value,
        },
        phoneNumber: formState.phoneNumber,
        businessDocument: formState.businessDocument,
        customFields: costCenterCustomFieldsState,
        stateRegistration: formState.stateRegistration,
      },
      customFields: orgCustomFieldsState,
    }

    createOrganizationRequest({
      variables: {
        input: organizationRequest,
      },
    })
      .then(response => {
        const statusRequest = response.data.createOrganizationRequest.status

        if (statusRequest === 'approved') {
          toastMessage(messages.toastApproved)
          setFormState({
            ...formState,
            isSubmitting: false,
          })
        } else {
          requestId = response.data.createOrganizationRequest.id
          localStore.setItem('b2b-organizations_orgRequestId', requestId)
          toastMessage(messages.toastSuccess)
          refetch({ id: requestId }).then(res => {
            if (
              res.data?.getOrganizationRequestById.status !== 'pending' &&
              sessionResponse.namespaces?.profile?.isAuthenticated?.value ===
                'true'
            ) {
              handleImpersonation()
            }
          })
          window.scrollTo({ top: 0, behavior: 'smooth' })
          setFormState({
            ...formState,
            isSubmitting: false,
            submitted: true,
          })
        }
      })
      .catch(error => {
        console.error(error)
        toastMessage(messages.toastFailure)
        setFormState({
          ...formState,
          isSubmitting: false,
        })
      })
  }

  if (!data) return null

  const renderIfSubmitted =
    formState.submitted &&
    existingRequestData?.getOrganizationRequestById?.status ? (
      <PageBlock>
        {existingRequestData.getOrganizationRequestById.status ===
          'pending' && (
          <Fragment>

              <Alert type="warning">
                <FormattedMessage
                  id="store/b2b-organizations.request-new-organization.pending-request"
                  values={{
                    created: formatDate(
                      existingRequestData.getOrganizationRequestById.created,
                      {
                        day: 'numeric',
                        month: 'numeric',
                        year: 'numeric',
                      }
                    ),
                  }}
                />
              </Alert>

            <CreateNewOrganizationRequest
              onClick={handleNewOrganizationRequest}
            />
          </Fragment>
        )}
        {existingRequestData.getOrganizationRequestById.status ===
          'approved' && (
          <Fragment>

              <Alert type="success">
                <FormattedMessage id="store/b2b-organizations.request-new-organization.approved-request" />
              </Alert>

            <CreateNewOrganizationRequest
              onClick={handleNewOrganizationRequest}
            />
          </Fragment>
        )}
        {existingRequestData.getOrganizationRequestById.status ===
          'declined' && (
          <Fragment>

              <Alert type="error">
                <FormattedMessage
                  id="store/b2b-organizations.request-new-organization.declined-request"
                  values={{
                    created: formatDate(
                      existingRequestData.getOrganizationRequestById.created,
                      {
                        day: 'numeric',
                        month: 'numeric',
                        year: 'numeric',
                      }
                    ),
                  }}
                />
              </Alert>

            <CreateNewOrganizationRequest
              onClick={handleNewOrganizationRequest}
            />
          </Fragment>
        )}
      </PageBlock>
    ) : (
      <Fragment>
        <PageBlock>
          <div
            className={`${handles.newOrganizationInput} mb5 flex flex-column`}
          >
            <Input
              autocomplete="off"
              size="large"
              label={translateMessage(messages.organizationName)}
              value={formState.organizationName}
              onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                setFormState({
                  ...formState,
                  organizationName: e.target.value,
                })
              }}
              required
            />

          <div
            className={`${handles.newOrganizationInput} mb5 flex flex-column`}
          >
            <Input
              autocomplete="off"
              size="large"
              label={translateMessage(messages.tradeName)}
              helpText={translateMessage(messages.tradeNameHelp)}
              value={formState.tradeName}
              onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                setFormState({
                  ...formState,
                  tradeName: e.target.value,
                })
              }}
            />

          <CustomFieldInputList
            customFields={
              defaultCustomFieldsDataLoading ? null : orgCustomFieldsState
            }
            onChange={setOrgCustomFieldsState}
          />
        </PageBlock>
        <PageBlock
          variation="full"
          title={translateMessage(messages.b2bCustomerAdmin)}
          subtitle={translateMessage(messages.b2bCustomerAdminHelpText)}
        >
          <div
            className={`${handles.newOrganizationInput} mb5 flex flex-column`}
          >
            <Input
              size="large"
              label={translateMessage(messages.firstName)}
              value={formState.firstName}
              onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                setFormState({
                  ...formState,
                  firstName: e.target.value,
                })
              }}
            />

          <div
            className={`${handles.newOrganizationInput} mb5 flex flex-column`}
          >
            <Input
              size="large"
              label={translateMessage(messages.lastName)}
              value={formState.lastName}
              onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                setFormState({
                  ...formState,
                  lastName: e.target.value,
                })
              }}
            />

          <div
            className={`${handles.newOrganizationInput} mb5 flex flex-column`}
          >
            <Input
              size="large"
              label={translateMessage(messages.email)}
              value={formState.email}
              onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                setFormState({
                  ...formState,
                  email: e.target.value,
                })
              }}
            />

        </PageBlock>
        <PageBlock
          variation="full"
          title={translateMessage(messages.defaultCostCenter)}
          subtitle={translateMessage(messages.defaultCostCenterHelpText)}
        >
          <div
            className={`${handles.newOrganizationInput} mb5 flex flex-column`}
          >
            <Input
              autocomplete="off"
              size="large"
              label={translateMessage(messages.defaultCostCenterName)}
              value={formState.defaultCostCenterName}
              onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                setFormState({
                  ...formState,
                  defaultCostCenterName: e.target.value,
                })
              }}
            />

          <div
            className={`${handles.newOrganizationInput} mb5 flex flex-column`}
          >
            <Input
              autocomplete="off"
              size="large"
              label={translateMessage(messages.phoneNumber)}
              helpText={translateMessage(messages.phoneNumberHelp)}
              value={formState.phoneNumber}
              error={
                formState.phoneNumber &&
                !validatePhoneNumber(formState.phoneNumber)
              }
              onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                setFormState({
                  ...formState,
                  phoneNumber: e.target.value,
                })
              }}
            />

          <div
            className={`${handles.newOrganizationInput} mb5 flex flex-column`}
          >
            <Input
              autocomplete="off"
              size="large"
              label={translateMessage(messages.businessDocument)}
              helpText={translateMessage(messages.businessDocumentHelp)}
              value={formState.businessDocument}
              onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                setFormState({
                  ...formState,
                  businessDocument: e.target.value,
                })
              }}
            />

          <CustomFieldInputList
            customFields={
              defaultCustomFieldsDataLoading
                ? null
                : costCenterCustomFieldsState
            }
            onChange={setCostCenterCustomFieldsState}
          />
          <div
            className={`${handles.newOrganizationInput} mb5 flex flex-column`}
          >
            <Input
              autocomplete="off"
              size="large"
              label={translateMessage(messages.stateRegistration)}
              helpText={translateMessage(messages.stateRegistrationHelp)}
              value={formState.stateRegistration}
              onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                setFormState({
                  ...formState,
                  stateRegistration: e.target.value,
                })
              }}
            />

          <div
            className={`${handles.newOrganizationAddressForm} mb5 flex flex-column`}
          >
            <AddressRules
              country={addressState?.country?.value}
              shouldUseIOFetching
              useGeolocation={false}
            >
              <AddressContainer
                address={addressState}
                Input={StyleguideInput}
                onChangeAddress={handleAddressChange}
                autoCompletePostalCode
              >
                <CountrySelector shipsTo={translateCountries()} />

                <PostalCodeGetter />

                <AddressForm
                  Input={StyleguideInput}
                  omitAutoCompletedFields={false}
                  omitPostalCodeFields
                />
              </AddressContainer>
            </AddressRules>

          <div
            className={`${handles.newOrganizationButtonsContainer} mb5 flex flex-column items-end pt6`}
          >

              <div className={`no-wrap ${handles.newOrganizationButtonSubmit}`}>
                <Button
                  variation="primary"
                  isLoading={formState.isSubmitting}
                  onClick={() => {
                    handleSubmit()
                  }}
                  disabled={
                    !formState.organizationName ||
                    !formState.defaultCostCenterName ||
                    !formState.firstName ||
                    !formState.lastName ||
                    !validateEmail(formState.email) ||
                    !isValidAddress(addressState) ||
                    (formState.phoneNumber &&
                      !validatePhoneNumber(formState.phoneNumber))
                  }
                >
                  <FormattedMessage id="store/b2b-organizations.request-new-organization.submit-button.label" />
                </Button>

        </PageBlock>
      </Fragment>
    )

  const renderIfAuthenticated = !isAuthenticated ? (
    <PageBlock>

        <FormattedMessage id="store/b2b-organizations.not-authenticated" />

    </PageBlock>
  ) : (
    renderIfSubmitted
  )

  return (
    <div className={`${handles.newOrganizationContainer} pv6 ph4 mw9 center`}>
      <Layout
        fullWidth
        pageHeader={
          <PageHeader
            title={translateMessage(messages.pageTitle)}
            subtitle={translateMessage(messages.helpText)}
          />
        }
      >
        {loading ? (

            <Spinner size={40} />

        ) : (
          <Fragment>{renderIfAuthenticated}</Fragment>
        )}
      </Layout>

  )
}

export default RequestOrganizationForm

```


[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 b2b-organizations/react/components/CostCenterDetails.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FunctionComponent } from 'react'
import React, { useEffect, useState, useContext, useCallback } from 'react'
import { useQuery, useMutation, useLazyQuery } from 'react-apollo'
import {
  Layout,
  PageHeader,
  PageBlock,
  Card,
  ActionMenu,
  IconOptionsDots,
  Button,
  Spinner,
  Input,
  Toggle,
  ToastContext,
} from 'vtex.styleguide'
import { useIntl, FormattedMessage } from 'react-intl'
import { AddressRules, AddressSummary } from 'vtex.address-form'
import { useCssHandles } from 'vtex.css-handles'

import { costCenterMessages as messages } from './utils/messages'
import storageFactory from '../utils/storage'
import { setGUID } from '../utils/addresses'
import { validatePhoneNumber } from '../modules/formValidators'
import { useSessionResponse } from '../modules/session'
import NewAddressModal from './NewAddressModal'
import EditAddressModal from './EditAddressModal'
import DeleteAddressModal from './DeleteAddressModal'
import DeleteCostCenterModal from './DeleteCostCenterModal'
import GET_COST_CENTER from '../graphql/getCostCenterStorefront.graphql'
import GET_ORGANIZATION from '../graphql/getOrganizationStorefront.graphql'
import UPDATE_COST_CENTER from '../graphql/updateCostCenter.graphql'
import DELETE_COST_CENTER from '../graphql/deleteCostCenter.graphql'
import GET_PERMISSIONS from '../graphql/getPermissions.graphql'
import GET_B2B_SETTINGS from '../graphql/getB2BSettings.graphql'

interface RouterProps {
  match: Match
  history: any
}

interface Match {
  isExact: boolean
  params: any
  path: string
  url: string
}

interface PaymentTerm {
  id: string
  name: string
}

const CSS_HANDLES = ['businessDocument', 'stateRegistration'] as const

const localStore = storageFactory(() => localStorage)
let isAuthenticated =
  JSON.parse(String(localStore.getItem('b2b-organizations_isAuthenticated'))) ??
  false

const CostCenterDetails: FunctionComponent<RouterProps> = ({
  match: { params },
  history,
}) => {
  const { formatMessage } = useIntl()

  const sessionResponse: any = useSessionResponse()

  if (sessionResponse) {
    isAuthenticated =
      sessionResponse?.namespaces?.profile?.isAuthenticated?.value === 'true'

    localStore.setItem(
      'b2b-organizations_isAuthenticated',
      JSON.stringify(isAuthenticated)
    )
  }

  const handles = useCssHandles(CSS_HANDLES)
  const { showToast } = useContext(ToastContext)

  const toastMessage = (message: MessageDescriptor) => {
    const translatedMessage = formatMessage(message)

    const action = undefined

    showToast({ message: translatedMessage, duration: 5000, action })
  }

  const [permissionsState, setPermissionsState] = useState([] as string[])
  const [loadingState, setLoadingState] = useState(false)
  const [costCenterName, setCostCenterName] = useState('')
  const [phoneNumber, setPhoneNumber] = useState('')
  const [businessDocument, setBusinessDocument] = useState('')
  const [stateRegistration, setStateRegistration] = useState('')
  const [addresses, setAddresses] = useState([] as Address[])
  const [paymentTerms, setPaymentTerms] = useState([] as PaymentTerm[])
  const [paymentTermOptions, setPaymentTermOptions] = useState(
    [] as PaymentTerm[]
  )

  const [settings, setSettings] = useState({
    businessReadOnly: false,
    stateReadOnly: false,
  })

  const [newAddressModalState, setNewAddressModalState] = useState({
    isOpen: false,
  })

  const [editAddressModalState, setEditAddressModalState] = useState({
    addressId: '',
    editAddress: null as Address | null,
    isOpen: false,
  })

  const [deleteAddressModalState, setDeleteAddressModalState] = useState({
    addressId: '',
    isOpen: false,
  })

  const [deleteCostCenterModalState, setDeleteCostCenterModalState] = useState({
    isOpen: false,
  })

  const { data, loading, refetch } = useQuery(GET_COST_CENTER, {
    variables: { id: params?.id },
    skip: !params?.id,
    ssr: false,
  })

  const [getOrganization, { data: organizationData }] = useLazyQuery(
    GET_ORGANIZATION
  )

  const { data: dataSettings } = useQuery(GET_B2B_SETTINGS, { ssr: false })

  const { data: permissionsData } = useQuery(GET_PERMISSIONS, { ssr: false })

  const [deleteCostCenter] = useMutation(DELETE_COST_CENTER)

  const [updateCostCenter] = useMutation(UPDATE_COST_CENTER)

  const handleSetAddresses = (_addresses: Address[]) => {
    setAddresses(
      _addresses.map((item, index) => {
        item.checked = index === 0

        return item
      })
    )
  }

  useEffect(() => {
    if (!data?.getCostCenterByIdStorefront) return
    const {
      organization: fetchedOrganization,
      name: fetchedName,
      addresses: fetchedAddresses,
      paymentTerms: fetchedPaymentTerms,
      phoneNumber: fetchedPhoneNumber = '',
      businessDocument: fetchedBusinessDocument = '',
      stateRegistration: fetchedStateRegistration = '',
    } = data.getCostCenterByIdStorefront

    setCostCenterName(fetchedName)
    handleSetAddresses(fetchedAddresses)
    setPaymentTerms(fetchedPaymentTerms ?? [])

    if (fetchedPhoneNumber) {
      setPhoneNumber(fetchedPhoneNumber)
    }

    if (fetchedBusinessDocument) {
      setBusinessDocument(fetchedBusinessDocument)
    }

    if (fetchedStateRegistration) {
      setStateRegistration(fetchedStateRegistration)
    }

    getOrganization({
      variables: { id: fetchedOrganization },
    })
  }, [data])

  useEffect(() => {
    if (!organizationData?.getOrganizationByIdStorefront) return

    const {
      paymentTerms: fetchedPaymentTermOptions,
    } = organizationData.getOrganizationByIdStorefront

    setPaymentTermOptions(fetchedPaymentTermOptions ?? [])

    // enable all available payment terms by default
    if (paymentTerms && !paymentTerms.length) {
      setPaymentTerms(fetchedPaymentTermOptions ?? [])
    }
  }, [organizationData])

  useEffect(() => {
    if (!permissionsData) return

    const { permissions = [] } = permissionsData.checkUserPermission ?? {}

    if (permissions?.length) {
      setPermissionsState(permissions)
    }
  }, [permissionsData])

  useEffect(() => {
    if (!dataSettings) {
      return
    }

    const { getB2BSettings } = dataSettings

    setSettings({
      businessReadOnly: getB2BSettings?.businessReadOnly,
      stateReadOnly: getB2BSettings?.stateReadOnly,
    })
  }, [dataSettings])

  const handleUpdateCostCenter = () => {
    setLoadingState(true)
    const _addresses = [...addresses]

    _addresses.sort(item => (item.checked ? -1 : 1))
    const variables = {
      id: params.id,
      input: {
        name: costCenterName,
        addresses: _addresses.map(item => {
          return {
            ...item,
            checked: undefined,
          }
        }),
        paymentTerms,
        phoneNumber,
        businessDocument,
        stateRegistration,
      },
    }

    updateCostCenter({ variables })
      .then(() => {
        toastMessage(messages.toastUpdateSuccess)
        refetch()
        handleSetAddresses(_addresses)
        setLoadingState(false)
      })
      .catch(error => {
        console.error(error)
        toastMessage(messages.toastUpdateFailure)
        setLoadingState(false)
      })
  }

  const handleDeleteCostCenter = () => {
    setLoadingState(true)
    deleteCostCenter({ variables: { id: params?.id } })
      .then(() => {
        history.push(
          `/organization/${data.getCostCenterByIdStorefront.organization}`
        )
      })
      .catch(error => {
        console.error(error)
        toastMessage(messages.toastDeleteFailure)
        setLoadingState(false)
      })
  }

  const handleCloseModals = () => {
    setDeleteAddressModalState({ addressId: '', isOpen: false })
    setEditAddressModalState({
      addressId: '',
      editAddress: null,
      isOpen: false,
    })
    setNewAddressModalState({ isOpen: false })
    setDeleteCostCenterModalState({ isOpen: false })
  }

  const handleNewAddressModal = () => {
    setNewAddressModalState({ isOpen: true })
  }

  const handleEditAddressModal = (addressId: string) => {
    const editAddress = addresses.find(
      address => address.addressId === addressId
    )

    if (!editAddress) return
    setEditAddressModalState({ addressId, editAddress, isOpen: true })
  }

  const handleDeleteAddressModal = (addressId: string) => {
    setDeleteAddressModalState({ addressId, isOpen: true })
  }

  const handleDeleteCostCenterModal = () => {
    setDeleteCostCenterModalState({ isOpen: true })
  }

  const handleAddNewAddress = useCallback(
    (address: AddressFormFields) => {
      const uid = setGUID(address)

      const duplicated = data?.getCostCenterByIdStorefront?.addresses?.find(
        ({ addressId }: { addressId: string }) => addressId === uid
      )

      let isDuplicatedError = false

      if (duplicated !== undefined) {
        isDuplicatedError = duplicated.postalCode === address.postalCode.value
      }

      if (!isDuplicatedError) {
        const newAddress = {
          addressId: uid,
          addressType: address.addressType.value,
          city: address.city.value,
          complement: address.complement.value,
          country: address.country.value,
          receiverName: address.receiverName.value,
          geoCoordinates: address.geoCoordinates.value,
          neighborhood: address.neighborhood.value,
          number: address.number.value,
          postalCode: address.postalCode.value,
          reference: address.reference.value,
          state: address.state.value,
          street: address.street.value,
          addressQuery: address.addressQuery.value,
        } as Address

        const newAddresses = [...addresses, newAddress]

        const variables = {
          id: params.id,
          input: {
            addresses: newAddresses.map(item => {
              return {
                ...item,
                checked: undefined,
              }
            }),
          },
        }

        setLoadingState(true)

        updateCostCenter({
          variables,
        })
          .then(() => {
            toastMessage(messages.toastUpdateSuccess)
            refetch()

            setLoadingState(false)
            handleCloseModals()

            setAddresses(
              newAddresses.map(item => {
                if (address.checked) {
                  item.checked = item === newAddress
                }

                return item
              })
            )
          })
          .catch(error => {
            console.error(error)
            toastMessage(messages.toastUpdateFailure)
            setLoadingState(false)
          })
      } else {
        showToast({
          variant: 'critical',
          message: formatMessage(messages.duplicateAddress),
        })
      }
    },
    [addresses]
  )

  const handleCheckDefault = (address: Address) => {
    setAddresses(
      addresses.map(item => {
        item.checked = item === address

        return item
      })
    )
  }

  const handleEditAddress = (modifiedAddress: AddressFormFields) => {
    const { addressId } = editAddressModalState
    const addressArray = addresses

    const addressIndex = addresses.findIndex(
      address => address.addressId === addressId
    )

    addressArray[addressIndex] = {
      addressId: modifiedAddress.addressId.value,
      addressType: modifiedAddress.addressType.value,
      city: modifiedAddress.city.value,
      complement: modifiedAddress.complement.value,
      country: modifiedAddress.country.value,
      receiverName: modifiedAddress.receiverName.value,
      geoCoordinates: modifiedAddress.geoCoordinates.value,
      neighborhood: modifiedAddress.neighborhood.value,
      number: modifiedAddress.number.value,
      postalCode: modifiedAddress.postalCode.value,
      reference: modifiedAddress.reference.value,
      state: modifiedAddress.state.value,
      street: modifiedAddress.street.value,
      addressQuery: modifiedAddress.addressQuery.value,
    } as Address

    const variables = {
      id: params.id,
      input: {
        addresses: addressArray.map(item => {
          return {
            ...item,
            checked: undefined,
          }
        }),
      },
    }

    setLoadingState(true)

    updateCostCenter({
      variables,
    })
      .then(() => {
        toastMessage(messages.toastUpdateSuccess)
        refetch()

        setLoadingState(false)

        setAddresses(addressArray)
        handleCloseModals()
      })
      .catch(error => {
        console.error(error)
        toastMessage(messages.toastUpdateFailure)
        setLoadingState(false)
      })
  }

  const handleDeleteAddress = useCallback(() => {
    const { addressId } = deleteAddressModalState
    const addressArray = addresses

    const addressIndex = addresses.findIndex(
      address => address.addressId === addressId
    )

    addresses.splice(addressIndex, 1)

    const variables = {
      id: params.id,
      input: {
        addresses: addressArray.map(item => {
          return {
            ...item,
            checked: undefined,
          }
        }),
      },
    }

    setLoadingState(true)

    updateCostCenter({
      variables,
    })
      .then(() => {
        toastMessage(messages.toastUpdateSuccess)
        refetch()

        setLoadingState(false)

        setAddresses(addressArray)
        handleCloseModals()
      })
      .catch(error => {
        console.error(error)
        toastMessage(messages.toastUpdateFailure)
        setLoadingState(false)
      })
  }, [addresses])

  const handleTogglePaymentTerm = (id: string) => {
    let newTerms = paymentTerms
    const termOption = paymentTermOptions.find(term => term.id === id)

    if (!termOption) return
    const enabled = paymentTerms.find(term => term.id === id)

    if (enabled) {
      newTerms = paymentTerms.filter(term => term.id !== enabled.id)
    } else {
      newTerms.push(termOption)
    }

    // spread operator is used here so that react can see that the array has changed
    setPaymentTerms([...newTerms])
  }

  const options = (addressId: string) => [
    {
      label: formatMessage(messages.addressEdit),
      onClick: () => handleEditAddressModal(addressId),
    },
    {
      label: formatMessage(messages.addressDelete),
      onClick: () => handleDeleteAddressModal(addressId),
    },
  ]

  if (!isAuthenticated) {
    return (
      <Layout
        fullWidth
        pageHeader={
          <PageHeader
            title={formatMessage(messages.pageTitle)}
            linkLabel={formatMessage(messages.back)}
            onLinkClick={() => {
              history.push(`/organization`)
            }}
          />
        }
      >
        <PageBlock>
          <FormattedMessage id="store/b2b-organizations.not-authenticated" />
        </PageBlock>
      </Layout>
    )
  }

  if (!data) {
    return (
      <Layout
        fullWidth
        pageHeader={
          <PageHeader
            title={formatMessage(messages.pageTitle)}
            linkLabel={formatMessage(messages.back)}
            onLinkClick={() => {
              history.push(`/organization`)
            }}
          />
        }
      >
        <PageBlock>
          {loading ? (
            <Spinner />
          ) : (
            <FormattedMessage id="store/b2b-organizations.costCenter-details.empty-state" />
          )}
        </PageBlock>
      </Layout>
    )
  }

  const isSaveButtonDisabled = () => {
    const hasRequiredFields = costCenterName && addresses.length > 0
    const hasValidPaymentTerms =
      paymentTermOptions.length === 0 || paymentTerms.length > 0

    const hasValidPhoneNumber = !phoneNumber || validatePhoneNumber(phoneNumber)
    const hasPermission = permissionsState.includes(
      'create-cost-center-organization'
    )

    return (
      !hasRequiredFields ||
      !hasValidPaymentTerms ||
      !hasValidPhoneNumber ||
      !hasPermission
    )
  }

  return (
    <Layout
      fullWidth
      pageHeader={
        <PageHeader
          title={formatMessage(messages.pageTitle)}
          linkLabel={
            organizationData?.getOrganizationByIdStorefront?.name ??
            formatMessage(messages.back)
          }
          onLinkClick={() => {
            history.push(
              `/organization/${data.getCostCenterByIdStorefront.organization}`
            )
          }}
        >

            <Button
              variation="primary"
              isLoading={loadingState}
              disabled={isSaveButtonDisabled()}
              onClick={() => handleUpdateCostCenter()}
            >
              <FormattedMessage id="store/b2b-organizations.costCenter-details.button.save" />
            </Button>

          <Button
            variation="danger"
            onClick={() => handleDeleteCostCenterModal()}
            disabled={
              !permissionsState.includes('create-cost-center-organization') ||
              loadingState
            }
          >
            <FormattedMessage id="store/b2b-organizations.costCenter-details.button.delete" />
          </Button>
        </PageHeader>
      }
    >
      <PageBlock>
        <Input
          autocomplete="off"
          size="large"
          label={formatMessage(messages.costCenterName)}
          value={costCenterName}
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            setCostCenterName(e.target.value)
          }}
          readOnly={
            !permissionsState.includes('create-cost-center-organization')
          }
          required
        />

          <Input
            autocomplete="off"
            size="large"
            label={formatMessage(messages.phoneNumber)}
            value={phoneNumber}
            helpText={formatMessage(messages.phoneNumberHelp)}
            error={phoneNumber && !validatePhoneNumber(phoneNumber)}
            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
              setPhoneNumber(e.target.value)
            }}
            readOnly={
              !permissionsState.includes('create-cost-center-organization')
            }
          />

        <div className={`${handles.businessDocument} mt6`}>
          <Input
            autocomplete="off"
            size="large"
            label={formatMessage(messages.businessDocument)}
            value={businessDocument}
            helpText={formatMessage(messages.businessDocumentHelp)}
            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
              setBusinessDocument(e.target.value)
            }}
            readOnly={
              !permissionsState.includes('create-cost-center-organization') ||
              settings.businessReadOnly
            }
          />

        <div className={`${handles.stateRegistration} mt6`}>
          <Input
            autocomplete="off"
            size="large"
            label={formatMessage(messages.stateRegistration)}
            value={stateRegistration}
            helpText={formatMessage(messages.stateRegistrationHelp)}
            onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
              setStateRegistration(e.target.value)
            }}
            readOnly={
              !permissionsState.includes('create-cost-center-organization') ||
              settings.stateReadOnly
            }
          />

      </PageBlock>
      {paymentTermOptions.length > 0 && (
        <PageBlock
          title={formatMessage(messages.paymentTerms)}
          subtitle={formatMessage(messages.paymentTermsSubtitle)}
        >
          {paymentTermOptions.map((option, index) => {
            return (

                <Toggle
                  label={option.name}
                  semantic
                  checked={paymentTerms.some(term => term.id === option.id)}
                  onChange={() => handleTogglePaymentTerm(option.id)}
                  disabled={
                    !permissionsState.includes(
                      'create-cost-center-organization'
                    )
                  }
                />

            )
          })}
        </PageBlock>
      )}
      <PageBlock
        title={formatMessage(messages.addresses)}
        subtitle={formatMessage(messages.addressesSubtitle)}
      >

          {addresses.map((address: Address, index) => {
            return (

                <Card>

                      <AddressRules
                        shouldUseIOFetching
                        country={address.country}
                        useGeolocation={false}
                      >
                        <AddressSummary canEditData={false} address={address} />
                      </AddressRules>

                        <Toggle
                          label={formatMessage(messages.defaultAddress)}
                          semantic
                          onChange={() => handleCheckDefault(address)}
                          checked={address.checked}
                          disabled={
                            !permissionsState.includes(
                              'create-cost-center-organization'
                            ) || loadingState
                          }
                        />

                      {permissionsState.includes(
                        'create-cost-center-organization'
                      ) && (
                        <ActionMenu
                          buttonProps={{
                            variation: 'tertiary',
                            icon: <IconOptionsDots color="currentColor" />,
                          }}
                          options={options(address.addressId)}
                        />
                      )}

                </Card>

            )
          })}
          {permissionsState.includes('create-cost-center-organization') && (

              <Card>

                  <Button
                    variation="primary"
                    onClick={() => handleNewAddressModal()}
                  >
                    <FormattedMessage id="store/b2b-organizations.costCenter-details.address.new" />
                  </Button>

              </Card>

          )}

      </PageBlock>

      <NewAddressModal
        isOpen={newAddressModalState.isOpen}
        loading={loadingState}
        handleAddNewAddress={handleAddNewAddress}
        handleCloseModals={handleCloseModals}
      />

      <EditAddressModal
        isOpen={editAddressModalState.isOpen}
        loading={loadingState}
        address={editAddressModalState.editAddress}
        handleEditAddress={handleEditAddress}
        handleCloseModals={handleCloseModals}
      />

      <DeleteAddressModal
        isOpen={deleteAddressModalState.isOpen}
        loading={loadingState}
        handleDeleteAddress={handleDeleteAddress}
        handleCloseModals={handleCloseModals}
      />

      <DeleteCostCenterModal
        isOpen={deleteCostCenterModalState.isOpen}
        loading={loadingState}
        handleDeleteCostCenter={handleDeleteCostCenter}
        handleCloseModals={handleCloseModals}
      />
    </Layout>
  )
}

export default CostCenterDetails

```


[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 b2b-organizations/react/components/WarningTopbarInactiveOrganization.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { useQuery } from 'react-apollo'
import { FormattedMessage } from 'react-intl'

import GET_B2B_SETTINGS from '../graphql/getB2BSettings.graphql'
import storageFactory from '../utils/storage'

function getContrastColor(hexColor: string) {
  const color = hexColor.replace('#', '')

  const r = parseInt(color.substring(0, 2), 16)
  const g = parseInt(color.substring(2, 4), 16)
  const b = parseInt(color.substring(4, 6), 16)

  const brightness = (r * 299 + g * 587 + b * 114) / 1000

  return brightness > 128 ? '#000000' : '#FFFFFF'
}

const localStore = storageFactory(() => localStorage)
const isAuthenticated =
  JSON.parse(String(localStore.getItem('b2b-organizations_isAuthenticated'))) ??
  false

const WarningTopbarInactiveOrganization = () => {
  const { data, loading } = useQuery(GET_B2B_SETTINGS, {
    ssr: false,
  })

  if (
    loading ||
    data?.getB2BSettings?.uiSettings?.autoApprove ||
    !isAuthenticated
  ) {
    return <></>
  }

  return (
    <div
      className="pv3"
      style={{
        width: '100%',
        textAlign: 'center',
        fontWeight: 500,
        color: getContrastColor(
          data?.getB2BSettings?.uiSettings?.topBar?.hexColor ?? '#656896'
        ),
        background:
          data?.getB2BSettings?.uiSettings?.topBar?.hexColor ?? '#656896',
      }}
    >
      {data?.getB2BSettings?.uiSettings?.topBar?.name || (
        <FormattedMessage id="store/b2b-organizations.warning-topbar-inative-org.message" />
      )}
    </div>
  )
}

export default WarningTopbarInactiveOrganization

```


[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 b2b-organizations/react/components/OrganizationUsersTable.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { Fragment, useState, useEffect, useContext } from 'react'
import type { FunctionComponent, ChangeEvent } from 'react'
import { useIntl } from 'react-intl'
import { Table, ToastContext } from 'vtex.styleguide'
import { useToast } from '@vtex/admin-ui'
import { useQuery, useMutation } from 'react-apollo'

import { organizationMessages as storeMessages } from './utils/messages'
import { organizationMessages as adminMessages } from '../admin/utils/messages'
import NewUserModal from './NewUserModal'
import EditUserModal from './EditUserModal'
import RemoveUserModal from './RemoveUserModal'
import GET_USERS from '../graphql/getUsers.graphql'
import ADD_USER from '../graphql/addUser.graphql'
import UPDATE_USER from '../graphql/updateUser.graphql'
import REMOVE_USER from '../graphql/removeUser.graphql'
import GET_COST_CENTER from '../graphql/getCostCenterStorefront.graphql'
import IMPERSONATE_USER from '../graphql/impersonateB2BUser.graphql'
import { B2B_CHECKOUT_SESSION_KEY, ORGANIZATION_EDIT } from '../utils/constants'
import { sendImpersonateMetric } from '../utils/metrics/impersonate'
import { useOrgPermission } from '../hooks/useOrgPermission'

interface Props {
  organizationId: string
  permissions: string[]
  refetchCostCenters: boolean
  isAdmin?: boolean
  isSalesAdmin?: boolean
}

interface CellRendererProps {
  rowData: B2BUserSimple
  updateCellMeasurements: () => void
}

export interface B2BUserSimple extends UserDetails {
  costCenterName: string
  role: RoleSimple
  organizationName: string
}

interface RoleSimple {
  name: string
  slug: string
}

const compareUsers = (a: B2BUserSimple, b: B2BUserSimple) => {
  return a.email.toLowerCase() < b.email.toLowerCase() ? -1 : 1
}

const initialState = {
  search: '',
  page: 1,
  pageSize: 25,
  sortOrder: 'ASC',
  sortedBy: 'email',
  organizationId: null,
  organizations: ['active', 'on-hold', 'inactive'],
}

const ruleClickEnabled = ({
  isAdmin,
  canEditSales,
  slug,
  canEdit,
  isSalesAdmin,
}: {
  isAdmin: boolean
  canEditSales: boolean
  slug: string
  canEdit: boolean
  isSalesAdmin: boolean
}) => {
  return !(
    !isAdmin &&
    ((!canEdit && !canEditSales) ||
      (slug.match(/sales-admin/) && !isSalesAdmin && !canEdit) ||
      (!slug.match(/sales/) && canEditSales) ||
      (slug.match(/sales/) && canEdit))
  )
}

const OrganizationUsersTable: FunctionComponent<Props> = ({
  organizationId,
  permissions,
  isAdmin = false,
  isSalesAdmin = false,
}) => {
  const { formatMessage } = useIntl()
  const { showToast } = useContext(ToastContext)
  const toast = isAdmin ? useToast() : null
  const [addUserLoading, setAddUserLoading] = useState(false)
  const [updateUserLoading, setUpdateUserLoading] = useState(false)
  const [removeUserLoading, setRemoveUserLoading] = useState(false)
  const [addUserModalOpen, setAddUserModalOpen] = useState(false)
  const [editUserModalOpen, setEditUserModalOpen] = useState(false)
  const [editUserDetails, setEditUserDetails] = useState({} as UserDetails)
  const [removeUserModalOpen, setRemoveUserModalOpen] = useState(false)
  const [usersState, setUsersState] = useState([])

  const contextualToast = (
    message: string,
    type: 'critical' | 'positive' | 'info' | 'warning' | undefined
  ) => {
    if (isAdmin && toast) {
      toast({ variant: type, message })
    } else {
      showToast(message)
    }
  }

  const canManageOrganization = permissions.includes('manage-organization')

  const canEdit = isAdmin || permissions.includes('add-users-organization')

  const canEditSales =
    isAdmin ||
    permissions.includes('add-sales-users-all') ||
    permissions.includes('add-sales-users-current')

  const canImpersonateAll = permissions.includes('impersonate-users-all')
  const canImpersonateOrg = permissions.includes(
    'impersonate-users-organization'
  )

  const { data: canEditBuyerOrg } = useOrgPermission({
    resourceCode: ORGANIZATION_EDIT,
  })

  const { data, loading, refetch } = useQuery(GET_USERS, {
    variables: {
      ...initialState,
      organizationId: isSalesAdmin ? null : organizationId,
    },
    fetchPolicy: 'network-only',
    ssr: false,
    notifyOnNetworkStatusChange: true,
    skip: !organizationId,
  })

  const [variableState, setVariables] = useState(initialState)

  const { data: costCenterData } = useQuery(GET_COST_CENTER, {
    ssr: false,
    skip: isAdmin,
  })

  const canImpersonateCost = (rowData: B2BUserSimple) =>
    permissions.includes('impersonate-users-costcenter') &&
    rowData.costId === costCenterData?.getCostCenterByIdStorefront?.id

  const [addUser] = useMutation(ADD_USER)
  const [updateUser] = useMutation(UPDATE_USER)
  const [removeUser] = useMutation(REMOVE_USER)
  const [impersonateUser] = useMutation(IMPERSONATE_USER)

  useEffect(() => {
    if (!data?.getUsersPaginated?.data) return

    const users = data.getUsersPaginated.data.sort(compareUsers)

    setUsersState(users)
  }, [data])

  const handleAddUser = (user: UserInput) => {
    setAddUserLoading(true)
    addUser({ variables: user })
      .then(
        ({
          data: {
            addUser: { status },
          },
        }) => {
          setAddUserModalOpen(false)
          if (status === 'error') {
            contextualToast(
              formatMessage(
                isAdmin
                  ? adminMessages.toastAddUserFailure
                  : storeMessages.toastAddUserFailure
              ),
              'critical'
            )
          } else if (status === 'duplicated-organization') {
            contextualToast(
              formatMessage(
                isAdmin
                  ? adminMessages.toastUserDuplicatedOrganization
                  : storeMessages.toastUserDuplicatedOrganization
              ),
              'critical'
            )
          } else if (status === 'duplicated') {
            contextualToast(
              formatMessage(
                isAdmin
                  ? adminMessages.toastUserDuplicated
                  : storeMessages.toastUserDuplicated
              ),
              'critical'
            )
          } else {
            contextualToast(
              formatMessage(
                isAdmin
                  ? adminMessages.toastAddUserSuccess
                  : storeMessages.toastAddUserSuccess
              ),
              'positive'
            )
          }

          setTimeout(() => {
            setAddUserLoading(false)
            refetch()
          }, 2000)
        }
      )
      .catch(error => {
        console.error(error)
        contextualToast(
          formatMessage(
            isAdmin
              ? adminMessages.toastAddUserFailure
              : storeMessages.toastAddUserFailure
          ),
          'critical'
        )
        setAddUserLoading(false)
      })
  }

  const handleCloseAddUserModal = () => {
    setAddUserModalOpen(false)
  }

  const handleUpdateUser = (user: UserDetails) => {
    setUpdateUserLoading(true)
    updateUser({ variables: user })
      .then(
        ({
          data: {
            updateUser: { status },
          },
        }) => {
          setEditUserModalOpen(false)
          setEditUserDetails({} as UserDetails)
          if (status === 'error') {
            contextualToast(
              formatMessage(
                isAdmin
                  ? adminMessages.toastUpdateUserFailure
                  : storeMessages.toastUpdateUserFailure
              ),
              'critical'
            )
          } else {
            contextualToast(
              formatMessage(
                isAdmin
                  ? adminMessages.toastUpdateUserSuccess
                  : storeMessages.toastUpdateUserSuccess
              ),
              'positive'
            )
          }

          setTimeout(() => {
            setUpdateUserLoading(false)
            refetch()
          }, 2000)
        }
      )
      .catch(error => {
        console.error(error)
        contextualToast(
          formatMessage(
            isAdmin
              ? adminMessages.toastUpdateUserFailure
              : storeMessages.toastUpdateUserFailure
          ),
          'critical'
        )
        setUpdateUserLoading(false)
      })
  }

  const handleCloseUpdateUserModal = () => {
    setEditUserModalOpen(false)
    setEditUserDetails({} as UserDetails)
  }

  const handleShowRemoveUserModal = () => {
    setEditUserModalOpen(false)
    setRemoveUserModalOpen(true)
  }

  const handleCloseRemoveUserModal = () => {
    setRemoveUserModalOpen(false)
    setEditUserDetails({} as UserDetails)
  }

  const handleRemoveUser = () => {
    setRemoveUserLoading(true)
    removeUser({ variables: editUserDetails })
      .then(() => {
        setRemoveUserModalOpen(false)
        setEditUserDetails({} as UserDetails)
        contextualToast(
          formatMessage(
            isAdmin
              ? adminMessages.toastRemoveUserSuccess
              : storeMessages.toastRemoveUserSuccess
          ),
          'positive'
        )
        setRemoveUserLoading(false)
        refetch()
      })
      .catch(error => {
        console.error(error)
        contextualToast(
          formatMessage(
            isAdmin
              ? adminMessages.toastRemoveUserFailure
              : storeMessages.toastRemoveUserFailure
          ),
          'critical'
        )
        setRemoveUserLoading(false)
      })
  }

  const handleImpersonation = (rowData: B2BUserSimple) => {
    if (
      !canImpersonateAll &&
      !canImpersonateOrg &&
      !canImpersonateCost(rowData)
    ) {
      showToast(formatMessage(storeMessages.toastImpersonateForbidden))

      return
    }

    showToast(formatMessage(storeMessages.toastImpersonateStarting))

    if (sessionStorage.getItem(B2B_CHECKOUT_SESSION_KEY)) {
      sessionStorage.removeItem(B2B_CHECKOUT_SESSION_KEY)
    }

    impersonateUser({
      variables: { id: rowData.id },
    })
      .then(result => {
        if (result?.data?.impersonateB2BUser?.status === 'error') {
          console.error(
            'Impersonation error:',
            result.data.impersonateB2BUser.message
          )
          if (
            result.data.impersonateB2BUser.message === 'userId not found in CL'
          ) {
            showToast(formatMessage(storeMessages.toastImpersonateIdMissing))
          } else {
            showToast(formatMessage(storeMessages.toastImpersonateFailure))
          }
        } else {
          const metricParams = {
            costCenterData,
            target: rowData,
          }

          sendImpersonateMetric(metricParams)
          window.location.reload()
        }
      })
      .catch(error => {
        console.error(error)
        showToast(formatMessage(storeMessages.toastImpersonateFailure))
      })
  }

  const getSchema = () => {
    const isEnabled = ({ role: { slug } }: { role: { slug: string } }) => {
      if (!canManageOrganization) {
        return ruleClickEnabled({
          isAdmin,
          canEditSales,
          slug,
          canEdit,
          isSalesAdmin,
        })
      }

      return canManageOrganization
    }

    const properties = {
      email: {
        title: formatMessage(
          isAdmin ? adminMessages.columnEmail : storeMessages.columnEmail
        ),
        cellRenderer: ({ rowData: { email, role } }: CellRendererProps) => (
          <span className={!isEnabled({ role }) ? 'c-disabled' : ''}>
            {email}
          </span>
        ),
      },
      roleId: {
        title: formatMessage(
          isAdmin ? adminMessages.columnRole : storeMessages.columnRole
        ),
        cellRenderer: ({ rowData: { role } }: CellRendererProps) => (
          <span className={!isEnabled({ role }) ? 'c-disabled' : ''}>
            {role?.name ?? ''}
          </span>
        ),
      },
      organizationName: {
        title: formatMessage(storeMessages.columnOrganizationName),
        cellRenderer: ({
          rowData: { organizationName, role },
        }: CellRendererProps) => (
          <span className={!isEnabled({ role }) ? 'c-disabled' : ''}>
            {organizationName}
          </span>
        ),
      },
      costCenterName: {
        title: formatMessage(
          isAdmin
            ? adminMessages.columnCostCenter
            : storeMessages.columnCostCenter
        ),
        cellRenderer: ({
          rowData: { costCenterName, role },
        }: CellRendererProps) => (
          <span className={!isEnabled({ role }) ? 'c-disabled' : ''}>
            {costCenterName}
          </span>
        ),
      },
    }

    if (!isSalesAdmin) {
      delete properties.organizationName
    }

    return { properties }
  }

  const handleNextClick = () => {
    const newPage: number = (variableState.page as number) + 1

    setVariables({
      ...variableState,
      page: newPage,
    })

    refetch({
      ...variableState,
      page: newPage,
      organizationId: isSalesAdmin ? null : organizationId,
    })
  }

  const handlePrevClick = () => {
    if (variableState.page === 1) return

    const newPage: number = (variableState.page as number) - 1

    setVariables({
      ...variableState,
      page: newPage,
    })

    refetch({
      ...variableState,
      page: newPage,
      organizationId: isSalesAdmin ? null : organizationId,
    })
  }

  const handleRowsChange = (e: ChangeEvent<HTMLInputElement>) => {
    const {
      target: { value },
    } = e

    setVariables({
      ...variableState,
      page: 1,
      pageSize: +value,
    })

    refetch({
      ...variableState,
      page: 1,
      pageSize: +value,
      organizationId: isSalesAdmin ? null : organizationId,
    })
  }

  const handleInputSearchChange = (e: React.FormEvent<HTMLInputElement>) => {
    const {
      currentTarget: { value },
    } = e

    setVariables({
      ...variableState,
      search: value,
    })
  }

  const handleInputSearchClear = () => {
    setVariables({
      ...variableState,
      search: '',
    })

    refetch({
      ...variableState,
      search: '',
      page: 1,
      organizationId: isSalesAdmin ? null : organizationId,
    })
  }

  const handleInputSearchSubmit = () => {
    refetch({
      ...variableState,
      organizationId: isSalesAdmin ? null : organizationId,
      page: 1,
    })
  }

  const handleRowClick = ({ rowData }: CellRendererProps) => {
    const canClick =
      canManageOrganization ||
      ruleClickEnabled({
        isAdmin,
        canEditSales,
        slug: rowData.role?.slug ?? '',
        canEdit,
        isSalesAdmin,
      })

    if (!canClick || !canEditBuyerOrg) return

    setEditUserDetails({
      id: rowData.id,
      roleId: rowData.roleId,
      userId: rowData.userId,
      clId: rowData.clId,
      orgId: rowData.orgId,
      costId: rowData.costId,
      name: rowData.name,
      email: rowData.email,
      canImpersonate: rowData.canImpersonate,
    })
    setEditUserModalOpen(true)
  }

  const handleSort = ({
    sortOrder: _sortOrder,
    sortedBy: _sortedBy,
  }: {
    sortOrder: string
    sortedBy: string
  }) => {
    setVariables({
      ...variableState,
      sortOrder: _sortOrder,
      sortedBy: _sortedBy,
    })
    refetch({
      ...variableState,
      page: 1,
      organizationId: isSalesAdmin ? null : organizationId,
      sortOrder: _sortOrder,
      sortedBy: _sortedBy,
    })
  }

  const lineActions = isAdmin
    ? null
    : [
        {
          label: () => formatMessage(storeMessages.impersonate),
          onClick: ({ rowData }: CellRendererProps) =>
            handleImpersonation(rowData),
        },
      ]

  const { page, pageSize, search, sortedBy, sortOrder } = variableState
  const { total } = data?.getUsersPaginated?.pagination ?? 0

  const isAddUserButtonDisabled = () => {
    const hasEditPermissions = canEdit || canEditSales
    const hasOrganizationEditPermission = canEditBuyerOrg

    return !hasEditPermissions || !hasOrganizationEditPermission
  }

  const toolbar = {
    inputSearch: {
      value: search,
      placeholder: formatMessage(
        isAdmin
          ? adminMessages.searchPlaceholder
          : storeMessages.searchPlaceholder
      ),
      onChange: handleInputSearchChange,
      onClear: handleInputSearchClear,
      onSubmit: handleInputSearchSubmit,
    },
    newLine: {
      label: formatMessage(isAdmin ? adminMessages.new : storeMessages.new),
      handleCallback: () => setAddUserModalOpen(true),
      disabled: isAddUserButtonDisabled(),
    },
  }

  const pagination = {
    onNextClick: handleNextClick,
    onPrevClick: handlePrevClick,
    onRowsChange: handleRowsChange,
    currentItemFrom: (page - 1) * pageSize + 1,
    currentItemTo: total < page * pageSize ? total : page * pageSize,
    textShowRows: formatMessage(
      isAdmin ? adminMessages.showRows : storeMessages.showRows
    ),
    textOf: formatMessage(isAdmin ? adminMessages.of : storeMessages.of),
    totalItems: total ?? 0,
    rowsOptions: [25, 50, 100],
  }

  return (
    <Fragment>
      <Table
        fullWidth
        schema={getSchema()}
        items={usersState}
        loading={loading || addUserLoading || updateUserLoading}
        emptyStateLabel={formatMessage(
          isAdmin ? adminMessages.usersEmptyState : storeMessages.emptyState
        )}
        lineActions={lineActions}
        sort={{
          sortedBy,
          sortOrder,
        }}
        pagination={pagination}
        toolbar={toolbar}
        onSort={handleSort}
        onRowClick={handleRowClick}
      />
      {addUserModalOpen && (
        <NewUserModal
          handleAddNewUser={handleAddUser}
          handleCloseModal={handleCloseAddUserModal}
          loading={addUserLoading}
          isOpen={addUserModalOpen}
          organizationId={organizationId}
          isAdmin={isAdmin}
          canEdit={canEdit}
          canEditSales={canEditSales}
          isSalesAdmin={isSalesAdmin}
          canManageOrg={canManageOrganization}
        />
      )}
      {editUserModalOpen && (
        <EditUserModal
          handleUpdateUser={handleUpdateUser}
          handleCloseModal={handleCloseUpdateUserModal}
          handleRemoveUser={handleShowRemoveUserModal}
          loading={updateUserLoading}
          isOpen={editUserModalOpen}
          organizationId={editUserDetails.orgId}
          user={editUserDetails}
          isAdmin={isAdmin}
          canEdit={canEdit}
          canEditSales={canEditSales}
          isSalesAdmin={isSalesAdmin}
          canManageOrg={canManageOrganization}
        />
      )}
      <RemoveUserModal
        handleRemoveUser={handleRemoveUser}
        handleCloseModal={handleCloseRemoveUserModal}
        loading={removeUserLoading}
        isOpen={removeUserModalOpen}
        user={editUserDetails}
        isAdmin={isAdmin}
      />
    </Fragment>
  )
}

export default OrganizationUsersTable

```


[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 b2b-organizations/react/components/EditAddressModal.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useEffect, useState } from 'react'
import type { FunctionComponent } from 'react'
import { Modal, Button } from 'vtex.styleguide'
import { useIntl } from 'react-intl'
import { useQuery } from 'react-apollo'
import { useRuntime } from 'vtex.render-runtime'
import {
  AddressRules,
  AddressForm,
  AddressContainer,
  CountrySelector,
  PostalCodeGetter,
} from 'vtex.address-form'
import { StyleguideInput } from 'vtex.address-form/inputs'
import { addValidation } from 'vtex.address-form/helpers'
import 'vtex.country-codes/locales'

import { costCenterMessages as messages } from './utils/messages'
import { getEmptyAddress, isValidAddress } from '../utils/addresses'
import GET_LOGISTICS from '../graphql/getLogistics.graphql'

interface Props {
  loading: boolean
  isOpen: boolean
  address: Address | null
  handleEditAddress: (address: AddressFormFields) => void
  handleCloseModals: () => void
}

const EditAddressModal: FunctionComponent<Props> = ({
  loading,
  isOpen,
  address,
  handleEditAddress,
  handleCloseModals,
}) => {
  const {
    culture: { country },
  } = useRuntime()

  const { formatMessage } = useIntl()

  const [editAddressState, setEditAddressState] = useState(() =>
    addValidation(getEmptyAddress(country))
  )

  const { data: logisticsData } = useQuery(GET_LOGISTICS, { ssr: false })

  const translateCountries = () => {
    const { shipsTo = [] } = logisticsData?.logistics ?? {}

    return shipsTo.map((code: string) => ({
      label: formatMessage({ id: `country.${code}` }),
      value: code,
    }))
  }

  const handleEditAddressChange = (changedAddress: AddressFormFields) => {
    const curAddress = editAddressState

    const newAddress = { ...curAddress, ...changedAddress }

    setEditAddressState(newAddress)
  }

  useEffect(() => {
    if (!address) return

    setEditAddressState(addValidation(address))
  }, [address])

  if (!address) return null

  return (
    <Modal
      centered
      bottomBar={
        <div className="nowrap">
          <span className="mr4">
            <Button
              variation="tertiary"
              onClick={() => handleCloseModals()}
              disabled={loading}
            >
              {formatMessage(messages.cancel)}
            </Button>
          </span>
          <span>
            <Button
              variation="primary"
              onClick={() => handleEditAddress(editAddressState)}
              isLoading={loading}
              disabled={!isValidAddress(editAddressState)}
            >
              {formatMessage(messages.update)}
            </Button>
          </span>
        </div>
      }
      isOpen={isOpen}
      onClose={() => handleCloseModals()}
      closeOnOverlayClick={false}
    >
      <AddressRules
        country={editAddressState?.country?.value}
        shouldUseIOFetching
        useGeolocation={false}
      >
        <AddressContainer
          address={editAddressState}
          Input={StyleguideInput}
          onChangeAddress={handleEditAddressChange}
          autoCompletePostalCode
        >
          <CountrySelector shipsTo={translateCountries()} />

          <PostalCodeGetter />

          <AddressForm
            Input={StyleguideInput}
            omitAutoCompletedFields={false}
            omitPostalCodeFields
          />
        </AddressContainer>
      </AddressRules>
    </Modal>
  )
}

export default EditAddressModal

```


[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 b2b-organizations/react/components/NewAddressModal.tsx from the b2b-organizations VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { FunctionComponent } from 'react'
import React, { useEffect, useState } from 'react'
import { Button, Modal, Toggle } from 'vtex.styleguide'
import { useIntl } from 'react-intl'
import { useQuery } from 'react-apollo'
import { useRuntime } from 'vtex.render-runtime'
import {
  AddressContainer,
  AddressForm,
  AddressRules,
  CountrySelector,
  PostalCodeGetter,
} from 'vtex.address-form'
import { StyleguideInput } from 'vtex.address-form/inputs'
import { addValidation } from 'vtex.address-form/helpers'
import 'vtex.country-codes/locales'

import { costCenterMessages as messages } from './utils/messages'
import { getEmptyAddress, isValidAddress } from '../utils/addresses'
import GET_LOGISTICS from '../graphql/getLogistics.graphql'

interface Props {
  loading: boolean
  isOpen: boolean
  handleAddNewAddress: (address: AddressFormFields) => void
  handleCloseModals: () => void
}

const NewAddressModal: FunctionComponent<Props> = ({
  loading,
  isOpen,
  handleAddNewAddress,
  handleCloseModals,
}) => {
  const {
    culture: { country },
  } = useRuntime()

  const { formatMessage } = useIntl()

  const [newAddressState, setNewAddressState] = useState(() => {
    return {
      ...addValidation(getEmptyAddress(country)),
      checked: false,
    }
  })

  const { data: logisticsData } = useQuery(GET_LOGISTICS, { ssr: false })

  const translateCountries = () => {
    const { shipsTo = [] } = logisticsData?.logistics ?? {}

    return shipsTo.map((code: string) => ({
      label: formatMessage({ id: `country.${code}` }),
      value: code,
    }))
  }

  useEffect(() => {
    if (isOpen) {
      setNewAddressState({
        ...addValidation(getEmptyAddress(country)),
        checked: false,
      })
    }
  }, [isOpen])

  const handleNewAddressChange = (changedAddress: AddressFormFields) => {
    const newAddress = {
      ...newAddressState,
      ...changedAddress,
    }

    setNewAddressState(newAddress)
  }

  return (
    <Modal
      centered
      bottomBar={
        <div className="nowrap">
          <span className="mr4">
            <Button
              variation="tertiary"
              onClick={() => handleCloseModals()}
              disabled={loading}
            >
              {formatMessage(messages.cancel)}
            </Button>
          </span>
          <span>
            <Button
              variation="primary"
              onClick={() => handleAddNewAddress(newAddressState)}
              isLoading={loading}
              disabled={!isValidAddress(newAddressState)}
            >
              {formatMessage(messages.add)}
            </Button>
          </span>
        </div>
      }
      isOpen={isOpen}
      onClose={() => handleCloseModals()}
      closeOnOverlayClick={false}
    >
      <AddressRules
        country={newAddressState?.country?.value}
        shouldUseIOFetching
        useGeolocation={false}
      >
        <AddressContainer
          address={newAddressState}
          Input={StyleguideInput}
          onChangeAddress={handleNewAddressChange}
          autoCompletePostalCode
        >
          <CountrySelector shipsTo={translateCountries()} />

          <PostalCodeGetter />

          <AddressForm
            Input={StyleguideInput}
            omitAutoCompletedFields={false}
            omitPostalCodeFields
          />
          <div className="mb6">
            <Toggle
              checked={newAddressState.checked}
              onChange={() =>
                setNewAddressState((prevState: AddressFormFields) => ({
                  ...newAddressState,
                  checked: !prevState.checked,
                }))
              }
              label={formatMessage(messages.defaultAddress)}
            />
          </div>
        </AddressContainer>
      </AddressRules>
    </Modal>
  )
}

export default NewAddressModal

```