# ivyforms/1.1/frontend/src/composables/useValidateBeforeSubmit.ts

The Innovative Form Builder – IvyForms, version 1.1. 248 lines.

- Page: https://pluginprobe.com/plugins/ivyforms/1.1/code/frontend/src/composables/useValidateBeforeSubmit.ts
- Raw: https://pluginprobe.com/plugins/ivyforms/1.1/raw/frontend/src/composables/useValidateBeforeSubmit.ts
- Modified: 2026-06-08T12:18:46+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/ivyforms/1.1/code/frontend/src/composables/useValidateBeforeSubmit.ts#L10-L20`.

```typescript
import { ref, type Ref } from 'vue'
import type { Field } from '@/types/field'
import { useApiClient } from '@/composables/useApiClient'

export type DuplicateCheckState = {
  value: string
  passed: boolean
}

export type ValidateBeforeSubmitResult = {
  valid: boolean
  is_duplicate?: boolean
  duplicate_errors?: Record<string, boolean>
}

type FormValues = Record<string, string | number | null | undefined>

export const getFieldValueKey = (field: Field): string => `${field.type}_${field.fieldIndex}`

export const processWebsiteFieldValues = (fields: Field[], values: FormValues): FormValues => {
  const processed = { ...values }

  fields.forEach((field) => {
    if (field.type !== 'website') {
      return
    }

    const key = getFieldValueKey(field)
    const fullValue = processed[key] || ''
    const prefix = field.inputPrefix || ''
    const suffix = field.inputSuffix || ''
    let userInput = String(fullValue)

    if (fullValue && (prefix || suffix)) {
      if (prefix && !userInput.startsWith(prefix)) {
        userInput = prefix + userInput
      }
      if (suffix && !userInput.endsWith(suffix)) {
        userInput = userInput + suffix
      }
    }

    processed[key] = userInput
  })

  return processed
}

const shouldSkipDuplicateCheck = (
  stateByKey: Record<string, DuplicateCheckState>,
  fieldKey: string,
  currentValue: string,
): boolean => {
  const state = stateByKey[fieldKey]
  return state !== undefined && state.value === currentValue && state.passed
}

export function buildDuplicateCheckValues(
  fields: Field[],
  formValues: FormValues,
  stateByKey: Record<string, DuplicateCheckState>,
  options?: { bypassCache?: boolean },
): { values: FormValues; fieldsToCheck: Field[] } {
  const values: FormValues = {}
  const fieldsToCheck: Field[] = []

  for (const field of fields) {
    if (!field.noDuplicates) {
      continue
    }

    const fieldKey = getFieldValueKey(field)
    const rawValue = formValues[fieldKey]
    if (rawValue === undefined || rawValue === null || String(rawValue).trim() === '') {
      continue
    }

    const currentValue = String(rawValue)
    if (!options?.bypassCache && shouldSkipDuplicateCheck(stateByKey, fieldKey, currentValue)) {
      continue
    }

    values[fieldKey] = rawValue
    fieldsToCheck.push(field)
  }

  return { values, fieldsToCheck }
}

export async function callValidateBeforeSubmit(
  request: ReturnType<typeof useApiClient>['request'],
  params: {
    formId: string | number
    values: FormValues
    nonce: string
    pageId?: string
  },
): Promise<{ result: ValidateBeforeSubmitResult | null; error: unknown }> {
  const { data, error, status } = await request('form/validate_before_submit/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    data: {
      formId: params.formId,
      values: params.values,
      nonce: params.nonce,
      ...(params.pageId ? { pageId: params.pageId } : {}),
    },
  })

  if (error || status !== 200 || !data) {
    return { result: null, error: error || data }
  }

  const payload = (data as { data?: { data?: ValidateBeforeSubmitResult } }).data?.data
  if (!payload) {
    return { result: null, error: 'Invalid validate_before_submit response' }
  }

  return { result: payload, error: null }
}

export function useValidateBeforeSubmit(options: {
  request: ReturnType<typeof useApiClient>['request']
  getLabel: (key: string) => string
  formData: Ref<{ id: string | number }>
  formFields: Ref<Field[]>
  formValues: Ref<FormValues>
  formNonce: Ref<string | null>
  fieldErrors: Ref<Record<string, string>>
  getPageFields: (pageId: string) => Field[]
}) {
  const duplicateCheckState = ref<Record<string, DuplicateCheckState>>({})

  const applyDuplicateErrors = (duplicateFields: Record<string, boolean>) => {
    for (const field of options.formFields.value) {
      const fieldId = String(field.id)
      if (!duplicateFields[field.id] && !duplicateFields[fieldId]) {
        continue
      }

      const fieldKey = getFieldValueKey(field)
      const fieldValue = options.formValues.value[fieldKey]
      if (!fieldValue || fieldValue.toString().trim() === '') {
        continue
      }

      options.fieldErrors.value[fieldKey] = options
        .getLabel('duplicate_value_error_label')
        .replace('{label}', field.label?.toLowerCase() || 'value')
    }
  }

  const recordDuplicateCheckResults = (fieldsToCheck: Field[], passed: boolean) => {
    for (const field of fieldsToCheck) {
      const fieldKey = getFieldValueKey(field)
      const rawValue = options.formValues.value[fieldKey]
      if (rawValue === undefined || rawValue === null) {
        continue
      }
      duplicateCheckState.value[fieldKey] = {
        value: String(rawValue),
        passed,
      }
    }
  }

  const validateDuplicatesBeforeProceed = async (
    pageId?: string,
    checkOptions?: { bypassCache?: boolean },
  ): Promise<'ok' | 'duplicate' | 'request_failed'> => {
    const scopeFields = pageId
      ? options.getPageFields(pageId).filter((field) => field.noDuplicates)
      : options.formFields.value.filter((field) => field.noDuplicates)

    if (scopeFields.length === 0) {
      return 'ok'
    }

    const { values, fieldsToCheck } = buildDuplicateCheckValues(
      scopeFields,
      options.formValues.value,
      duplicateCheckState.value,
      checkOptions,
    )

    if (fieldsToCheck.length === 0) {
      return 'ok'
    }

    const processedValues = processWebsiteFieldValues(scopeFields, values)

    const nonce = options.formNonce.value
    if (!nonce) {
      return 'request_failed'
    }

    const { result, error } = await callValidateBeforeSubmit(options.request, {
      formId: options.formData.value.id,
      values: processedValues,
      nonce,
      pageId,
    })

    if (!result) {
      console.error('validate_before_submit failed:', error)
      return 'request_failed'
    }

    if (result.valid) {
      recordDuplicateCheckResults(fieldsToCheck, true)
      return 'ok'
    }

    recordDuplicateCheckResults(fieldsToCheck, false)
    if (result.duplicate_errors) {
      applyDuplicateErrors(result.duplicate_errors)
    }
    return 'duplicate'
  }

  return {
    duplicateCheckState,
    validateDuplicatesBeforeProceed,
    applyDuplicateErrors,
  }
}

/**
 * Standalone client for integrations (e.g. Amelia booking flow).
 */
export async function validateBeforeSubmitForIntegration(params: {
  formId: string | number
  values: FormValues
  nonce: string
  pageId?: string
}): Promise<ValidateBeforeSubmitResult> {
  const { request } = useApiClient()
  const { result, error } = await callValidateBeforeSubmit(request, params)

  if (!result) {
    throw error instanceof Error ? error : new Error(String(error))
  }

  return result
}

```
