/** * Checkout Field Editor Hook * * Custom hook for managing checkout field editor state and API calls */ import { useState, useEffect, useCallback } from 'react' import { FieldEditorSettings, CheckoutField, SectionConfig, defaultSettings, defaultSections, getDefaultFields, generateFieldId, generateFieldKey, CheckoutSection, FieldType } from '../config' interface UseFieldEditorReturn { // State settings: FieldEditorSettings fields: CheckoutField[] sections: SectionConfig[] isLoading: boolean isSaving: boolean error: string | null hasChanges: boolean // Settings actions updateSettings: (key: K, value: FieldEditorSettings[K]) => void // Field actions addField: (type: FieldType, section: CheckoutSection) => CheckoutField updateField: (id: string, updates: Partial) => void deleteField: (id: string) => void duplicateField: (id: string) => CheckoutField | null toggleFieldEnabled: (id: string) => void reorderFields: (section: CheckoutSection, fieldIds: string[]) => void moveFieldToSection: (fieldId: string, newSection: CheckoutSection) => void // Section actions updateSection: (id: CheckoutSection, updates: Partial) => void toggleSectionEnabled: (id: CheckoutSection) => void // Persistence saveSettings: () => Promise resetToDefaults: () => void exportSettings: () => string importSettings: (json: string) => boolean } export function useFieldEditor(): UseFieldEditorReturn { const [settings, setSettings] = useState(defaultSettings) const [fields, setFields] = useState(getDefaultFields()) const [sections, setSections] = useState(defaultSections) const [isLoading, setIsLoading] = useState(true) const [isSaving, setIsSaving] = useState(false) const [error, setError] = useState(null) const [hasChanges, setHasChanges] = useState(false) const [initialState, setInitialState] = useState('') // Get API URL and nonce from WordPress // apiUrl already includes 'swift-commerce/v1' from rest_url('swift-commerce/v1') const apiUrl = window.swiftCommerceData?.apiUrl || '/wp-json/swift-commerce/v1' const restNonce = window.swiftCommerceData?.restNonce || '' // Load settings from API useEffect(() => { const loadSettings = async () => { try { setIsLoading(true) setError(null) const response = await fetch(`${apiUrl}/checkout-field-editor/settings`, { headers: { 'X-WP-Nonce': restNonce, }, }) if (response.ok) { const data = await response.json() if (data.settings) { setSettings({ ...defaultSettings, ...data.settings }) } if (data.fields && data.fields.length > 0) { setFields(data.fields) } if (data.sections && data.sections.length > 0) { setSections(data.sections) } // Store initial state for comparison setInitialState(JSON.stringify({ settings: data.settings, fields: data.fields, sections: data.sections })) } } catch (err) { console.error('Failed to load checkout field editor settings:', err) setError('Failed to load settings') } finally { setIsLoading(false) } } loadSettings() }, [apiUrl, restNonce]) // Track changes useEffect(() => { const currentState = JSON.stringify({ settings, fields, sections }) setHasChanges(currentState !== initialState) }, [settings, fields, sections, initialState]) // Update settings const updateSettings = useCallback(( key: K, value: FieldEditorSettings[K] ) => { setSettings(prev => ({ ...prev, [key]: value })) }, []) // Add new field const addField = useCallback((type: FieldType, section: CheckoutSection): CheckoutField => { const id = generateFieldId() const label = `New ${type.charAt(0).toUpperCase() + type.slice(1)} Field` const key = generateFieldKey(label, section) // Get max priority in section const sectionFields = fields.filter(f => f.section === section) const maxPriority = sectionFields.length > 0 ? Math.max(...sectionFields.map(f => f.priority)) : 0 const newField: CheckoutField = { id, key, type, label, placeholder: '', section, priority: maxPriority + 10, enabled: true, isCore: false, width: 'full', validation: { required: false } } setFields(prev => [...prev, newField]) return newField }, [fields]) // Update field const updateField = useCallback((id: string, updates: Partial) => { setFields(prev => prev.map(field => field.id === id ? { ...field, ...updates } : field )) }, []) // Delete field const deleteField = useCallback((id: string) => { setFields(prev => { const field = prev.find(f => f.id === id) // Don't delete core fields, just disable them if (field?.isCore) { return prev.map(f => f.id === id ? { ...f, enabled: false } : f) } return prev.filter(f => f.id !== id) }) }, []) // Duplicate field const duplicateField = useCallback((id: string): CheckoutField | null => { const originalField = fields.find(f => f.id === id) if (!originalField) return null const newId = generateFieldId() const newKey = generateFieldKey(originalField.label + ' Copy', originalField.section) const duplicatedField: CheckoutField = { ...originalField, id: newId, key: newKey, label: `${originalField.label} (Copy)`, priority: originalField.priority + 1, isCore: false } setFields(prev => [...prev, duplicatedField]) return duplicatedField }, [fields]) // Toggle field enabled const toggleFieldEnabled = useCallback((id: string) => { setFields(prev => prev.map(field => field.id === id ? { ...field, enabled: !field.enabled } : field )) }, []) // Reorder fields within a section const reorderFields = useCallback((section: CheckoutSection, fieldIds: string[]) => { setFields(prev => { const otherFields = prev.filter(f => f.section !== section) const sectionFields = fieldIds.map((id, index) => { const field = prev.find(f => f.id === id) if (field) { return { ...field, priority: (index + 1) * 10 } } return null }).filter(Boolean) as CheckoutField[] return [...otherFields, ...sectionFields] }) }, []) // Move field to different section const moveFieldToSection = useCallback((fieldId: string, newSection: CheckoutSection) => { setFields(prev => { const sectionFields = prev.filter(f => f.section === newSection) const maxPriority = sectionFields.length > 0 ? Math.max(...sectionFields.map(f => f.priority)) : 0 return prev.map(field => { if (field.id === fieldId) { // Update key prefix for new section const newKey = field.key.replace(/^(billing|shipping|order|additional)_/, `${newSection}_`) return { ...field, section: newSection, key: newKey, priority: maxPriority + 10 } } return field }) }) }, []) // Update section const updateSection = useCallback((id: CheckoutSection, updates: Partial) => { setSections(prev => prev.map(section => section.id === id ? { ...section, ...updates } : section )) }, []) // Toggle section enabled const toggleSectionEnabled = useCallback((id: CheckoutSection) => { setSections(prev => prev.map(section => section.id === id ? { ...section, enabled: !section.enabled } : section )) }, []) // Save settings to API const saveSettings = useCallback(async (): Promise => { try { setIsSaving(true) setError(null) const response = await fetch(`${apiUrl}/checkout-field-editor/settings`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': restNonce, }, body: JSON.stringify({ settings, fields, sections }), }) if (!response.ok) { throw new Error('Failed to save settings') } // Update initial state after successful save setInitialState(JSON.stringify({ settings, fields, sections })) setHasChanges(false) return true } catch (err) { console.error('Failed to save checkout field editor settings:', err) setError('Failed to save settings') return false } finally { setIsSaving(false) } }, [apiUrl, restNonce, settings, fields, sections]) // Reset to defaults const resetToDefaults = useCallback(() => { setSettings(defaultSettings) setFields(getDefaultFields()) setSections(defaultSections) }, []) // Export settings as JSON const exportSettings = useCallback((): string => { return JSON.stringify({ settings, fields, sections }, null, 2) }, [settings, fields, sections]) // Import settings from JSON const importSettings = useCallback((json: string): boolean => { try { const data = JSON.parse(json) if (data.settings) setSettings({ ...defaultSettings, ...data.settings }) if (data.fields) setFields(data.fields) if (data.sections) setSections(data.sections) return true } catch { setError('Invalid import data') return false } }, []) return { settings, fields, sections, isLoading, isSaving, error, hasChanges, updateSettings, addField, updateField, deleteField, duplicateField, toggleFieldEnabled, reorderFields, moveFieldToSection, updateSection, toggleSectionEnabled, saveSettings, resetToDefaults, exportSettings, importSettings } }