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

The Innovative Form Builder – IvyForms, version 0.3. 365 lines.

- Page: https://pluginprobe.com/plugins/ivyforms/0.3/code/frontend/src/stores/useSettingsStore.ts
- Raw: https://pluginprobe.com/plugins/ivyforms/0.3/raw/frontend/src/stores/useSettingsStore.ts
- Modified: 2025-08-20T10:20:12+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.3/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('')

  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,
  })

  // 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)

  // 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)

  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 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> => {
    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 || ''
        }

        // 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
    }
  }

  // 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
  }

  // 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()
  }

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

    // All Settings State
    allSettings,

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

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

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

    // All Settings API
    loadAllSettings,
    updateSettingInState,

    // Generic Settings API
    loadSetting,
    saveSetting,

    // Validation utilities
    validateRecaptchaKeyFormat,
  }
})

```
