[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-quotes-graphql/graphql/schema.graphql from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```graphql
type Query {
  getAppSettings: AppSettings
    @auditAccess
    @cacheControl(scope: PRIVATE, maxAge: SHORT)
  getQuoteEnabledForUser(email: String!): Boolean
    @auditAccess
    @cacheControl(scope: PRIVATE, maxAge: SHORT)
  getQuote(id: String): Quote
    @validateStoreUserAccess
    @withPermissions
    @withSession
    @withSegment
    @cacheControl(scope: PRIVATE, maxAge: SHORT)
  getQuotes(
    organization: [String]
    costCenter: [String]
    status: [String]
    search: String
    page: Int = 1
    pageSize: Int = 25
    sortOrder: String = "DESC"
    sortedBy: String = "lastUpdate"
  ): Quotes
    @validateStoreUserAccess
    @withPermissions
    @withSession
    @withSegment
    @cacheControl(scope: PRIVATE, maxAge: SHORT)
  getChildrenQuotes(
    id: String
    sortOrder: String = "DESC"
    sortedBy: String = "lastUpdate"
  ): [Quote]
    @validateStoreUserAccess
    @withPermissions
    @withSession
    @withSegment
    @cacheControl(scope: PRIVATE, maxAge: SHORT)
  checkSellerQuotes(sellers: [String!]!): [Seller!]!
    @validateStoreUserAccess
    @withPermissions
    @withSession
    @withSegment
    @cacheControl(scope: PRIVATE, maxAge: SHORT)
}

type Mutation {
  createQuote(input: QuoteInput!): String
    @validateStoreUserAccess
    @withPermissions
    @withSession
    @withSegment
  updateQuote(input: QuoteUpdateInput!): String
    @validateStoreUserAccess
    @withPermissions
    @withSession
  useQuote(id: String, orderFormId: String): String
    @validateStoreUserAccess
    @withPermissions
    @withSession
  clearCart(orderFormId: String): String @validateStoreUserAccess
  saveAppSettings(input: AppSettingsInput!): AppSettings
    @validateAdminUserAccess
    @cacheControl(scope: PRIVATE)
}

```


[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-quotes-graphql/graphql/quote.graphql from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```graphql
input QuoteInput {
  referenceName: String
  items: [QuoteItemInput]
  subtotal: Float
  note: String
  sendToSalesRep: Boolean
}

input QuoteItemInput {
  name: String
  skuName: String
  refId: String
  id: String
  productId: String
  imageUrl: String
  listPrice: Float
  price: Float
  quantity: Int
  sellingPrice: Float
  seller: String
}

input QuoteUpdateInput {
  id: String
  items: [QuoteItemInput]
  subtotal: Float
  note: String
  decline: Boolean
  expirationDate: String
}

type Quotes {
  data: [Quote]
  pagination: Pagination
}

type Pagination {
  page: Int
  pageSize: Int
  total: Int
}

type Quote {
  id: String
  referenceName: String
  creatorEmail: String
  creatorName: String
  creatorRole: String
  creationDate: String
  expirationDate: String
  lastUpdate: String
  updateHistory: [QuoteUpdate]
  items: [QuoteItem]
  subtotal: Float
  status: String
  organization: String
  organizationName: String
  costCenter: String
  costCenterName: String
  viewedBySales: Boolean
  viewedByCustomer: Boolean
  salesChannel: String
  seller: String
  sellerName: String
  parentQuote: String
  hasChildren: Boolean
  childrenQuantity: Int
}

type QuoteUpdate {
  email: String
  role: String
  date: String
  status: String
  note: String
}

type QuoteItem {
  name: String
  skuName: String
  refId: String
  id: String
  productId: String
  imageUrl: String
  listPrice: Float
  price: Float
  quantity: Int
  sellingPrice: Float
  seller: String
}

type Seller {
  id: String
  name: 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 b2b-quotes-graphql/graphql/directives.graphql from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```graphql
directive @withSession on FIELD | FIELD_DEFINITION
directive @withPermissions on FIELD | FIELD_DEFINITION
directive @withSegment on FIELD | FIELD_DEFINITION
directive @validateAdminUserAccess on FIELD | FIELD_DEFINITION
directive @validateStoreUserAccess on FIELD | FIELD_DEFINITION
directive @auditAccess on FIELD | FIELD_DEFINITION

```


[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-quotes-graphql/graphql/appSettings.graphql from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```graphql
type AppSettings {
  adminSetup: AdminSetup
}
type AdminSetup {
  cartLifeSpan: Int
  quotesManagedBy: QuotesManagedBy!
}
input AppSettingsInput {
  cartLifeSpan: Int
  quotesManagedBy: QuotesManagedBy
}
enum QuotesManagedBy {
  MARKETPLACE
  SELLER
}
```


[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-quotes-graphql/node/service.json from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "stack": "nodejs",
  "memory": 1024,
  "ttl": 300,
  "timeout": 60,
  "events": {
    "broadcasterOrder": {
      "sender": "vtex.orders-broadcast",
      "topics": ["order-created"]
    }
  },
  "routes": {
    "host": {
      "path": "/b2b-quotes-graphql/_v/0/host",
      "public": true,
      "access": "authorized",
      "policies": [
        {
          "effect": "allow",
          "actions": ["get"]
        }
      ]
    },
    "queueHandler": {
      "path": "/b2b-quotes-graphql/_v/0/process-queue",
      "public": true,
      "access": "authorized",
      "policies": [
        {
          "effect": "allow",
          "actions": ["get"],
          "principals": ["vrn:apps:*:*:*:app/vtex.scheduler@*"]
        }
      ]
    },
    "getSellerQuote": {
      "path": "/b2b-quotes-graphql/_v/0/get-seller-quote/:seller/:id",
      "public": true,
      "access": "authorized",
      "policies": [
        {
          "effect": "allow",
          "actions": ["get"],
          "principals": ["vrn:apps:*:*:*:app/vtex.b2b-seller-quotes@*"]
        }
      ]
    },
    "getSellerQuotesPaginated": {
      "path": "/b2b-quotes-graphql/_v/0/get-seller-quotes-paginated/:seller",
      "public": true,
      "access": "authorized",
      "policies": [
        {
          "effect": "allow",
          "actions": ["get"],
          "principals": ["vrn:apps:*:*:*:app/vtex.b2b-seller-quotes@*"]
        }
      ]
    },
    "saveSellerQuote": {
      "path": "/b2b-quotes-graphql/_v/0/save-seller-quote/:seller/:id",
      "public": true,
      "access": "authorized",
      "policies": [
        {
          "effect": "allow",
          "actions": ["post"],
          "principals": ["vrn:apps:*:*:*:app/vtex.b2b-seller-quotes@*"]
        }
      ]
    }
  }
}

```


[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-quotes-graphql/node/typings.d.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
interface Quote {
  id: string
  referenceName: string
  creatorEmail: string
  creatorName: string
  creatorRole: string
  creationDate: string
  expirationDate: string
  lastUpdate: string
  updateHistory: QuoteUpdate[]
  items: QuoteItem[]
  subtotal: number
  status: string
  organization: string
  costCenter: string
  viewedBySales: boolean
  viewedByCustomer: boolean
  salesChannel: string | null
  seller?: string | null
  sellerName?: string | null
  parentQuote?: string | null
  hasChildren?: boolean | null
  childrenQuantity?: number | null
}

interface QuoteUpdate {
  email: string
  role: string
  date: string
  status: string
  note: string
}

interface QuoteItem {
  name: string
  skuName: string
  refId: string
  id: string
  productId: string
  imageUrl: string
  listPrice: number
  price: number
  quantity: number
  sellingPrice: number
  seller: string
}

interface ReqContext {
  account: string
  workspace: string
  authToken: string
  region: string
  production: boolean
  userAgent: string
}

interface Logger {
  log(
    content: string,
    level: LogLevel,
    details?: Record<string, unknown>
  ): PromiseLike<void>
}

interface OperationState {
  orderFormId: string
  ctx: ReqContext
  data?: OperationData
  logger: Logger
}

interface OperationData {
  orderForm?: any
  userProfileId: string
  cookie: string
}

type ProcessPaymentStep = (
  state: OperationState,
  next: () => Promise<void>
) => Promise<void>

type LogLevel = 'info' | 'error' | 'warning'

interface Timings {
  [middleware: string]: [number, number]
}

declare module '*.json' {
  const value: any
  export default value
}

interface OrderFormConfiguration {
  paymentConfiguration?: PaymentConfiguration
  taxConfiguration?: TaxConfiguration
  minimumQuantityAccumulatedForItems?: number
  decimalDigitsPrecision?: number
  minimumValueAccumulated?: number
  apps?: [App]
  allowMultipleDeliveries?: boolean
  allowManualPrice?: boolean
  maxIntOfWhiteLabelSellers?: number
  maskFirstPurchaseData?: boolean
  recaptchaValidation?: boolean
}

interface Settings {
  adminSetup: {
    cartLifeSpan: number
    allowManualPrice: boolean
    hasCron?: boolean
    cronExpression?: string
    cronWorkspace?: string
    quotesManagedBy?: string
  }
  schemaVersion: string
  schemaHash: string | null
  templateHash: string | null
}

interface SessionData {
  namespaces: {
    profile: {
      id: { value: string }
      email: { value: string }
      firstName: { value: string }
      lastName: { value: string }
    }
    account: {
      accountName: { value: string }
    }
    'storefront-permissions': {
      organization: { value: string }
      costcenter: { value: string }
    }
  }
}

interface SellerQuoteInput {
  items: QuoteItem[]
  subtotal: number
  seller: string
  sellerName: string
}

type SellerQuoteMap = Record<string, SellerQuoteInput>

interface VerifyQuoteSettingsResponse {
  receiveQuotes: boolean
}

interface SellerQuoteNotifyInput {
  quoteId: string
  marketplaceAccount: string
  creationDate: string
}

interface Seller {
  id: string
  name: 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 b2b-quotes-graphql/node/package.json from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "name": "vtex.b2b-quotes-graphql",
  "version": "4.0.1",
  "dependencies": {
    "co-body": "^6.0.0",
    "graphql": "^14.0.0",
    "graphql-tools": "^4.0.0",
    "jsonwebtoken": "^8.5.0",
    "ramda": "^0.25.0",
    "atob": "^2.1.2",
    "axios": "0.27.2",
    "@vtex/api": "6.48.0"
  },
  "devDependencies": {
    "@types/atob": "^2.1.2",
    "@types/bluebird": "^3.5.25",
    "@types/co-body": "0.0.3",
    "@types/jsonwebtoken": "^8.5.0",
    "@types/node": "^12.0.0",
    "@types/ramda": "types/npm-ramda#dist",
    "@vtex/prettier-config": "^0.3.1",
    "tslint": "^5.12.0",
    "tslint-config-prettier": "^1.18.0",
    "tslint-config-vtex": "^2.1.0",
    "typescript": "3.9.7",
    "vtex.b2b-quotes-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.b2b-quotes-graphql@2.0.2/public/@types/vtex.b2b-quotes-graphql",
    "vtex.orders-broadcast": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.orders-broadcast@0.3.0/public/_types/react",
    "vtex.storefront-permissions": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.storefront-permissions@1.23.0/public/@types/vtex.storefront-permissions"
  },
  "scripts": {
    "lint": "tsc --noEmit && tslint -c tslint.json './**/*.ts'"
  }
}

```


[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-quotes-graphql/node/constants.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
export const APP_NAME = 'b2b-quotes-graphql'
export const SCHEMA_VERSION = 'v1.3'
export const QUOTE_DATA_ENTITY = 'quotes'
export const B2B_USER_SCHEMA_VERSION = 'v0.1.2'
export const B2B_USER_DATA_ENTITY = 'b2b_users'
export const CRON_EXPRESSION = '0 */12 * * *'

export const QUOTE_FIELDS = [
  'id',
  'referenceName',
  'creatorEmail',
  'creatorName',
  'creatorRole',
  'creationDate',
  'expirationDate',
  'lastUpdate',
  'updateHistory',
  'items',
  'subtotal',
  'status',
  'organization',
  'costCenter',
  'viewedBySales',
  'viewedByCustomer',
  'salesChannel',
  'seller',
  'sellerName',
  'parentQuote',
  'hasChildren',
  'childrenQuantity',
]

export const routes = {
  addCustomData: ({
    account,
    orderFormId,
    appId,
    property,
  }: {
    account: string
    orderFormId: string
    appId: string
    property?: string
  }) =>
    `${routes.orderForm(account)}/${orderFormId}/customData/${appId}/${
      property ?? ''
    }`,
  addPriceToItems: (account: string, orderFormId: string) =>
    `${routes.orderForm(account)}/${orderFormId}/items/update`,
  addToCart: (account: string, orderFormId: string) =>
    `${routes.orderForm(account)}/${orderFormId}/items/`,
  baseUrl: (account: string) =>
    `http://${account}.vtexcommercestable.com.br/api`,
  checkoutConfig: (account: string) =>
    `${routes.baseUrl(account)}/checkout/pvt/configuration/orderForm`,
  clearCart: (account: string, id: string) =>
    `${routes.orderForm(account)}/${id}/items/removeAll`,
  getQuote: (account: string, id: string) =>
    `${routes.quoteEntity(
      account
    )}/documents/${id}?_fields=id,email,cartName,status,description,items,creationDate,subtotal,discounts,shipping,taxes,total,customData,address`,
  listQuotes: (account: string, email: string) =>
    `${routes.quoteEntity(
      account
    )}/search?email=${email}&_schema=${SCHEMA_VERSION}&_fields=id,email,cartName,status,description,items,creationDate,subtotal,discounts,taxes,shipping,total,customData,address&_sort=creationDate DESC`,
  orderForm: (account: string) =>
    `${routes.baseUrl(account)}/checkout/pub/orderForm`,
  quoteEntity: (account: string) =>
    `${routes.baseUrl(account)}/dataentities/quote`,
  saveSchema: (account: string) =>
    `${routes.quoteEntity(account)}/schemas/${SCHEMA_VERSION}`,
}

export const getAppId = (): string => {
  return process.env.VTEX_APP_ID ?? ''
}

export const schema = {
  properties: {
    costCenter: {
      title: 'Cost Center',
      type: ['null', 'string'],
    },
    creationDate: {
      format: 'date-time',
      title: 'Creation Date',
      type: 'string',
    },
    creatorEmail: {
      title: 'Creator Email',
      type: 'string',
    },
    creatorName: {
      title: 'Creator Name',
      type: ['null', 'string'],
    },
    creatorRole: {
      title: 'Creator Role',
      type: 'string',
    },
    expirationDate: {
      format: 'date-time',
      title: 'Expiration Date',
      type: 'string',
    },
    items: {
      title: 'Cart',
      type: 'array',
    },
    lastUpdate: {
      format: 'date-time',
      title: 'Last Update',
      type: 'string',
    },
    organization: {
      title: 'Organization',
      type: ['null', 'string'],
    },
    referenceName: {
      title: 'Reference Name',
      type: 'string',
    },
    salesChannel: {
      title: 'Sales Channel',
      type: ['null', 'string'],
    },
    status: {
      title: 'Status',
      type: 'string',
    },
    subtotal: {
      title: 'Subtotal',
      type: 'number',
    },
    updateHistory: {
      title: 'Update History',
      type: 'array',
    },
    viewedByCustomer: {
      title: 'Viewed by Customer',
      type: 'boolean',
    },
    viewedBySales: {
      title: 'Viewed by Sales',
      type: 'boolean',
    },
    seller: {
      title: 'Seller',
      type: ['null', 'string'],
    },
    sellerName: {
      title: 'Seller Name',
      type: ['null', 'string'],
    },
    parentQuote: {
      title: 'Parent quote',
      type: ['null', 'string'],
    },
    hasChildren: {
      title: 'Has children',
      type: ['null', 'boolean'],
    },
    childrenQuantity: {
      title: 'Children Quantity',
      type: ['null', 'number'],
    },
  },
  'v-cache': false,
  'v-default-fields': [
    'referenceName',
    'creatorEmail',
    'creatorName',
    'creationDate',
    'expirationDate',
    'lastUpdate',
    'items',
    'subtotal',
    'status',
    'seller',
    'sellerName',
    'parentQuote',
    'hasChildren',
    'childrenQuantity',
  ],
  'v-immediate-indexing': true,
  'v-indexed': [
    'creatorEmail',
    'creatorName',
    'creationDate',
    'expirationDate',
    'lastUpdate',
    'referenceName',
    'status',
    'organization',
    'costCenter',
    'salesChannel',
    'seller',
    'sellerName',
    'parentQuote',
    'hasChildren',
  ],
}

```


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

[Assistant]
Here's the implementation:

```typescript
import type {
  ClientsConfig,
  EventContext,
  IOContext,
  ParamsContext,
  RecorderState,
  ServiceContext,
} from '@vtex/api'
import { LRUCache, Service } from '@vtex/api'

import { Clients } from './clients'
import { orderHandler } from './middlewares/order'
import { resolvers } from './resolvers'
import { schemaDirectives } from './resolvers/directives'
import type SellerQuotesController from './resolvers/utils/sellerQuotesController'

const TIMEOUT_MS = 5000

// Create a LRU memory cache for the Status client.
// The @vtex/api HttpClient respects Cache-Control headers and uses the provided cache.
const memoryCache = new LRUCache<string, any>({ max: 5000 })

metrics.trackCache('status', memoryCache)

// This is the configuration for clients available in `ctx.clients`.
const clients: ClientsConfig<Clients> = {
  // We pass our custom implementation of the clients bag, containing the Status client.
  implementation: Clients,
  options: {
    // All IO Clients will be initialized with these options, unless otherwise specified.
    default: {
      retries: 2,
      timeout: TIMEOUT_MS,
    },
    // This key will be merged with the default options and add this cache to our Status client.
    status: {
      memoryCache,
    },
  },
}

declare global {
  // We declare a global Context type just to avoid re-writing ServiceContext<Clients, State> in every handler and resolver
  type Context = ServiceContext<Clients> & {
    vtex: IOContext & {
      sellerQuotesController?: SellerQuotesController
    }
  }

  type NextFn = () => Promise<unknown>

  // The shape of our State object found in `ctx.state`. This is used as state bag to communicate between middlewares.
  interface State {
    code: number
  }
  type EventBroadcastContext = EventContext<Clients, RecorderState>
}
// Export a service that defines route handlers and client options.
export default new Service<Clients, RecorderState, ParamsContext>({
  clients,
  events: {
    broadcasterOrder: [orderHandler],
  },
  graphql: {
    resolvers: {
      Mutation: resolvers.Mutation,
      Query: resolvers.Query,
      Quote: resolvers.Quote,
    },
    schemaDirectives,
  },
  routes: resolvers.Routes,
})

```


[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-quotes-graphql/node/metrics/createQuote.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { Metric } from '../clients/analytics'
import { B2B_METRIC_NAME } from '../clients/analytics'

type UserData = {
  orgId: string
  costId: string
  roleId: string
}

type CreateQuoteMetricParam = {
  sessionData: SessionData
  sendToSalesRep: boolean
  userData: UserData
  quoteId: string
  quoteReferenceName: string
  creationDate: string
}

type CreateQuoteFieldsMetric = {
  cost_center_id: string
  buyer_org_id: string
  member_email: string
  role: string
  creation_date: string
  quote_id: string
  quote_reference_name: string
  send_to_sales_rep: boolean
}

export class CreateQuoteMetric implements Metric {
  public readonly description: string
  public readonly kind: string
  public readonly account: string
  public readonly fields: CreateQuoteFieldsMetric
  public readonly name = B2B_METRIC_NAME

  constructor(account: string, fields: CreateQuoteFieldsMetric) {
    this.account = account
    this.fields = fields
    this.kind = 'create-quote-graphql-event'
    this.description = 'Create Quotation Action - Graphql'
  }
}

const buildQuoteMetric = (
  metricsParam: CreateQuoteMetricParam
): CreateQuoteMetric => {
  const { namespaces } = metricsParam.sessionData
  const accountName = namespaces?.account?.accountName?.value
  const userEmail = namespaces?.profile?.email?.value

  return new CreateQuoteMetric(accountName, {
    buyer_org_id: metricsParam.userData?.orgId,
    cost_center_id: metricsParam.userData?.costId,
    member_email: userEmail,
    role: metricsParam.userData?.roleId,
    creation_date: metricsParam.creationDate,
    quote_id: metricsParam.quoteId,
    quote_reference_name: metricsParam.quoteReferenceName,
    send_to_sales_rep: metricsParam.sendToSalesRep,
  })
}

export const sendCreateQuoteMetric = async (
  ctx: Context,
  metricsParam: CreateQuoteMetricParam
) => {
  const {
    clients: { analytics },
  } = ctx

  try {
    const metric = buildQuoteMetric(metricsParam)

    await analytics.sendMetric(metric)
  } catch (error) {
    console.warn('Unable to log Create Quote Metrics', error)
  }
}

```


[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-quotes-graphql/node/metrics/sendMessage.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { Metric } from '../clients/analytics'
import { B2B_METRIC_NAME } from '../clients/analytics'

type Quote = {
  costCenter: string
  id: string
  organization: string
}

type SendMessageMetricParam = {
  quote: Quote
  account: string
  sentTo: string
  templateName: string
}

type SendMessageFieldsMetric = {
  cost_center_name: string
  quote_id: string
  buyer_org_name: string
  template_name: string
  sent_to: string
  sent_date: string
}

export class SendMessageMetric implements Metric {
  public readonly description: string
  public readonly kind: string
  public readonly account: string
  public readonly fields: SendMessageFieldsMetric
  public readonly name = B2B_METRIC_NAME

  constructor(account: string, fields: SendMessageFieldsMetric) {
    this.account = account
    this.fields = fields
    this.kind = 'send-message-graphql-event'
    this.description = 'Send Message Action - Graphql'
  }
}

const buildSendMessageMetric = (
  metricParam: SendMessageMetricParam
): SendMessageMetric => {
  return new SendMessageMetric(metricParam.account, {
    buyer_org_name: metricParam.quote?.organization,
    cost_center_name: metricParam.quote?.costCenter,
    quote_id: metricParam.quote?.id,
    template_name: metricParam.templateName,
    sent_to: metricParam.sentTo,
    sent_date: new Date().toISOString(),
  })
}

export const sendMessageMetric = async (
  ctx: Context,
  metricsParam: SendMessageMetricParam
) => {
  const {
    clients: { analytics },
  } = ctx

  try {
    const metric = buildSendMessageMetric(metricsParam)

    await analytics.sendMetric(metric)
  } catch (error) {
    console.warn('Unable to log Send Quote Message Metrics', error)
  }
}

```


[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-quotes-graphql/node/metrics/useQuote.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { Metric } from '../clients/analytics'
import { B2B_METRIC_NAME } from '../clients/analytics'

type UseQuoteFieldsMetric = {
  quote_id: string
  quote_reference_name: string
  order_form_id: string
  quote_creation_date: string
  quote_use_date: string
  creator_email: string
  user_email: string
  cost_center_id: string
  buyer_org_id: string
  quote_last_update: string
}

export class UseQuoteMetric implements Metric {
  public readonly description: string
  public readonly kind: string
  public readonly account: string
  public readonly fields: UseQuoteFieldsMetric
  public readonly name = B2B_METRIC_NAME

  constructor(account: string, fields: UseQuoteFieldsMetric) {
    this.account = account
    this.fields = fields
    this.kind = 'use-quote-graphql-event'
    this.description = 'Use Quotation Action - Graphql'
  }
}

export type UseQuoteMetricsParams = {
  quote: Quote
  orderFormId: string
  account: string
  userEmail: string
}

const buildUseQuoteMetric = (
  metricsParam: UseQuoteMetricsParams
): UseQuoteMetric => {
  const { quote, orderFormId, account, userEmail } = metricsParam

  return new UseQuoteMetric(account, {
    buyer_org_id: quote.organization,
    cost_center_id: quote.costCenter,
    quote_id: quote.id,
    quote_reference_name: quote.referenceName,
    order_form_id: orderFormId,
    quote_creation_date: quote.creationDate,
    quote_use_date: new Date().toISOString(),
    creator_email: quote.creatorEmail,
    user_email: userEmail,
    quote_last_update: quote.lastUpdate,
  })
}

export const sendUseQuoteMetric = async (
  ctx: Context,
  metricsParam: UseQuoteMetricsParams
) => {
  const {
    clients: { analytics },
  } = ctx

  try {
    const metric = buildUseQuoteMetric(metricsParam)

    await analytics.sendMetric(metric)
  } catch (error) {
    console.warn('Unable to log metrics', error)
  }
}

```


[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-quotes-graphql/node/metrics/auth.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { Logger } from '@vtex/api/lib/service/logger/logger'

import type { Metric } from '../clients/analytics'
import { B2B_METRIC_NAME } from '../clients/analytics'

export interface AuthAuditMetric {
  operation: string
  forwardedHost: string
  caller: string
  userAgent: string
  role?: string
  permissions?: string[]
  hasAdminToken?: boolean
  hasValidAdminToken?: boolean
  hasStoreToken?: boolean
  hasCurrentValidStoreToken?: boolean
  hasValidStoreToken?: boolean
  hasApiToken?: boolean
  hasValidApiToken?: boolean
  hasAdminTokenOnHeader?: boolean
  hasValidAdminTokenOnHeader?: boolean
}

export class AuthMetric implements Metric {
  public readonly description: string
  public readonly kind: string
  public readonly account: string
  public readonly fields: AuthAuditMetric
  public readonly name = B2B_METRIC_NAME

  constructor(account: string, fields: AuthAuditMetric, description?: string) {
    this.account = account
    this.fields = fields
    this.kind = 'b2b-quotes-graphql-auth-event'
    this.description = description ?? 'Auth metric event'
  }
}

const sendAuthMetric = async (
  ctx: Context,
  logger: Logger,
  authMetric: AuthMetric
) => {
  const {
    clients: { analytics },
  } = ctx

  try {
    await analytics.sendMetric(authMetric)
  } catch (error) {
    logger.error({
      error,
      message: `Error to send metrics from auth metric`,
    })
  }
}

export default sendAuthMetric

```


[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-quotes-graphql/node/clients/SellerQuotesClient.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

const SELLER_CLIENT_OPTIONS: InstanceOptions = {
  retries: 5,
  timeout: 5000,
  exponentialTimeoutCoefficient: 2,
  exponentialBackoffCoefficient: 2,
  initialBackoffDelay: 100,
}

const BASE_PATH = 'b2b-seller-quotes/_v/0'

const ROUTES = {
  verifyQuoteSettings: 'verify-quote-settings',
  notifyNewQuote: 'notify-new-quote',
}

export default class SellerQuotesClient extends ExternalClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super('', ctx, {
      ...options,
      ...SELLER_CLIENT_OPTIONS,
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
        VtexIdclientAutCookie: ctx.authToken,
      },
    })
  }

  private getRoute(sellerAccount: string, path: string) {
    const subdomain = this.context.production
      ? sellerAccount
      : `${this.context.workspace}--${sellerAccount}`

    return `http://${subdomain}.myvtex.com/${BASE_PATH}/${path}`
  }

  public async verifyQuoteSettings(sellerAccount: string) {
    return this.http.get<VerifyQuoteSettingsResponse>(
      this.getRoute(sellerAccount, ROUTES.verifyQuoteSettings)
    )
  }

  public async notifyNewQuote(
    sellerAccount: string,
    quoteId: string,
    creationDate: string
  ) {
    const payload: SellerQuoteNotifyInput = {
      quoteId,
      creationDate,
      marketplaceAccount: this.context.account,
    }

    return this.http.postRaw(
      this.getRoute(sellerAccount, ROUTES.notifyNewQuote),
      payload
    )
  }
}

```


[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-quotes-graphql/node/clients/storefrontPermissions.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { AppGraphQLClient } from '@vtex/api'

import { getTokenToHeader } from './index'

export const QUERIES = {
  getPermission: `query permissions {
    checkUserPermission {
      role {
        id
        name
        slug
      }
      permissions
    }
  }`,
  getRole: `query role($id: ID!) {
    getRole(id: $id) {
      id
      name
      slug
    }
  }`,
  getUser: `query user($id: ID!) {
    getUser(id: $id) {
      id
      roleId
      userId
      clId
      orgId
      costId
      name
      email
      canImpersonate
    }
  }`,
  listRoles: `query roles {
    listRoles {
      id
      name
      slug
    }
  }`,
  getOrgSalesAdminEmail: `query users($organizationId: ID, $roleId: ID) {
    listUsersPaginated(organizationId: $organizationId, roleId: $roleId) {
      data {
        email
      }
    }
  }`,
}

export default class StorefrontPermissions extends AppGraphQLClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super('vtex.storefront-permissions@3.x', ctx, options)
  }

  public checkUserPermission = async (): Promise<any> => {
    return this.query({
      extensions: this.getPersistedQuery(),
      query: QUERIES.getPermission,
      variables: {},
    })
  }

  public listRoles = async (): Promise<any> => {
    return this.query({
      extensions: this.getPersistedQuery(),
      query: QUERIES.listRoles,
      variables: {},
    })
  }

  public getOrgSalesAdminEmail = async ({
    roleId,
    organizationId,
  }: {
    roleId: string
    organizationId?: string
  }): Promise<any> => {
    return this.query({
      extensions: this.getPersistedQuery(),
      query: QUERIES.getOrgSalesAdminEmail,
      variables: {
        roleId,
        ...(organizationId && { organizationId }),
      },
    })
  }

  private readonly getPersistedQuery = () => {
    return {
      persistedQuery: {
        provider: 'vtex.storefront-permissions@3.x',
        sender: 'vtex.b2b-quotes@3.x',
      },
    }
  }

  private readonly query = async (param: {
    query: string
    variables: any
    extensions: any
  }): Promise<any> => {
    const { query, variables, extensions } = param

    return this.graphql.query(
      {
        extensions,
        query,
        variables,
      },
      {
        headers: getTokenToHeader(this.context),
        params: {
          locale: this.context.locale,
        },
      }
    )
  }
}

```


[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-quotes-graphql/node/clients/analytics.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

const ENDPOINT = 'http://rc.vtex.com'
const METRICS_PATH = '/api/analytics/schemaless-events'
const MAX_RETRIES = 2
const RETRY_INTERVAL = 1000 // 1 second

export const B2B_METRIC_NAME = 'b2b-suite-buyerorg-data'

export interface Metric {
  readonly account: string
  readonly kind: string
  readonly description: string
  readonly name: typeof B2B_METRIC_NAME
}

export default class AnalyticsClient extends ExternalClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super(ENDPOINT, context, {
      ...options,
      headers: {
        'X-Vtex-Use-Https': 'true',
      },
    })
  }

  public async sendMetric(metric: Metric, retries = 0) {
    try {
      await this.http.post(METRICS_PATH, metric)
    } catch (error) {
      if (retries < MAX_RETRIES) {
        await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL))
        await this.sendMetric(metric, retries + 1)
      } else {
        throw new Error(`${error.message} - after ${MAX_RETRIES} retries.`)
      }
    }
  }
}

```


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

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { AppGraphQLClient } from '@vtex/api'

import { getTokenToHeader } from './index'

export default class Organizations extends AppGraphQLClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super('vtex.b2b-organizations-graphql@2.x', ctx, options)
  }

  public getOrganizationById = async (id: string): Promise<any> => {
    const graphQLQuery = `query GetOrganizationById($id: ID!) {
        getOrganizationById(id: $id) {
            name
        }
      }
      `

    return this.query({
      extensions: this.getPersistedQuery(),
      query: graphQLQuery,
      variables: {
        id,
      },
    })
  }

  public getCostCenterById = async (id: string): Promise<any> => {
    const graphQLQuery = `query GetCostCenterById($id: ID!) {
        getCostCenterById(id: $id) {
            name
        }
      }
      `

    return this.query({
      extensions: this.getPersistedQuery(),
      query: graphQLQuery,
      variables: {
        id,
      },
    })
  }

  private readonly getPersistedQuery = () => {
    return {
      persistedQuery: {
        provider: 'vtex.b2b-organizations-graphql@2.x',
        sender: 'vtex.b2b-quotes@3.x',
      },
    }
  }

  private readonly query = async (param: {
    query: string
    variables: any
    extensions: any
  }): Promise<any> => {
    const { query, variables, extensions } = param

    return this.graphql.query(
      {
        extensions,
        query,
        variables,
      },
      {
        headers: getTokenToHeader(this.context),
        params: {
          locale: this.context.locale,
        },
      }
    )
  }
}

```


[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-quotes-graphql/node/clients/HostClient.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { JanusClient } from '@vtex/api'

export default class HostClient extends JanusClient {
  private readonly ctx: IOContext
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super(ctx, {
      ...options,
      headers: {
        ...(ctx.adminUserAuthToken
          ? {
              VtexIdclientAutCookie: ctx.adminUserAuthToken,
            }
          : {}),
      },
    })
    this.ctx = ctx
  }

  public getHost = async (): Promise<any> => {
    const { account, workspace } = this.ctx
    const link = `http://${workspace}--${account}.myvtex.com/b2b-quotes-graphql/_v/0/host`

    return this.http.get(link)
  }
}

```


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

[User] Show me the implementation of b2b-quotes-graphql/node/clients/checkout.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type {
  InstanceOptions,
  IOContext,
  RequestTracingConfig,
} from '@vtex/api'
import { JanusClient } from '@vtex/api'

import { APP_NAME } from '../constants'
import { createTracing } from '../utils/index'

const CHECKOUT_ENDPOINT = 'api/checkout/pvt/configuration/orderForm'

export default class Checkout extends JanusClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super(ctx, {
      ...options,
      headers: {
        ...(ctx.adminUserAuthToken
          ? {
              VtexIdclientAutCookie: ctx.adminUserAuthToken,
            }
          : {}),
      },
    })
  }

  public getOrderFormConfiguration(tracingConfig?: RequestTracingConfig) {
    const metric = `${APP_NAME}-getOrderForm`

    return this.http.get<OrderFormConfiguration>(CHECKOUT_ENDPOINT, {
      metric,
      tracing: createTracing(metric, tracingConfig),
    })
  }

  public setOrderFormConfiguration(
    body: OrderFormConfiguration,
    userToken: string,
    tracingConfig?: RequestTracingConfig
  ) {
    const metric = `${APP_NAME}-setOrderForm`

    return this.http.post(CHECKOUT_ENDPOINT, body, {
      headers: {
        VtexIdclientAutCookie: userToken,
      },
      metric,
      tracing: createTracing(metric, tracingConfig),
    })
  }
}

```


[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-quotes-graphql/node/clients/email.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { JanusClient } from '@vtex/api'

const MAIL_SERVICE_PATH = '/api/mail-service/pvt/sendmail'
const TEMPLATE_RENDER_PATH = '/api/template-render/pvt/templates'

export default class MailClient extends JanusClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super(context, {
      ...options,
      headers: {
        VtexIdClientAutCookie: context.authToken,
      },
    })
  }

  public async sendMail(mailData: MailData): Promise<string> {
    return this.http.post(MAIL_SERVICE_PATH, mailData, {
      metric: 'mail-post-send',
    })
  }

  public getTemplate(name: string) {
    return this.http.get<Template>(`${TEMPLATE_RENDER_PATH}/${name}`, {
      metric: 'mail-get-template',
    })
  }

  public publishTemplate(template: Template) {
    return this.http.post(TEMPLATE_RENDER_PATH, template, {
      metric: 'mail-post-template',
    })
  }
}

export interface MailData {
  templateName: string
  jsonData: JsonData
}

export interface JsonData {
  message: {
    to: string
  }
  quote: JsonDataQuote
}

export type JsonDataQuote = {
  name: string
  id: string
  link: string
  organization: string
  costCenter: string
  lastUpdate: {
    email: string
    status: string
    note?: string
  }
  expired?: boolean
}

interface Template {
  AccountId?: string
  AccountName?: string
  ApplicationId?: string
  Description?: string
  FriendlyName: string
  IsDefaultTemplate: boolean
  IsPersisted: boolean
  IsRemoved: boolean
  Name: string
  Type: string
  Templates: {
    email: {
      To: string
      CC?: string
      BCC?: string
      Subject: string
      Message: string
      Type: string
      ProviderId: string
      ProviderName?: string
      IsActive: boolean
      withError: boolean
    }
    sms: {
      Type: string
      ProviderId?: string
      ProviderName?: string
      IsActive: boolean
      withError: boolean
      Parameters: 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 b2b-quotes-graphql/node/clients/vtexId.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

interface AuthenticatedUser {
  user: string
}

export default class VtexId extends ExternalClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super('http://vtexid.vtex.com.br/api/', context, options)
  }

  public async getAuthenticatedUser(
    authToken: string
  ): Promise<AuthenticatedUser> {
    return this.http.get('vtexid/pub/authenticated/user/', {
      metric: 'authenticated-user-get',
      params: { authToken },
    })
  }
}

```


[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-quotes-graphql/node/clients/OrdersClient.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

const FOUR_SECONDS = 4 * 1000

export default class OrdersClient extends ExternalClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super(`http://${ctx.account}.vtexcommercestable.com.br`, ctx, {
      ...options,
      timeout: FOUR_SECONDS,
    })
  }

  public getOrder = (orderId: string): Promise<Order> =>
    this.http.get(`/api/oms/pvt/admin/orders/${orderId}`, {
      headers: {
        VtexIdclientAutCookie: this.context.authToken,
      },
      metric: 'ordersClient-getOrder',
    })
}

```


[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-quotes-graphql/node/clients/LMClient.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { InstanceOptions, IOContext } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

export default class LMClient extends ExternalClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super(`http://${ctx.account}.vtexcommercestable.com.br/`, ctx, {
      ...options,
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
        VtexIdclientAutCookie: ctx.authToken,
      },
    })
  }

  public getUserAdminPermissions = async (account: string, userId: string) => {
    return this.get(
      `/api/license-manager/pvt/accounts/${encodeURI(
        account
      )}/logins/${encodeURI(userId)}/granted`
    ).then((res: any) => {
      return res
    })
  }

  protected get = <T>(url: string) => {
    return this.http.get<T>(url)
  }
}

```


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

[Assistant]
Here's the implementation:

```typescript
import type { IOContext } from '@vtex/api'
import { IOClients } from '@vtex/api'

import AnalyticsClient from './analytics'
import RequestHub from '../utils/Hub'
import Identity from '../utils/Identity'
import { Scheduler } from '../utils/Scheduler'
import Checkout from './checkout'
import LMClient from './LMClient'
import MailClient from './email'
import HostClient from './HostClient'
import OrdersClient from './OrdersClient'
import Organizations from './organizations'
import StorefrontPermissions from './storefrontPermissions'
import VtexId from './vtexId'
import SellerQuotesClient from './SellerQuotesClient'
import SellerClient from './SellerClient'

export const getTokenToHeader = (ctx: IOContext) => {
  // provide authToken (app token) as an admin token as this is a call
  // between b2b suite apps and no further token validation is needed
  const adminToken = ctx.authToken
  const userToken = ctx.storeUserAuthToken ?? null
  const { sessionToken, account } = ctx

  let allCookies = `VtexIdclientAutCookie=${adminToken}`

  if (userToken) {
    allCookies += `; VtexIdclientAutCookie_${account}=${userToken}`
  }

  return {
    'x-vtex-credential': ctx.authToken,
    VtexIdclientAutCookie: adminToken,
    cookie: allCookies,
    ...(sessionToken && {
      'x-vtex-session': sessionToken,
    }),
  }
}

// Extend the default IOClients implementation with our own custom clients.
export class Clients extends IOClients {
  public get analytics() {
    return this.getOrSet('analytics', AnalyticsClient)
  }

  public get hub() {
    return this.getOrSet('hub', RequestHub)
  }

  public get vtexId() {
    return this.getOrSet('vtexId', VtexId)
  }

  public get checkout() {
    return this.getOrSet('checkout', Checkout)
  }

  public get lm() {
    return this.getOrSet('lm', LMClient)
  }

  public get mail() {
    return this.getOrSet('mail', MailClient)
  }

  public get organizations() {
    return this.getOrSet('organizations', Organizations)
  }

  public get storefrontPermissions() {
    return this.getOrSet('storefrontPermissions', StorefrontPermissions)
  }

  public get scheduler() {
    return this.getOrSet('scheduler', Scheduler)
  }

  public get orders() {
    return this.getOrSet('orders', OrdersClient)
  }

  public get host() {
    return this.getOrSet('host', HostClient)
  }

  public get identity() {
    return this.getOrSet('identity', Identity)
  }

  public get sellerQuotes() {
    return this.getOrSet('sellerQuotes', SellerQuotesClient)
  }

  public get seller() {
    return this.getOrSet('seller', SellerClient)
  }
}

```


[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-quotes-graphql/node/clients/SellerClient.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { IOContext, InstanceOptions } from '@vtex/api'
import { JanusClient } from '@vtex/api'

export default class SellerClient extends JanusClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super(context, {
      ...options,
      headers: {
        ...options?.headers,
        VtexIdclientAutcookie: context.authToken,
      },
    })
  }

  public async getSeller(sellerId: string) {
    return this.http.get<Seller>(`/api/seller-register/pvt/sellers/${sellerId}`)
  }
}

```


[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-quotes-graphql/node/typings/orders.d.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
interface Order {
  followUpEmail?: string
  orderId: string
  clientProfileData: ClientProfileData
  creationDate: string
  items: OrderItem[]
  value: number
  totals: Total[]
  status: string
  statusDescription: string
  sequence: string
  salesChannel: string
  affiliateId: string | null
  origin: string | null
  authorizedDate: string | null
  invoicedDate: string | null
  marketingData?: MarketingData
  shippingData: ShippingData
  paymentData: PaymentData
  checkedInPickupPointId: number
  fulfillment?: Fulfillment
  customData: { customApps: any[] }
}

interface ClientProfileData {
  email: string
  firstName: string
  lastName: string
  documentType: string
  document: string
  phone: string
  corporateName: string | null
  tradeName: string | null
  corporateDocument: string | null
  stateInscription: string | null
  corporatePhone: string | null
  isCorporate: boolean
  userProfileId: string | null
}

interface OrderItem {
  seller: string
  quantity: number
  description: string | null
  ean: string | null
  refId: string | null
  id: string
  productId: string | null
  sellingPrice: number
  listPrice: number
  price: number
  imageUrl: string | null
  skuName: string | null
  name: string
  bundleItems: Bundle[]
  additionalInfo: ItemAdditionalInfo
}

interface Bundle {
  id: string
  attachments: Attachment[]
  name: string
  price: number
  quantity: number
  imageUrl: string | null
  measurementUnit: string
  unitMultiplier: number
}

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

interface MarketingData {
  coupon: 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 b2b-quotes-graphql/node/middlewares/order.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { QUOTE_DATA_ENTITY, QUOTE_FIELDS, SCHEMA_VERSION } from '../constants'
import SellerQuotesController from '../resolvers/utils/sellerQuotesController'
import { isEmail, NO_REPLY_EMAIL } from '../utils'
import message from '../utils/message'

export async function orderHandler(
  ctx: EventBroadcastContext,
  next: () => Promise<unknown>
) {
  const {
    clients: { orders, masterdata, host: hostClient },
    vtex: { logger },
    body,
  } = ctx

  let order: Order = {} as Order

  if (body.currentState !== 'order-created') {
    return next()
  }

  const { host } = await hostClient.getHost()

  ctx.vtex.host = host
  let id

  try {
    order = await orders.getOrder(body.orderId)
    if (!order || !order.status || !order.items) {
      return
    }

    // update to placed order
    const { customData } = order
    const index = customData?.customApps?.findIndex(
      (item) => item.id === 'b2b-quotes-graphql'
    )

    if (index !== -1 && customData && customData.customApps) {
      const { quoteId } = customData?.customApps[index].fields

      id = quoteId

      if (quoteId && quoteId.length > 1) {
        const item = (await masterdata.getDocument({
          dataEntity: QUOTE_DATA_ENTITY,
          fields: QUOTE_FIELDS,
          id: quoteId,
        })) as Quote

        const quotes: Quote[] = []

        quotes.push(item)

        if (item.hasChildren) {
          const sellerQuotesController = new SellerQuotesController(ctx)
          const childrenQuotes = await sellerQuotesController.getAllChildrenQuotes(
            quoteId
          )

          quotes.push(...childrenQuotes)
        }

        await Promise.all(
          quotes.map((quote) => processQuote(ctx, order, quote))
        )
      }
    }
  } catch (error) {
    logger.error({
      data: ctx.body,
      error,
      message: 'OrderHandler-updateQuoteStatusError',
      id,
    })
  }

  return next()
}

async function processQuote(
  ctx: EventBroadcastContext,
  order: Order,
  quote: Quote
) {
  const { orderId } = order

  const mustAddUpdateHistory =
    !quote.seller ||
    (quote.seller && order.items.some((item) => item.seller === quote.seller))

  if (mustAddUpdateHistory) {
    quote.updateHistory.push({
      date: new Date().toISOString(),
      email: NO_REPLY_EMAIL,
      note: `Order ID: ${orderId}`,
      role: 'order-notification-system',
      status: 'placed',
    })
  }

  const quoteUpdated = {
    ...quote,
    status: 'placed',
    updateHistory: quote.updateHistory,
  }

  await ctx.clients.masterdata.updateEntireDocument({
    dataEntity: QUOTE_DATA_ENTITY,
    fields: quoteUpdated,
    id: quote.id,
    schema: SCHEMA_VERSION,
  })

  // just users from single or children quotes need to be notified
  if (quote.hasChildren) return

  const users = quoteUpdated.updateHistory.map((anUpdate) => anUpdate.email)

  const uniqueUsers = [
    ...new Set(users.filter((userEmail: string) => isEmail.test(userEmail))),
  ]

  message(ctx)
    .quoteUpdated({
      costCenter: quoteUpdated.costCenter,
      id: quote.id,
      lastUpdate: {
        email: 'order-notification-system',
        note: `Order ID: ${orderId}`,
        status: 'PLACED',
      },
      name: quoteUpdated.referenceName,
      orderId,
      organization: quoteUpdated.organization,
      templateName: 'quote-order-placed',
      users: uniqueUsers,
    })
    .then(() => {
      ctx.vtex.logger.info({
        message: `[Quote placed] E-mail sent ${uniqueUsers.join(', ')}`,
      })
    })
}

```


[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-quotes-graphql/node/utils/Identity.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { JanusClient } from '@vtex/api'

export default class Identity extends JanusClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super(ctx, {
      ...options,
      headers: {
        ...options?.headers,
      },
    })
  }

  public async validateToken(token: string): Promise<any> {
    return this.http.post('/api/vtexid/credential/validate', { token })
  }

  public async getToken({
    appkey,
    apptoken,
  }: {
    appkey: string
    apptoken: string
  }): Promise<any> {
    return this.http.post('/api/vtexid/apptoken/login', { appkey, apptoken })
  }
}

```


[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-quotes-graphql/node/utils/readFile.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import fs from 'fs'
import path from 'path'

const readFile = (filePath: string) =>
  fs.readFileSync(path.resolve(__dirname, filePath), 'utf8')

export default readFile

```


[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-quotes-graphql/node/utils/Scheduler.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { JanusClient } from '@vtex/api'
import type { InstanceOptions, IOContext } from '@vtex/api'

const VERSION = 4

const routes = {
  getQueueSchedule: (workspace: string, account: string) =>
    `/api/scheduler/${workspace}/${account}/b2b-quotes-queue-schedule?version=${VERSION}`,
  getSyncSchedule: (workspace: string, account: string) =>
    `/api/scheduler/${workspace}/${account}/b2b-quotes-sync-schedule?version=${VERSION}`,
  scheduler: (workspace: string, account: string) =>
    `/api/scheduler/${workspace}/${account}?version=${VERSION}`,
}

export class Scheduler extends JanusClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super(ctx, {
      ...options,
      headers: {
        ...options?.headers,
        VtexIdclientAutCookie: ctx.authToken,
      },
    })
  }

  public createOrUpdate = (scheduler: any) => {
    return this.http.put(
      routes.scheduler(this.context.workspace, this.context.account),
      scheduler,
      {
        metric: 'b2b-quotes-scheduler-createupdate',
      }
    )
  }

  public getQueue = () => {
    return this.http.get(
      routes.getQueueSchedule(this.context.workspace, this.context.account),
      {
        metric: 'b2b-quotes-scheduler-get',
      }
    )
  }

  public deleteQueue = () => {
    return this.http.delete(
      routes.getQueueSchedule(this.context.workspace, this.context.account),
      {
        metric: 'b2b-quotes-scheduler-delete',
      }
    )
  }

  public getSync = () => {
    return this.http.get(
      routes.getSyncSchedule(this.context.workspace, this.context.account),
      {
        metric: 'b2b-quotes-scheduler-get',
      }
    )
  }

  public deleteSync = () => {
    return this.http.delete(
      routes.getSyncSchedule(this.context.workspace, this.context.account),
      {
        metric: 'b2b-quotes-scheduler-delete',
      }
    )
  }
}

```


[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-quotes-graphql/node/utils/Hub.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

export default class RequestHub extends ExternalClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super(
      `http://${context.account}.vtexcommercestable.com.br/api/`,
      context,
      options
    )
  }

  public authToken() {
    return this.context.authToken
  }

  public account() {
    return this.context.account
  }

  public get(url: string, headers: any, data?: any) {
    return this.http.getRaw(url, {
      data,
      headers,
    })
  }

  public post(url: string, data: any, headers?: any) {
    return this.http.postRaw(url, data, {
      headers,
    })
  }

  public put(url: string, data: any, headers?: any) {
    return this.http.putRaw(url, data, {
      headers,
    })
  }

  public delete(url: string) {
    return this.http.delete(url)
  }
}

```


[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-quotes-graphql/node/utils/message.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { JsonDataQuote } from '../clients/email'
import type MailClient from '../clients/email'
import type StorefrontPermissions from '../clients/storefrontPermissions'
import { sendMessageMetric } from '../metrics/sendMessage'

interface QuoteUpdate {
  email: string
  status: string
  note: string
}

// As this is currently used only to get the sales-admin users to
// send an email notification when a quote is created, we only get
// the first page of users (25) and return them.
const getOrgSalesAdminEmail = async (
  storefrontPermissions: StorefrontPermissions,
  organizationId?: string
) => {
  const {
    data: { listRoles },
  }: any = await storefrontPermissions.listRoles()

  const role = listRoles.find((r: any) => r.slug === 'sales-admin')

  if (!role) {
    return []
  }

  const {
    data: { listUsersPaginated },
  }: any = await storefrontPermissions.getOrgSalesAdminEmail({
    roleId: role.id,
    ...(organizationId && { organizationId }),
  })

  // we only return the first page of sales-admin users (25)
  return listUsersPaginated.data
}

const getOrgAndCostCenterNames = async (
  ctx: Context | EventBroadcastContext,
  organizationId: string,
  costCenterId: string
) => {
  const {
    clients: { organizations },
    vtex: { logger },
  } = ctx

  try {
    const {
      data: {
        getOrganizationById: { name: organizationName },
      },
    } = await organizations.getOrganizationById(organizationId)

    const {
      data: {
        getCostCenterById: { name: costCenterName },
      },
    } = await organizations.getCostCenterById(costCenterId)

    return { organizationName, costCenterName }
  } catch (error) {
    logger.error({
      error,
      message: 'quoteUpdatedMessage-getOrgNamesError',
    })

    return { organizationName: null, costCenterName: null }
  }
}

const sendNotificationToUser = async (
  ctx: Context,
  mailClient: MailClient,
  user: string,
  quote: JsonDataQuote,
  templateName: string,
  account: string
) => {
  await mailClient.sendMail({
    jsonData: {
      message: { to: user },
      quote,
    },
    templateName,
  })

  sendMessageMetric(ctx, {
    quote,
    account,
    sentTo: user,
    templateName,
  })
}

const sendMailNotificationToUsers = async (
  ctx: Context | EventBroadcastContext,
  { quote, mail: sender, users }: any,
  templateName: string
) => {
  const {
    vtex: { logger },
  } = ctx

  try {
    const promises = []

    for (const user of users) {
      promises.push(
        sendNotificationToUser(
          ctx as Context,
          sender,
          user,
          quote,
          templateName,
          sender.context.account
        )
      )
    }

    return Promise.all(promises)
  } catch (error) {
    logger.error({
      error,
      message: 'sendMailNotificationToUsers-Error',
    })
  }

  return false
}

const message = (ctx: Context | EventBroadcastContext) => {
  const {
    clients: { mail, storefrontPermissions },
    vtex: { logger, host },
  } = ctx

  let rootPath = 'get' in ctx ? ctx.get('x-vtex-root-path') : '/'

  // Defend against malformed root path. It should always start with `/`.
  if (rootPath && !rootPath.startsWith('/')) {
    rootPath = `/${rootPath}`
  }

  if (rootPath === '/') {
    rootPath = ''
  }

  const quoteCreated = async ({
    name,
    id,
    organization,
    costCenter,
    lastUpdate,
  }: {
    name: string
    id: string
    organization: string
    costCenter: string
    lastUpdate: QuoteUpdate
  }) => {
    let users = []

    try {
      users = (
        await getOrgSalesAdminEmail(storefrontPermissions, organization)
      ).map((user: any) => user.email)
    } catch (error) {
      logger.error({
        error,
        message: 'quoteCreatedMessage-getOrgSalesAdminEmailError',
      })
    }

    const { organizationName, costCenterName } = await getOrgAndCostCenterNames(
      ctx,
      organization,
      costCenter
    )

    if (!organizationName || !costCenterName || users.length === 0) {
      return
    }

    const link = `https://${host}${rootPath}/b2b-quotes/${id}`
    const quote = {
      costCenter: costCenterName,
      id,
      lastUpdate,
      link,
      name,
      organization: organizationName,
    }

    return sendMailNotificationToUsers(
      ctx,
      { quote, mail, users },
      'quote-created'
    )
  }

  const quoteUpdated = async ({
    users,
    name,
    id,
    organization,
    costCenter,
    lastUpdate,
    templateName = 'quote-updated',
    orderId = '',
  }: {
    users: string[]
    name: string
    id: string
    organization: string
    costCenter: string
    lastUpdate: QuoteUpdate
    templateName?: string
    orderId?: string
  }) => {
    const { organizationName, costCenterName } = await getOrgAndCostCenterNames(
      ctx,
      organization,
      costCenter
    )

    if (!organizationName || !costCenterName) {
      return
    }

    const link = `https://${host}${rootPath}/b2b-quotes/${id}`
    const quote = {
      costCenter: costCenterName,
      id,
      lastUpdate,
      link,
      name,
      orderId,
      organization: organizationName,
    }

    return sendMailNotificationToUsers(
      ctx,
      { quote, mail, users, ctx },
      templateName
    )
  }

  return {
    quoteCreated,
    quoteUpdated,
  }
}

export default message

```


[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-quotes-graphql/node/utils/GraphQLError.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
export default class GraphQLError extends Error {
  public extensions: any

  constructor(message: string, details?: any) {
    super(message)
    this.extensions = { message, ...details }
  }
}

```


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

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as crypto from 'crypto'

import type { RequestTracingConfig } from '@vtex/api'
import { AuthenticationError, ForbiddenError, UserInputError } from '@vtex/api'
import type { AxiosError } from 'axios'

export const toHash = (obj: any) => {
  return crypto.createHash('md5').update(JSON.stringify(obj)).digest('hex')
}

export const NO_REPLY_EMAIL = 'noreply@vtexcommerce.com.br'

export function statusToError(e: any) {
  if (!e.response) {
    throw e
  }

  const { response } = e as AxiosError
  const { status } = response!

  if (status === 401) {
    throw new AuthenticationError(e)
  }

  if (status === 403) {
    throw new ForbiddenError(e)
  }

  if (status === 400) {
    throw new UserInputError(e)
  }

  throw e
}

export const isEmail = new RegExp(/[a-z0-9]+@[a-z]+.[a-z]{2,3}/)

export const createTracing = (
  metric: string,
  tracingConfig?: RequestTracingConfig
) => ({
  requestSpanNameSuffix: metric,
  ...tracingConfig?.tracing,
})

```


[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-quotes-graphql/node/utils/Queue.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { QUOTE_DATA_ENTITY, QUOTE_FIELDS, SCHEMA_VERSION } from '../constants'
import { NO_REPLY_EMAIL } from './index'
import message from './message'

const processItem = ({
  ctx,
  item,
  baseDate,
  index,
}: {
  ctx: Context
  item: Quote
  baseDate: Date
  index: number
}) => {
  const {
    clients: { masterdata },
    vtex: { logger },
  } = ctx

  const { id, referenceName, organization, costCenter, updateHistory } = item

  if (item.status === 'placed' || item.status === 'declined') {
    return
  }

  const status = 'expired'
  const now = new Date(baseDate)

  now.setSeconds(now.getSeconds() + index)

  const nowISO = now.toISOString()

  const users = updateHistory.map((anUpdate) => anUpdate.email)
  const uniqueUsers = [...new Set(users)]

  const lastUpdate = nowISO
  const update = {
    date: nowISO,
    email: NO_REPLY_EMAIL,
    note: '',
    role: 'expiration-system',
    status,
  }

  updateHistory.push(update)

  masterdata
    .updatePartialDocument({
      dataEntity: QUOTE_DATA_ENTITY,
      fields: { lastUpdate, updateHistory, status },
      id,
      schema: SCHEMA_VERSION,
    })
    .then(() => {
      message(ctx).quoteUpdated({
        costCenter,
        id,
        lastUpdate: {
          email: 'expiration-system',
          note: '',
          status: status.toUpperCase(),
        },
        name: referenceName,
        organization,
        users: uniqueUsers,
      })
    })
    .catch((error) => {
      logger.error({ message: 'quoteExpired-Error', error })
    })
}

export const processQueue = (ctx: Context) => {
  const {
    clients: { masterdata },
    vtex: { logger },
  } = ctx

  const now = new Date()
  const nowISO = now.toISOString()

  masterdata
    .searchDocuments({
      dataEntity: QUOTE_DATA_ENTITY,
      fields: QUOTE_FIELDS,
      pagination: {
        page: 1,
        pageSize: 500,
      },
      schema: SCHEMA_VERSION,
      sort: 'creationDate ASC',
      where: `status <> 'expired' AND expirationDate < ${nowISO}`,
    })
    .then((data: any) => {
      if (Array.isArray(data)) {
        logger.info({
          itemsToBeProcessed: data.length,
          message: `expirationQueue-foundItems`,
        })

        data.forEach((item, index) => {
          processItem({ ctx, item, index, baseDate: now })
        })
      }
    })
    .catch((error) => {
      logger.error({ message: 'expirationQueue-error', error })
      throw error
    })
}

```


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

[Assistant]
Here's the implementation:

```typescript
import { AuditAccess } from './directives/auditAccess'
import { ValidateAdminUserAccess } from './directives/validateAdminUserAccess'
import { ValidateStoreUserAccess } from './directives/validateStoreUserAccess'
import { WithPermissions } from './directives/withPermissions'
import { WithSegment } from './directives/withSegment'
import { WithSession } from './directives/withSession'

export const schemaDirectives = {
  withPermissions: WithPermissions as any,
  withSession: WithSession as any,
  withSegment: WithSegment as any,
  validateAdminUserAccess: ValidateAdminUserAccess as any,
  validateStoreUserAccess: ValidateStoreUserAccess as any,
  auditAccess: AuditAccess as any,
}

```


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

[User] Show me the implementation of b2b-quotes-graphql/node/resolvers/fieldResolvers.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
export const organizationName = async (
  { organization }: { organization: string },
  _: any,
  ctx: Context | EventBroadcastContext
) => {
  const {
    clients: { organizations },
    vtex: { logger },
  } = ctx

  try {
    const organizationData = await organizations.getOrganizationById(
      organization
    )

    return organizationData?.data?.getOrganizationById?.name ?? null
  } catch (error) {
    logger.warn({
      error,
      message: 'getOrganizationName-error',
    })
  }
}

export const costCenterName = async (
  { costCenter }: { costCenter: string },
  _: any,
  ctx: Context | EventBroadcastContext
) => {
  const {
    clients: { organizations },
    vtex: { logger },
  } = ctx

  try {
    const costCenterData = await organizations.getCostCenterById(costCenter)

    return costCenterData?.data?.getCostCenterById?.name ?? null
  } catch (error) {
    logger.warn({
      error,
      message: 'getCostCenterName-error',
    })
  }
}

```


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

[Assistant]
Here's the implementation:

```typescript
import { Routes } from './routes'
import { Query } from './queries'
import { Mutation } from './mutations'
import { costCenterName, organizationName } from './fieldResolvers'

export const resolvers = {
  Mutation,
  Query,
  Quote: {
    costCenterName,
    organizationName,
  },
  Routes,
}

```


[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-quotes-graphql/node/templates/quotePlaced.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import readFile from '../utils/readFile'

const MESSAGE_BODY = readFile('../assets/quotePlaced.html')

export const quotePlacedMessage = {
  FriendlyName: 'Quote Placed',
  IsDefaultTemplate: false,
  IsPersisted: true,
  IsRemoved: false,
  Name: 'quote-order-placed',
  // Description: null,
  // AccountId: null,
  // AccountName: null,
  // ApplicationId: null,
  Templates: {
    email: {
      IsActive: true,
      Message: MESSAGE_BODY,
      ProviderId: '00000000-0000-0000-0000-000000000000',
      Subject: '[{{quote.organization}}] Quote Placed',
      // CC: null,
      // BCC: null,
      To: '{{message.to}}',
      Type: 'E',
      // ProviderName: null,
      withError: false,
    },
    sms: {
      // ProviderId: null,
      // ProviderName: null,
      IsActive: false,
      Parameters: [],
      Type: 'S',
      withError: false,
    },
  },
  Type: '',
}

```


[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-quotes-graphql/node/templates/quoteUpdated.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import readFile from '../utils/readFile'

const MESSAGE_BODY = readFile('../assets/quoteUpdated.html')

export const quoteUpdatedMessage = {
  FriendlyName: 'Quote Updated',
  IsDefaultTemplate: false,
  IsPersisted: true,
  IsRemoved: false,
  Name: 'quote-updated',
  // Description: null,
  // AccountId: null,
  // AccountName: null,
  // ApplicationId: null,
  Templates: {
    email: {
      // CC: null,
      // BCC: null,
      IsActive: true,
      Message: MESSAGE_BODY,
      ProviderId: '00000000-0000-0000-0000-000000000000',
      Subject:
        '[{{quote.organization}}] Quote {{#if quote.expired}}Expired{{else}}Updated{{/if}}',
      To: '{{message.to}}',
      Type: 'E',
      // ProviderName: null,
      withError: false,
    },
    sms: {
      IsActive: false,
      Parameters: [],
      Type: 'S',
      // ProviderId: null,
      // ProviderName: null,
      withError: false,
    },
  },
  Type: '',
}

```


[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-quotes-graphql/node/templates/quoteCreated.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import readFile from '../utils/readFile'

const MESSAGE_BODY = readFile('../assets/quoteCreated.html')

export const quoteCreatedMessage = {
  FriendlyName: 'Quote Created',
  IsDefaultTemplate: false,
  IsPersisted: true,
  IsRemoved: false,
  Name: 'quote-created',
  // Description: null,
  // AccountId: null,
  // AccountName: null,
  // ApplicationId: null,
  Templates: {
    email: {
      // CC: null,
      // BCC: null,
      IsActive: true,
      Message: MESSAGE_BODY,
      ProviderId: '00000000-0000-0000-0000-000000000000',
      Subject: '[{{quote.organization}}] Request for Quotation',
      To: '{{message.to}}',
      Type: 'E',
      // ProviderName: null,
      withError: false,
    },
    sms: {
      IsActive: false,
      Parameters: [],
      Type: 'S',
      // ProviderId: null,
      // ProviderName: null,
      withError: false,
    },
  },
  Type: '',
}

```


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

[Assistant]
Here's the implementation:

```typescript
import { quoteCreatedMessage } from './quoteCreated'
import { quotePlacedMessage } from './quotePlaced'
import { quoteUpdatedMessage } from './quoteUpdated'

const templates = [quoteCreatedMessage, quotePlacedMessage, quoteUpdatedMessage]

export default templates

```


[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-quotes-graphql/node/resolvers/directives/auditAccess.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'
import { SchemaDirectiveVisitor } from 'graphql-tools'

import sendAuthMetric, { AuthMetric } from '../../metrics/auth'
import {
  validateAdminToken,
  validateAdminTokenOnHeader,
  validateApiToken,
  validateStoreToken,
} from './helper'

export class AuditAccess extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field

    field.resolve = async (
      root: any,
      args: any,
      context: Context,
      info: any
    ) => {
      this.sendAuthMetric(field, context)

      return resolve(root, args, context, info)
    }
  }

  private async sendAuthMetric(field: GraphQLField<any, any>, context: any) {
    const {
      vtex: { adminUserAuthToken, storeUserAuthToken, logger },
    } = context

    const { hasAdminToken, hasValidAdminToken } = await validateAdminToken(
      context,
      adminUserAuthToken as string
    )

    const {
      hasAdminTokenOnHeader,
      hasValidAdminTokenOnHeader,
    } = await validateAdminTokenOnHeader(context)

    const { hasApiToken, hasValidApiToken } = await validateApiToken(context)

    const { hasStoreToken, hasValidStoreToken } = await validateStoreToken(
      context,
      storeUserAuthToken as string
    )

    // now we emit a metric with all the collected data before we proceed
    const operation = field?.astNode?.name?.value ?? context?.request?.url
    const userAgent = context?.request?.headers['user-agent'] as string
    const caller = context?.request?.headers['x-vtex-caller'] as string
    const forwardedHost = context?.request?.headers[
      'x-forwarded-host'
    ] as string

    const auditMetric = new AuthMetric(context?.vtex?.account, {
      operation,
      forwardedHost,
      caller,
      userAgent,
      hasAdminToken,
      hasValidAdminToken,
      hasApiToken,
      hasValidApiToken,
      hasStoreToken,
      hasValidStoreToken,
      hasAdminTokenOnHeader,
      hasValidAdminTokenOnHeader,
    })

    await sendAuthMetric(context, logger, auditMetric)
  }
}

```


[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-quotes-graphql/node/resolvers/directives/helper.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { isUserPartOfBuyerOrg } from '../queries/index'

export const validateAdminToken = async (
  context: Context,
  adminUserAuthToken: string
): Promise<{
  hasAdminToken: boolean
  hasValidAdminToken: boolean
  hasCurrentValidAdminToken: boolean
}> => {
  const {
    clients: { identity, lm },
    vtex: { account, logger },
  } = context

  // check if has admin token and if it is valid
  const hasAdminToken = !!adminUserAuthToken
  let hasValidAdminToken = false
  // this is used to check if the token is valid by current standards
  let hasCurrentValidAdminToken = false

  if (hasAdminToken) {
    try {
      const authUser = await identity.validateToken(adminUserAuthToken)

      // we set this flag to true if the token is valid by current standards
      // in the future we should remove this line
      hasCurrentValidAdminToken = true

      if (authUser?.audience === 'admin' && authUser?.account === account) {
        hasValidAdminToken = await lm.getUserAdminPermissions(
          account,
          authUser.id
        )
      }
    } catch (err) {
      // noop so we leave hasValidAdminToken as false
      logger.warn({
        message: 'Error validating admin token',
        err,
      })
    }
  }

  return { hasAdminToken, hasValidAdminToken, hasCurrentValidAdminToken }
}

export const validateApiToken = async (
  context: Context
): Promise<{
  hasApiToken: boolean
  hasValidApiToken: boolean
  hasCurrentValidApiToken: boolean
}> => {
  const {
    clients: { identity, lm },
    vtex: { account, logger },
  } = context

  // check if has api token and if it is valid
  const apiToken = context?.headers['vtex-api-apptoken'] as string
  const appKey = context?.headers['vtex-api-appkey'] as string
  const hasApiToken = !!(apiToken?.length && appKey?.length)
  let hasValidApiToken = false

  // this is used to check if the token is valid by current standards
  let hasCurrentValidApiToken = false

  if (hasApiToken) {
    try {
      const { token } = await identity.getToken({
        appkey: appKey,
        apptoken: apiToken,
      })

      const authUser = await identity.validateToken(token)

      // we set this flag to true if the token is valid by current standards
      // in the future we should remove this line
      hasCurrentValidApiToken = true
      // keeping this behavior for now, but we should remove it in the future as well
      context.cookies.set('VtexIdclientAutCookie', token)
      context.vtex.adminUserAuthToken = token

      if (authUser?.audience === 'admin' && authUser?.account === account) {
        hasValidApiToken = await lm.getUserAdminPermissions(
          account,
          authUser.id
        )
      }
    } catch (err) {
      // noop so we leave hasValidApiToken as false
      logger.warn({
        message: 'Error validating API token',
        err,
      })
    }
  }

  return { hasApiToken, hasValidApiToken, hasCurrentValidApiToken }
}

export const validateStoreToken = async (
  context: Context,
  storeUserAuthToken: string
): Promise<{
  hasStoreToken: boolean
  hasValidStoreToken: boolean
  hasCurrentValidStoreToken: boolean
}> => {
  const {
    clients: { vtexId },
    vtex: { logger },
  } = context

  // check if has store token and if it is valid
  const hasStoreToken = !!storeUserAuthToken
  let hasValidStoreToken = false
  // this is used to check if the token is valid by current standards
  let hasCurrentValidStoreToken = false

  if (hasStoreToken) {
    try {
      const authUser = await vtexId.getAuthenticatedUser(storeUserAuthToken)

      if (authUser?.user) {
        // we set this flag to true if the token is valid by current standards
        // in the future we should remove this line
        hasCurrentValidStoreToken = true

        const userIsPartOfBuyerOrg = await isUserPartOfBuyerOrg(
          authUser?.user,
          context
        )

        if (userIsPartOfBuyerOrg) {
          hasValidStoreToken = true
        }

        // adding log to better understand invalid store token use cases
        // in the future we should remove this log
        if (hasCurrentValidStoreToken && !hasValidStoreToken) {
          logger.warn({
            message: 'Invalid store token:',
            operation: context?.request?.url,
            userAgent: context?.request?.headers['user-agent'] as string,
            caller: context?.request?.headers['x-vtex-caller'] as string,
            forwardedHost: context?.request?.headers[
              'x-forwarded-host'
            ] as string,
            authUser,
          })
        }
      }
    } catch (err) {
      // noop so we leave hasValidStoreToken as false
      logger.warn({
        message: 'Error validating store token:',
        err,
      })
    }
  }

  return { hasStoreToken, hasValidStoreToken, hasCurrentValidStoreToken }
}

export const validateAdminTokenOnHeader = async (
  context: Context
): Promise<{
  hasAdminTokenOnHeader: boolean
  hasValidAdminTokenOnHeader: boolean
  hasCurrentValidAdminTokenOnHeader: boolean
}> => {
  const adminUserAuthToken = context?.headers.vtexidclientautcookie as string
  const hasAdminTokenOnHeader = !!adminUserAuthToken?.length

  if (!hasAdminTokenOnHeader) {
    return {
      hasAdminTokenOnHeader: false,
      hasValidAdminTokenOnHeader: false,
      hasCurrentValidAdminTokenOnHeader: false,
    }
  }

  const {
    hasAdminToken,
    hasCurrentValidAdminToken,
    hasValidAdminToken,
  } = await validateAdminToken(context, adminUserAuthToken)

  return {
    hasAdminTokenOnHeader: hasAdminToken,
    hasValidAdminTokenOnHeader: hasValidAdminToken,
    hasCurrentValidAdminTokenOnHeader: hasCurrentValidAdminToken,
  }
}

```


[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-quotes-graphql/node/resolvers/directives/validateAdminUserAccess.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { SchemaDirectiveVisitor } from 'graphql-tools'
import { AuthenticationError, ForbiddenError } from '@vtex/api'
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'

import type { AuthAuditMetric } from '../../metrics/auth'
import sendAuthMetric, { AuthMetric } from '../../metrics/auth'
import {
  validateAdminToken,
  validateAdminTokenOnHeader,
  validateApiToken,
} from './helper'

export class ValidateAdminUserAccess extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field

    field.resolve = async (
      root: any,
      args: any,
      context: Context,
      info: any
    ) => {
      const {
        vtex: { adminUserAuthToken, logger },
      } = context

      // get metrics data
      const operation = field?.astNode?.name?.value ?? context?.request?.url
      const userAgent = context?.request?.headers['user-agent'] as string
      const caller = context?.request?.headers['x-vtex-caller'] as string
      const forwardedHost = context?.request?.headers[
        'x-forwarded-host'
      ] as string

      // set metric fields with initial data
      let metricFields: AuthAuditMetric = {
        operation,
        forwardedHost,
        caller,
        userAgent,
      }

      const { hasAdminToken, hasValidAdminToken } = await validateAdminToken(
        context,
        adminUserAuthToken as string
      )

      // add admin token metrics
      metricFields = {
        ...metricFields,
        hasAdminToken,
        hasValidAdminToken,
      }

      // allow access if has valid admin token
      if (hasValidAdminToken) {
        sendAuthMetric(
          context,
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateAdminUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      // If there's no valid admin token on context, search for it on header
      const {
        hasAdminTokenOnHeader,
        hasValidAdminTokenOnHeader,
      } = await validateAdminTokenOnHeader(context)

      // add admin header token metrics
      metricFields = {
        ...metricFields,
        hasAdminTokenOnHeader,
        hasValidAdminTokenOnHeader,
      }

      // allow access if has valid admin token
      if (hasValidAdminTokenOnHeader) {
        sendAuthMetric(
          context,
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateAdminUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      const { hasApiToken, hasValidApiToken } = await validateApiToken(context)

      // add API token metrics
      metricFields = {
        ...metricFields,
        hasApiToken,
        hasValidApiToken,
      }

      // allow access if has valid API token
      if (hasValidApiToken) {
        sendAuthMetric(
          context,
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateAdminUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      // deny access if no tokens were provided
      if (!hasAdminToken && !hasAdminTokenOnHeader && !hasApiToken) {
        logger.warn({
          message: 'ValidateAdminUserAccess: No token provided',
          ...metricFields,
        })
        throw new AuthenticationError('No token was provided')
      }

      // deny access if no valid tokens were provided
      logger.warn({
        message: 'ValidateAdminUserAccess: Invalid token',
        ...metricFields,
      })
      throw new ForbiddenError('Unauthorized Access')
    }
  }
}

```


[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-quotes-graphql/node/resolvers/directives/withSegment.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-params */
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'
import { SchemaDirectiveVisitor } from 'graphql-tools'
import atob from 'atob'

export class WithSegment extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field

    field.resolve = async (root: any, args: any, context: any, info: any) => {
      const {
        clients: { segment },
        vtex: { segmentToken },
      } = context

      context.vtex.segmentData = segmentToken
        ? JSON.parse(atob(segmentToken))
        : await segment.getSegment()

      return resolve(root, args, context, info)
    }
  }
}

```


[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-quotes-graphql/node/resolvers/directives/validateStoreUserAccess.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { SchemaDirectiveVisitor } from 'graphql-tools'
import { AuthenticationError, ForbiddenError } from '@vtex/api'
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'

import type { AuthAuditMetric } from '../../metrics/auth'
import sendAuthMetric, { AuthMetric } from '../../metrics/auth'
import {
  validateAdminToken,
  validateAdminTokenOnHeader,
  validateApiToken,
  validateStoreToken,
} from './helper'

export class ValidateStoreUserAccess extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field

    field.resolve = async (
      root: any,
      args: any,
      context: Context,
      info: any
    ) => {
      const {
        vtex: { adminUserAuthToken, storeUserAuthToken, logger },
      } = context

      // get metrics data
      const operation = field?.astNode?.name?.value ?? context?.request?.url
      const userAgent = context?.request?.headers['user-agent'] as string
      const caller = context?.request?.headers['x-vtex-caller'] as string
      const forwardedHost = context?.request?.headers[
        'x-forwarded-host'
      ] as string

      // set metric fields with initial data
      let metricFields: AuthAuditMetric = {
        operation,
        forwardedHost,
        caller,
        userAgent,
      }

      const { hasAdminToken, hasValidAdminToken } = await validateAdminToken(
        context,
        adminUserAuthToken as string
      )

      // add admin token metrics
      metricFields = {
        ...metricFields,
        hasAdminToken,
        hasValidAdminToken,
      }

      // allow access if has valid admin token
      if (hasValidAdminToken) {
        sendAuthMetric(
          context,
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateStoreUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      // If there's no valid admin token on context, search for it on header
      const {
        hasAdminTokenOnHeader,
        hasValidAdminTokenOnHeader,
      } = await validateAdminTokenOnHeader(context)

      // add admin header token metrics
      metricFields = {
        ...metricFields,
        hasAdminTokenOnHeader,
        hasValidAdminTokenOnHeader,
      }

      // allow access if has valid admin token
      if (hasValidAdminTokenOnHeader) {
        sendAuthMetric(
          context,
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateStoreUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      const { hasApiToken, hasValidApiToken } = await validateApiToken(context)

      // add API token metrics
      metricFields = {
        ...metricFields,
        hasApiToken,
        hasValidApiToken,
      }

      // allow access if has valid API token
      if (hasValidApiToken) {
        sendAuthMetric(
          context,
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateStoreUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      const { hasStoreToken, hasValidStoreToken } = await validateStoreToken(
        context,
        storeUserAuthToken as string
      )

      // add store token metrics
      metricFields = {
        ...metricFields,
        hasStoreToken,
        hasValidStoreToken,
      }

      // allow access if has valid store token
      if (hasValidStoreToken) {
        sendAuthMetric(
          context,
          logger,
          new AuthMetric(
            context?.vtex?.account,
            metricFields,
            'ValidateStoreUserAccessAudit'
          )
        )

        return resolve(root, args, context, info)
      }

      // deny access if no tokens were provided
      if (!hasAdminToken && !hasApiToken && !hasStoreToken) {
        logger.warn({
          message: 'ValidateStoreUserAccess: No token provided',
          ...metricFields,
        })
        throw new AuthenticationError('No token was provided')
      }

      // deny access if no valid tokens were provided
      logger.warn({
        message: `ValidateStoreUserAccess: Invalid token`,
        ...metricFields,
      })
      throw new ForbiddenError('Unauthorized Access')
    }
  }
}

```


[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-quotes-graphql/node/resolvers/directives/withSession.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-params */
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'
import { SchemaDirectiveVisitor } from 'graphql-tools'

export class WithSession extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field

    field.resolve = async (root: any, args: any, context: any, info: any) => {
      const {
        clients: { session },
        vtex: { sessionToken },
      } = context

      context.vtex.sessionData = await session
        .getSession(sessionToken as string, ['*'])
        .then((currentSession: any) => {
          return currentSession.sessionData
        })
        .catch(() => null)

      return resolve(root, args, context, info)
    }
  }
}

```


[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-quotes-graphql/node/resolvers/directives/withPermissions.ts from the b2b-quotes-graphql VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-params */
import type { GraphQLField } from 'graphql'
import { defaultFieldResolver } from 'graphql'
import { SchemaDirectiveVisitor } from 'graphql-tools'

export class WithPermissions extends SchemaDirectiveVisitor {
  public visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field

    field.resolve = async (root: any, args: any, context: any, info: any) => {
      const {
        clients: { storefrontPermissions },
        vtex: { logger },
      } = context

      context.vtex.storefrontPermissions = await storefrontPermissions
        .checkUserPermission()
        .then((result: any) => {
          return result.data.checkUserPermission
        })
        .catch((error: any) => {
          logger.error({
            error,
            message: 'getPermissionsError',
          })
        })

      return resolve(root, args, context, info)
    }
  }
}

```