# ivyforms/0.6/frontend/src/stores/useSettingsStore.ts

The Innovative Form Builder – IvyForms, version 0.6. 537 lines.

- Page: https://pluginprobe.com/plugins/ivyforms/0.6/code/frontend/src/stores/useSettingsStore.ts
- Raw: https://pluginprobe.com/plugins/ivyforms/0.6/raw/frontend/src/stores/useSettingsStore.ts
- Modified: 2025-11-19T18:42:10+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/0.6/code/frontend/src/stores/useSettingsStore.ts#L10-L20`.

```typescript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useApiClient } from '@/composables/useApiClient'
import { useLabels } from '@/composables/useLabels'
import { useUnsavedChangesStore } from '@/stores/useUnsavedChangesStore'
import IvyMessage from '@/views/_components/message/ivyMessage.ts'
import type { RecaptchaSettings } from '@/types/recaptcha/recaptcha-interface'
import type { RecaptchaType } from '@/types/recaptcha/recaptcha-type'
import type { AllSettings } from '@/types/settings'

/**
 * Settings store for managing application settings
 * This store handles reCAPTCHA settings and other security/general settings
 */
export const useSettingsStore = defineStore('settingsStore', () => {
  // State using refs (composition API style like useFormBuilder)

  // All settings state
  const allSettings = ref<AllSettings | null>(null)
  const isLoading = ref(false)

  // reCAPTCHA specific state (for backward compatibility)
  const recaptchaType = ref<RecaptchaType>('v2')
  const recaptchaSiteKey = ref('')
  const recaptchaSecretKey = ref('')
  const recaptchaLanguage = ref('')

  // WCAG Compliance state
  const wcagBackendCompliance = ref(false)

  // Request deduplication - prevent multiple simultaneous calls
  let loadPromise: Promise<boolean> | null = null

  // Template favorites state
  const favoriteTemplates = ref<string[]>([])

  const { getLabel } = useLabels()
  const { request } = useApiClient()

  // Use unsaved changes store for state management
  const unsavedChangesStore = useUnsavedChangesStore()

  // Create complete entity manager for settings
  const entityManager = unsavedChangesStore.createEntityManager('settingsBuilder', {
    type: recaptchaType,
    siteKey: recaptchaSiteKey,
    secretKey: recaptchaSecretKey,
    language: recaptchaLanguage,
    wcagBackendCompliance: wcagBackendCompliance,
    favoriteTemplates: favoriteTemplates,
  })

  // Computed values
  const recaptchaSettings = computed(() => ({
    type: recaptchaType.value,
    siteKey: recaptchaSiteKey.value,
    secretKey: recaptchaSecretKey.value,
    language: recaptchaLanguage.value,
  }))

  // Computed getters for all settings
  const securitySettings = computed(() => allSettings.value?.security || null)
  const generalSettings = computed(() => allSettings.value?.general || null)
  const integrationsSettings = computed(() => allSettings.value?.integrations || null)

  // Specific settings getters
  const currentRecaptchaSettings = computed(() => allSettings.value?.security?.recaptcha || null)
  // Future extensions can be added here:
  // const currentHCaptchaSettings = computed(() => allSettings.value?.security?.hcaptcha || null)
  // const currentTurnstileSettings = computed(() => allSettings.value?.security?.turnstile || null)

  // Integration settings getters
  const wpDataTablesIntegration = computed(
    () => allSettings.value?.integrations?.wpdatatables || null,
  )

  const isRecaptchaConfigured = computed(() => {
    // reCAPTCHA is configured only when both keys are present and valid
    const siteKeyPresent = recaptchaSiteKey.value && recaptchaSiteKey.value.trim() !== ''
    const secretKeyPresent = recaptchaSecretKey.value && recaptchaSecretKey.value.trim() !== ''

    return (
      siteKeyPresent &&
      secretKeyPresent &&
      validateRecaptchaKeyFormat(recaptchaSiteKey.value) &&
      validateRecaptchaKeyFormat(recaptchaSecretKey.value)
    )
  })

  const hasChanges = computed(() => {
    return entityManager.hasUnsavedChanges().value
  })

  // Actions - following the same pattern as useFormBuilder
  const setRecaptchaType = (type: RecaptchaType) => {
    recaptchaType.value = type
  }

  const setRecaptchaSiteKey = (siteKey: string) => {
    recaptchaSiteKey.value = siteKey
  }

  const setRecaptchaSecretKey = (secretKey: string) => {
    recaptchaSecretKey.value = secretKey
  }

  const setRecaptchaLanguage = (language: string) => {
    recaptchaLanguage.value = language
  }

  const setWcagBackendCompliance = (value: boolean) => {
    wcagBackendCompliance.value = value
  }

  const setRecaptchaSettings = (settings: RecaptchaSettings) => {
    recaptchaType.value = settings.type
    recaptchaSiteKey.value = settings.siteKey
    recaptchaSecretKey.value = settings.secretKey
    // Set defaults for new fields if not provided
    recaptchaLanguage.value = settings.language || ''
  }

  // Load all settings for admin page - single API call for better performance
  const loadAllSettings = async (): Promise<boolean> => {
    // If there's already a request in progress, return it
    if (loadPromise) {
      return loadPromise
    }

    loadPromise = (async () => {
      try {
        isLoading.value = true
        const { data, error, status } = await request('settings/all', {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
          },
        })

        if (status === 200 && !error && data?.data?.data) {
          allSettings.value = data.data.data

          // Update individual reCAPTCHA refs for backward compatibility
          const recaptcha = data.data.data.security?.recaptcha
          if (recaptcha) {
            recaptchaType.value = recaptcha.type || 'v2'
            recaptchaSiteKey.value = recaptcha.siteKey || ''
            recaptchaSecretKey.value = recaptcha.secretKey || ''
            recaptchaLanguage.value = recaptcha.language || ''
          }

          // Update WCAG compliance setting
          const general = data.data.data.general
          if (general && general.wcagBackend !== undefined) {
            wcagBackendCompliance.value = general.wcagBackend
          }

          // Update favorite templates
          if (general && general.favoriteTemplates) {
            favoriteTemplates.value = general.favoriteTemplates
          }

          // Reset unsaved changes after successful load
          entityManager.resetUnsavedChanges()
          return true
        } else {
          IvyMessage({
            message: getLabel('failed_to_load_settings'),
            type: 'error',
          })
          return false
        }
      } catch (error) {
        IvyMessage({
          message: getLabel('error_loading_settings'),
          type: 'error',
        })
        console.error(getLabel('error_loading_settings'), error)
        return false
      } finally {
        isLoading.value = false
        loadPromise = null
      }
    })()

    return loadPromise
  }

  // Generic settings loader - can be used for any category/option combination
  const loadSetting = async (category: string, option: string) => {
    try {
      isLoading.value = true
      const { data, error, status } = await request(`setting/${category}/${option}`, {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
        },
      })

      if (status === 200 && !error && data?.data?.value) {
        return data.data.value
      } else {
        IvyMessage({
          message: getLabel('failed_to_load_settings'),
          type: 'error',
        })
        return null
      }
    } catch (err) {
      console.error(getLabel('failed_to_load_settings'), err)
      IvyMessage({
        message: getLabel('failed_to_load_settings'),
        type: 'error',
      })
      return null
    } finally {
      isLoading.value = false
    }
  }

  // Generic settings saver - can be used for any category/option combination
  const saveSetting = async (
    category: string,
    option: string,
    value: unknown,
  ): Promise<boolean> => {
    try {
      isLoading.value = true
      const { data, error, status } = await request('setting/update', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        data: {
          settingsCategory: category,
          settingsOption: option,
          settingsValue: value,
        },
      })

      if (status === 200 && !error) {
        IvyMessage({
          message: getLabel('settings_saved'),
          type: 'success',
        })
        return true
      } else {
        IvyMessage({
          message: getLabel('failed_to_save_settings'),
          type: 'error',
        })
        console.error(getLabel('failed_to_save_settings'), data?.message)
        return false
      }
    } catch (error) {
      IvyMessage({
        message: getLabel('error_saving_settings'),
        type: 'error',
      })
      console.error(getLabel('error_saving_settings'), error)
      return false
    } finally {
      isLoading.value = false
    }
  }

  // Update specific setting in allSettings state (for real-time updates)
  const updateSettingInState = (category: keyof AllSettings, option: string, value: unknown) => {
    if (allSettings.value) {
      // Create a deep copy to trigger reactivity
      const updatedSettings = { ...allSettings.value }
      if (updatedSettings[category]) {
        ;(updatedSettings[category] as Record<string, unknown>)[option] = value
        allSettings.value = updatedSettings
      }
    }
  }

  // Load reCAPTCHA settings - following the async pattern from useFormBuilder
  const loadRecaptchaSettings = async () => {
    const settings = await loadSetting('security', 'recaptcha')

    if (settings) {
      recaptchaType.value = settings.type || 'v2'
      recaptchaSiteKey.value = settings.siteKey || ''
      recaptchaSecretKey.value = settings.secretKey || ''
      recaptchaLanguage.value = settings.language || ''

      // Reset unsaved changes after successful load
      entityManager.resetUnsavedChanges()
    }
  }

  // Save reCAPTCHA settings - following the async pattern from useFormBuilder
  const saveRecaptchaSettings = async (): Promise<boolean> => {
    // Basic frontend validation - only validate non-empty keys
    const siteKeyValue = recaptchaSiteKey.value.trim()
    const secretKeyValue = recaptchaSecretKey.value.trim()

    // Validate both keys at once if they are provided
    if (
      (siteKeyValue && !validateRecaptchaKeyFormat(siteKeyValue)) ||
      (secretKeyValue && !validateRecaptchaKeyFormat(secretKeyValue))
    ) {
      const errorMessage =
        siteKeyValue && !validateRecaptchaKeyFormat(siteKeyValue)
          ? getLabel('site_key_invalid')
          : getLabel('secret_key_invalid')

      IvyMessage({
        message: errorMessage,
        type: 'error',
      })
      return false
    }

    const settingsValue = {
      type: recaptchaType.value,
      siteKey: recaptchaSiteKey.value,
      secretKey: recaptchaSecretKey.value,
      language: recaptchaLanguage.value,
    }

    const success = await saveSetting('security', 'recaptcha', settingsValue)

    if (success) {
      // Update the allSettings state with the new values
      updateSettingInState('security', 'recaptcha', settingsValue)

      // Reset unsaved changes after successful save
      entityManager.resetUnsavedChanges()
    }

    return success
  }

  // Save general settings
  const saveGeneralSettings = async (): Promise<boolean> => {
    const success = await saveSetting('general', 'wcagBackend', wcagBackendCompliance.value)

    if (success) {
      // Update the allSettings state with the new values
      updateSettingInState('general', 'wcagBackend', wcagBackendCompliance.value)

      // Reset unsaved changes after successful save
      entityManager.resetUnsavedChanges()
    }

    return success
  }

  // Basic reCAPTCHA key format validation
  const validateRecaptchaKeyFormat = (key: string): boolean => {
    // Allow empty keys (for clearing/resetting settings)
    if (!key || key.trim() === '') {
      return true
    }

    const trimmedKey = key.trim()

    // reCAPTCHA keys are typically 40 characters long, but can vary
    if (trimmedKey.length < 20 || trimmedKey.length > 100) {
      return false
    }

    // Basic format validation - should contain alphanumeric characters and some special chars
    return /^[A-Za-z0-9_-]+$/.test(trimmedKey)
  }

  // Reset changes - following the pattern from useFormBuilder
  const resetChanges = () => {
    entityManager.resetUnsavedChanges()
  }

  // Integration settings methods
  /**
   * Get integration settings dynamically by name
   */
  const getIntegrationSettings = (integrationName: string) => {
    return allSettings.value?.integrations?.[integrationName] || null
  }

  /**
   * Check if integration is enabled
   */
  const isIntegrationEnabled = (integrationName: string): boolean => {
    return allSettings.value?.integrations?.[integrationName]?.enabled || false
  }

  /**
   * Update integration enabled status (simple toggle)
   */
  const updateIntegrationEnabled = async (
    integrationName: string,
    enabled: boolean,
  ): Promise<boolean> => {
    const integrationValue = { enabled }
    const success = await saveSetting('integrations', integrationName, integrationValue)

    if (success) {
      updateSettingInState('integrations', integrationName, integrationValue)
    }

    return success
  }

  /**
   * Update integration settings (for complex integrations with multiple settings)
   */
  const updateIntegrationSettings = async (
    integrationName: string,
    settings: Record<string, unknown>,
  ): Promise<boolean> => {
    const success = await saveSetting('integrations', integrationName, settings)

    if (success) {
      updateSettingInState('integrations', integrationName, settings)
    }

    return success
  }

  /**
   * Toggle a template as favorite
   */
  const toggleFavoriteTemplate = async (templateId: string): Promise<boolean> => {
    const index = favoriteTemplates.value.indexOf(templateId)
    if (index > -1) {
      // Remove from favorites
      favoriteTemplates.value = favoriteTemplates.value.filter((id) => id !== templateId)
    } else {
      // Add to favorites
      favoriteTemplates.value = [...favoriteTemplates.value, templateId]
    }
    // Save to backend
    const success = await saveSetting('general', 'favoriteTemplates', favoriteTemplates.value)
    if (success) {
      updateSettingInState('general', 'favoriteTemplates', favoriteTemplates.value)
    }
    return success
  }

  /**
   * Check if a template is favorited
   */
  const isTemplateFavorited = (templateId: string): boolean => {
    return favoriteTemplates.value.includes(templateId)
  }

  /**
   * Load favorite templates
   */
  const loadFavoriteTemplates = async () => {
    // If allSettings is already loaded, use it
    if (allSettings.value?.general?.favoriteTemplates) {
      favoriteTemplates.value = allSettings.value.general.favoriteTemplates as string[]
      return
    }

    // Otherwise, load from individual endpoint
    const favorites = await loadSetting('general', 'favoriteTemplates')
    if (favorites && Array.isArray(favorites)) {
      favoriteTemplates.value = favorites as string[]
    } else if (favorites === null) {
      // If it returns null, try loading all settings
      await loadAllSettings()
      if (allSettings.value?.general?.favoriteTemplates) {
        favoriteTemplates.value = allSettings.value.general.favoriteTemplates as string[]
      }
    }
  }

  return {
    // State
    recaptchaType,
    recaptchaSiteKey,
    recaptchaSecretKey,
    recaptchaLanguage,
    wcagBackendCompliance,
    favoriteTemplates,
    isLoading,

    // All Settings State
    allSettings,

    // Computed
    recaptchaSettings,
    isRecaptchaConfigured,
    hasChanges,
    getCurrentState: entityManager.getCurrentState,

    // All Settings Computed
    securitySettings,
    generalSettings,
    integrationsSettings,
    currentRecaptchaSettings,
    wpDataTablesIntegration,
    // Future extensions:
    // currentHCaptchaSettings,
    // currentTurnstileSettings,

    // Actions
    setRecaptchaType,
    setRecaptchaSiteKey,
    setRecaptchaSecretKey,
    setRecaptchaLanguage,
    setWcagBackendCompliance,
    setRecaptchaSettings,
    loadRecaptchaSettings,
    saveRecaptchaSettings,
    saveGeneralSettings,
    resetChanges,

    // All Settings API
    loadAllSettings,
    updateSettingInState,

    // Generic Settings API
    loadSetting,
    saveSetting,

    // Integration Settings API
    updateIntegrationEnabled,
    getIntegrationSettings,
    isIntegrationEnabled,
    updateIntegrationSettings,

    // Template Favorites API
    toggleFavoriteTemplate,
    isTemplateFavorited,
    loadFavoriteTemplates,

    // Validation utilities
    validateRecaptchaKeyFormat,
  }
})

```
