[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-graphql/node/resolvers/Queries/Organizations.ts from the b2b-organizations-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import {
  ORGANIZATION_DATA_ENTITY,
  ORGANIZATION_FIELDS,
  ORGANIZATION_REQUEST_DATA_ENTITY,
  ORGANIZATION_REQUEST_FIELDS,
  ORGANIZATION_REQUEST_SCHEMA_VERSION,
  ORGANIZATION_SCHEMA_VERSION,
} from '../../mdSchema'
import type {
  GetOrganizationsByEmailWithStatus,
  Organization,
} from '../../typings'
import GraphQLError, { getErrorMessage } from '../../utils/GraphQLError'
import checkConfig from '../config'
import { organizationStatus } from '../fieldResolvers'

const getWhereByStatus = ({ status }: { status: string[] }) => {
  const whereArray = []

  if (status?.length) {
    const statusArray = [] as string[]

    status.forEach((stat) => {
      statusArray.push(`status=${stat}`)
    })
    const statuses = `(${statusArray.join(' OR ')})`

    whereArray.push(statuses)
  }

  return whereArray
}

const Organizations = {
  checkOrganizationIsActive: async (
    _: void,
    params: { id: string } | null,
    ctx: Context
  ) => {
    const {
      clients: { session },
      vtex: { logger, sessionToken, adminUserAuthToken },
    } = ctx

    const sessionData = await session
      .getSession(sessionToken as string, ['*'])
      .then((currentSession: any) => {
        return currentSession.sessionData
      })
      .catch((error: any) => {
        logger.warn({
          error,
          message: 'checkOrganizationIsActive-error',
        })

        return null
      })

    if (!sessionData) {
      throw new Error('No session data for this current user')
    }

    let orgId =
      sessionData?.namespaces?.['storefront-permissions']?.organization?.value

    if (params?.id && adminUserAuthToken) {
      orgId = params?.id
    }

    const organization = (await Organizations.getOrganizationById(
      _,
      { id: orgId },
      ctx
    )) as { status: string; permissions?: { createQuote: boolean } }

    if (!organization) {
      throw new Error('Organization not found')
    }

    return organization?.status === 'active'
  },

  getOrganizationById: async (
    _: void,
    { id }: { id: string },
    ctx: Context
  ) => {
    const {
      clients: { masterdata },
      vtex: { logger },
    } = ctx

    // create schema if it doesn't exist
    await checkConfig(ctx)

    try {
      const org: Organization = await masterdata.getDocument({
        dataEntity: ORGANIZATION_DATA_ENTITY,
        fields: ORGANIZATION_FIELDS,
        id,
      })

      return {
        ...org,
        // the previous data registered doesn't have this propertty on masterdata
        // so we need to add it to the response
        permissions: org.permissions ?? { createQuote: true },
      }
    } catch (error) {
      logger.error({ error, message: 'getOrganizationById-error' })
      throw new GraphQLError(getErrorMessage(error))
    }
  },

  getOrganizations: async (
    _: void,
    {
      status,
      search,
      page,
      pageSize,
      sortOrder,
      sortedBy,
    }: {
      status: string[]
      search: string
      page: number
      pageSize: number
      sortOrder: string
      sortedBy: string
    },
    ctx: Context
  ) => {
    const {
      clients: { masterdata },
      vtex: { logger },
    } = ctx

    // create schema if it doesn't exist
    await checkConfig(ctx)

    const whereArray = getWhereByStatus({ status })

    if (search) {
      whereArray.push(`name="*${search}*"`)
    }

    const where = whereArray.join(' AND ')

    try {
      const organizationsDB =
        (await masterdata.searchDocumentsWithPaginationInfo({
          dataEntity: ORGANIZATION_DATA_ENTITY,
          fields: ORGANIZATION_FIELDS,
          pagination: { page, pageSize },
          schema: ORGANIZATION_SCHEMA_VERSION,
          sort: `${sortedBy} ${sortOrder}`,
          ...(where && { where }),
        })) as {
          data: Organization[]
          pagination: { total: number; page: number; pageSize: number }
        }

      const mappedOrganizations = organizationsDB.data.map((org) => {
        return {
          ...org,
          // the previous data registered doesn't have this propertty on masterdata
          // so we need to add it to the response
          permissions: org.permissions ?? { createQuote: true },
        }
      })

      return {
        data: mappedOrganizations,
        pagination: organizationsDB.pagination,
      }
    } catch (error) {
      logger.error({
        error,
        message: 'getOrganizations-error',
      })
      throw new GraphQLError(getErrorMessage(error))
    }
  },

  getOrganizationsByEmail: async (
    _: void,
    { email }: { email: string },
    ctx: Context
  ) => {
    const {
      clients: { storefrontPermissions, session },
      vtex: { logger, sessionToken, adminUserAuthToken },
    } = ctx

    const organizationFilters: string[] = []
    let fromSession = false

    const sessionData = await session
      .getSession(sessionToken as string, ['*'])
      .then((currentSession: any) => {
        return currentSession.sessionData
      })
      .catch((error: any) => {
        logger.warn({
          error,
          message: 'getOrganizationsByEmail-session-error',
        })

        return null
      })

    let checkUserPermission = null

    if (sessionData?.namespaces) {
      const checkUserPermissionResult = await storefrontPermissions
        .checkUserPermission('vtex.b2b-organizations@3.x')
        .catch((error: any) => {
          logger.error({
            error,
            message: 'checkUserPermission-error',
          })

          return {
            data: {
              checkUserPermission: null,
            },
          }
        })

      checkUserPermission = checkUserPermissionResult?.data?.checkUserPermission
    }

    if (
      (!adminUserAuthToken &&
        !checkUserPermission?.permissions.includes('add-sales-users-all')) ||
      !(email?.length > 0)
    ) {
      if (checkUserPermission?.permissions.includes('add-users-organization')) {
        const orgId =
          sessionData?.namespaces?.['storefront-permissions']?.organization
            ?.value

        if (!orgId) {
          throw new Error('No permission for getting the organizations')
        }

        organizationFilters.push(orgId)
      }

      if (!(email?.length > 0)) {
        email = sessionData?.namespaces?.profile?.email?.value
        fromSession = true
      }
    }

    const organizations = (
      await storefrontPermissions.getOrganizationsByEmail(email)
    ).data?.getOrganizationsByEmail?.filter(({ orgId }: { orgId: string }) => {
      return (
        fromSession ||
        (organizationFilters.length > 0
          ? organizationFilters.find((id: string) => orgId === id)
          : true)
      )
    })

    try {
      return organizations
    } catch (error) {
      logger.error({
        error,
        message: 'getOrganizationsByEmail-error',
      })
      throw new GraphQLError(getErrorMessage(error))
    }
  },

  getActiveOrganizationsByEmail: async (
    _: void,
    { email }: { email: string },
    ctx: Context
  ) => {
    const {
      vtex: { logger },
    } = ctx

    const organizations = await Organizations.getOrganizationsByEmail(
      _,
      { email },
      ctx
    )

    const organizationsWithStatus: GetOrganizationsByEmailWithStatus[] =
      await Promise.all(
        organizations.map(async (organization: { orgId: string }) => {
          const status = await organizationStatus(
            { orgId: organization.orgId },
            _,
            ctx
          )

          return { ...organization, status }
        })
      )

    const activeOrganizations = organizationsWithStatus.filter(
      (organization) => organization.status === 'active'
    )

    try {
      return activeOrganizations
    } catch (error) {
      logger.error({
        error,
        message: 'getActiveOrganizationsByEmail-error',
      })
      throw new GraphQLError(getErrorMessage(error))
    }
  },

  getOrganizationsPaginatedByEmail: async (
    _: void,
    {
      email,
      page = 1,
      pageSize = 25,
    }: {
      email?: string
      page: number
      pageSize: number
    },
    { clients: { storefrontPermissions }, vtex: { logger } }: any
  ) => {
    try {
      const {
        data: { getOrganizationsPaginatedByEmail },
      } = await storefrontPermissions.getOrganizationsPaginatedByEmail(
        email,
        page,
        pageSize
      )

      return getOrganizationsPaginatedByEmail
    } catch (error) {
      logger.error({
        error,
        message: 'getOrganizationsPaginatedByEmail-error',
      })
      throw new GraphQLError(getErrorMessage(error))
    }
  },

  getOrganizationByIdStorefront: async (
    _: void,
    { id }: { id: string },
    ctx: Context
  ) => {
    const {
      clients: { masterdata },
      vtex: { sessionData, logger },
    } = ctx as any

    // create schema if it doesn't exist
    await checkConfig(ctx)

    if (!sessionData?.namespaces['storefront-permissions']) {
      throw new GraphQLError('organization-data-not-found')
    }

    const {
      organization: { value: userOrganizationId },
    } = sessionData.namespaces['storefront-permissions'] ?? {
      organization: {
        value: null,
      },
    }

    if (!id) {
      // get user's organization from session
      id = userOrganizationId
    }

    if (id !== userOrganizationId) {
      throw new GraphQLError('operation-not-permitted')
    }

    const organization: Organization = await masterdata.getDocument({
      dataEntity: ORGANIZATION_DATA_ENTITY,
      fields: ORGANIZATION_FIELDS,
      id,
    })

    if (organization?.status !== 'active') {
      throw new Error('This organization is not active')
    }

    try {
      return {
        ...organization,
        permissions: organization.permissions ?? { createQuote: true },
      }
    } catch (error) {
      logger.error({
        error,
        message: 'getOrganizationByIdStorefront-error',
      })
      throw new GraphQLError(getErrorMessage(error))
    }
  },

  getOrganizationRequestById: async (
    _: void,
    { id }: { id: string },
    ctx: Context
  ) => {
    const {
      clients: { masterdata },
      vtex: { logger },
    } = ctx

    // create schema if it doesn't exist
    await checkConfig(ctx)

    try {
      return await masterdata.getDocument({
        dataEntity: ORGANIZATION_REQUEST_DATA_ENTITY,
        fields: ORGANIZATION_REQUEST_FIELDS,
        id,
      })
    } catch (error) {
      logger.error({
        error,
        message: 'getOrganizationRequestById-error',
      })
      throw new GraphQLError(getErrorMessage(error))
    }
  },

  getOrganizationRequests: async (
    _: void,
    {
      status,
      search,
      page,
      pageSize,
      sortOrder,
      sortedBy,
    }: {
      status: string[]
      search: string
      page: number
      pageSize: number
      sortOrder: string
      sortedBy: string
    },
    ctx: Context
  ) => {
    const {
      clients: { masterdata },
      vtex: { logger },
    } = ctx

    // create schema if it doesn't exist
    await checkConfig(ctx)
    const whereArray = getWhereByStatus({ status })

    if (search) {
      if (search.match(/[a-z\d]+@[a-z]+\.[a-z]{2,3}/gm)) {
        whereArray.push(`b2bCustomerAdmin.email=${search}`)
      } else {
        whereArray.push(`name="*${search}*"`)
      }
    }

    const where = whereArray.join(' AND ')

    try {
      return await masterdata.searchDocumentsWithPaginationInfo({
        dataEntity: ORGANIZATION_REQUEST_DATA_ENTITY,
        fields: ORGANIZATION_REQUEST_FIELDS,
        pagination: { page, pageSize },
        schema: ORGANIZATION_REQUEST_SCHEMA_VERSION,
        sort: `${sortedBy} ${sortOrder}`,
        ...(where && { where }),
      })
    } catch (error) {
      logger.error({
        error,
        message: 'getOrganizationRequests-error',
      })
      throw new GraphQLError(getErrorMessage(error))
    }
  },
}

export default Organizations

```


[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-graphql/node/resolvers/Queries/CostCenters.ts from the b2b-organizations-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import {
  COST_CENTER_DATA_ENTITY,
  COST_CENTER_FIELDS,
  COST_CENTER_SCHEMA_VERSION,
} from '../../mdSchema'
import type { Address, CostCenter } from '../../typings'
import GraphQLError, { getErrorMessage } from '../../utils/GraphQLError'
import checkConfig from '../config'
import Organizations from './Organizations'

const getCostCenters = async ({
  id,
  masterdata,
  page,
  pageSize,
  search,
  sortOrder,
  sortedBy,
}: any) => {
  const searchClause = search ? ` AND name="*${search}*"` : ''
  const where = `organization=${id}${searchClause}`

  try {
    return await masterdata.searchDocumentsWithPaginationInfo({
      dataEntity: COST_CENTER_DATA_ENTITY,
      fields: COST_CENTER_FIELDS,
      pagination: { page, pageSize },
      schema: COST_CENTER_SCHEMA_VERSION,
      sort: `${sortedBy} ${sortOrder}`,
      ...(where && { where }),
    })
  } catch (error) {
    throw new GraphQLError(getErrorMessage(error))
  }
}

const hashCode = function hash(arg: null | string | number | number[]) {
  const str = arg === null ? '' : arg.toString()

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

  return str.split('').reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0)
}

const setGUID = (address: Address) => {
  return (
    hashCode(address.street) +
    hashCode(address.complement) +
    hashCode(address.city) +
    hashCode(address.state)
  ).toString()
}

const addMissingAddressIds = async (costCenter: CostCenter, ctx: Context) => {
  const {
    clients: { masterdata },
  } = ctx

  let changed = false
  const { addresses } = costCenter

  for (const [index, address] of addresses.entries()) {
    if (!address.addressId) {
      addresses[index].addressId = setGUID(address)
      changed = true
    }
  }

  if (changed) {
    await masterdata.createOrUpdatePartialDocument({
      dataEntity: COST_CENTER_DATA_ENTITY,
      fields: { addresses },
      id: costCenter.id,
    })
  }

  return addresses
}

const costCenters = {
  getCostCenterById: async (_: void, { id }: { id: string }, ctx: Context) => {
    const {
      clients: { masterdata },
      vtex: { logger },
    } = ctx

    // create schema if it doesn't exist
    await checkConfig(ctx)

    try {
      const result: CostCenter = await masterdata.getDocument({
        dataEntity: COST_CENTER_DATA_ENTITY,
        fields: COST_CENTER_FIELDS,
        id,
      })

      /* MasterData client returns empty string when it doesn't find the document */
      if ((result as unknown as string) !== '') {
        result.addresses = await addMissingAddressIds(result, ctx)
      }

      return result
    } catch (error) {
      logger.error({ error, message: 'getCostCenterById-error' })
      throw new GraphQLError(getErrorMessage(error))
    }
  },

  getCostCenterByIdStorefront: async (
    _: void,
    { id }: { id: string },
    ctx: Context
  ) => {
    const {
      clients: { masterdata },
      vtex: { logger },
      vtex,
    } = ctx

    // create schema if it doesn't exist
    await checkConfig(ctx)

    if (!(await Organizations.checkOrganizationIsActive(_, null, ctx))) {
      throw new Error('This organization is not active')
    }

    const { sessionData } = vtex as any

    if (!sessionData?.namespaces['storefront-permissions']) {
      throw new GraphQLError('organization-data-not-found')
    }

    const {
      organization: { value: userOrganizationId },
      costcenter: { value: userCostCenterId },
    } = sessionData.namespaces['storefront-permissions'] ?? {
      organization: {
        value: null,
      },
      costcenter: {
        value: null,
      },
    }

    if (!id) {
      // get user's organization from session
      id = userCostCenterId
    }

    try {
      const costCenter: CostCenter = await masterdata.getDocument({
        dataEntity: COST_CENTER_DATA_ENTITY,
        fields: COST_CENTER_FIELDS,
        id,
      })

      if (costCenter.organization !== userOrganizationId) {
        const error = () => {
          throw new GraphQLError('operation-not-permitted')
        }

        error()
      }

      costCenter.addresses = await addMissingAddressIds(costCenter, ctx)

      return costCenter
    } catch (error) {
      logger.error({ error, message: 'getCostCenterByIdStorefront-error' })
      throw new GraphQLError(getErrorMessage(error))
    }
  },

  getPaymentTerms: async (_: void, __: void, ctx: Context) => {
    const {
      clients: { payments },
      vtex: { logger },
    } = ctx

    try {
      return await payments.getPaymentTerms()
    } catch (error) {
      logger.error({ error, message: 'getPaymentTerms-error' })
      throw new GraphQLError(getErrorMessage(error))
    }
  },

  getCostCenters: async (
    _: void,
    {
      search,
      page,
      pageSize,
      sortOrder,
      sortedBy,
    }: {
      search: string
      page: number
      pageSize: number
      sortOrder: string
      sortedBy: string
    },
    ctx: Context
  ) => {
    const {
      clients: { masterdata },
      vtex: { logger },
    } = ctx

    // create schema if it doesn't exist
    await checkConfig(ctx)

    let where = ''

    if (search) {
      where = `name="*${search}*" OR businessDocument="*${search}*"`
    }

    try {
      return await masterdata.searchDocumentsWithPaginationInfo({
        dataEntity: COST_CENTER_DATA_ENTITY,
        fields: COST_CENTER_FIELDS,
        pagination: { page, pageSize },
        schema: COST_CENTER_SCHEMA_VERSION,
        sort: `${sortedBy} ${sortOrder}`,
        ...(where && { where }),
      })
    } catch (error) {
      logger.error({
        error,
        message: 'getCostCenters-error',
      })
      throw new GraphQLError(getErrorMessage(error))
    }
  },

  getCostCentersByOrganizationId: async (
    _: void,
    {
      id,
      search,
      page,
      pageSize,
      sortOrder,
      sortedBy,
    }: {
      id: string
      search: string
      page: number
      pageSize: number
      sortOrder: string
      sortedBy: string
    },
    ctx: Context
  ) => {
    const {
      clients: { masterdata },
      vtex: { logger },
    } = ctx

    // create schema if it doesn't exist
    await checkConfig(ctx)

    try {
      return await getCostCenters({
        id,
        masterdata,
        page,
        pageSize,
        search,
        sortOrder,
        sortedBy,
      })
    } catch (error) {
      logger.error({
        error,
        message: 'getCostCentersByOrganizationId-error',
      })
      throw error
    }
  },

  getCostCentersByOrganizationIdStorefront: async (
    _: void,
    {
      id,
      search,
      page,
      pageSize,
      sortOrder,
      sortedBy,
    }: {
      id: string
      search: string
      page: number
      pageSize: number
      sortOrder: string
      sortedBy: string
    },
    ctx: Context
  ) => {
    const {
      clients: { masterdata, storefrontPermissions },
      vtex: { logger, sessionData },
    } = ctx as any

    // create schema if it doesn't exist
    await checkConfig(ctx)

    if (!(await Organizations.checkOrganizationIsActive(_, null, ctx))) {
      throw new Error('This organization is not active')
    }

    let checkUserPermission = null

    if (sessionData?.namespaces) {
      const checkUserPermissionResult = await storefrontPermissions
        .checkUserPermission('vtex.b2b-organizations@3.x')
        .catch((error: any) => {
          logger.error({
            error,
            message: 'checkUserPermission-error',
          })

          return {
            data: {
              checkUserPermission: null,
            },
          }
        })

      checkUserPermission = checkUserPermissionResult?.data?.checkUserPermission
    }

    const isSalesAdmin = checkUserPermission?.role.slug.match(/sales-admin/)

    if (!isSalesAdmin) {
      if (!sessionData?.namespaces['storefront-permissions']) {
        throw new GraphQLError('organization-data-not-found')
      }

      const {
        organization: { value: userOrganizationId },
      } = sessionData.namespaces['storefront-permissions'] ?? {
        organization: {
          value: null,
        },
      }

      if (!id) {
        // get user's organization from session
        id = userOrganizationId
      }

      if (id !== userOrganizationId) {
        throw new GraphQLError('operation-not-permitted')
      }
    }

    try {
      return await getCostCenters({
        id,
        masterdata,
        page,
        pageSize,
        search,
        sortOrder,
        sortedBy,
      })
    } catch (error) {
      logger.error({
        error,
        message: 'getCostCentersByOrganizationId-error',
      })
      throw error
    }
  },
}

export default costCenters

```


[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-graphql/node/resolvers/Queries/Users.ts from the b2b-organizations-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import {
  ORGANIZATION_DATA_ENTITY,
  ORGANIZATION_FIELDS,
  ORGANIZATION_SCHEMA_VERSION,
  schemas,
} from '../../mdSchema'
import { toHash } from '../../utils'
import GraphQLError, { getErrorMessage } from '../../utils/GraphQLError'
import { getAppId } from '../config'

const SCROLL_AWAIT_TIME = 100
const SLEEP_ADD_PERCENTAGE = 0.1
const SCROLL_SIZE = 1000

const USER_DATA_ENTITY = 'b2b_users'
const USER_DATA_ENTITY_SCHEMA = 'v0.1.2'

// This function checks if given email is an user part of a buyer org.
export const isUserPartOfBuyerOrg = async (email: string, ctx: Context) => {
  const {
    clients: { masterdata },
  } = ctx

  try {
    const where = `email=${email}`
    const resp = await masterdata.searchDocumentsWithPaginationInfo({
      dataEntity: USER_DATA_ENTITY,
      fields: ['id'], // we don't need to fetch all fields, only if there is an entry or not
      pagination: {
        page: 1,
        pageSize: 1, // we only need to know if there is at least one user entry
      },
      schema: USER_DATA_ENTITY_SCHEMA,
      ...(where ? { where } : {}),
    })

    const { data } = resp as unknown as {
      data: any
    }

    if (data.length > 0) {
      return true
    }
  } catch (error) {
    // if it fails at somepoint, we treat it like no user was found
    // on any buyer org, so we just let the function return false
  }

  return false
}

/**
 * Checks the permissions by applying the following rules:
 * @param checkUserPermission
 */
const isSalesAdmin = (checkUserPermission: any) =>
  checkUserPermission?.role.slug.match(/sales-admin/) || false

const getCheckUserPermission = async ({
  storefrontPermissions,
  logger,
}: any) => {
  const {
    data: { checkUserPermission },
  }: any = await storefrontPermissions
    .checkUserPermission('vtex.b2b-organizations@3.x')
    .catch((error: any) => {
      logger.error({
        error,
        message: 'checkUserPermission-error',
      })

      return {
        data: {
          checkUserPermission: null,
        },
      }
    })

  return checkUserPermission
}

const getChannels = async (ctx: Context) => {
  const {
    clients: { vbase },
    vtex: { logger },
  } = ctx

  let channels = {}

  try {
    channels = await vbase.getJSON('b2borg', 'salesChannels')
  } catch (err) {
    if (err.response.status === 404) {
      await vbase.saveJSON('b2borg', 'salesChannels', {})
    } else {
      logger.error({
        error: err,
        message: 'getChannels-Error',
      })
    }
  }

  return channels
}

/**
 * Checks the user permissions and returns the organizationId updated
 * @param validateUserAdmin
 * @param vtex
 * @param storefrontPermissions
 * @param adminUserAuthToken
 * @param organizationId
 * @param logger
 */
const checkUserPermissions = async ({
  validateUserAdmin,
  vtex,
  storefrontPermissions,
  adminUserAuthToken,
  organizationId,
  logger,
}: any) => {
  const { sessionData } = vtex

  let checkUserPermission = null

  if (sessionData?.namespaces) {
    checkUserPermission = await getCheckUserPermission({
      logger,
      storefrontPermissions,
    })
  }

  const condition = validateUserAdmin
    ? !adminUserAuthToken && !isSalesAdmin(checkUserPermission)
    : !adminUserAuthToken

  if (condition) {
    if (!sessionData?.namespaces['storefront-permissions']) {
      throw new GraphQLError('organization-data-not-found')
    }

    const {
      organization: { value: userOrganizationId },
    } = sessionData?.namespaces['storefront-permissions'] ?? {
      organization: {
        value: null,
      },
    }

    if (!organizationId) {
      // get user's organization from session
      organizationId = userOrganizationId
    }

    if (organizationId !== userOrganizationId) {
      throw new GraphQLError('operation-not-permitted')
    }
  }

  return { organizationId }
}

const sleep = (ms: number) => {
  const time = ms + SLEEP_ADD_PERCENTAGE * ms

  return new Promise((resolve) => setTimeout(resolve, time))
}

const Users = {
  getAppSettings: async (_: void, __: void, ctx: Context) => {
    const {
      clients: { masterdata, vbase },
      vtex: { logger },
    } = ctx

    const app: string = getAppId()
    const settings: any = await vbase.getJSON('b2borg', app).catch((error) => {
      logger.error({
        error,
        message: 'b2borg.getAppSettings-Error',
      })

      return {}
    })

    if (!settings.adminSetup) {
      settings.adminSetup = {}
    }

    const currHash = toHash(schemas)

    if (
      !settings.adminSetup?.schemaHash ||
      settings.adminSetup?.schemaHash !== currHash
    ) {
      const updates: any = []

      schemas.forEach((schema) => {
        updates.push(
          masterdata
            .createOrUpdateSchema({
              dataEntity: schema.name,
              schemaBody: schema.body,
              schemaName: schema.version,
            })
            .then(() => true)
            .catch((e: any) => {
              if (e.response.status !== 304) {
                throw e
              }

              return true
            })
        )
      })

      await Promise.all(updates)
        .then(() => {
          settings.adminSetup.schemaHash = currHash
        })
        .catch((e) => {
          if (e.response.status !== 304) {
            throw new Error(e)
          }
        })

      await vbase.saveJSON('b2borg', app, settings)
    }

    return settings
  },

  getOrganizationsWithoutSalesManager: async (
    _: void,
    __: void,
    ctx: Context
  ) => {
    const {
      clients: { storefrontPermissions, session, masterdata },
      vtex: { adminUserAuthToken, logger, sessionToken },
    } = ctx

    const sessionData = await session
      .getSession(sessionToken as string, ['*'])
      .then((currentSession: any) => {
        return currentSession.sessionData
      })
      .catch((error: any) => {
        logger.warn({
          error,
          message: 'getOrganizationsWithoutSalesManager-session-error',
        })

        return null
      })

    let checkUserPermission = null

    if (sessionData?.namespaces) {
      checkUserPermission = await getCheckUserPermission({
        logger,
        storefrontPermissions,
      })
    }

    if (!adminUserAuthToken && !isSalesAdmin(checkUserPermission)) {
      throw new GraphQLError('operation-not-permitted')
    }

    // getting all users
    const users = await storefrontPermissions
      .listAllUsers()
      .then((result: any) => {
        return result.data.listAllUsers
      })
      .catch((error: any) => {
        logger.error({
          error,
          message: 'getOrganizationsWithoutSalesManager-getUsers-error',
        })
        throw new GraphQLError(getErrorMessage(error))
      })

    let token: string | undefined
    let hasMore = true
    const organizations = [] as any[]

    const scrollMasterData = async () => {
      await sleep(SCROLL_AWAIT_TIME)
      const {
        mdToken,
        data,
      }: {
        mdToken: string
        data: any
      } = await masterdata.scrollDocuments({
        dataEntity: ORGANIZATION_DATA_ENTITY,
        fields: ORGANIZATION_FIELDS,
        mdToken: token,
        schema: ORGANIZATION_SCHEMA_VERSION,
        size: SCROLL_SIZE,
      })

      if (!data.length && token) {
        hasMore = false
      }

      if (!token && mdToken) {
        token = mdToken
      }

      organizations.push(...data)
      if (hasMore) {
        await scrollMasterData()
      }
    }

    try {
      await scrollMasterData()
    } catch (error) {
      logger.error({
        error,
        message: 'scrollMasterData-error',
      })
      throw new GraphQLError(getErrorMessage(error))
    }

    return organizations.filter((organization) => {
      return !users
        .filter((user: any) => user.role === 'sales-manager')
        .find((user: any) => user.orgId === organization.id)
    })
  },

  getUsers: async (
    _: void,
    {
      organizationId,
      costCenterId,
    }: { organizationId: string; costCenterId: string },
    ctx: Context
  ) => {
    const {
      clients: { storefrontPermissions },
      vtex: { adminUserAuthToken, logger },
      vtex,
    } = ctx

    if (!adminUserAuthToken) {
      const { organizationId: orgId } = await checkUserPermissions({
        adminUserAuthToken,
        logger,
        organizationId,
        storefrontPermissions,
        validateUserAdmin: false,
        vtex,
      })

      organizationId = orgId
    }

    const variables = {
      ...(organizationId && { organizationId }),
      ...(costCenterId && { costCenterId }),
    }

    return storefrontPermissions
      .listUsers(variables)
      .then((result: any) => {
        return result.data.listUsers
      })
      .catch((error) => {
        logger.error({
          error,
          message: 'getUsers-getUsers-error',
        })

        throw new GraphQLError(getErrorMessage(error))
      })
  },

  getUsersPaginated: async (
    _: void,
    {
      organizationId,
      costCenterId,
      search = '',
      page = 1,
      pageSize = 25,
      sortOrder = 'asc',
      sortedBy = 'email',
    }: {
      organizationId: string
      costCenterId: string
      search: string
      page: number
      pageSize: number
      sortOrder: string
      sortedBy: string
    },
    ctx: Context
  ) => {
    const {
      clients: { storefrontPermissions },
      vtex: { adminUserAuthToken, logger },
      vtex,
    } = ctx

    if (!adminUserAuthToken) {
      const { organizationId: orgId } = await checkUserPermissions({
        adminUserAuthToken,
        logger,
        organizationId,
        storefrontPermissions,
        validateUserAdmin: true,
        vtex,
      })

      organizationId = orgId
    }

    const variables = {
      ...(organizationId && { organizationId }),
      ...(costCenterId && { costCenterId }),
      page,
      pageSize,
      search,
      sortOrder,
      sortedBy,
    }

    return storefrontPermissions
      .listUsersPaginated(variables)
      .then((result: any) => {
        return result.data.listUsersPaginated
      })
      .catch((error) => {
        logger.error({
          error,
          message: 'getUsers-error',
        })
        throw new GraphQLError(getErrorMessage(error))
      })
  },

  getSalesChannels: async (_: void, __: void, ctx: Context) => {
    return getChannels(ctx)
  },

  getBinding: async (_: void, { email }: { email: string }, ctx: Context) => {
    const {
      clients: { catalog },
      vtex: { logger },
    } = ctx

    let access = false
    let availableSalesChannels: any = {}

    try {
      availableSalesChannels = await catalog
        .salesChannelAvailable(email)
        .then((res: any) => {
          return res.map((item: any) => {
            return item.Id.toString()
          })
        })
    } catch {
      logger.info({
        message: 'getBinding-availableSalesChannels',
        data: { email },
      })
    }

    try {
      const selectedChannels: any = await getChannels(ctx).then((res: any) => {
        if (res.length) {
          return res.map((item: any) => {
            return item.id
          })
        }

        return null
      })

      if (selectedChannels) {
        if (availableSalesChannels.length) {
          access =
            selectedChannels.filter((item: any) =>
              availableSalesChannels.includes(item)
            ).length > 0
        }
      } else {
        access = true
      }
    } catch (err) {
      logger.warn({
        error: err,
        message: 'getBinding-Error',
      })
    }

    return access
  },
}

export default Users

```


[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-graphql/node/resolvers/Queries/MarketingTags.ts from the b2b-organizations-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { MARKETING_TAGS } from '../../utils/constants'

const MarketingTags = {
  getMarketingTags: async (
    _: void,
    { costId }: { costId: string },
    ctx: Context
  ) => {
    const {
      clients: { vbase },
      vtex: { logger },
    } = ctx

    try {
      return await vbase.getJSON(MARKETING_TAGS.VBASE_BUCKET, costId)
    } catch (error) {
      const { data } = error.response as any

      if (data.code !== 'FileNotFound') {
        logger.error({
          error,
          message: 'getMarketingTags.error',
        })
      }

      return { status: 'error', message: error }
    }
  },
}

export default MarketingTags

```


[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-graphql/node/resolvers/Routes/index.ts from the b2b-organizations-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { ForbiddenError, UserInputError } from '@vtex/api'

import {
  COST_CENTER_DATA_ENTITY,
  ORGANIZATION_DATA_ENTITY,
} from '../../mdSchema'

const getUserAndPermissions = async (ctx: Context) => {
  const {
    vtex: { storeUserAuthToken, sessionToken, logger },
    clients: { vtexId, session, storefrontPermissions },
  } = ctx

  const token: any = storeUserAuthToken

  const sessionData = await session
    .getSession(sessionToken as string, ['*'])
    .then((currentSession: any) => {
      return currentSession.sessionData
    })
    .catch((error: any) => {
      logger.error({
        error,
        message: 'orders-getSession-error',
      })

      return null
    })

  const profileEmail = sessionData?.namespaces?.profile?.email?.value
  const impersonatedEmail =
    sessionData?.namespaces?.impersonate?.storeUserEmail?.value

  if (!impersonatedEmail && !token) {
    throw new ForbiddenError('Access denied')
  }

  let checkUserPermission = null

  if (sessionData?.namespaces) {
    const checkUserPermissionResult = await storefrontPermissions
      // It is necessary to send the app name, because the check user return the permissions relative to orders-history to access the page.
      .checkUserPermission('vtex.b2b-orders-history@2.x')
      .catch((error: any) => {
        logger.error({
          message: 'checkUserPermission-error',
          error,
        })

        return {
          data: {
            checkUserPermission: null,
          },
        }
      })

    checkUserPermission = checkUserPermissionResult?.data?.checkUserPermission
  }

  const organizationId =
    sessionData?.namespaces['storefront-permissions']?.organization?.value

  const costCenterId =
    sessionData?.namespaces['storefront-permissions']?.costcenter?.value

  const authUser = impersonatedEmail
    ? { user: impersonatedEmail }
    : await vtexId.getAuthenticatedUser(token)

  return {
    authEmail: authUser?.user,
    impersonatedEmail,
    profileEmail,
    permissions: checkUserPermission?.permissions,
    organizationId,
    costCenterId,
  }
}

const checkPermissionAgainstOrder = ({
  permissions,
  authEmail,
  profileEmail,
  organizationId,
  costCenterId,
  orderData,
}: {
  permissions?: string[]
  authEmail?: string
  profileEmail: string
  organizationId?: string
  costCenterId?: string
  orderData: any
}) => {
  if (
    authEmail === orderData?.clientProfileData?.email ||
    profileEmail === orderData?.clientProfileData?.email
  ) {
    return true
  }

  if (permissions?.includes('all-orders')) {
    return true
  }

  if (
    permissions?.includes('organization-orders') &&
    organizationId === orderData?.marketingData?.utmCampaign
  ) {
    return true
  }

  if (
    permissions?.includes('costcenter-orders') &&
    costCenterId === orderData?.marketingData?.utmMedium
  ) {
    return true
  }

  return false
}

const getOrder = async (ctx: Context) => {
  const {
    vtex: {
      route: {
        params: { orderId },
      },
    },
    clients: { orders },
  } = ctx

  if (!orderId) {
    throw new UserInputError('Order ID is required')
  }

  const order: any = await orders.order(String(orderId))

  // Some dependencies rely on this property not being null but an empty array instead.
  // The new orders endpoint might return null for this property instead of an empty array,
  // so we make sure to set it to an empty array to avoid breaking dependencies.
  // For more info: https://vtex-dev.atlassian.net/browse/B2BTEAM-1376
  order.marketplaceItems = order.marketplaceItems ?? []

  return order
}

const Index = {
  checkout: async (ctx: Context) => {
    const {
      vtex: { storeUserAuthToken, sessionToken, logger },
      clients: { session, masterdata },
    } = ctx

    const token: any = storeUserAuthToken
    const response: any = {}

    ctx.response.status = !token ? 403 : 200

    if (token) {
      const sd = await session
        .getSession(sessionToken as string, ['*'])
        .then((currentSession: any) => {
          return currentSession.sessionData
        })
        .catch((error: any) => {
          logger.error({
            error,
            message: 'checkout-getSession-error',
          })

          return null
        })

      if (sd?.namespaces['storefront-permissions']) {
        if (sd.namespaces['storefront-permissions']?.organization?.value) {
          response.organization = await masterdata.getDocument({
            dataEntity: ORGANIZATION_DATA_ENTITY,
            fields: ['paymentTerms'],
            id: sd.namespaces['storefront-permissions']?.organization?.value,
          })
        }

        if (sd.namespaces['storefront-permissions']?.costcenter?.value) {
          response.costcenter = await masterdata.getDocument({
            dataEntity: COST_CENTER_DATA_ENTITY,
            fields: ['addresses'],
            id: sd.namespaces['storefront-permissions']?.costcenter?.value,
          })
        }
      }
    }

    ctx.set('Content-Type', 'application/json')
    ctx.set('Cache-Control', 'no-cache, no-store')

    ctx.response.body = response
  },
  order: async (ctx: Context) => {
    const order = await getOrder(ctx)

    const {
      permissions,
      authEmail,
      impersonatedEmail,
      profileEmail,
      organizationId,
      costCenterId,
    } = await getUserAndPermissions(ctx)

    const permitted = checkPermissionAgainstOrder({
      permissions,
      orderData: order,
      authEmail: impersonatedEmail ?? authEmail,
      profileEmail,
      organizationId,
      costCenterId,
    })

    if (!permitted) {
      throw new ForbiddenError('Access denied')
    }

    ctx.set('Content-Type', 'application/json')
    ctx.set('Cache-Control', 'no-cache, no-store')

    ctx.response.body = order

    ctx.response.status = 200
  },
  orders: async (ctx: Context) => {
    const {
      clients: { oms },
      request: { querystring },
    } = ctx

    const {
      permissions,
      authEmail,
      impersonatedEmail,
      organizationId,
      costCenterId,
    } = await getUserAndPermissions(ctx)

    const filterByPermission = (userPermissions: string[]) => {
      if (userPermissions.includes('all-orders')) {
        return ``
      }

      if (userPermissions.includes('organization-orders')) {
        return `&f_UtmCampaign=${organizationId}`
      }

      if (userPermissions.includes('costcenter-orders')) {
        return `&f_UtmMedium=${costCenterId}`
      }

      return `&clientEmail=${impersonatedEmail ?? authEmail}`
    }

    const pastYear: any = new Date()

    pastYear.setDate(pastYear.getDate() - 365)

    const now = new Date().toISOString()
    let query = `f_creationDate=creationDate:[${pastYear.toISOString()} TO ${now}]&${querystring}`

    if (permissions?.length) {
      query += filterByPermission(permissions)
    } else {
      query += `&clientEmail=${impersonatedEmail ?? authEmail}`
    }

    const orders: any = await oms.search(query)

    ctx.set('Content-Type', 'application/json')
    ctx.set('Cache-Control', 'no-cache, no-store')

    ctx.response.body = orders

    ctx.response.status = 200
  },
  requestCancellation: async (ctx: Context) => {
    const {
      vtex: {
        route: {
          params: { orderId },
        },
      },
      clients: { checkout },
    } = ctx

    const order = await getOrder(ctx)

    const {
      permissions,
      authEmail,
      impersonatedEmail,
      profileEmail,
      organizationId,
      costCenterId,
    } = await getUserAndPermissions(ctx)

    const permitted = checkPermissionAgainstOrder({
      permissions,
      orderData: order,
      authEmail: impersonatedEmail ?? authEmail,
      profileEmail,
      organizationId,
      costCenterId,
    })

    if (!permitted) {
      throw new ForbiddenError('Access denied')
    }

    const reason = `order canceled by user: ${authEmail}`

    const requestCancellationResponse = await checkout.requestCancellation(
      orderId as string,
      reason
    )

    ctx.set('Content-Type', 'application/json')
    ctx.set('Cache-Control', 'no-cache, no-store')

    ctx.response.body = requestCancellationResponse.data

    ctx.response.status = requestCancellationResponse.status
  },
}

export default Index

```


[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 btg360/docs/README.md from the btg360 VTEX app.

[Assistant]
Here's the implementation:

```markdown
# BTG360

BTG 360 uses behavioral targert technology so you can achieve even more assertive results
with email marketing campaigns based on browsing and / or purchasing behavior of each
one of the registered users in your lists.

For more information please visit, [AllIn](https://allinmail.com.br/) website.

## How to install

Open the VTEX App Store and install this app on your store.

Now, open the app settings on your admin and fill in the BTG360 options.

or

Run the following command:

```sh
vtex install vtex.btg360@0.x
```
```


[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 btg360/react/index.tsx from the btg360 VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { PixelMessage } from './typings/events'
import { canUseDOM } from 'vtex.render-runtime'
import { fetchEmail, clientEvent } from './helpers'

const eventQueue: PixelMessage[] = []

function processMessageQueue() {
  for (const e of eventQueue) {
    handleMessage(e)
  }
}

function handleOrEnqueueMessage (e: PixelMessage) {
  if (window.__BTG_SCRIPTS_READY__) {
    handleMessage(e)
  } else {
    eventQueue.push(e)
  }
}

async function handleMessage(e: PixelMessage) {
  const {
    Btg360,
    __btg360: { BTGId: account, BTGDomain: domain },
    location: { pathname },
  } = window

  switch (e.data.eventName) {
    case 'vtex:pageInfo': {
      clientEvent(account, domain, Btg360)
      if (e.data.eventType === 'internalSiteSearchView') {
        const items = pathname
          .split('?')[0]
          .split('/')
          .filter(item => item.toLocaleLowerCase())
          .map(keyword => ({ keyword }))
        const event = 'search'
        const BTG360SearchEvent: Btg360Event = {
          account,
          domain,
          event,
          items,
        }
        Btg360.add(BTG360SearchEvent)
      }
      break
    }
    case 'vtex:orderPlaced': {
      const email = await fetchEmail()
      const { transactionId, transactionProducts: items } = e.data
      const BTG360TransactionEvent: Btg360Event = {
        account,
        domain,
        event: 'transaction',
        items: items.map(
          ({
            id,
            name,
            price,
            categoryTree: [department = '', category = '', subcategory = ''],
            brand,
          }): Btg360EventItemTransaction => ({
            email,
            transactionId,
            id,
            name,
            price: price.toFixed(2),
            department,
            category,
            subcategory,
            brand,
          })
        ),
      }

      Btg360.add(BTG360TransactionEvent)
      clientEvent(account, domain, Btg360)
      break
    }
    case 'vtex:productView': {
      const {
        product: {
          productId,
          productName: name,
          categories,
          brand,
          items: [
            {
              sellers: [
                {
                  commertialOffer: { Price: price },
                },
              ],
            },
          ],
        },
      } = e.data
      const email = await fetchEmail()
      const [categoryTree] = categories
      const [
        department = '',
        category = '',
        subcategory = '',
      ] = categoryTree.split('/').filter(item => item)
      const BTG360ProductEventItem: Btg360EventItemProduct = {
        id: productId,
        name,
        email,
        price: price.toFixed(2),
        department,
        category,
        subcategory,
        brand,
      }
      const BTG360ProductEvent: Btg360Event = {
        account,
        domain,
        event: 'product',
        items: [BTG360ProductEventItem],
      }

      Btg360.add(BTG360ProductEvent)
      break
    }
    case 'vtex:addToCart': {
      const { items } = e.data
      const email = await fetchEmail()
      const BTG360CartEvent = {
        account,
        domain,
        event: 'cart',
        items: items.map(
          ({ name, price, skuId: id, brand, category: categoryTree }) => {
            const [
              department = '',
              category = '',
              subcategory = '',
            ] = categoryTree.split('/')
            return {
              email,
              id,
              name: name.toUpperCase(),
              price: price.toFixed(2),
              department,
              category,
              subcategory,
              brand,
            }
          }
        ),
      }

      Btg360.add(BTG360CartEvent)
      break
    }
  }
}

if (canUseDOM) {
  window.addEventListener('message', handleOrEnqueueMessage)
  window.addEventListener('btgReady', processMessageQueue)
}

```


[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 btg360/react/package.json from the btg360 VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "name": "vtex.btg360",
  "version": "1.1.0",
  "scripts": {
    "lint": "tsc --noEmit --pretty && tslint -c tslint.json './**/*.ts'"
  },
  "devDependencies": {
    "@types/node": "^10.12.18",
    "tslint": "^5.11.0",
    "tslint-config-vtex": "^2.0.0",
    "tslint-eslint-rules": "^5.4.0",
    "typescript": "3.7.3"
  }
}

```


[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 btg360/react/tslint.json from the btg360 VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "compilerOptions": {
    "alwaysStrict": true,
    "esModuleInterop": true,
    "jsx": "react",
    "lib": ["es2017", "dom", "es2018.promise"],
    "module": "es6",
    "moduleResolution": "node",
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "strictFunctionTypes": true,
    "strictNullChecks": true,
    "strictPropertyInitialization": true,
    "target": "es2017",
    "typeRoots": ["node_modules/@types"],
    "types": ["node", "jest", "graphql"]
  },
  "exclude": ["node_modules"],
  "include": ["./typings/*.d.ts", "./**/*.tsx", "./**/*.ts"],
  "typeAcquisition": {
    "enable": false
  }
}

```


[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 btg360/store/interfaces.json from the btg360 VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "pixel.btg360": {
    "component": "index"
  }
}

```


[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 btg360/react/typings/events.d.ts from the btg360 VTEX app.

[Assistant]
Here's the implementation:

```typescript
export interface PixelMessage extends MessageEvent {
  data:
    | ProductViewData
    | ProductClickData
    | OrderPlacedData
    | PageViewData
    | PageInfoData
    | ProductImpressionData
    | AddToCartData
    | RemoveToCartData
    | CategoryViewData
    | DepartmentViewData;
}

export interface EventData {
  event: string;
  eventName: string;
  currency: string;
}

export interface PageViewData extends EventData {
  event: "pageView";
  eventName: "vtex:pageView";
  pageTitle: string;
  pageUrl: string;
  referrer: string;
}

export interface PageInfoData extends EventData {
  event: "pageInfo";
  eventName: "vtex:pageInfo";
  eventType: string;
  accountName: string;
  pageCategory: string;
  pageDepartment: string;
  pageFacets: string[];
  pageTitle: string;
  pageUrl: string;
}

export interface CategoryViewData extends EventData {
  event: "categoryView";
  eventName: "vtex:categoryView";
  products: Product[];
}

export interface DepartmentViewData extends EventData {
  event: "departmentView";
  eventName: "vtex:departmentView";
  products: Product[];
}

export interface AddToCartData extends EventData {
  event: "addToCart";
  eventName: "vtex:addToCart";
  items: AddToCartProduct[];
}

export interface RemoveToCartData extends EventData {
  event: "removeFromCart";
  eventName: "vtex:removeFromCart";
  items: any[];
}

export interface OrderPlacedData extends Order, EventData {
  event: "orderPlaced";
  eventName: "vtex:orderPlaced";
}

export interface ProductViewData extends EventData {
  event: "productView";
  eventName: "vtex:productView";
  product: Product;
}

export interface ProductClickData extends EventData {
  event: "productClick";
  eventName: "vtex:productClick";
  product: Product;
}

export interface ProductImpressionData extends EventData {
  event: "productImpression";
  eventName: "vtex:productImpression";
  product: Product;
  position: number;
  list: string;
}

export interface Order {
  currency: string;
  accountName: string;
  orderGroup: string;
  salesChannel: string;
  coupon: string;
  visitorType: string;
  visitorContactInfo: string[];
  transactionId: string;
  transactionDate: string;
  transactionAffiliation: string;
  transactionTotal: number;
  transactionShipping: number;
  transactionTax: number;
  transactionCurrency: string;
  transactionPaymentType: PaymentType[];
  transactionShippingMethod: ShippingMethod[];
  transactionProducts: ProductOrder[];
  transactionPayment: {
    id: string;
  };
}

interface PaymentType {
  group: string;
  paymentSystemName: string;
  installments: number;
  value: number;
}

interface ShippingMethod {
  itemId: string;
  selectedSla: string;
}

interface ProductOrder {
  id: string;
  name: string;
  sku: string;
  skuRefId: string;
  skuName: string;
  brand: string;
  brandId: string;
  seller: string;
  sellerId: string;
  category: string;
  categoryId: string;
  categoryTree: string[];
  categoryIdTree: string[];
  originalPrice: number;
  price: number;
  sellingPrice: number;
  tax: number;
  quantity: number;
  components: any[];
  measurementUnit: string;
  unitMultiplier: number;
}

export interface Product {
  brand: string;
  categoryId?: string; // inconsistency
  categories: string[];
  productId: string;
  productName: string;
  selectedSku: {
    itemId: string
  }; // inconsistency
  items: Item[];
  sku: Item;
  [key: string]: any;
}

interface Item {
  itemId: string;
  name: string;
  seller?: Seller;
  [key: string]: any;
}

interface Seller {
  commertialOffer: CommertialOffer;
  AvailableQuantity: number;
  [key: string]: any;
}

interface CommertialOffer {
  Price: number;
  [key: string]: any;
}

interface AddToCartProduct {
  brand: string;
  name: string;
  price: number;
  quantity: number;
  skuId: string;
  variant: string;
  category: string;
}

```


[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 btg360/react/typings/vtex.render-runtime.d.ts from the btg360 VTEX app.

[Assistant]
Here's the implementation:

```typescript
declare module 'vtex.render-runtime' {
  export const canUseDOM: boolean
}

```


[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 btg360/react/typings/global.d.ts from the btg360 VTEX app.

[Assistant]
Here's the implementation:

```typescript
interface Window extends Window {
  __btg360: BTG360Configs
  Btg360: Btg360
  __BTG_SCRIPTS_READY__?: boolean
}

interface ProfileData {
  UserId: string
  IsReturningUser: boolean
  IsUserDefined: boolean
  IsPJ: boolean
  FirstName: string
  LastName: string
  Gender: null
  Email: string
}

interface BTG360Configs {
  BTGId: string
  BTGDomain: string
}

interface Btg360 {
  add(e: Btg360Event): void
  debug(): void
}

interface Btg360Event {
  account: string
  domain: string
  event: string
  items: (
    | Btg360EventItemTransaction
    | Btg360EventItemProduct
    | Btg360EventItemKeyword)[]
    | Array<>
}

interface BTG360EventItemClient {
  email: string
}

interface Btg360EventItemTransaction {
  email: string
  transactionId: string
  id: string
  name: string
  price: string
  department: string
  category: string
  subcategory: string
  brand: string
}

interface Btg360EventItemProduct {
  email: string
  id: string
  name: string
  price: string
  department: string
  category: string
  subcategory: string
  brand: string
}

interface Btg360EventItemKeyword {
  keyword: string
}

```


[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 btg360/react/helpers/index.tsx from the btg360 VTEX app.

[Assistant]
Here's the implementation:

```tsx
export async function fetchEmail(): Promise<string> {
    const { Email: email } = await (await fetch(
      '/no-cache/profileSystem/getProfile'
    )).json()

    if(email == undefined) {
      return ''
    }

    return email
}

export async function clientEvent(account: string, domain: string, Btg360: any) {
  const email = await fetchEmail()
  const BTG360ClientEvent: Btg360Event = {
    account,
    domain,
    event: 'client',
    items: [{email}]
  }

  Btg360.add(BTG360ClientEvent)
}

```


[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 ideacrm/docs/README.md from the ideacrm VTEX app.

[Assistant]
Here's the implementation:

```markdown
# Idea CRM

📢 Use this project, [contribute](https://github.com/vtex-apps/ideacrm) to it or open issues to help evolve it using [Store Discussion](https://github.com/vtex-apps/store-discussion).

Idea CRM is a first party integration app for the platform's customer relationship management solution. It enables you to control customer interactions, providing measurements and trackability through Chat, Phone, Email, SMS, Tickets, Social Network and other applications.
For more on the Idea CRM solution, please visit their [website](https://www.gvp.com.br/crm)

## Configuration

1. Open the **VTEX App Store** and install the Idea CRM app on your store. You can also run the following command in your terminal:

```sh
vtex install vtex.ideacrm@0.x
```

2. In the admin's side bar, access **Apps** within the **Account settings** section.
3. Select the **Idea CRM** box and fill out the necessary fields in order to complete the app's configuration.
```


[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 speedy-gtm/docs/README.md from the speedy-gtm VTEX app.

[Assistant]
Here's the implementation:

```markdown
📢 Use this project, [contribute](https://github.com/vtex-apps/google-tag-manager) to it or open issues to help evolve it using [Store Discussion](https://github.com/vtex-apps/store-discussion).

# Google Tag Manager

<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-0-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->

The VTEX Google Tag Manager (GTM) app is a first party integration to the [Google Tag Manager solution](https://tagmanager.google.com).

![google-tag-manager-app](https://user-images.githubusercontent.com/52087100/84321347-55e11c80-ab49-11ea-9445-24eec6a07785.png)

## Installing the Google Tag Manager app

1. Access the **Apps** section in your account's admin page and look for the Google Tag Manager box;
2. Then, click on the **Install** button;
3. You'll see a warning message about needing to enter the necessary configurations. Scroll down and type in your **GTM ID** in the `Google Tag Manager` field.
4. Click on **Save**.

> Access the [Google Tag Manager page](https://tagmanager.google.com/) and log in to your account in order to find out what is your account **GTM ID**. Use the number provided by the `Container ID` column.

### Step 2 - Creating Google Analytics variables
To set up Google Tag Manager in your store, you must create and set up all necessary variables, triggers, and tags. Follow the [Setting up Google Tag Manager documentation](https://developers.vtex.com/vtex-developer-docs/docs/vtex-io-documentation-setting-up-google-tag-manager) to create them.

### Step 3 - Persisting campaign data throughout a user session
To prevent GTM from creating additional session identifiers every time a user navigates the website, you must add the variables `originalLocation` and `originalReferrer` to your GTM container and configure your store’s Google Analytics tags. Notice that this is important to persist campaign data throughout a user session and avoid providing inconsistent campaign data to Google Analytics (GA).

>⚠ The `originalLocation` and `originalReferrer` variables are available for VTEX IO Google Tag Manager versions `2.x` and `3.x`.

#### Creating the variables Original Location and Original Referrer

1. Log in to your [GTM account](https://tagmanager.google.com) and click on the GTM container you want to work with.

    ![gtm-container](https://user-images.githubusercontent.com/67270558/136798596-cc0add2d-e110-4176-bc8d-665ded39da29.png)

2. On the container page, click on **Variables**.
3. In the **Built-In Variables** section, check if the `Page URL` and `Page Path` variables are enabled. Otherwise, click on `Configure` and select `Page URL` and `Page Path` to enable them.
4. Go to the **User-Defined Variables** section and click on `New`. A side popup will open.
5. Replace the `Untitled Variable` value with `Original Location`.
6. Click on **Variable Configuration**.
7. On **Page Variables**, click on **Data Layer Variable**.
8. In the `Data Layer Variable Name` field, type `originalLocation`.
9. Enable the `Set Default Value` option and fill in the `Default Value` field with the following value:

    ```
    {{Page URL}}
    ```

    ![gtm-variable-location](https://user-images.githubusercontent.com/67270558/139482165-21f93c6a-48e5-421a-8e06-c942bda01974.gif)

10. Click `Save`. Once you have saved the `originalLocation` variable, create the `originalReferrer` as described in the steps below.
11. In the **User-Defined Variables** section, click on `New`. A side popup will open.
12. Replace the `Untitled Variable` value with `Original Referrer`.
13. Click on **Variable Configuration**.
14. On **Page Variables**, click on **Data Layer Variable**.
15. In the `Data Layer Variable Name` field, type `originalReferrer`.
16. Enable the `Set Default Value` option and fill in the `Default Value` field with the following value:

    ```
    {{Referrer}}
    ```

    ![gtm-variable-referrer](https://user-images.githubusercontent.com/67270558/141315033-56e6e498-8c44-490d-a6dd-51f226dd6fc9.gif)

17. Click `Save`.

#### Updating Google Analytics Settings variables and tags

Now, let's configure every Google Analytics Settings variable that fires the `originalLocation` and `originalReferrer` variables.

1. Go to the **Variables** section;
2. On **User-Defined Variables**, click on the name of one of the Google Analytics Settings variables

    ![ga-variables](https://user-images.githubusercontent.com/67270558/136799579-f1bb7e68-ec4c-4deb-beb2-0dfedb88de10.png)

3. Click on the **Variable Configuration** box;
4. Go to **More Settings > Fields to Set**;
5. Click on `Add Field`;
6. Set the `Field Name` field as `location` and `Value` as `{{Original Location}}`;
7. Then, click on `Add Field` again;
6. Set the `Field Name` field as `referrer` and `Value` as `{{Original Referrer}}`;
8. Click on `Save`.

If you have any Google Analytics tags using the Google Analytics Settings variables you have changed, apply the same changes above directly on the tags that need it.

#### Publishing your changes

Once you have set up the Google Analytics variables and tags, follow Google's official guide on [how to submit and publish your store’s container](https://support.google.com/tagmanager/answer/6107163).

## Restrictions

In order to avoid performance problems and unforeseen behavior, our VTEX IO Google Tag Manager solution uses the native GTM **blacklist** feature. You can read more about this feature on the [Google Developer Guide](https://developers.google.com/tag-manager/web/restrict).

We, by default, blacklist the `html` ID, which automatically blocklists all the tags, variables, and triggers of the type `customScripts`. The main consequence of this blocklist is that **Custom HTML tags will not be triggered**.

>⚠ The HTML blacklist is the VTEX Google Tag Manager app's default. If you want to disable this restriction, go to `https://{accountName}.myvtex.com/admin/apps/vtex.google-tag-manager@2.x/setup` and check the toggle below.

<img src="https://user-images.githubusercontent.com/11340665/103930428-7c762e80-50fd-11eb-9cab-bc9e542b4dbf.png"/>

Most of the widely used Custom HTML tags are integrations with third-party services, like Customer Chat, Analytics, Remarketing, and Pixel tags. If your store needs a Custom HTML for one of those cases, the integration can either be done by transforming the tags into a [VTEX IO Pixel App](https://developers.vtex.com/docs/guides/pixel-apps) or by removing this restriction.

Check below the full list of tags and variables that are blocked, by default, in the VTEX IO Google Tag Manager solution.

### Blocked tags

- Custom HTML Tag - `html`
- Eulerian Analytics Tag - `ela`
- SaleCycle JavaScript Tag  - `scjs`
- Upsellit Global Footer Tag - `uslt`
- Upsellit Confirmation Tag - `uspt`

### Blocked variables

- Custom JavaScript Variable - `jsm`

Check out a list with all the GTM available tags on the [Google Developer Guide](https://developers.google.com/tag-manager/devguide).

<!-- DOCS-IGNORE:start -->

## Contributors ✨

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind are welcome!

<!-- DOCS-IGNORE:end -->

```


[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 speedy-gtm/react/index.tsx from the speedy-gtm VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { canUseDOM } from 'vtex.render-runtime'

import { sendEnhancedEcommerceEvents } from './modules/enhancedEcommerceEvents'
import { sendExtraEvents } from './modules/extraEvents'
import { sendLegacyEvents } from './modules/legacyEvents'
import { PixelMessage } from './typings/events'

// no-op for extension point
export default function() {
  return null
}

export function handleEvents(e: PixelMessage) {
  sendEnhancedEcommerceEvents(e)
  sendExtraEvents(e)
  sendLegacyEvents(e)
}

if (canUseDOM) {
  window.addEventListener('message', handleEvents)
}

```


[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 speedy-gtm/react/package.json from the speedy-gtm VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "version": "3.5.4",
  "scripts": {
    "test": "vtex-test-tools test"
  },
  "author": "VTEX",
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^12.0.0",
    "@vtex/test-tools": "^3.3.2"
  }
}

```


[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 speedy-gtm/public/metadata/messages/es-AR.json from the speedy-gtm VTEX app.

[Assistant]
Here's the implementation:

```json
{
    "name": "Google Tag Manager",
      "headline": "Integre su tienda con la plataforma de tracking de conversiones y navegación de Google.",
    "overview": "Google Tag Manager es un servicio para rastrear las actividades de sus visitantes en ecommerce y puede ser usado para evaluar la efectividad de sus anuncios, generar audiencias de remarketing y mapear información sobre cómo su tienda está sendo utilizada. \\n\\n Esta aplicación implementa la tag de Google Tag Manager en el código de cada página de su sitio, lo que le permite realizar un tracking de los eventos de conversión y navegación dentro de las mejores prácticas de análisis de Google. Usted deberá ingresar su ID de cuenta GTM en la página de configuración de la aplicación.",
    "features": [
      "Configura Google Tag Manager con el admin de VTEX",
      "Envia todo los eventos de Google Enhanced Ecommerce"
    ],
      "additionalInformation": "La gestión de tags, eventos y triggers se realiza directamente utilizando la plataforma de Google Tag Manager (http://tagmanager.google.com/) después de instalar y configurar la aplicación. Los eventos disponibles por defecto con la implementación de la aplicación se pueden consultar en la documentación.",
    "websiteURL": "https://vtex.com/",
    "support": {
        "url": "https://support.vtex.com/hc/requests"
      }
  }

```


[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 speedy-gtm/public/metadata/messages/pt-BR.json from the speedy-gtm VTEX app.

[Assistant]
Here's the implementation:

```json
{
    "name": "Google Tag Manager",
      "headline": "Integre-se à plataforma de tracking de navegação e conversão do Google.",
    "overview": "O Google Tag Manager é um serviço de rastreamento das atividades dos visitantes do seu ecommerce que pode ser utilizado para avaliar a eficiência de seus anúncios, gerar públicos de remarketing e mapear insights sobre o uso da sua loja.\\n\\n Este app implementa a tag do Google Tag Manager no código de todas as páginas do seu site, permitindo que você rastreie eventos de navegação e conversão dentro dos padrões e boas práticas de analytics do Google. Você precisará inserir o ID da sua conta do GTM na página de configuração do aplicativo.",
    "features": [
      "Configure o Google Tag Manager pelo admin da VTEX",
      "Envia todos os eventos do Google Enhanced Ecommerce"
    ],
      "additionalInformation": "A gestão das tags, eventos e triggers é feita diretamente pela platafoma do Google Tag Manager (http://tagmanager.google.com/) após a instalação e configuração do app. Os eventos disponíveis por padrão com a implementação do app podem ser consultados na documentação.",
    "websiteURL": "https://vtex.com/",
    "support": {
        "url": "https://support.vtex.com/hc/requests"
      }
  }

```


[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 speedy-gtm/public/metadata/messages/en-US.json from the speedy-gtm VTEX app.

[Assistant]
Here's the implementation:

```json
{
    "name": "Google Tag Manager",
      "headline": "Integrate your store with Google's navigation and conversion tracking platform.",
    "overview": "Google Tag Manager is a service for tracking the activities of your ecommerce visitors and can be used to assess the effectiveness of your ads, generate remarketing audiences and map insights into how your store is being used.\\n\\n This app implements the Google Tag Manager tag in the code of every page on your site, allowing you to track navigation and conversion events within Google’s best practices and analytics standards. You will need to enter your GTM account ID on the application's setup page.",
    "features": [
      "Configure Google Tag Manager using the VTEX admin panel",
      "Send all Google Enhanced Commerce events"
    ],
      "additionalInformation": "The management of tags, events and triggers is done directly through the Google Tag Manager platform (http://tagmanager.google.com/) after installing and configuring the app. The events available by default with the app's implementation can be consulted in our documentation.",
    "websiteURL": "https://vtex.com/",
    "support": {
        "url": "https://support.vtex.com/hc/requests"
      }
  }

```


[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 speedy-gtm/react/typings/events.d.ts from the speedy-gtm VTEX app.

[Assistant]
Here's the implementation:

```typescript
export interface PixelMessage extends MessageEvent {
  data:
    | ProductViewData
    | ProductClickData
    | OrderPlacedData
    | OrderPlacedTrackedData
    | PageViewData
    | LegacyProductViewData
    | ProductImpressionData
    | AddToCartData
    | RemoveFromCartData
    | CartChangedData
    | HomePageInfo
    | ProductPageInfoData
    | SearchPageInfoData
    | UserData
    | CartIdData
    | CartLoadedData
    | PromoViewData
    | PromotionClickData
    | AddPaymentInfoData
    | AddShippingInfoData
    | SignUpData
    | LoginData
    | ShareData
    | BeginCheckoutData
    | ViewCartData
    | RefundData
    | AddToWishlistData
    | SearchData
}

export interface EventData {
  event: string
  eventName: string
  currency: string
}

export interface PageInfoData extends EventData {
  event: 'pageInfo'
  eventName: 'vtex:pageInfo'
  accountName: string
  pageTitle: string
  pageUrl: string
}

export interface LegacyProductViewData extends EventData {
  event: 'pageInfo'
  eventName: 'vtex:pageInfo'
  eventType: 'productView'
}

export interface UserData extends PageInfoData {
  eventType: 'userData'
  eventName: 'vtex:userData'
  firstName?: string
  lastName?: string
  document?: string
  id?: string
  email?: string
  phone?: string
  isAuthenticated: boolean
}

export interface CartIdData extends PageInfoData {
  eventType: 'cartId'
  eventName: 'vtex:cartId'
  cartId: string
}

export interface HomePageInfo extends PageInfoData {
  eventType: 'homeView'
}

export interface ProductPageInfoData extends PageInfoData {
  eventType: 'productPageInfo'
}

export interface SearchPageInfoData extends PageInfoData {
  eventType:
    | 'internalSiteSearchView'
    | 'categoryView'
    | 'departmentView'
    | 'emptySearchView'
  category?: CategoryMetaData
  department?: DepartmentMetaData
  search?: SearchMetaData
}

interface CategoryMetaData {
  id: string
  name: string
}

interface DepartmentMetaData {
  id: string
  name: string
}

interface SearchMetaData {
  term: string
  category: CategoryMetaData
  results: number
}

export interface PageViewData extends EventData {
  event: 'pageView'
  eventName: 'vtex:pageView'
  pageTitle: string
  pageUrl: string
  referrer: string
}

export interface AddToCartData extends EventData {
  event: 'addToCart'
  eventName: 'vtex:addToCart'
  items: CartItem[]
}

export interface RemoveFromCartData extends EventData {
  event: 'removeFromCart'
  eventName: 'vtex:removeFromCart'
  items: CartItem[]
}

export interface CartChangedData extends EventData {
  event: 'cartChanged'
  eventName: 'vtex:cartChanged'
  items: CartItem[]
}

export interface OrderPlacedData extends Order, EventData {
  event: 'orderPlaced'
  eventName: 'vtex:orderPlaced'
}

export interface OrderPlacedTrackedData extends Order, EventData {
  event: 'orderPlacedTracked'
  eventName: 'vtex:orderPlacedTracked'
}

export interface ProductViewData extends EventData {
  event: 'productView'
  eventName: 'vtex:productView'
  product: Product
  list?: string
}

export interface ProductClickData extends EventData {
  event: 'productClick'
  eventName: 'vtex:productClick'
  product: ProductSummary
  position: number
  list?: string
}

export interface ProductImpressionData extends EventData {
  event: 'productImpression'
  eventName: 'vtex:productImpression'
  impressions: Impression[]
  product?: ProductSummary // deprecated, use impressions list!
  position?: number // deprecated, use impressions list!
  list: string
}

export interface CartLoadedData extends EventData {
  event: 'cartLoaded'
  eventName: 'vtex:cartLoaded'
  orderForm: OrderForm
}

export interface PromoViewData extends EventData {
  event: 'promoView'
  eventType: 'vtex:promoView'
  eventName: 'vtex:promoView'
  promotions: Promotion[]
}

export interface PromotionClickData extends EventData {
  event: 'promotionClick'
  eventType: 'vtex:promotionClick'
  eventName: 'vtex:promotionClick'
  promotions: Promotion[]
}

export interface AddPaymentInfoData extends EventData {
  event: 'addPaymentInfo'
  eventType: 'vtex:addPaymentInfo'
  eventName: 'vtex:addPaymentInfo'
  payment: PaymentType
  items: CartItem[]
}

export interface BeginCheckoutData extends EventData {
  event: 'beginCheckout'
  eventType: 'vtex:beginCheckout'
  eventName: 'vtex:beginCheckout'
  items: CartItem[]
}

export interface AddShippingInfoData extends EventData {
  event: 'addShippingInfo'
  eventType: 'vtex:addShippingInfo'
  eventName: 'vtex:addShippingInfo'
  items: CartItem[]
  shippingTier: string
  value: number
}

export interface ViewCartData extends EventData {
  event: 'viewCart'
  eventType: 'vtex:viewCart'
  eventName: 'vtex:viewCart'
  items: CartItem[]
}

export interface AddToWishlistData extends EventData {
  event: 'addToWishlist'
  eventType: 'vtex:addToWishlist'
  eventName: 'vtex:addToWishlist'
  items: {
    selectedItem: SelectedItem
    product: ProductSummary
  }
  list: string
}

export interface SelectedItem {
  attachments?: any[]
  complementName?: string
  ean?: string
  estimatedDateArrival?: any
  images?: Image[]
  itemId?: string
  kitItems?: any[]
  measurementUnit?: string
  name?: string
  nameComplete?: string
  referenceId?: any
  sellers: Seller[]
  unitMultiplier?: number
  variations?: any[]
  videos?: any[]
}

export interface Seller {
  addToCartLink: string
  commercialOffer: any
  sellerDefault: boolean
  sellerId: string
  sellerName: string
}

export interface Image {
  cacheId: string
  imageId: string
  imageLabel: string
  imageTag: string
  imageText: string
  imageUrl: string
}

export interface RefundData extends EventData {
  event: 'refund'
  eventType: 'vtex:refund'
  eventName: 'vtex:refund'
  order: {
    transactionId: string
    tax: number
    shipping: number
    value: number
    items: ProductOrder[]
  }
}

export interface SearchData extends EventData {
  event: 'search'
  eventType: 'vtex:search'
  eventName: 'vtex:search'
  term: string
}

export interface ShareData extends EventData {
  event: 'share'
  eventType: 'vtex:share'
  eventName: 'vtex:share'
  method: string
  contentType: string
  itemId: string
}

export interface LoginData extends EventData {
  event: 'login'
  eventType: 'vtex:login'
  eventName: 'vtex:login'
  method: string
}

export interface SignUpData extends LoginData, EventData {
  event: 'signUp'
  eventType: 'vtex:signUp'
  eventName: 'vtex:signUp'
}

type PromotionProduct = Pick<ProductSummary, 'productId' | 'productName'>

interface Promotion {
  id?: string
  name?: string
  creative?: string
  position?: string
  products?: PromotionProduct[]
}

interface CartItemAdditionalInfo {
  brandName: string
  brandId: string
}

interface CartItem {
  id: string
  productCategories: Record<string, string> | null
  productCategoryIds?: string
  additionalInfo: CartItemAdditionalInfo | null
  brand: string
  ean: string
  category: string
  detailUrl: string
  imageUrl: string
  name: string
  skuName: string
  price: number
  priceIsInt?: boolean
  sellingPrice: number
  productId: string
  productRefId: string
  quantity: number
  skuId: string
  referenceId: string // SKU reference id
  variant: string
}

interface Totalizer {
  id: string
  name: string
  value: number
}

interface ClientProfileData {
  email: string
  firstName: string
  lastName: string
}

interface Address {
  addressId: string
  postalCode: string
  street: string
  number: string
  neighborhood: string
  complement: string
  city: string
  state: string
}

interface DeliveryOption {
  id: string
  price: number
  estimate: string
  isSelected: boolean
}

interface Shipping {
  selectedAddress: Address
  deliveryOptions?: DeliveryOption[]
}

interface MarketingData {
  coupon: string
  utmCampaign: string
  utmSource: string
  utmMedium: string
  utmiCampaign: string
  utmiPage: string
  utmiPart: string
}

export interface OrderForm {
  id: string
  totalizers: Totalizer[]
  sellers: Seller[]
  salesChannel: string
  items: CartItem[]
  canEditData: boolean
  loggedIn: boolean
  clientProfileData?: ClientProfileData
  shipping?: Shipping
  value: number
  marketingData?: MarketingData
}

export interface Order {
  accountName: string
  corporateName: string
  coupon: string
  currency: string
  openTextField: string
  orderGroup: string
  salesChannel: string
  visitorAddressCity: string
  visitorAddressComplement: string
  visitorAddressCountry: string
  visitorAddressNeighborhood: string
  visitorAddressNumber: string
  visitorAddressPostalCode: string
  visitorAddressState: string
  visitorAddressStreet: string
  visitorContactInfo: string[]
  visitorContactPhone: string
  visitorType: string
  transactionId: string
  transactionDate: string
  transactionAffiliation: string
  transactionTotal: number
  transactionShipping: number
  transactionSubtotal: number
  transactionDiscounts: number
  transactionTax: number
  transactionCurrency: string
  transactionPaymentType: PaymentType[]
  transactionShippingMethod: ShippingMethod[]
  transactionLatestShippingEstimate: Date
  transactionProducts: ProductOrder[]
  transactionPayment: {
    id: string
  }
}

export interface Impression {
  product: ProductSummary
  position: number
}

export interface PaymentType {
  group: string
  paymentSystemName: string
  installments: number
  value: number
}

export interface ShippingMethod {
  itemId: string
  selectedSla: string
}

export interface ProductOrder {
  id: string
  name: string
  sku: string
  skuRefId: string
  skuName: string
  productRefId: string
  ean: string
  slug: string
  brand: string
  brandId: string
  seller: string
  sellerId: string
  category: string
  categoryId: string
  categoryTree: string[]
  categoryIdTree: string[]
  priceTags: PriceTag[]
  originalPrice: number
  price: number
  sellingPrice: number
  tax: number
  quantity: number
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  components: any[]
  measurementUnit: string
  unitMultiplier: number
}

export interface PriceTag {
  identifier: string
  isPercentual: boolean
  value: number
}

export interface Product {
  brand: string
  brandId: string
  categories: string[]
  categoryId: string
  categoryTree: Array<{ id: string; name: string }>
  detailUrl: string
  items: Item[]
  linkText: string
  productId: string
  productName: string
  productReference: string
  selectedSku: Item
}

export interface Item {
  itemId: string
  name: string
  ean: string
  referenceId: { Key: string; Value: string }
  imageUrl: string
  sellers: Seller[]
}

export interface ProductSummary {
  brand: string
  brandId: string
  categories: string[]
  items: ItemSummary[]
  linkText: string
  productId: string
  productName: string
  productReference: string
  selectedItem: SelectedItem
  sku: ItemSummary
}

interface ItemSummary {
  itemId: string
  ean: string
  name: string
  referenceId: { Key: string; Value: string }
  seller: Seller
  sellers: Seller[]
}

export interface Seller {
  commertialOffer: CommertialOffer
  sellerId: string
  sellerDefault: boolean
}

export interface CommertialOffer {
  Price: number
  PriceWithoutDiscount: number
  ListPrice: number
  AvailableQuantity: number
}

export type ProductViewReferenceId = Array<Item['referenceId']>

```


[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 speedy-gtm/react/typings/vtex.render-runtime.d.ts from the speedy-gtm VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* Typings for `render-runtime` */
declare module 'vtex.render-runtime' {
  import { Component, ComponentType, ReactElement, ReactType } from 'react'

  export interface NavigationOptions {
    page: string
    params?: any
  }

  export interface RenderContextProps {
    runtime: {
      navigate: (options: NavigationOptions) => void
    }
  }

  interface ExtensionPointProps {
    id: string
    [key: string]: any
  }

  export const ExtensionPoint: ComponentType<ExtensionPointProps>

  interface ChildBlockProps {
    id: string
  }

  export const ChildBlock: ComponentType<ChildBlockProps>
  export const useChildBlock = function({
    id: string,
  }): Record<string, unknown> {}

  export const Helmet: ReactElement
  export const Link: ReactType
  export const NoSSR: ReactElement
  export const RenderContextConsumer: ReactElement
  export const canUseDOM: boolean
  export const withRuntimeContext: <TOriginalProps extends unknown>(
    Component: ComponentType<TOriginalProps & RenderContextProps>
  ) => ComponentType<TOriginalProps>
}

```


[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 speedy-gtm/react/typings/global.d.ts from the speedy-gtm VTEX app.

[Assistant]
Here's the implementation:

```typescript
interface Window extends Window {
  dataLayer: any[]

  __gtm__: {
    sendGA4Events: string
  }
}

```


[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 speedy-gtm/react/typings/gtm.d.ts from the speedy-gtm VTEX app.

[Assistant]
Here's the implementation:

```typescript
export interface AnalyticsEcommerceProduct {
  id: string
  name: string
  category: string
  brand: string
  variant: string
  price: number
  quantity: number
  dimension1: string
  dimension2: string
  dimension3: string
}

```