# Trainer Profile Implementation - Technical Complexities Addendum ## Overview This document addresses advanced technical complexities not covered in the main implementation guide. These details are critical for robust production implementation. ## 1. Public Directory & Gutenberg Compatibility ### REST API Integration The `show_in_rest => true` setting enables several critical features: ```php register_post_type('trainer_profile', [ 'show_in_rest' => true, 'rest_base' => 'trainer-profiles', 'rest_controller_class' => 'HVAC_Trainer_Profile_REST_Controller' ]); ``` ### Custom REST Controller ```php class HVAC_Trainer_Profile_REST_Controller extends WP_REST_Posts_Controller { public function get_items_permissions_check($request) { // Allow public reading but restrict by profile visibility return true; } public function prepare_item_for_response($post, $request) { $response = parent::prepare_item_for_response($post, $request); // Add custom meta fields to REST response $meta_fields = [ 'certification_status', 'trainer_city', 'trainer_state', 'business_type', 'is_public_profile' ]; foreach ($meta_fields as $field) { $response->data[$field] = get_post_meta($post->ID, $field, true); } return $response; } } ``` ### Gutenberg Query Loop Filters ```php // Add custom query parameters for Gutenberg function modify_trainer_profiles_rest_query($args, $request) { // Only show public profiles in frontend queries if (!is_admin() && !current_user_can('hvac_master_trainer')) { $args['meta_query'][] = [ 'key' => 'is_public_profile', 'value' => '1', 'compare' => '=' ]; } // Filter by certification status if ($request->get_param('certification_status')) { $args['meta_query'][] = [ 'key' => 'certification_status', 'value' => $request->get_param('certification_status'), 'compare' => '=' ]; } // Filter by location if ($request->get_param('trainer_city')) { $args['meta_query'][] = [ 'key' => 'trainer_city', 'value' => $request->get_param('trainer_city'), 'compare' => 'LIKE' ]; } // Proximity search using coordinates if ($request->get_param('latitude') && $request->get_param('longitude')) { $lat = floatval($request->get_param('latitude')); $lng = floatval($request->get_param('longitude')); $radius = intval($request->get_param('radius')) ?: 50; // Default 50km // Add proximity calculation to query add_filter('posts_fields', function($fields) use ($lat, $lng) { global $wpdb; $fields .= ", ( 6371 * acos( cos(radians($lat)) * cos(radians(CAST(lat_meta.meta_value AS DECIMAL(10,8)))) * cos(radians(CAST(lng_meta.meta_value AS DECIMAL(11,8))) - radians($lng)) + sin(radians($lat)) * sin(radians(CAST(lat_meta.meta_value AS DECIMAL(10,8)))) ) ) AS distance"; return $fields; }); add_filter('posts_join', function($join) { global $wpdb; $join .= " LEFT JOIN {$wpdb->postmeta} lat_meta ON {$wpdb->posts}.ID = lat_meta.post_id AND lat_meta.meta_key = 'latitude'"; $join .= " LEFT JOIN {$wpdb->postmeta} lng_meta ON {$wpdb->posts}.ID = lng_meta.post_id AND lng_meta.meta_key = 'longitude'"; return $join; }); add_filter('posts_where', function($where) use ($radius) { $where .= " HAVING distance < $radius"; return $where; }); add_filter('posts_orderby', function($orderby) { return "distance ASC"; }); } return $args; } add_filter('rest_trainer_profile_query', 'modify_trainer_profiles_rest_query', 10, 2); ``` ### Gutenberg Block Registration ```php function register_trainer_directory_block() { register_block_type('hvac/trainer-directory', [ 'render_callback' => 'render_trainer_directory_block', 'attributes' => [ 'certification_status' => ['type' => 'string'], 'location' => ['type' => 'string'], 'radius' => ['type' => 'number', 'default' => 50], 'show_map' => ['type' => 'boolean', 'default' => false] ] ]); } add_action('init', 'register_trainer_directory_block'); ``` ## 2. Detailed Permission Matrix ### Field-Level Permission System ```php class HVAC_Trainer_Profile_Permissions { private static $field_permissions = [ // Fields editable by profile owner only 'owner_only' => [ 'linkedin_profile_url', 'personal_accreditation', 'biographical_info', 'training_audience', 'training_formats', 'training_locations', 'training_resources', 'annual_revenue_target' ], // Fields editable by master trainers only 'master_trainer_only' => [ 'certification_status', 'date_certified', 'certification_type', 'is_public_profile' ], // Fields editable by both owner and master trainer 'shared_edit' => [ 'trainer_first_name', 'trainer_last_name', 'trainer_display_name', 'trainer_city', 'trainer_state', 'trainer_country', 'business_type', 'application_details' ], // Read-only fields (auto-generated) 'readonly' => [ 'latitude', 'longitude', 'last_geocoded_timestamp', 'geocoding_status' ], // Never accessible via trainer profile 'restricted' => [ 'user_email', 'user_pass', 'user_login' ] ]; public static function can_edit_field($field_name, $user_id, $profile_user_id) { $is_owner = ($user_id === $profile_user_id); $is_master = user_can($user_id, 'hvac_master_trainer'); $is_admin = user_can($user_id, 'administrator'); // Admins can edit everything except restricted if ($is_admin && !in_array($field_name, self::$field_permissions['restricted'])) { return true; } // Check field-specific permissions if (in_array($field_name, self::$field_permissions['owner_only'])) { return $is_owner; } if (in_array($field_name, self::$field_permissions['master_trainer_only'])) { return $is_master; } if (in_array($field_name, self::$field_permissions['shared_edit'])) { return $is_owner || $is_master; } // Readonly and restricted fields return false; } public static function filter_editable_fields($fields, $user_id, $profile_user_id) { $editable = []; foreach ($fields as $field_name => $field_data) { if (self::can_edit_field($field_name, $user_id, $profile_user_id)) { $editable[$field_name] = $field_data; } } return $editable; } } ``` ### Context-Based Permission Checks ```php function trainer_profile_context_permissions($context, $user_id, $profile_id) { $profile_user_id = get_post_meta($profile_id, 'user_id', true); switch ($context) { case 'public_directory': // Only show public profiles return get_post_meta($profile_id, 'is_public_profile', true) === '1'; case 'admin_list': // Admins see all, master trainers see all, owners see own return user_can($user_id, 'administrator') || user_can($user_id, 'hvac_master_trainer') || $user_id == $profile_user_id; case 'edit_form': // Only owners and master trainers can access edit forms return $user_id == $profile_user_id || user_can($user_id, 'hvac_master_trainer') || user_can($user_id, 'administrator'); case 'single_view': // Public profiles visible to all, private to authorized users only $is_public = get_post_meta($profile_id, 'is_public_profile', true) === '1'; if ($is_public) return true; return $user_id == $profile_user_id || user_can($user_id, 'hvac_master_trainer') || user_can($user_id, 'administrator'); } return false; } ``` ## 3. Data Synchronization Edge Cases ### Infinite Loop Prevention ```php class HVAC_Profile_Sync_Handler { private static $sync_in_progress = []; public static function sync_user_to_profile($user_id, $old_user_data = null) { // Prevent infinite loops $sync_key = "user_to_profile_{$user_id}"; if (isset(self::$sync_in_progress[$sync_key])) { return; } self::$sync_in_progress[$sync_key] = true; try { $profile_id = get_user_meta($user_id, 'trainer_profile_id', true); if (!$profile_id) { unset(self::$sync_in_progress[$sync_key]); return; } // Get current user data $user = get_userdata($user_id); // Sync shared fields $sync_fields = [ 'first_name' => 'trainer_first_name', 'last_name' => 'trainer_last_name', 'display_name' => 'trainer_display_name' ]; foreach ($sync_fields as $user_field => $profile_field) { $user_value = $user->$user_field; $profile_value = get_post_meta($profile_id, $profile_field, true); // Only update if values differ if ($user_value !== $profile_value) { update_post_meta($profile_id, $profile_field, $user_value); } } // Log sync operation error_log("HVAC Profile Sync: User {$user_id} synced to profile {$profile_id}"); } catch (Exception $e) { error_log("HVAC Profile Sync Error: " . $e->getMessage()); } finally { unset(self::$sync_in_progress[$sync_key]); } } public static function sync_profile_to_user($post_id, $post = null) { if (get_post_type($post_id) !== 'trainer_profile') { return; } // Prevent infinite loops $sync_key = "profile_to_user_{$post_id}"; if (isset(self::$sync_in_progress[$sync_key])) { return; } self::$sync_in_progress[$sync_key] = true; try { $user_id = get_post_meta($post_id, 'user_id', true); if (!$user_id) { unset(self::$sync_in_progress[$sync_key]); return; } // Sync shared fields $sync_fields = [ 'trainer_first_name' => 'first_name', 'trainer_last_name' => 'last_name', 'trainer_display_name' => 'display_name' ]; $update_data = ['ID' => $user_id]; $needs_update = false; foreach ($sync_fields as $profile_field => $user_field) { $profile_value = get_post_meta($post_id, $profile_field, true); $user_value = get_user_meta($user_id, $user_field, true); if ($profile_value !== $user_value) { $update_data[$user_field] = $profile_value; $needs_update = true; } } if ($needs_update) { wp_update_user($update_data); } } catch (Exception $e) { error_log("HVAC Profile Sync Error: " . $e->getMessage()); } finally { unset(self::$sync_in_progress[$sync_key]); } } // Handle concurrent updates public static function handle_concurrent_update($user_id, $profile_id, $field, $user_value, $profile_value, $timestamp) { // Conflict resolution: most recent update wins $user_modified = get_user_meta($user_id, "_{$field}_modified", true); $profile_modified = get_post_meta($profile_id, "_{$field}_modified", true); if ($user_modified > $profile_modified) { // User data is more recent, sync to profile update_post_meta($profile_id, "trainer_{$field}", $user_value); update_post_meta($profile_id, "_{$field}_modified", $timestamp); } else { // Profile data is more recent, sync to user update_user_meta($user_id, $field, $profile_value); update_user_meta($user_id, "_{$field}_modified", $timestamp); } // Log conflict resolution error_log("HVAC Sync Conflict Resolved: Field '{$field}' for user {$user_id}"); } } ``` ### Failed Sync Recovery ```php class HVAC_Sync_Recovery { public static function schedule_sync_verification() { if (!wp_next_scheduled('hvac_verify_sync_integrity')) { wp_schedule_event(time(), 'hourly', 'hvac_verify_sync_integrity'); } } public static function verify_sync_integrity() { $profiles = get_posts([ 'post_type' => 'trainer_profile', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => 'user_id', 'compare' => 'EXISTS' ] ] ]); $sync_issues = []; foreach ($profiles as $profile) { $user_id = get_post_meta($profile->ID, 'user_id', true); $user = get_userdata($user_id); if (!$user) { $sync_issues[] = [ 'type' => 'orphaned_profile', 'profile_id' => $profile->ID, 'user_id' => $user_id ]; continue; } // Check field synchronization $sync_fields = [ 'first_name' => 'trainer_first_name', 'last_name' => 'trainer_last_name', 'display_name' => 'trainer_display_name' ]; foreach ($sync_fields as $user_field => $profile_field) { $user_value = $user->$user_field; $profile_value = get_post_meta($profile->ID, $profile_field, true); if ($user_value !== $profile_value) { $sync_issues[] = [ 'type' => 'field_mismatch', 'profile_id' => $profile->ID, 'user_id' => $user_id, 'field' => $user_field, 'user_value' => $user_value, 'profile_value' => $profile_value ]; } } } if (!empty($sync_issues)) { self::repair_sync_issues($sync_issues); } } private static function repair_sync_issues($issues) { foreach ($issues as $issue) { switch ($issue['type']) { case 'orphaned_profile': // Handle orphaned profiles wp_update_post([ 'ID' => $issue['profile_id'], 'post_status' => 'draft' ]); add_post_meta($issue['profile_id'], '_sync_status', 'orphaned'); break; case 'field_mismatch': // Auto-repair field mismatches (user data takes precedence) update_post_meta( $issue['profile_id'], 'trainer_' . $issue['field'], $issue['user_value'] ); break; } } // Log repair actions error_log("HVAC Sync Repair: Fixed " . count($issues) . " sync issues"); } } add_action('hvac_verify_sync_integrity', ['HVAC_Sync_Recovery', 'verify_sync_integrity']); ``` ## 4. Advanced Geocoding Implementation ### Rate Limiting & Caching System ```php class HVAC_Geocoding_Service { private static $api_key; private static $rate_limit = 50; // requests per minute private static $cache_duration = DAY_IN_SECONDS; public static function init() { self::$api_key = get_option('hvac_google_maps_api_key'); add_action('updated_post_meta', [__CLASS__, 'maybe_geocode'], 10, 4); } public static function maybe_geocode($meta_id, $post_id, $meta_key, $meta_value) { if (get_post_type($post_id) !== 'trainer_profile') { return; } $address_fields = ['trainer_city', 'trainer_state', 'trainer_country']; if (!in_array($meta_key, $address_fields)) { return; } // Debounce rapid updates $last_geocode = get_post_meta($post_id, '_last_geocode_attempt', true); if ($last_geocode && (time() - $last_geocode) < 30) { return; } // Schedule geocoding with delay to allow all address fields to be saved wp_schedule_single_event(time() + 5, 'hvac_geocode_address', [$post_id]); } public static function geocode_address($post_id) { // Check rate limiting if (!self::check_rate_limit()) { // Reschedule for later wp_schedule_single_event(time() + 60, 'hvac_geocode_address', [$post_id]); return; } $address = self::build_address($post_id); if (empty($address)) { return; } update_post_meta($post_id, '_last_geocode_attempt', time()); // Check cache first $cache_key = 'geocode_' . md5($address); $cached = get_transient($cache_key); if ($cached !== false) { self::update_coordinates($post_id, $cached); return; } // Make API request $result = self::make_geocoding_request($address); if ($result && isset($result['lat'], $result['lng'])) { // Cache successful result set_transient($cache_key, $result, self::$cache_duration); self::update_coordinates($post_id, $result); update_post_meta($post_id, '_geocoding_status', 'success'); update_post_meta($post_id, '_last_geocoded', time()); } else { // Handle failure self::handle_geocoding_failure($post_id, $result); } } private static function check_rate_limit() { $rate_key = 'hvac_geocoding_rate_' . gmdate('Y-m-d-H-i'); $current_count = get_transient($rate_key) ?: 0; if ($current_count >= self::$rate_limit) { return false; } set_transient($rate_key, $current_count + 1, 60); return true; } private static function make_geocoding_request($address) { if (empty(self::$api_key)) { return ['error' => 'No API key configured']; } $url = 'https://maps.googleapis.com/maps/api/geocode/json'; $params = [ 'address' => $address, 'key' => self::$api_key, 'components' => 'country:US|country:CA' // Restrict to North America ]; $response = wp_remote_get($url . '?' . http_build_query($params), [ 'timeout' => 10, 'user-agent' => 'HVAC Trainer Directory/1.0' ]); if (is_wp_error($response)) { return ['error' => $response->get_error_message()]; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (!$data || $data['status'] !== 'OK' || empty($data['results'])) { return ['error' => $data['status'] ?? 'Unknown error']; } $location = $data['results'][0]['geometry']['location']; return [ 'lat' => $location['lat'], 'lng' => $location['lng'], 'formatted_address' => $data['results'][0]['formatted_address'], 'confidence' => self::calculate_confidence($data['results'][0]) ]; } private static function handle_geocoding_failure($post_id, $error_result) { $error_message = $error_result['error'] ?? 'Unknown error'; update_post_meta($post_id, '_geocoding_status', 'failed'); update_post_meta($post_id, '_geocoding_error', $error_message); // Implement retry logic based on error type switch ($error_message) { case 'OVER_QUERY_LIMIT': // Retry in 1 hour wp_schedule_single_event(time() + HOUR_IN_SECONDS, 'hvac_geocode_address', [$post_id]); break; case 'ZERO_RESULTS': // Try fallback geocoding service self::try_fallback_geocoding($post_id); break; case 'REQUEST_DENIED': // Log API key issue error_log("HVAC Geocoding: API key issue - {$error_message}"); break; default: // Retry in 5 minutes for transient errors wp_schedule_single_event(time() + 300, 'hvac_geocode_address', [$post_id]); } } private static function try_fallback_geocoding($post_id) { // Implement OpenStreetMap Nominatim as fallback $address = self::build_address($post_id); $url = 'https://nominatim.openstreetmap.org/search'; $params = [ 'q' => $address, 'format' => 'json', 'limit' => 1, 'countrycodes' => 'us,ca' ]; $response = wp_remote_get($url . '?' . http_build_query($params), [ 'timeout' => 10, 'user-agent' => 'HVAC Trainer Directory/1.0' ]); if (!is_wp_error($response)) { $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (!empty($data) && isset($data[0]['lat'], $data[0]['lon'])) { $result = [ 'lat' => floatval($data[0]['lat']), 'lng' => floatval($data[0]['lon']), 'formatted_address' => $data[0]['display_name'], 'confidence' => 0.7, // Lower confidence for fallback 'source' => 'nominatim' ]; self::update_coordinates($post_id, $result); update_post_meta($post_id, '_geocoding_status', 'success_fallback'); return; } } // Final fallback failed update_post_meta($post_id, '_geocoding_status', 'failed_all'); } private static function calculate_confidence($result) { $types = $result['types'] ?? []; // Higher confidence for more specific location types if (in_array('street_address', $types)) return 1.0; if (in_array('premise', $types)) return 0.9; if (in_array('subpremise', $types)) return 0.85; if (in_array('locality', $types)) return 0.8; if (in_array('administrative_area_level_3', $types)) return 0.7; if (in_array('administrative_area_level_2', $types)) return 0.6; if (in_array('administrative_area_level_1', $types)) return 0.5; return 0.4; // Low confidence for country-level matches } } add_action('init', ['HVAC_Geocoding_Service', 'init']); add_action('hvac_geocode_address', ['HVAC_Geocoding_Service', 'geocode_address']); ``` ## 5. CSV Migration Complexity Handling ### Migration State Management ```php class HVAC_CSV_Migration_Manager { private static $migration_log = []; public static function migrate_csv_data($csv_file_path) { // Initialize migration tracking $migration_id = uniqid('migration_'); $start_time = time(); update_option('hvac_migration_status', [ 'id' => $migration_id, 'status' => 'in_progress', 'start_time' => $start_time, 'total_records' => 0, 'processed' => 0, 'errors' => [] ]); try { // Validate CSV file if (!file_exists($csv_file_path)) { throw new Exception("CSV file not found: {$csv_file_path}"); } // Parse CSV and count records $csv_data = self::parse_csv($csv_file_path); $total_records = count($csv_data); self::update_migration_status($migration_id, [ 'total_records' => $total_records ]); // Process each record foreach ($csv_data as $index => $row) { try { self::process_csv_row($row, $index); self::update_migration_status($migration_id, [ 'processed' => $index + 1 ]); } catch (Exception $e) { self::log_migration_error($migration_id, $index, $e->getMessage(), $row); } } // Complete migration self::complete_migration($migration_id); } catch (Exception $e) { self::fail_migration($migration_id, $e->getMessage()); throw $e; } } private static function process_csv_row($row, $index) { // Validate required fields $required_fields = ['email', 'first_name', 'last_name']; foreach ($required_fields as $field) { if (empty($row[$field])) { throw new Exception("Missing required field: {$field}"); } } // Check if user already exists $user = get_user_by('email', $row['email']); if ($user) { // Update existing user $profile_id = self::get_or_create_trainer_profile($user->ID); self::update_profile_from_csv($profile_id, $row); } else { // Create new user $user_id = self::create_user_from_csv($row); $profile_id = self::create_trainer_profile($user_id, $row); } // Validate profile creation if (!$profile_id) { throw new Exception("Failed to create trainer profile for user: {$row['email']}"); } // Trigger geocoding if address data exists if (!empty($row['trainer_city']) || !empty($row['trainer_state'])) { wp_schedule_single_event(time() + (5 * $index), 'hvac_geocode_address', [$profile_id]); } } private static function get_or_create_trainer_profile($user_id) { $existing_profile_id = get_user_meta($user_id, 'trainer_profile_id', true); if ($existing_profile_id && get_post($existing_profile_id)) { return $existing_profile_id; } // Create new profile $user = get_userdata($user_id); $profile_id = wp_insert_post([ 'post_type' => 'trainer_profile', 'post_title' => $user->display_name . ' - Trainer Profile', 'post_status' => 'publish', 'post_author' => $user_id ]); if (is_wp_error($profile_id)) { throw new Exception("Failed to create trainer profile: " . $profile_id->get_error_message()); } // Establish relationships update_post_meta($profile_id, 'user_id', $user_id); update_user_meta($user_id, 'trainer_profile_id', $profile_id); return $profile_id; } private static function rollback_migration($migration_id) { $status = get_option('hvac_migration_status'); if (!$status || $status['id'] !== $migration_id) { return false; } // Get all profiles created during this migration $created_profiles = get_option("hvac_migration_created_{$migration_id}", []); foreach ($created_profiles as $profile_id) { // Remove trainer profile wp_delete_post($profile_id, true); // Clean up user meta $user_id = get_post_meta($profile_id, 'user_id', true); if ($user_id) { delete_user_meta($user_id, 'trainer_profile_id'); } } // Get all users created during this migration $created_users = get_option("hvac_migration_users_{$migration_id}", []); foreach ($created_users as $user_id) { wp_delete_user($user_id); } // Clean up migration data delete_option("hvac_migration_created_{$migration_id}"); delete_option("hvac_migration_users_{$migration_id}"); update_option('hvac_migration_status', [ 'id' => $migration_id, 'status' => 'rolled_back', 'rollback_time' => time() ]); return true; } } ``` ## 6. Form State Management & UX ### Auto-save & Unsaved Changes Detection ```php // JavaScript for form state management function initFormStateManagement() { let formData = new FormData(); let hasUnsavedChanges = false; let autoSaveInterval; // Capture initial form state function captureFormState() { const form = document.getElementById('trainer-profile-form'); formData = new FormData(form); } // Check for changes function checkForChanges() { const form = document.getElementById('trainer-profile-form'); const currentData = new FormData(form); // Compare form data let hasChanges = false; for (let [key, value] of currentData.entries()) { if (formData.get(key) !== value) { hasChanges = true; break; } } if (hasChanges !== hasUnsavedChanges) { hasUnsavedChanges = hasChanges; toggleUnsavedIndicator(hasChanges); if (hasChanges && !autoSaveInterval) { startAutoSave(); } else if (!hasChanges && autoSaveInterval) { stopAutoSave(); } } } // Auto-save functionality function startAutoSave() { autoSaveInterval = setInterval(() => { if (hasUnsavedChanges) { autoSaveForm(); } }, 30000); // Auto-save every 30 seconds } function autoSaveForm() { const form = document.getElementById('trainer-profile-form'); const formData = new FormData(form); formData.append('action', 'hvac_auto_save_profile'); formData.append('auto_save', '1'); fetch(hvac_ajax.ajax_url, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { showAutoSaveIndicator(); captureFormState(); // Update baseline hasUnsavedChanges = false; toggleUnsavedIndicator(false); } }) .catch(error => { console.error('Auto-save failed:', error); }); } // Prevent navigation with unsaved changes window.addEventListener('beforeunload', (e) => { if (hasUnsavedChanges) { e.preventDefault(); e.returnValue = 'You have unsaved changes. Are you sure you want to leave?'; return e.returnValue; } }); // Real-time validation function setupRealTimeValidation() { const form = document.getElementById('trainer-profile-form'); const inputs = form.querySelectorAll('input, select, textarea'); inputs.forEach(input => { input.addEventListener('blur', () => { validateField(input); }); input.addEventListener('input', debounce(() => { checkForChanges(); if (input.value.length > 0) { validateField(input); } }, 300)); }); } function validateField(field) { const fieldName = field.name; const fieldValue = field.value; // Client-side validation rules const validationRules = { 'linkedin_profile_url': { pattern: /^https:\/\/(www\.)?linkedin\.com\/in\/[a-zA-Z0-9-]+\/?$/, message: 'Please enter a valid LinkedIn profile URL' }, 'trainer_email': { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: 'Please enter a valid email address' }, 'annual_revenue_target': { pattern: /^\d+$/, message: 'Please enter a valid number' } }; if (validationRules[fieldName]) { const rule = validationRules[fieldName]; const isValid = rule.pattern.test(fieldValue); toggleFieldValidation(field, isValid, rule.message); } } // Initialize everything captureFormState(); setupRealTimeValidation(); // Form submission handling document.getElementById('trainer-profile-form').addEventListener('submit', (e) => { e.preventDefault(); // Show loading state const submitButton = e.target.querySelector('button[type="submit"]'); const originalText = submitButton.textContent; submitButton.textContent = 'Saving...'; submitButton.disabled = true; // Submit form via AJAX const formData = new FormData(e.target); formData.append('action', 'hvac_save_trainer_profile'); fetch(hvac_ajax.ajax_url, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { showSuccessMessage('Profile saved successfully!'); captureFormState(); // Update baseline hasUnsavedChanges = false; toggleUnsavedIndicator(false); // Trigger geocoding if address changed if (data.data && data.data.geocoding_triggered) { showGeocodingIndicator(); } } else { showErrorMessage(data.data || 'An error occurred while saving.'); } }) .catch(error => { showErrorMessage('Network error occurred. Please try again.'); }) .finally(() => { submitButton.textContent = originalText; submitButton.disabled = false; }); }); } // Initialize when DOM is ready document.addEventListener('DOMContentLoaded', initFormStateManagement); ``` This addendum addresses the critical technical complexities that ensure robust, production-ready implementation of the trainer profile system.