diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 8f02aa40..c8a91c00 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -121,7 +121,18 @@ "Bash(DISPLAY=:0 XAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.90WDB3 node test-master-trainer-debug.js)", "Bash(DISPLAY=:0 XAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.90WDB3 node test-page-source-debug.js)", "Bash(DISPLAY=:0 XAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.90WDB3 node test-logged-in-master.js)", - "Bash(DISPLAY=:0 XAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.90WDB3 node test-master-nav-colors.js)" + "Bash(DISPLAY=:0 XAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.90WDB3 node test-master-nav-colors.js)", + "Read(//tmp/playwright-mcp-output/2025-08-23T02-04-04.729Z/**)", + "Read(//tmp/playwright-mcp-output/2025-08-23T02-33-36.058Z/**)", + "Bash(DISPLAY=:0 XAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.90WDB3 node test-safari-fix.js)", + "Bash(who)", + "Bash(DISPLAY=:0 XAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.90WDB3 node test-hvac-comprehensive-e2e.js)", + "Bash(DISPLAY=:0 XAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.90WDB3 HEADLESS=false node test-hvac-comprehensive-e2e.js)", + "mcp__playwright__browser_select_option", + "Bash(scripts/verify-plugin-fixes.sh:*)", + "Read(//tmp/playwright-mcp-output/2025-08-24T02-48-35.660Z/**)", + "Read(//tmp/playwright-mcp-output/2025-08-24T05-54-43.212Z/**)", + "Read(//tmp/playwright-mcp-output/2025-08-24T06-09-48.600Z/**)" ], "deny": [] }, diff --git a/CLAUDE.md b/CLAUDE.md index aa57a1d2..878f61f7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -294,4 +294,8 @@ The following systems are commented out in `/includes/class-hvac-plugin.php` lin - **JavaScript Simplification (2025-08-18)**: Removed 200+ lines of unnecessary jQuery compatibility code following WordPress best practices. Eliminated hvac-jquery-compatibility-fix.js and class-hvac-jquery-compatibility.php. Updated community-login.js to use standard `jQuery(document).ready()` pattern. WordPress handles jQuery in no-conflict mode automatically - complex compatibility layers violate best practices and add unnecessary complexity. Production deployment successful with all functionality working correctly. - **Event Management Page UI Enhancement (2025-08-19)**: Improved trainer/event/manage/ page UX by removing redundant buttons and adding helpful event creation guide. Changes: Removed "Add New Event" and "View My Events" buttons to reduce clutter, added breadcrumb navigation to harmonize with other trainer pages, introduced "Quick Guide to Creating Events" section with 8 essential bullet points covering event types, requirements, registration options, and approval process. Guide styled with light gray background for improved readability. Maintains The Events Calendar shortcode integration. - **Navigation Menu Desktop Visibility Fix (2025-08-21)**: Resolved critical navigation issue where HVAC trainer menu was completely invisible on desktop browsers. Root cause: CSS responsive design was incomplete - mobile rule set `display: none !important` for menu at ≤992px, but no corresponding desktop rule existed to show menu at ≥993px. HTML structure and JavaScript handlers were functioning correctly, but CSS was hiding the entire navigation. Solution: Added desktop media query to `assets/css/hvac-menu-system.css` with `@media (min-width: 993px) { .hvac-trainer-menu { display: flex !important; visibility: visible !important; opacity: 1 !important; } }`. Investigation used Zen debug workflow with GPT-5, systematic DOM inspection, computed style analysis, and browser width testing. Navigation now displays correctly as horizontal navbar with working dropdown functionality. Deployed to staging and user-verified working on desktop browsers. -- **Master Trainer Area Comprehensive Audit & Implementation (2025-08-23)**: Completed systematic audit of Master Trainer area identifying inconsistencies, anti-patterns, missing pages, and navigation issues. Successfully implemented ALL missing functionality: 1) **Missing Pages**: Implemented 5 critical pages - Master Events Overview (/master-trainer/events/) with KPI dashboard and filtering, Import/Export Data Management (/master-trainer/import-export/) with CSV operations and security validation, Communication Templates (/trainer/communication-templates/) with professional accordion interface and copy functionality, Enhanced Announcements (/master-trainer/announcements/) with dynamic shortcode integration, Pending Approvals System (/master-trainer/pending-approvals/) with workflow management. 2) **Navigation Improvements**: Removed redundant Events link from top-level menu, reorganized all administrative functions under Tools dropdown for cleaner UX following best practices. 3) **Architecture**: Added 4 new singleton manager classes following WordPress patterns, comprehensive role-based access control (hvac_master_trainer), complete security implementation (nonces, sanitization, escaping), performance optimizations with transient caching, professional error handling and user feedback systems. 4) **Implementation**: 16 new files added (4 manager classes, 4 CSS/JS pairs, 2 new templates, 2 enhanced templates), 14 existing files enhanced, 8,438+ lines of production-ready code. 5) **Testing**: Comprehensive testing with Playwright automation, successful staging deployment and verification, all missing pages now fully functional. Used sequential thinking, Zen consensus (GPT-5/Gemini 2.5 Pro), specialized backend-architect agents, and systematic code review workflows. Master Trainer area now 100% complete with production-ready functionality. See MASTER-TRAINER-AUDIT-IMPLEMENTATION.md for full technical documentation. \ No newline at end of file +- **Master Trainer Area Comprehensive Audit & Implementation (2025-08-23)**: Completed systematic audit of Master Trainer area identifying inconsistencies, anti-patterns, missing pages, and navigation issues. Successfully implemented ALL missing functionality: 1) **Missing Pages**: Implemented 5 critical pages - Master Events Overview (/master-trainer/events/) with KPI dashboard and filtering, Import/Export Data Management (/master-trainer/import-export/) with CSV operations and security validation, Communication Templates (/trainer/communication-templates/) with professional accordion interface and copy functionality, Enhanced Announcements (/master-trainer/announcements/) with dynamic shortcode integration, Pending Approvals System (/master-trainer/pending-approvals/) with workflow management. 2) **Navigation Improvements**: Removed redundant Events link from top-level menu, reorganized all administrative functions under Tools dropdown for cleaner UX following best practices. 3) **Architecture**: Added 4 new singleton manager classes following WordPress patterns, comprehensive role-based access control (hvac_master_trainer), complete security implementation (nonces, sanitization, escaping), performance optimizations with transient caching, professional error handling and user feedback systems. 4) **Implementation**: 16 new files added (4 manager classes, 4 CSS/JS pairs, 2 new templates, 2 enhanced templates), 14 existing files enhanced, 8,438+ lines of production-ready code. 5) **Testing**: Comprehensive testing with Playwright automation, successful staging deployment and verification, all missing pages now fully functional. Used sequential thinking, Zen consensus (GPT-5/Gemini 2.5 Pro), specialized backend-architect agents, and systematic code review workflows. Master Trainer area now 100% complete with production-ready functionality. See MASTER-TRAINER-AUDIT-IMPLEMENTATION.md for full technical documentation. +- **Event Edit Page 500 Error Fix (2025-08-24)**: Fixed critical HTTP 500 error on event edit page (/trainer/event/edit/). Root cause: Template file attempted to instantiate non-existent class `HVAC_Custom_Event_Edit`. Solution: Updated `/templates/page-edit-event-custom.php` line 26 to use correct `HVAC_Event_Manager::instance()`. Event edit functionality now fully operational with all form fields, venue/organizer selection, and category management working correctly. +- **Registration Form Display Fix (2025-08-24)**: Fixed critical issue where registration form shortcode wasn't rendering any content. Root cause: `HVAC_Security_Helpers` dependency wasn't loaded when shortcode executed, causing silent PHP failure. Solution: Added `require_once` for both `class-hvac-security-helpers.php` and `class-hvac-registration.php` in the `render_registration()` method in `class-hvac-shortcodes.php` (lines 470-479). Registration form now displays correctly with all 40+ fields and conditional sections working properly. +- **Comprehensive E2E Testing Implementation (2025-08-24)**: Created complete end-to-end test suite (`test-hvac-comprehensive-e2e.js`) using MCP Playwright browser automation. Tests cover: Find a Trainer, Registration, Login, Event Creation/Editing, Certificate Generation, and Master Trainer features. Achieved 70% test success rate. Used parallel debugging agents with sequential thinking and GPT-5 for issue diagnosis. Test infrastructure includes automatic screenshots, JSON reporting, and support for both headless and headed browser modes. +- You will only use a headed browser (in the existing gnome xwayland session on display 0) when doing tests. \ No newline at end of file diff --git a/assets/css/find-trainer.css b/assets/css/find-trainer.css new file mode 100644 index 00000000..465c8db1 --- /dev/null +++ b/assets/css/find-trainer.css @@ -0,0 +1,1571 @@ +/** + * Find a Trainer Page Styles + * Matches the specification mockups exactly + * + * @package HVAC_Plugin + * @since 1.0.0 + */ + +/* ======================================== + Page Layout & Container Structure + ======================================== */ + +.hvac-find-trainer-page { + background: #fff; + padding: 20px 0; + width: 100%; + clear: both; +} + +.hvac-find-trainer-page .ast-container { + max-width: 1200px !important; + margin: 0 auto !important; + padding: 0 20px !important; + width: 100% !important; + box-sizing: border-box !important; + /* Single column layout */ + display: block !important; +} + +/* Page Title */ +.hvac-page-title { + font-size: 32px; + font-weight: 600; + margin: 0 0 20px 0; + color: #333; +} + +/* ======================================== + Container 1: Summary + ======================================== */ + +.hvac-summary-container { + background: #fff; + border: 2px solid #e0e0e0; + border-radius: 8px; + padding: 20px; + margin-bottom: 20px; +} + +.hvac-summary-container p { + margin: 0; + font-size: 16px; + line-height: 1.6; + color: #333; +} + +/* ======================================== + Container 2: Map & Filters Layout (SINGLE ROW) + ======================================== */ + +.hvac-map-filters-container { + display: flex !important; + gap: 20px; + margin-bottom: 40px; + min-height: 500px; + width: 100%; + background: #fff; + border: 2px solid #e0e0e0; + border-radius: 8px; + padding: 20px; + box-sizing: border-box; +} + +/* Map Section (LEFT - 2/3 width) */ +.hvac-map-section { + flex: 2; + min-width: 0; + position: relative; + min-height: 450px; + overflow: hidden !important; + max-width: 100% !important; +} + +/* MapGeo Integration - Force map to display properly */ +.hvac-map-section .map_wrapper { + display: block !important; + visibility: visible !important; + opacity: 1 !important; + width: 100% !important; + max-width: 100% !important; + height: 450px !important; + position: relative !important; + overflow: hidden !important; + box-sizing: border-box !important; +} + +/* Ensure map render div is visible even with loading class */ +.hvac-map-section .map_render, +.hvac-map-section .map_loading { + display: block !important; + visibility: visible !important; + opacity: 1 !important; + background: transparent !important; +} + +/* Make sure the map container and box are visible with proper width */ +.hvac-map-section .map_box { + display: block !important; + visibility: visible !important; + opacity: 1 !important; + width: 100% !important; + max-width: 100% !important; + overflow: hidden !important; + box-sizing: border-box !important; +} + +.hvac-map-section .map_container { + display: block !important; + visibility: visible !important; + opacity: 1 !important; +} + +/* Ensure SVG inside map is visible */ +.hvac-map-section .map_render svg { + display: block !important; + visibility: visible !important; + opacity: 1 !important; +} + +/* Hide ONLY the sidebar content area if MapGeo creates one */ +.hvac-find-trainer-page .igm_content_right_1_3 { + display: none !important; +} + +/* Prevent MapGeo from showing any trainer profiles in its own containers */ +.igm_content_right_1_3 .hvac-trainer-card, +.igm_content_left_2_3 .hvac-trainer-card, +.map_wrapper .hvac-trainer-card { + display: none !important; +} + +/* Force MapGeo containers to have dimensions */ +.hvac-map-section .map_box, +.hvac-map-section .map_aspect_ratio { + width: 100% !important; + height: 450px !important; + min-height: 450px !important; + position: relative !important; + display: block !important; +} + +.hvac-map-section .map_container { + position: absolute !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + width: 100% !important; + height: 100% !important; +} + +.hvac-map-section .map_render { + width: 100% !important; + height: 100% !important; + position: absolute !important; + top: 0 !important; + left: 0 !important; +} + +/* Force the map ID to show */ +.hvac-map-section #map_5872 { + display: block !important; + width: 100% !important; + height: 100% !important; + min-height: 450px !important; +} + +/* Override MapGeo aspect ratio if needed */ +.hvac-map-section .map_aspect_ratio { + padding-top: 56% !important; /* 16:9 aspect ratio */ + position: relative !important; + max-width: 100% !important; + overflow: hidden !important; +} + +/* Ensure map canvas is visible and constrained */ +.hvac-map-section canvas { + max-width: 100% !important; + width: 100% !important; + height: auto !important; + overflow: hidden !important; +} + +/* Comprehensive MapGeo element constraints */ +.hvac-map-section svg, +.hvac-map-section g, +.hvac-map-section path, +.hvac-map-section circle { + max-width: 100% !important; + overflow: visible !important; +} + +/* Prevent any MapGeo elements from overflowing */ +.hvac-map-section * { + box-sizing: border-box !important; +} + +.hvac-map-section .map_render, +.hvac-map-section .map_wrapper, +.hvac-map-section .map_box, +.hvac-map-section .map_container, +.hvac-map-section .map_aspect_ratio { + max-width: 100% !important; + overflow: hidden !important; + contain: layout size style !important; +} + +.hvac-map-placeholder { + width: 100%; + height: 450px; + display: flex; + align-items: center; + justify-content: center; + background: #f5f5f5; + border-radius: 4px; +} + +.hvac-map-placeholder img { + max-width: 100%; + height: auto; +} + +/* Filters Section (RIGHT - 1/3 width) */ +.hvac-filters-section { + flex: 1; + display: flex; + flex-direction: column; + gap: 15px; + min-width: 250px; + padding-left: 20px; + border-left: 1px solid #e0e0e0; +} + +/* Search Box */ +.hvac-search-box { + position: relative; + width: 100%; +} + +.hvac-search-box input { + width: 100%; + padding: 12px 40px 12px 15px; + border: 2px solid #e0e0e0; + border-radius: 25px; + font-size: 14px; + background: #fff; + transition: all 0.3s; +} + +.hvac-search-box input:focus { + outline: none; + border-color: #0073aa; + box-shadow: 0 0 0 3px rgba(0, 115, 170, 0.1); +} + +.hvac-search-box input::placeholder { + color: #999; +} + +.hvac-search-box .dashicons { + position: absolute; + right: 15px; + top: 50%; + transform: translateY(-50%); + color: #666; + font-size: 18px; + pointer-events: none; +} + +/* Filters Header */ +.hvac-filters-header { + display: flex; + align-items: center; + justify-content: space-between; + margin: 10px 0 5px 0; +} + +/* Filters Label */ +.hvac-filters-label { + font-weight: 600; + font-size: 16px; + color: #333; +} + +/* Clear Filters Button */ +.hvac-clear-filters { + padding: 6px 12px; + background: #dc3545; + color: white; + border: none; + border-radius: 4px; + font-size: 13px; + cursor: pointer; + transition: all 0.3s; + font-weight: 500; +} + +.hvac-clear-filters:hover { + background: #c82333; + transform: translateY(-1px); +} + +.hvac-clear-filters:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.25); +} + +/* Filter Buttons */ +.hvac-filter-btn { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 12px 15px; + background: #fff; + border: 2px solid #e0e0e0; + border-radius: 25px; + font-size: 14px; + color: #333; + cursor: pointer; + transition: all 0.3s; + text-align: left; +} + +.hvac-filter-btn:hover { + background: #f8f9fa; + border-color: #0073aa; +} + +.hvac-filter-btn:focus { + outline: none; + border-color: #0073aa; + box-shadow: 0 0 0 3px rgba(0, 115, 170, 0.1); +} + +.hvac-filter-btn .dashicons { + font-size: 16px; + color: #666; + margin-left: auto; +} + +/* Active Filters */ +.hvac-active-filters { + margin-top: 10px; + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.hvac-active-filter { + display: inline-flex; + align-items: center; + padding: 6px 12px; + background: #0073aa; + color: white; + border-radius: 20px; + font-size: 13px; + gap: 8px; +} + +.hvac-active-filter button { + background: none; + border: none; + color: white; + cursor: pointer; + font-size: 16px; + padding: 0; + width: 18px; + height: 18px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + transition: background 0.2s; +} + +.hvac-active-filter button:hover { + background: rgba(255, 255, 255, 0.2); +} + +/* ======================================== + Container 3: Trainer Directory + ======================================== */ + +.hvac-trainer-directory-container { + background: #fff; + border: 2px solid #e0e0e0; + border-radius: 8px; + padding: 20px; + margin-bottom: 20px; + width: 100%; + display: block; + box-sizing: border-box; +} + +/* Trainer Grid - 2 columns */ +.hvac-trainer-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 20px; + margin-bottom: 20px; +} + +/* Trainer Card */ +.hvac-trainer-card { + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 20px; + background: #f8f8f8; + transition: all 0.3s; +} + +/* Only trainers (not champions) should have hover effects and cursor */ +.hvac-trainer-card:not(.hvac-champion-card) { + cursor: pointer; +} + +.hvac-trainer-card:not(.hvac-champion-card):hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transform: translateY(-2px); +} + +/* Champion cards have a different visual treatment */ +.hvac-trainer-card.hvac-champion-card { + cursor: default; + opacity: 0.85; +} + +/* Certified measureQuick Trainer cards - green styling */ +.hvac-trainer-card.hvac-trainer-card-certified { + background-color: rgba(137, 201, 46, 0.5); /* #89c92e @ 50% transparency */ + border-color: rgba(137, 201, 46, 0.3); +} + +.hvac-trainer-card.hvac-trainer-card-certified:hover { + background-color: #89c92e; /* Solid green on hover */ + border-color: #7bb528; + box-shadow: 0 4px 12px rgba(137, 201, 46, 0.3); +} + +.hvac-trainer-card-content { + display: flex; + gap: 15px; + align-items: flex-start; +} + +/* Trainer Image/Avatar */ +.hvac-trainer-image { + width: 80px; + height: 80px; + flex-shrink: 0; +} + +.hvac-trainer-image img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 50%; + background: #ddd; +} + +.hvac-trainer-avatar { + width: 100%; + height: 100%; + background: #6c757d; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +.hvac-trainer-avatar .dashicons { + font-size: 40px; + color: white; +} + +/* mQ Certified Trainer Badge Overlay */ +.hvac-trainer-image, +.hvac-modal-image { + position: relative; /* Enable positioning for overlay */ +} + +.hvac-mq-badge-overlay { + position: absolute; + top: -5px; /* Slightly outside the circle */ + right: -5px; /* Positioned in top-right corner */ + width: 35px; + height: 35px; + z-index: 10; /* Ensure it appears above the profile image */ + pointer-events: none; /* Don't interfere with clicks */ +} + +.hvac-mq-badge { + width: 100%; + height: 100%; + object-fit: contain; + filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2)); /* Add subtle shadow for visibility */ +} + +/* Trainer Details */ +.hvac-trainer-details { + flex: 1; +} + +.hvac-trainer-name { + margin: 0 0 8px 0; + font-size: 18px; + font-weight: 600; +} + +.hvac-trainer-name a { + color: #333; + text-decoration: none; + font-weight: 600; /* Bold for clickable trainers */ +} + +.hvac-trainer-name a:hover { + color: #0073aa; +} + +/* Champion names - not clickable, not bold */ +.hvac-champion-name { + color: #333; + font-weight: 400; /* Normal weight for champions */ + cursor: default; +} + +.hvac-trainer-location { + margin: 0 0 8px 0; + font-size: 14px; + color: #666; +} + +.hvac-trainer-certification { + margin: 0; + font-size: 14px; + color: #0073aa; + font-weight: 500; +} + +.hvac-see-events { + display: inline-flex; + align-items: center; + gap: 5px; + margin-top: 10px; + color: #333; + text-decoration: none; + font-size: 14px; +} + +.hvac-see-events:hover { + color: #0073aa; +} + +.hvac-see-events .dashicons { + font-size: 16px; +} + +/* Pagination */ +.hvac-pagination { + text-align: center; + margin-top: 20px; +} + +.hvac-pagination a, +.hvac-pagination span { + display: inline-block; + padding: 8px 12px; + margin: 0 3px; + border: 1px solid #dee2e6; + border-radius: 4px; + color: #333; + text-decoration: none; + transition: all 0.3s; +} + +.hvac-pagination a:hover { + background: #0073aa; + color: white; + border-color: #0073aa; +} + +.hvac-pagination .current { + background: #0073aa; + color: white; + border-color: #0073aa; +} + +/* No Results */ +.hvac-no-trainers { + text-align: center; + padding: 60px 20px; + color: #666; +} + +.hvac-no-trainers p { + font-size: 16px; + margin: 0; +} + +/* ======================================== + Container 4: CTA Section + ======================================== */ + +.hvac-cta-container { + background: #fff; + border: 2px solid #e0e0e0; + border-radius: 8px; + padding: 30px; + text-align: center; + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + width: 100%; + box-sizing: border-box; +} + +.hvac-cta-text { + margin: 0; + font-size: 18px; + font-style: italic; + color: #333; + flex: 1; + text-align: left; +} + +.hvac-cta-button { + display: inline-block; + padding: 12px 30px; + background: #333; + color: white; + border-radius: 25px; + text-decoration: none; + font-size: 16px; + font-weight: 600; + transition: all 0.3s; + white-space: nowrap; +} + +.hvac-cta-button:hover { + background: #555; + color: white; + text-decoration: none; + transform: translateY(-2px); +} + +/* ======================================== + Filter Modal + ======================================== */ + +/* CRITICAL FIX: Filter modal must be hidden by default */ +.hvac-filter-modal, +#hvac-filter-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 999998; + display: none !important; /* CRITICAL: Force hidden */ + align-items: center; + justify-content: center; + padding: 20px; + visibility: hidden; + opacity: 0; + pointer-events: none; +} + +/* Only show when JavaScript explicitly activates it */ +.hvac-filter-modal.modal-active, +#hvac-filter-modal.modal-active { + display: flex !important; + visibility: visible; + opacity: 1; + pointer-events: auto; +} + +.hvac-filter-modal-content { + background: white; + border-radius: 8px; + padding: 30px; + width: 90%; + max-width: 450px; + max-height: 70vh; + overflow-y: auto; + position: relative; +} + +.hvac-filter-modal-title { + margin: 0 0 25px 0; + font-size: 22px; + font-weight: 600; + color: #333; +} + +.hvac-filter-options { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: 25px; +} + +.hvac-filter-option { + display: flex; + align-items: center; + padding: 12px 15px; + border: 1px solid #dee2e6; + border-radius: 6px; + cursor: pointer; + transition: all 0.3s; +} + +.hvac-filter-option:hover { + background: #f8f9fa; + border-color: #0073aa; +} + +.hvac-filter-option input { + margin-right: 12px; + cursor: pointer; +} + +.hvac-filter-option label { + cursor: pointer; + margin: 0; + flex: 1; +} + +.hvac-filter-apply { + width: 100%; + padding: 12px; + background: #0073aa; + color: white; + border: none; + border-radius: 6px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.3s; +} + +.hvac-filter-apply:hover { + background: #005a87; +} + +/* ======================================== + Trainer Profile Modal + ======================================== */ + +.hvac-trainer-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.8); + z-index: 999999; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.hvac-trainer-modal-content { + background: white; + border-radius: 8px; + width: 100%; + max-width: 700px; + max-height: 90vh; + overflow-y: auto; + position: relative; + padding: 30px; +} + +/* Close Button */ +.hvac-modal-close { + position: absolute; + top: 15px; + right: 15px; + background: white; + border: 2px solid #333; + border-radius: 50%; + width: 32px; + height: 32px; + cursor: pointer; + padding: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s; +} + +.hvac-modal-close:hover { + background: #333; +} + +.hvac-modal-close .dashicons { + font-size: 20px; + color: #333; +} + +.hvac-modal-close:hover .dashicons { + color: white; +} + +/* Modal Title */ +.hvac-modal-title { + margin: 0 0 25px 0; + font-size: 28px; + font-weight: 600; + color: #333; + text-align: center; +} + +/* Container 1: Profile Info */ +.hvac-modal-profile { + display: flex; + gap: 20px; + padding: 20px; + background: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 6px; + margin-bottom: 20px; +} + +.hvac-modal-image { + width: 120px; + height: 120px; + flex-shrink: 0; +} + +.hvac-modal-image img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 50%; + background: #dee2e6; +} + +.hvac-modal-info { + flex: 1; +} + +.hvac-modal-info p { + margin: 0 0 10px 0; + font-size: 16px; + color: #333; +} + +.hvac-modal-location { + font-size: 18px !important; + color: #666 !important; +} + +.hvac-modal-certification { + color: #0073aa !important; + font-weight: 500; +} + +.hvac-modal-business { + color: #666; +} + +.hvac-modal-events span { + font-weight: 600; +} + +/* Container 2: Training Details */ +.hvac-modal-training { + padding: 20px; + border: 1px solid #dee2e6; + border-radius: 6px; + margin-bottom: 20px; +} + +.hvac-training-row { + margin-bottom: 15px; + font-size: 16px; +} + +.hvac-training-row strong { + display: inline-block; + min-width: 150px; + color: #333; +} + +.hvac-training-events { + margin-top: 20px; +} + +.hvac-training-events strong { + display: block; + margin-bottom: 10px; + font-size: 16px; + color: #333; +} + +.hvac-events-list { + margin: 0; + padding-left: 20px; +} + +.hvac-events-list li { + margin-bottom: 8px; + color: #666; +} + +.hvac-events-list a { + color: #0073aa; + text-decoration: none; +} + +.hvac-events-list a:hover { + text-decoration: underline; +} + +/* Container 3: Contact Form */ +.hvac-modal-contact { + padding: 20px; + background: #f8f9fa; + border-radius: 6px; +} + +.hvac-modal-contact h3 { + margin: 0 0 20px 0; + font-size: 20px; + font-weight: 600; + text-align: center; + color: #333; +} + +.hvac-contact-form { + display: flex; + flex-direction: column; + gap: 15px; +} + +.hvac-form-row { + display: flex; + gap: 15px; +} + +.hvac-form-row input { + flex: 1; +} + +.hvac-form-full { + width: 100%; +} + +.hvac-contact-form input, +.hvac-contact-form textarea { + width: 100%; + padding: 10px 15px; + border: 2px solid #dee2e6; + border-radius: 25px; + font-size: 14px; + font-family: inherit; + background: #fff; + transition: all 0.3s; +} + +.hvac-contact-form textarea { + border-radius: 15px; + resize: vertical; + min-height: 100px; +} + +.hvac-contact-form input:focus, +.hvac-contact-form textarea:focus { + outline: none; + border-color: #0073aa; + box-shadow: 0 0 0 3px rgba(0, 115, 170, 0.1); +} + +.hvac-contact-form input::placeholder, +.hvac-contact-form textarea::placeholder { + color: #999; +} + +.hvac-form-submit { + padding: 12px 40px; + background: #333; + color: white; + border: none; + border-radius: 25px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s; + align-self: center; + margin-top: 10px; +} + +.hvac-form-submit:hover { + background: #555; + transform: translateY(-2px); +} + +.hvac-form-submit:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* Form Messages */ +.hvac-form-message { + margin-top: 15px; + padding: 12px; + border-radius: 6px; + text-align: center; +} + +.hvac-form-success { + background: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; +} + +.hvac-form-error { + background: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; +} + +/* ======================================== + Mobile Responsive + ======================================== */ + +@media (max-width: 768px) { + /* Stack map and filters vertically */ + .hvac-map-filters-container { + flex-direction: column; + } + + .hvac-map-section { + width: 100%; + margin-bottom: 20px; + } + + .hvac-filters-section { + width: 100%; + } + + /* Single column trainer grid on mobile */ + .hvac-trainer-grid { + grid-template-columns: 1fr; + } + + /* Stack CTA content */ + .hvac-cta-container { + flex-direction: column; + text-align: center; + } + + .hvac-cta-text { + text-align: center; + } + + /* Stack form fields on mobile */ + .hvac-form-row { + flex-direction: column; + } + + /* Adjust modal padding */ + .hvac-trainer-modal-content { + padding: 20px; + } + + .hvac-modal-profile { + flex-direction: column; + text-align: center; + } + + .hvac-modal-image { + margin: 0 auto; + } +} + +@media (max-width: 480px) { + .hvac-page-title { + font-size: 24px; + } + + .hvac-trainer-card-content { + flex-direction: column; + text-align: center; + } + + .hvac-trainer-image { + margin: 0 auto 15px auto; + } +} + +/* ======================================== + Loading States + ======================================== */ + +.hvac-loading { + position: relative; + opacity: 0.5; + pointer-events: none; +} + +.hvac-loading::after { + content: "Loading..."; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: white; + padding: 10px 20px; + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + z-index: 1000; +} + +/* ======================================== + Direct Profile Display + ======================================== */ + +.hvac-direct-profile-container { + margin-bottom: 40px; +} + +.hvac-direct-profile-header { + margin-bottom: 20px; +} + +.hvac-back-to-directory { + display: inline-flex; + align-items: center; + gap: 8px; + color: #0073aa; + text-decoration: none; + font-size: 16px; + font-weight: 500; + transition: color 0.3s; +} + +.hvac-back-to-directory:hover { + color: #005a87; + text-decoration: none; +} + +.hvac-back-to-directory .dashicons { + font-size: 18px; +} + +.hvac-direct-profile-card { + background: #fff; + border: 2px solid #e0e0e0; + border-radius: 12px; + padding: 40px; + max-width: 800px; + margin: 0 auto; +} + +.hvac-direct-profile-content { + display: flex; + align-items: center; + gap: 40px; +} + +.hvac-direct-profile-image { + width: 200px; + height: 200px; + flex-shrink: 0; + position: relative; +} + +.hvac-direct-profile-image img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 50%; + background: #ddd; +} + +.hvac-direct-profile-avatar { + width: 100%; + height: 100%; + background: #6c757d; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +.hvac-direct-profile-avatar .dashicons { + font-size: 80px; + color: white; +} + +.hvac-direct-profile-details { + flex: 1; +} + +.hvac-direct-profile-details h2 { + margin: 0 0 12px 0; + font-size: 36px; + font-weight: 600; + color: #333; + line-height: 1.2; +} + +.hvac-direct-business-name { + margin: 0 0 12px 0; + font-size: 20px; + color: #666; + font-weight: 500; +} + +.hvac-direct-location { + margin: 0 0 12px 0; + font-size: 18px; + color: #666; +} + +.hvac-direct-certification { + margin: 0 0 24px 0; + font-size: 18px; + color: #0073aa; + font-weight: 600; +} + +.hvac-contact-trainer-btn { + display: inline-block; + padding: 16px 32px; + background: #333; + color: white; + border: none; + border-radius: 25px; + font-size: 18px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s; + text-decoration: none; +} + +.hvac-contact-trainer-btn:hover { + background: #555; + transform: translateY(-2px); + color: white; +} + +/* Mobile responsive for direct profile */ +@media (max-width: 768px) { + .hvac-direct-profile-card { + padding: 20px; + } + + .hvac-direct-profile-content { + flex-direction: column; + text-align: center; + gap: 20px; + } + + .hvac-direct-profile-image { + width: 150px; + height: 150px; + margin: 0 auto; + } + + .hvac-direct-profile-details h2 { + font-size: 28px; + } + + .hvac-direct-profile-avatar .dashicons { + font-size: 60px; + } +} + +/* ======================================== + Utility Classes + ======================================== */ + +.hvac-hidden { + display: none !important; +} + +.hvac-visible { + display: block !important; +} + +/* Fix for Astra theme conflicts */ +.hvac-find-trainer-page .ast-separate-container .ast-article-single { + background: transparent; + padding: 0; +} + +.hvac-find-trainer-page .entry-content { + margin: 0; +} + +/* ======================================== + MapGeo Plugin Minimal Fixes + ======================================== */ + +/* Hide MapGeo hidden footer content */ +#igm-hidden-footer-content { + display: none !important; +} + +/* Force proper display context */ +.hvac-find-trainer-page .hvac-map-section > * { + position: relative !important; +} + +/* Override any absolute positioning from MapGeo */ +.hvac-find-trainer-page .map_wrapper .map_box { + position: relative !important; + top: auto !important; + left: auto !important; + right: auto !important; + bottom: auto !important; +} + +/* ======================================== + Direct Profile Display Styles + ======================================== */ + +.hvac-direct-profile-container { + background: #fff; + border-radius: 8px; + margin-bottom: 30px; +} + +.hvac-direct-profile-header { + margin-bottom: 20px; +} + +.hvac-back-to-directory { + display: inline-flex; + align-items: center; + gap: 8px; + color: #0073aa; + text-decoration: none; + font-size: 16px; + font-weight: 500; + transition: color 0.2s ease; +} + +.hvac-back-to-directory:hover { + color: #005a87; + text-decoration: none; +} + +.hvac-back-to-directory .dashicons { + font-size: 18px; +} + +/* Full Trainer Profile Display */ +.hvac-trainer-profile-full { + background: #fff; + border: 2px solid #e0e0e0; + border-radius: 12px; + padding: 30px; + max-width: 800px; + margin: 0 auto; +} + +/* Profile Header Section */ +.hvac-trainer-profile-header { + display: flex; + gap: 30px; + align-items: flex-start; + margin-bottom: 30px; + padding-bottom: 20px; + border-bottom: 2px solid #f0f0f0; +} + +.hvac-trainer-image-section { + position: relative; + flex-shrink: 0; +} + +.hvac-trainer-main-image { + width: 150px; + height: 150px; + border-radius: 50%; + object-fit: cover; + border: 4px solid #e0e0e0; +} + +.hvac-trainer-avatar-large { + width: 150px; + height: 150px; + background: #6c757d; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + border: 4px solid #e0e0e0; +} + +.hvac-trainer-avatar-large .dashicons { + font-size: 80px; + color: white; +} + +.hvac-trainer-header-info { + flex: 1; +} + +.hvac-trainer-header-info .hvac-trainer-name { + font-size: 28px; + font-weight: 600; + margin: 0 0 8px 0; + color: #333; +} + +.hvac-trainer-header-info .hvac-trainer-location { + font-size: 18px; + color: #666; + margin: 0 0 8px 0; +} + +.hvac-trainer-header-info .hvac-trainer-certification { + font-size: 16px; + color: #0073aa; + font-weight: 500; + margin: 0 0 8px 0; +} + +.hvac-trainer-header-info .hvac-trainer-business { + font-size: 16px; + color: #666; + font-weight: 500; + margin: 0 0 8px 0; +} + +.hvac-trainer-header-info .hvac-trainer-events-stat { + font-size: 16px; + color: #333; + margin: 0; +} + +/* Training Details Section */ +.hvac-trainer-details-section, +.hvac-upcoming-events-section, +.hvac-trainer-about-section, +.hvac-contact-section { + margin-bottom: 30px; +} + +.hvac-trainer-details-section h3, +.hvac-upcoming-events-section h3, +.hvac-trainer-about-section h3, +.hvac-contact-section h3 { + font-size: 20px; + font-weight: 600; + color: #333; + margin: 0 0 15px 0; + padding-bottom: 8px; + border-bottom: 2px solid #f0f0f0; +} + +.hvac-training-details-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 15px; +} + +.hvac-training-detail { + display: flex; + flex-direction: column; + gap: 5px; +} + +.hvac-training-detail strong { + color: #333; + font-size: 14px; + font-weight: 600; +} + +.hvac-training-detail span { + color: #666; + font-size: 16px; +} + +/* Events List */ +.hvac-events-list { + list-style: none; + padding: 0; + margin: 0; +} + +.hvac-events-list li { + padding: 10px 0; + border-bottom: 1px solid #f0f0f0; +} + +.hvac-events-list li:last-child { + border-bottom: none; +} + +.hvac-events-list a { + color: #0073aa; + text-decoration: none; + font-weight: 500; +} + +.hvac-events-list a:hover { + color: #005a87; + text-decoration: underline; +} + +/* About Section */ +.hvac-trainer-bio { + color: #333; + line-height: 1.6; + font-size: 16px; +} + +.hvac-trainer-bio p { + margin: 0 0 15px 0; +} + +.hvac-trainer-bio p:last-child { + margin-bottom: 0; +} + +/* Mobile Responsive for Direct Profile */ +@media (max-width: 768px) { + .hvac-trainer-profile-full { + padding: 20px; + margin: 0 10px; + } + + .hvac-trainer-profile-header { + flex-direction: column; + text-align: center; + gap: 20px; + } + + .hvac-trainer-image-section { + align-self: center; + } + + .hvac-trainer-main-image, + .hvac-trainer-avatar-large { + width: 120px; + height: 120px; + } + + .hvac-trainer-avatar-large .dashicons { + font-size: 60px; + } + + .hvac-trainer-header-info .hvac-trainer-name { + font-size: 24px; + } + + .hvac-training-details-grid { + grid-template-columns: 1fr; + gap: 10px; + } +} \ No newline at end of file diff --git a/assets/js/feature-detection.js b/assets/js/feature-detection.js new file mode 100644 index 00000000..b26551e8 --- /dev/null +++ b/assets/js/feature-detection.js @@ -0,0 +1,397 @@ +/** + * Feature Detection System + * Detects browser capabilities instead of relying on user agent strings + * + * @package HVAC_Community_Events + * @since 2.0.0 + */ + +var HVACFeatureDetection = (function() { + 'use strict'; + + var features = {}; + var _hasRunDetection = false; + + /** + * Detect all features + */ + function detectAll() { + if (_hasRunDetection) { + return features; + } + + console.log('[Feature Detection] Running capability tests...'); + + features = { + // Storage capabilities + localStorage: testLocalStorage(), + sessionStorage: testSessionStorage(), + cookies: navigator.cookieEnabled || false, + indexedDB: testIndexedDB(), + + // JavaScript features + es6: testES6Support(), + promises: typeof Promise !== 'undefined', + fetch: typeof fetch !== 'undefined', + async: testAsyncSupport(), + + // DOM features + mutationObserver: 'MutationObserver' in window, + intersectionObserver: 'IntersectionObserver' in window, + resizeObserver: 'ResizeObserver' in window, + + // CSS features + cssGrid: testCSSSupport('display', 'grid'), + cssFlexbox: testCSSSupport('display', 'flex'), + cssVariables: testCSSVariables(), + cssTransforms: testCSSSupport('transform', 'translateX(1px)'), + cssTransitions: testCSSSupport('transition', 'all 0.3s'), + cssFilters: testCSSSupport('filter', 'blur(1px)'), + + // Media features + webGL: testWebGL(), + canvas: testCanvas(), + svg: testSVG(), + webAudio: 'AudioContext' in window || 'webkitAudioContext' in window, + + // Network features + serviceWorker: 'serviceWorker' in navigator, + webSockets: 'WebSocket' in window, + webRTC: testWebRTC(), + + // Input features + touch: testTouch(), + pointer: 'PointerEvent' in window, + + // Performance features + performanceAPI: 'performance' in window, + navigationTiming: !!(window.performance && window.performance.timing), + + // Safari-specific issues + safariPrivateBrowsing: testSafariPrivateBrowsing(), + safariITP: testSafariITP() + }; + + _hasRunDetection = true; + + console.log('[Feature Detection] Capabilities detected:', features); + + return features; + } + + /** + * Test localStorage availability + */ + function testLocalStorage() { + try { + var test = '__test__'; + localStorage.setItem(test, test); + localStorage.removeItem(test); + return true; + } catch(e) { + return false; + } + } + + /** + * Test sessionStorage availability + */ + function testSessionStorage() { + try { + var test = '__test__'; + sessionStorage.setItem(test, test); + sessionStorage.removeItem(test); + return true; + } catch(e) { + return false; + } + } + + /** + * Test IndexedDB availability + */ + function testIndexedDB() { + try { + return !!(window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB); + } catch(e) { + return false; + } + } + + /** + * Test ES6 support + */ + function testES6Support() { + try { + // Test arrow functions + eval('(() => {})'); + // Test template literals + eval('`test`'); + // Test let/const + eval('let a = 1; const b = 2;'); + // Test destructuring + eval('const {a, b} = {a: 1, b: 2}'); + return true; + } catch(e) { + return false; + } + } + + /** + * Test async/await support + */ + function testAsyncSupport() { + try { + eval('(async function() {})'); + return true; + } catch(e) { + return false; + } + } + + /** + * Test CSS support + */ + function testCSSSupport(property, value) { + // Use CSS.supports if available + if (window.CSS && window.CSS.supports) { + return CSS.supports(property, value); + } + + // Fallback method + var el = document.createElement('div'); + el.style.cssText = property + ':' + value; + return el.style[property] !== ''; + } + + /** + * Test CSS variables support + */ + function testCSSVariables() { + return testCSSSupport('--test', '1px'); + } + + /** + * Test WebGL support + */ + function testWebGL() { + try { + var canvas = document.createElement('canvas'); + return !!(window.WebGLRenderingContext && + (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))); + } catch(e) { + return false; + } + } + + /** + * Test Canvas support + */ + function testCanvas() { + var elem = document.createElement('canvas'); + return !!(elem.getContext && elem.getContext('2d')); + } + + /** + * Test SVG support + */ + function testSVG() { + return !!(document.createElementNS && document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect); + } + + /** + * Test WebRTC support + */ + function testWebRTC() { + return !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection); + } + + /** + * Test touch support + */ + function testTouch() { + return ('ontouchstart' in window) || + (navigator.maxTouchPoints > 0) || + (navigator.msMaxTouchPoints > 0); + } + + /** + * Test Safari private browsing mode + */ + function testSafariPrivateBrowsing() { + try { + // Safari private mode throws quota exceeded immediately + localStorage.setItem('__private_test__', '1'); + localStorage.removeItem('__private_test__'); + return false; + } catch(e) { + // Check if it's quota exceeded error + if (e.code === 22 || e.code === 1014 || e.name === 'QuotaExceededError') { + return true; + } + return false; + } + } + + /** + * Test Safari ITP restrictions + */ + function testSafariITP() { + // Check if this looks like Safari + var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + + if (!isSafari) { + return false; + } + + // Safari with ITP has specific storage restrictions + // This is a heuristic check + try { + // Check if third-party cookies are blocked + var testCookie = '__itp_test__=1;SameSite=None;Secure'; + document.cookie = testCookie; + var hasITP = document.cookie.indexOf('__itp_test__') === -1; + + // Clean up + if (!hasITP) { + document.cookie = '__itp_test__=;expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + } + + return hasITP; + } catch(e) { + return false; + } + } + + /** + * Get feature support level + */ + function getSupportLevel() { + if (!_hasRunDetection) { + detectAll(); + } + + var critical = [ + 'localStorage', + 'sessionStorage', + 'cookies', + 'es6', + 'promises', + 'mutationObserver', + 'cssFlexbox' + ]; + + var enhanced = [ + 'fetch', + 'async', + 'intersectionObserver', + 'cssGrid', + 'cssVariables' + ]; + + var allCritical = critical.every(function(feature) { + return features[feature]; + }); + + var allEnhanced = enhanced.every(function(feature) { + return features[feature]; + }); + + if (!allCritical) { + return 'minimal'; + } else if (!allEnhanced) { + return 'basic'; + } else { + return 'full'; + } + } + + /** + * Check if feature is supported + */ + function isSupported(feature) { + if (!_hasRunDetection) { + detectAll(); + } + return !!features[feature]; + } + + /** + * Get recommended polyfills + */ + function getRecommendedPolyfills() { + if (!_hasRunDetection) { + detectAll(); + } + + var polyfills = []; + + if (!features.promises) { + polyfills.push('promise-polyfill'); + } + + if (!features.fetch) { + polyfills.push('whatwg-fetch'); + } + + if (!features.intersectionObserver) { + polyfills.push('intersection-observer'); + } + + if (!features.cssVariables) { + polyfills.push('css-vars-ponyfill'); + } + + return polyfills; + } + + /** + * Initialize and run detection + */ + function init() { + detectAll(); + + // Add data attributes to body for CSS targeting + var body = document.body; + if (body) { + body.setAttribute('data-feature-level', getSupportLevel()); + + // Add specific feature flags + if (features.safariPrivateBrowsing) { + body.setAttribute('data-safari-private', 'true'); + } + + if (features.safariITP) { + body.setAttribute('data-safari-itp', 'true'); + } + + if (!features.es6) { + body.setAttribute('data-legacy-js', 'true'); + } + } + + return features; + } + + // Public API + return { + init: init, + detectAll: detectAll, + isSupported: isSupported, + getSupportLevel: getSupportLevel, + getRecommendedPolyfills: getRecommendedPolyfills, + features: function() { return features; } + }; +})(); + +// Initialize on DOM ready +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', function() { + HVACFeatureDetection.init(); + }); +} else { + HVACFeatureDetection.init(); +} + +// Make globally available +window.HVACFeatureDetection = HVACFeatureDetection; \ No newline at end of file diff --git a/assets/js/find-trainer-safari-compatible.js b/assets/js/find-trainer-safari-compatible.js index a7b26922..b29bb245 100644 --- a/assets/js/find-trainer-safari-compatible.js +++ b/assets/js/find-trainer-safari-compatible.js @@ -590,27 +590,52 @@ return; } - $.post(hvac_find_trainer.ajax_url, { - action: 'hvac_get_trainer_upcoming_events', - nonce: hvac_find_trainer.nonce, - profile_id: profileId - }, function(response) { - if (response.success && response.data.events) { - var eventsHtml = ''; - if (response.data.events.length > 0) { - response.data.events.forEach(function(event) { - eventsHtml += '
We've detected an issue loading this page in Safari. This can happen due to browser compatibility issues.
+Please try one of the following:
+We've detected an issue loading this page in Safari.
+Please try:
+' . __('Registration functionality not available.', 'hvac-community-events') . '
'; } @@ -471,6 +487,21 @@ class HVAC_Shortcodes { return $registration->render_registration_form($atts); } + /** + * Render edit profile shortcode + * + * @param array $atts Shortcode attributes + * @return string + */ + public function render_edit_profile($atts = array()) { + if (!class_exists('HVAC_Registration')) { + return '' . __('Profile editing functionality not available.', 'hvac-community-events') . '
'; + } + + $registration = new HVAC_Registration(); + return $registration->render_edit_profile_form($atts); + } + /** * Render trainer profile shortcode * diff --git a/templates/page-edit-event-custom.php b/templates/page-edit-event-custom.php index d927a475..ef190c01 100644 --- a/templates/page-edit-event-custom.php +++ b/templates/page-edit-event-custom.php @@ -23,7 +23,7 @@ if (!is_user_logged_in()) { $event_id = isset($_GET['event_id']) ? (int) $_GET['event_id'] : 0; // Initialize form handler -$form_handler = HVAC_Custom_Event_Edit::instance(); +$form_handler = HVAC_Event_Manager::instance(); // Check permissions (after login check) if (!$form_handler->canUserEditEvent($event_id)) { diff --git a/test-hvac-comprehensive-e2e.js b/test-hvac-comprehensive-e2e.js new file mode 100644 index 00000000..0b4fd97c --- /dev/null +++ b/test-hvac-comprehensive-e2e.js @@ -0,0 +1,652 @@ +const { chromium } = require('playwright'); +const fs = require('fs').promises; +const path = require('path'); + +// Configuration +const BASE_URL = process.env.UPSKILL_STAGING_URL || 'https://upskill-staging.measurequick.com'; +const HEADLESS = process.env.HEADLESS !== 'false'; +const TIMEOUT = 30000; + +// Test Credentials +const TEST_ACCOUNTS = { + trainer: { + username: 'test_trainer', + password: 'TestTrainer123!', + email: 'test_trainer@example.com' + }, + master: { + username: 'test_master', + password: 'TestMaster123!', + email: 'test_master@example.com' + }, + joe_master: { + username: 'JoeMedosch@gmail.com', + password: 'JoeTrainer2025@', + email: 'JoeMedosch@gmail.com' + }, + new_user: { + username: `test_user_${Date.now()}`, + email: `test_${Date.now()}@example.com`, + password: 'Test@Pass123!' + } +}; + +// Test Results Tracking +class TestResults { + constructor() { + this.results = []; + this.passed = 0; + this.failed = 0; + this.skipped = 0; + this.startTime = Date.now(); + } + + add(name, status, details = '', screenshot = null) { + const result = { + name, + status, + details, + screenshot, + timestamp: new Date().toISOString() + }; + this.results.push(result); + + if (status === 'PASS') { + this.passed++; + console.log(`✅ ${name}`); + } else if (status === 'FAIL') { + this.failed++; + console.log(`❌ ${name}`); + } else if (status === 'SKIP') { + this.skipped++; + console.log(`⏭️ ${name}`); + } + + if (details) { + console.log(` ${details}`); + } + } + + async generateReport() { + const duration = Math.round((Date.now() - this.startTime) / 1000); + const total = this.passed + this.failed + this.skipped; + const successRate = total > 0 ? Math.round((this.passed / total) * 100) : 0; + + const report = { + summary: { + total, + passed: this.passed, + failed: this.failed, + skipped: this.skipped, + successRate: `${successRate}%`, + duration: `${duration}s`, + timestamp: new Date().toISOString() + }, + results: this.results + }; + + // Save JSON report + await fs.writeFile( + `test-results-${Date.now()}.json`, + JSON.stringify(report, null, 2) + ); + + // Print summary + console.log('\n' + '='.repeat(60)); + console.log('📊 TEST EXECUTION SUMMARY'); + console.log('='.repeat(60)); + console.log(`Total Tests: ${total}`); + console.log(`✅ Passed: ${this.passed}`); + console.log(`❌ Failed: ${this.failed}`); + console.log(`⏭️ Skipped: ${this.skipped}`); + console.log(`Success Rate: ${successRate}%`); + console.log(`Duration: ${duration} seconds`); + + if (this.failed > 0) { + console.log('\n❌ Failed Tests:'); + this.results.filter(r => r.status === 'FAIL').forEach(r => { + console.log(` - ${r.name}`); + if (r.details) console.log(` ${r.details}`); + }); + } + + return report; + } +} + +// Test Suite Class +class HVACTestSuite { + constructor() { + this.browser = null; + this.context = null; + this.page = null; + this.results = new TestResults(); + this.screenshotDir = 'test-screenshots'; + this.currentUser = null; + } + + async setup() { + console.log('🚀 Initializing HVAC E2E Test Suite'); + console.log(`📍 Target: ${BASE_URL}`); + console.log(`🖥️ Mode: ${HEADLESS ? 'Headless' : 'Headed'}`); + console.log('='.repeat(60) + '\n'); + + // Create screenshot directory + await fs.mkdir(this.screenshotDir, { recursive: true }); + + // Launch browser + this.browser = await chromium.launch({ + headless: HEADLESS, + timeout: TIMEOUT + }); + + this.context = await this.browser.newContext({ + viewport: { width: 1920, height: 1080 }, + userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + }); + + this.page = await this.context.newPage(); + this.page.setDefaultTimeout(TIMEOUT); + } + + async teardown() { + if (this.browser) { + await this.browser.close(); + } + await this.results.generateReport(); + } + + async takeScreenshot(name) { + const filename = `${this.screenshotDir}/${name}-${Date.now()}.png`; + try { + await this.page.screenshot({ + path: filename, + fullPage: true + }); + return filename; + } catch (error) { + console.error(`Failed to take screenshot: ${error.message}`); + return null; + } + } + + async waitAndCheck(selector, timeout = 5000) { + try { + await this.page.waitForSelector(selector, { timeout }); + return true; + } catch { + return false; + } + } + + // ========== TEST: Find a Trainer ========== + async testFindTrainer() { + console.log('\n🗺️ Testing Find a Trainer Feature'); + console.log('-'.repeat(40)); + + try { + await this.page.goto(`${BASE_URL}/find-a-trainer/`); + await this.page.waitForLoadState('networkidle'); + + // Check page title + const title = await this.page.title(); + this.results.add( + 'Find Trainer - Page Loads', + title.includes('Find') || title.includes('Trainer') ? 'PASS' : 'FAIL', + `Page title: ${title}` + ); + + // Check for map container + const hasMap = await this.waitAndCheck('#mapgeo-map-5872, .mapgeo-map, #map'); + this.results.add( + 'Find Trainer - Map Container', + hasMap ? 'PASS' : 'FAIL', + hasMap ? 'Map container found' : 'Map container not found' + ); + + // Check for filter section + const hasFilters = await this.waitAndCheck('.hvac-trainer-filters, .trainer-filters, .filter-section'); + this.results.add( + 'Find Trainer - Filter Section', + hasFilters ? 'PASS' : 'FAIL' + ); + + // Check for trainer cards + const hasTrainerCards = await this.waitAndCheck('.trainer-card, .hvac-trainer-card, .trainer-profile'); + this.results.add( + 'Find Trainer - Trainer Cards', + hasTrainerCards ? 'PASS' : 'FAIL' + ); + + await this.takeScreenshot('find-trainer'); + + } catch (error) { + this.results.add( + 'Find Trainer - Feature Test', + 'FAIL', + error.message + ); + } + } + + // ========== TEST: Registration ========== + async testRegistration() { + console.log('\n📝 Testing Registration Flow'); + console.log('-'.repeat(40)); + + try { + await this.page.goto(`${BASE_URL}/trainer/registration/`); + await this.page.waitForLoadState('networkidle'); + + // Check registration form sections + const sections = [ + { name: 'Personal Information', selector: 'h3:has-text("Personal Information")' }, + { name: 'Training Organization', selector: 'h3:has-text("Training Organization")' }, + { name: 'Training Venue', selector: 'h3:has-text("Training Venue")' }, + { name: 'Organization Logo', selector: 'label:has-text("Organization Logo")' } + ]; + + for (const section of sections) { + const exists = await this.waitAndCheck(section.selector); + this.results.add( + `Registration - ${section.name}`, + exists ? 'PASS' : 'FAIL' + ); + } + + // Test form field interactions + const testData = TEST_ACCOUNTS.new_user; + + // Fill Personal Information + const filled = await this.fillRegistrationForm(testData); + this.results.add( + 'Registration - Form Fill', + filled ? 'PASS' : 'FAIL', + filled ? 'Form filled successfully' : 'Failed to fill form' + ); + + await this.takeScreenshot('registration-form'); + + } catch (error) { + this.results.add( + 'Registration - Flow Test', + 'FAIL', + error.message + ); + } + } + + async fillRegistrationForm(data) { + try { + // Personal Information + await this.page.fill('#first_name', 'Test'); + await this.page.fill('#last_name', 'User'); + await this.page.fill('#email', data.email); + await this.page.fill('#phone', '555-123-4567'); + + // Training Organization + await this.page.fill('#business_name', 'Test HVAC Company'); + await this.page.fill('#business_email', data.email); + + // Select Business Type + const businessTypeExists = await this.waitAndCheck('#business_type'); + if (businessTypeExists) { + await this.page.selectOption('#business_type', 'Training Organization'); + } + + // Organization Headquarters + const countryExists = await this.waitAndCheck('#hq_country'); + if (countryExists) { + await this.page.selectOption('#hq_country', 'United States'); + await this.page.waitForTimeout(1000); // Wait for state dropdown to populate + + const stateExists = await this.waitAndCheck('#hq_state'); + if (stateExists) { + await this.page.selectOption('#hq_state', 'TX'); + } + } + + await this.page.fill('#hq_city', 'Dallas'); + + return true; + } catch (error) { + console.error('Form fill error:', error.message); + return false; + } + } + + // ========== TEST: Login ========== + async testLogin() { + console.log('\n🔐 Testing Login Functionality'); + console.log('-'.repeat(40)); + + try { + await this.page.goto(`${BASE_URL}/training-login/`); + await this.page.waitForLoadState('networkidle'); + + // Check login form + const hasLoginForm = await this.waitAndCheck('#loginform, .login-form'); + this.results.add( + 'Login - Form Present', + hasLoginForm ? 'PASS' : 'FAIL' + ); + + // Perform login + await this.page.fill('#user_login', TEST_ACCOUNTS.trainer.username); + await this.page.fill('#user_pass', TEST_ACCOUNTS.trainer.password); + + await this.takeScreenshot('login-form'); + + await this.page.click('#wp-submit'); + await this.page.waitForLoadState('networkidle'); + await this.page.waitForTimeout(2000); + + // Check if redirected to dashboard + const url = this.page.url(); + const loginSuccess = url.includes('/trainer/') || url.includes('dashboard'); + + this.results.add( + 'Login - Authentication', + loginSuccess ? 'PASS' : 'FAIL', + `Redirected to: ${url}` + ); + + if (loginSuccess) { + this.currentUser = TEST_ACCOUNTS.trainer; + await this.takeScreenshot('dashboard-after-login'); + } + + } catch (error) { + this.results.add( + 'Login - Test', + 'FAIL', + error.message + ); + } + } + + // ========== TEST: Event Creation ========== + async testEventCreation() { + console.log('\n🎯 Testing Event Creation'); + console.log('-'.repeat(40)); + + // Ensure logged in + if (!this.currentUser) { + await this.ensureLoggedIn(TEST_ACCOUNTS.trainer); + } + + try { + await this.page.goto(`${BASE_URL}/trainer/event/manage/`); + await this.page.waitForLoadState('networkidle'); + + // Check for event management page + const hasEventPage = await this.waitAndCheck('.tribe-community-events, .hvac-event-manage, #tribe-events-community-form'); + this.results.add( + 'Event Creation - Management Page', + hasEventPage ? 'PASS' : 'FAIL' + ); + + // Look for create event button/link + const createEventLink = await this.page.$('a:has-text("Create Event"), a:has-text("Add Event"), button:has-text("New Event")'); + if (createEventLink) { + await createEventLink.click(); + await this.page.waitForLoadState('networkidle'); + } + + // Check for event form fields + const eventFields = [ + { name: 'Event Title', selector: 'input[name*="title"], #EventTitle, input[name="post_title"]' }, + { name: 'Event Description', selector: 'textarea[name*="description"], #EventDescription, .wp-editor-area' }, + { name: 'Event Date', selector: 'input[name*="EventStartDate"], input[type="date"], .tribe-datepicker' }, + { name: 'Event Venue', selector: 'select[name*="venue"], #saved_tribe_venue, input[name*="Venue"]' } + ]; + + for (const field of eventFields) { + const exists = await this.waitAndCheck(field.selector, 3000); + this.results.add( + `Event Creation - ${field.name}`, + exists ? 'PASS' : 'FAIL' + ); + } + + await this.takeScreenshot('event-creation-form'); + + // Try to fill basic event data + const titleField = await this.page.$('input[name*="title"], #EventTitle, input[name="post_title"]'); + if (titleField) { + await titleField.fill(`Test Event ${Date.now()}`); + this.results.add( + 'Event Creation - Fill Title', + 'PASS' + ); + } + + } catch (error) { + this.results.add( + 'Event Creation - Test', + 'FAIL', + error.message + ); + } + } + + // ========== TEST: Event Editing ========== + async testEventEditing() { + console.log('\n✏️ Testing Event Editing'); + console.log('-'.repeat(40)); + + if (!this.currentUser) { + await this.ensureLoggedIn(TEST_ACCOUNTS.trainer); + } + + try { + // Navigate to event list/manage page + await this.page.goto(`${BASE_URL}/trainer/event/manage/`); + await this.page.waitForLoadState('networkidle'); + + // Look for edit links + const editLink = await this.page.$('a:has-text("Edit"), .edit-event, a[href*="edit"]'); + + if (editLink) { + await editLink.click(); + await this.page.waitForLoadState('networkidle'); + + const hasEditForm = await this.waitAndCheck('form, .edit-event-form, #tribe-events-community-form'); + this.results.add( + 'Event Edit - Form Access', + hasEditForm ? 'PASS' : 'FAIL' + ); + + await this.takeScreenshot('event-edit-form'); + } else { + this.results.add( + 'Event Edit - No Events', + 'SKIP', + 'No events available to edit' + ); + } + + } catch (error) { + this.results.add( + 'Event Edit - Test', + 'FAIL', + error.message + ); + } + } + + // ========== TEST: Certificate Generation ========== + async testCertificateGeneration() { + console.log('\n📜 Testing Certificate Generation'); + console.log('-'.repeat(40)); + + if (!this.currentUser) { + await this.ensureLoggedIn(TEST_ACCOUNTS.trainer); + } + + try { + await this.page.goto(`${BASE_URL}/trainer/generate-certificates/`); + await this.page.waitForLoadState('networkidle'); + + // Check certificate page + const hasCertPage = await this.waitAndCheck('.hvac-generate-certificates, .certificate-generator, #certificate-form'); + this.results.add( + 'Certificates - Page Access', + hasCertPage ? 'PASS' : 'FAIL' + ); + + // Check for event selection + const hasEventSelect = await this.waitAndCheck('select[name*="event"], #event_id, .event-select'); + this.results.add( + 'Certificates - Event Selection', + hasEventSelect ? 'PASS' : 'FAIL' + ); + + // Check for generate button + const hasGenerateBtn = await this.waitAndCheck('button:has-text("Generate"), input[type="submit"], .generate-certificates-btn'); + this.results.add( + 'Certificates - Generate Button', + hasGenerateBtn ? 'PASS' : 'FAIL' + ); + + await this.takeScreenshot('certificate-generation'); + + // Also check certificate reports + await this.page.goto(`${BASE_URL}/trainer/certificate-reports/`); + await this.page.waitForLoadState('networkidle'); + + const hasReports = await this.waitAndCheck('.hvac-certificate-reports, .certificate-reports, table'); + this.results.add( + 'Certificates - Reports Page', + hasReports ? 'PASS' : 'FAIL' + ); + + } catch (error) { + this.results.add( + 'Certificates - Test', + 'FAIL', + error.message + ); + } + } + + // ========== TEST: Master Trainer Features ========== + async testMasterTrainerFeatures() { + console.log('\n👑 Testing Master Trainer Features'); + console.log('-'.repeat(40)); + + // Login as master trainer + await this.logout(); + await this.ensureLoggedIn(TEST_ACCOUNTS.master); + + try { + // Test master dashboard + await this.page.goto(`${BASE_URL}/master-trainer/master-dashboard/`); + await this.page.waitForLoadState('networkidle'); + + const hasMasterDash = await this.waitAndCheck('.hvac-master-dashboard, .master-dashboard-content'); + this.results.add( + 'Master - Dashboard Access', + hasMasterDash ? 'PASS' : 'FAIL' + ); + + // Test master pages + const masterPages = [ + { name: 'Events Overview', url: '/master-trainer/events/' }, + { name: 'Pending Approvals', url: '/master-trainer/pending-approvals/' }, + { name: 'Import/Export', url: '/master-trainer/import-export/' } + ]; + + for (const page of masterPages) { + await this.page.goto(`${BASE_URL}${page.url}`); + await this.page.waitForLoadState('networkidle'); + + const isAccessible = !this.page.url().includes('login'); + this.results.add( + `Master - ${page.name}`, + isAccessible ? 'PASS' : 'FAIL' + ); + } + + await this.takeScreenshot('master-dashboard'); + + } catch (error) { + this.results.add( + 'Master Features - Test', + 'FAIL', + error.message + ); + } + } + + // ========== Helper Methods ========== + async ensureLoggedIn(account) { + try { + // Check if already logged in + await this.page.goto(`${BASE_URL}/trainer/dashboard/`, { waitUntil: 'networkidle' }); + + if (this.page.url().includes('login')) { + // Not logged in, perform login + await this.page.fill('#user_login', account.username); + await this.page.fill('#user_pass', account.password); + await this.page.click('#wp-submit'); + await this.page.waitForLoadState('networkidle'); + await this.page.waitForTimeout(2000); + } + + this.currentUser = account; + } catch (error) { + console.error('Login error:', error.message); + } + } + + async logout() { + try { + await this.page.goto(`${BASE_URL}/wp-login.php?action=logout`, { waitUntil: 'networkidle' }); + const logoutLink = await this.page.$('a:has-text("log out"), a:has-text("Log out")'); + if (logoutLink) { + await logoutLink.click(); + } + this.currentUser = null; + } catch (error) { + console.error('Logout error:', error.message); + } + } + + // ========== Main Test Runner ========== + async runAllTests() { + await this.setup(); + + try { + // Public Features + await this.testFindTrainer(); + await this.testRegistration(); + + // Trainer Features + await this.testLogin(); + await this.testEventCreation(); + await this.testEventEditing(); + await this.testCertificateGeneration(); + + // Master Trainer Features + await this.testMasterTrainerFeatures(); + + } catch (error) { + console.error('Test suite error:', error); + } finally { + await this.teardown(); + } + } +} + +// Execute Tests +async function main() { + console.log('\n🏁 HVAC Community Events - Comprehensive E2E Test Suite\n'); + + const suite = new HVACTestSuite(); + await suite.runAllTests(); + + process.exit(suite.results.failed > 0 ? 1 : 0); +} + +main().catch(console.error); \ No newline at end of file diff --git a/test-safari-fix.js b/test-safari-fix.js new file mode 100644 index 00000000..05c3404b --- /dev/null +++ b/test-safari-fix.js @@ -0,0 +1,187 @@ +const { webkit } = require('playwright'); + +(async () => { + console.log('🧪 Testing Safari compatibility fix with comprehensive resource monitoring...'); + + const browser = await webkit.launch({ + headless: true // headless to avoid display issues + }); + + const context = await browser.newContext({ + // Simulate Safari browser exactly as it would appear + userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15' + }); + + const page = await context.newPage(); + + // Track console messages and errors + const consoleMessages = []; + const pageErrors = []; + + page.on('console', msg => { + const message = `[${msg.type().toUpperCase()}] ${msg.text()}`; + console.log(message); + consoleMessages.push(message); + }); + + page.on('pageerror', error => { + const errorMsg = `[PAGE ERROR] ${error.message}`; + console.log(errorMsg); + pageErrors.push(errorMsg); + }); + + page.on('response', response => { + if (response.url().includes('.css') || response.url().includes('.js')) { + console.log(`📄 Resource: ${response.status()} - ${response.url()}`); + } + }); + + console.log('📍 Navigating to find-a-trainer page with Safari user agent...'); + try { + await page.goto('https://upskill-staging.measurequick.com/find-a-trainer/', { + waitUntil: 'networkidle', + timeout: 30000 + }); + console.log('✅ Page loaded successfully'); + } catch (error) { + console.log('⚠️ Page load error:', error.message); + } + + console.log('⏳ Waiting for Safari Script Blocker to initialize...'); + await page.waitForTimeout(3000); + + // Check Safari Script Blocker activation + console.log('🛡️ Checking Safari Script Blocker activation...'); + const safariBlockerStatus = await page.evaluate(() => { + // Look for Safari blocker console messages + const scriptTags = Array.from(document.querySelectorAll('script')); + const hasBlockerScript = scriptTags.some(script => + script.innerHTML.includes('Safari Script Protection System') || + script.innerHTML.includes('Safari Script Blocker activated') + ); + + return { + hasBlockerScript: hasBlockerScript, + totalScripts: scriptTags.length, + scriptsWithSrc: scriptTags.filter(s => s.src).length, + bodyExists: !!document.body, + hasContent: document.body ? document.body.innerHTML.length > 1000 : false, + readyState: document.readyState, + url: window.location.href + }; + }); + + console.log('📊 Safari Script Blocker Status:', safariBlockerStatus); + + // Check resource loading - should be minimal for Safari + console.log('🔍 Analyzing resource loading for Safari compatibility...'); + const resourceAnalysis = await page.evaluate(() => { + const allLinks = Array.from(document.querySelectorAll('link[rel="stylesheet"]')); + const allScripts = Array.from(document.querySelectorAll('script[src]')); + + const cssFiles = allLinks.map(link => ({ + href: link.href, + isHVAC: link.href.includes('hvac'), + isSafariCompatible: link.href.includes('safari') || link.href.includes('minimal') + })); + + const jsFiles = allScripts.map(script => ({ + src: script.src, + isHVAC: script.src.includes('hvac'), + isSafariCompatible: script.src.includes('safari-compatible') + })); + + return { + totalCSSFiles: cssFiles.length, + hvacCSSFiles: cssFiles.filter(f => f.isHVAC), + totalJSFiles: jsFiles.length, + hvacJSFiles: jsFiles.filter(f => f.isHVAC), + safariCompatibleJS: jsFiles.filter(f => f.isSafariCompatible) + }; + }); + + console.log('📈 Resource Analysis:', resourceAnalysis); + + // Test specific Safari fix indicators + console.log('🔍 Testing Safari-specific fixes...'); + const safariFixStatus = await page.evaluate(() => { + return { + safariMinimalMode: typeof window.HVAC_SAFARI_MINIMAL_MODE !== 'undefined', + safariScriptBlocker: typeof window.hvacSafariScriptBlocker !== 'undefined', + hasMapGeo: typeof window.MapGeoWidget !== 'undefined', + hasJQuery: typeof window.jQuery !== 'undefined', + documentTitle: document.title, + pageHasTrainerCards: document.querySelectorAll('.hvac-trainer-card').length > 0, + pageHasMap: document.querySelectorAll('[id*="map"]').length > 0 + }; + }); + + console.log('🎯 Safari Fix Status:', safariFixStatus); + + // Test page functionality if content loaded + if (safariBlockerStatus.hasContent) { + console.log('🧪 Testing page functionality...'); + try { + // Check if trainer cards are present and functional + const trainerCards = await page.$$('.hvac-trainer-card'); + console.log(`👥 Found ${trainerCards.length} trainer cards`); + + // Test if interactive elements work + const interactiveTest = await page.evaluate(() => { + return { + hasModals: document.querySelectorAll('[id*="modal"]').length, + hasButtons: document.querySelectorAll('button, .btn').length, + hasTrainerCards: document.querySelectorAll('.hvac-trainer-card').length, + hasContactForm: document.querySelectorAll('form').length, + canAccessDOM: !!document.getElementById || !!document.querySelector + }; + }); + + console.log('⚙️ Interactive Elements Test:', interactiveTest); + + // Test if page content is actually rendered + const contentCheck = await page.evaluate(() => { + const bodyText = document.body ? document.body.textContent : ''; + return { + bodyTextLength: bodyText.length, + hasTrainerText: bodyText.includes('trainer') || bodyText.includes('HVAC'), + hasLoadingText: bodyText.includes('Loading'), + visibleElements: document.querySelectorAll('*:not([style*="display: none"])').length + }; + }); + + console.log('📝 Content Check:', contentCheck); + + } catch (error) { + console.log('❌ Error testing functionality:', error.message); + } + } + + // Summary + console.log('\n🎉 Safari Compatibility Test Summary:'); + console.log(`🛡️ Safari Script Blocker: ${safariBlockerStatus.hasBlockerScript ? 'ACTIVE' : 'NOT FOUND'}`); + console.log(`📄 Page Loaded: ${safariBlockerStatus.hasContent ? 'SUCCESS' : 'FAILED'}`); + console.log(`📊 Total CSS Files: ${resourceAnalysis.totalCSSFiles} (HVAC: ${resourceAnalysis.hvacCSSFiles.length})`); + console.log(`📊 Total JS Files: ${resourceAnalysis.totalJSFiles} (HVAC: ${resourceAnalysis.hvacJSFiles.length})`); + console.log(`🔧 Safari Minimal Mode: ${safariFixStatus.safariMinimalMode ? 'ACTIVE' : 'NOT ACTIVE'}`); + console.log(`🎯 Page Functionality: ${safariBlockerStatus.hasContent && safariFixStatus.hasJQuery ? 'WORKING' : 'LIMITED'}`); + console.log(`📱 Console Messages: ${consoleMessages.length}`); + console.log(`❌ Page Errors: ${pageErrors.length}`); + + if (pageErrors.length === 0 && safariBlockerStatus.hasContent) { + console.log('\n✅ SAFARI COMPATIBILITY FIX APPEARS SUCCESSFUL!'); + console.log('✅ No critical errors detected'); + console.log('✅ Page content loaded properly'); + console.log('✅ Safari Script Blocker active'); + } else { + console.log('\n⚠️ Issues detected:'); + if (pageErrors.length > 0) { + console.log('❌ Page errors found:', pageErrors); + } + if (!safariBlockerStatus.hasContent) { + console.log('❌ Page content did not load properly'); + } + } + + await browser.close(); +})(); \ No newline at end of file diff --git a/test-safari-headless.js b/test-safari-headless.js new file mode 100644 index 00000000..863c0c6c --- /dev/null +++ b/test-safari-headless.js @@ -0,0 +1,110 @@ +const { webkit } = require('playwright'); + +(async () => { + console.log('🧪 Testing Safari compatibility with headless WebKit...'); + + const browser = await webkit.launch({ + headless: true // headless to avoid CPU/display issues + }); + + const context = await browser.newContext({ + // Simulate Safari browser + userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15' + }); + + const page = await context.newPage(); + + // Track console messages and errors + page.on('console', msg => { + console.log(`[${msg.type().toUpperCase()}] ${msg.text()}`); + }); + + page.on('pageerror', error => { + console.log(`[PAGE ERROR] ${error.message}`); + }); + + console.log('📍 Navigating to find-a-trainer page...'); + try { + await page.goto('https://upskill-staging.measurequick.com/find-a-trainer/', { + waitUntil: 'networkidle', + timeout: 30000 + }); + console.log('✅ Page loaded successfully'); + } catch (error) { + console.log('⚠️ Page load error:', error.message); + + // Try to get current page state even if navigation failed + try { + const currentUrl = await page.url(); + console.log('📍 Current URL:', currentUrl); + } catch (e) { + console.log('❌ Could not get page URL'); + } + } + + console.log('⏳ Waiting for page to stabilize...'); + await page.waitForTimeout(3000); + + // Get comprehensive browser state + console.log('🔍 Analyzing browser state...'); + const pageState = await page.evaluate(() => { + return { + url: window.location.href, + title: document.title, + readyState: document.readyState, + scriptsLoaded: Array.from(document.querySelectorAll('script')).length, + safariScriptBlocker: window.hvacSafariScriptBlocker ? 'active' : 'not found', + hvacErrors: window.hvacErrors || [], + bodyExists: !!document.body, + hasContent: document.body ? document.body.innerHTML.length > 1000 : false + }; + }); + + console.log('📊 Page state:', pageState); + + // Check Safari-specific scripts + console.log('🔍 Checking Safari script loading...'); + const allScripts = await page.evaluate(() => { + return Array.from(document.querySelectorAll('script[src]')) + .map(script => ({ + src: script.src, + isSafariCompatible: script.src.includes('safari-compatible'), + isHVAC: script.src.includes('hvac') || script.src.includes('find-trainer') + })); + }); + + console.log('📄 All scripts:', allScripts.length); + const hvacScripts = allScripts.filter(s => s.isHVAC); + const safariScripts = allScripts.filter(s => s.isSafariCompatible); + + console.log('🎯 HVAC scripts:', hvacScripts); + console.log('✅ Safari-compatible scripts:', safariScripts); + + // Test basic page functionality if content loaded + if (pageState.hasContent) { + console.log('🧪 Testing page functionality...'); + try { + // Check if trainer cards exist + const trainerCards = await page.$$('.hvac-trainer-card'); + console.log(`👥 Found ${trainerCards.length} trainer cards`); + + // Test if interactive elements are present + const interactiveElements = await page.evaluate(() => { + return { + modals: document.querySelectorAll('[id*="modal"]').length, + buttons: document.querySelectorAll('button, .btn').length, + forms: document.querySelectorAll('form').length, + maps: document.querySelectorAll('[id*="map"]').length + }; + }); + + console.log('🎯 Interactive elements:', interactiveElements); + + } catch (error) { + console.log('❌ Error testing functionality:', error.message); + } + } + + console.log('🎉 Safari compatibility test completed'); + await browser.close(); +})(); \ No newline at end of file