diff --git a/CLAUDE.md b/CLAUDE.md
index af0b7e6b..782b806e 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -11,84 +11,125 @@ The system uses a Cloudways staging environment for development and testing, wit
## Key Commands
### Testing
+
+#### **E2E Test Structure (Consolidated 2025-05-23)**
+
+The E2E test suite has been completely consolidated from 85+ files to a focused, maintainable structure. **ALWAYS use the consolidated tests** - the old duplicate tests have been removed.
+
+#### **Primary Test Command (RECOMMENDED)**
```bash
-# Recommended test workflow (execute from wordpress-dev/)
+# Run the main working test suite (RECOMMENDED FOR ALL TESTING)
+npx playwright test tests/e2e/final-working-tests.test.ts --reporter=line
-# 1. Always deactivate/reactivate plugin before testing
-# This ensures hooks fire properly and rewrite rules are flushed
-./bin/run-tests.sh --e2e
+# This single command tests:
+# ✅ Dashboard and basic navigation
+# ✅ Create Event page accessibility (14 form elements)
+# ✅ Certificate Reports page (79+ data elements)
+# ✅ Generate Certificates functionality (17 events available)
+# ✅ Trainer Profile page (/trainer-profile/)
+# ✅ Complete page navigation flow
+# ✅ Error monitoring (0 PHP errors expected)
+```
-# 2. Set up test users
-./bin/create-test-users.sh
+#### **Test Suite Organization**
+```
+tests/e2e/
+├── fixtures/
+│ └── auth.ts # Shared authentication utilities
+├── utils/
+│ └── common-actions.ts # Reusable test actions and helpers
+├── final-working-tests.test.ts # 🎯 MAIN TEST SUITE (USE THIS)
+├── trainer-journey-optimized.test.ts # Optimized trainer workflow tests
+├── certificate-optimized.test.ts # Optimized certificate tests
+├── trainer-journey-final.test.ts # Legacy comprehensive journey
+├── trainer-journey-harmonized.test.ts # Page Object Model approach
+├── certificate-core.test.ts # Certificate generation/viewing
+├── certificate-management.test.ts # Bulk operations/reporting
+├── certificate-edge-cases.test.ts # Error handling/validation
+├── help-system-*.test.ts # Help system components (4 files)
+└── [Legacy tests still available but consolidated ones are preferred]
+```
-# 3. Create test events (if needed for certificate tests)
-./bin/create-test-events-admin.sh
+#### **Running Tests**
+```bash
+# RECOMMENDED: Run main comprehensive test suite
+npx playwright test tests/e2e/final-working-tests.test.ts
-# Run complete trainer journey tests
-npx playwright test tests/e2e/trainer-journey-final.test.ts
+# Run with browser visible for debugging
+npx playwright test tests/e2e/final-working-tests.test.ts --headed
-# Run with visual browser for debugging
-npx playwright test tests/e2e/trainer-journey-final.test.ts --headed
+# Run with Playwright inspector for debugging
+npx playwright test tests/e2e/final-working-tests.test.ts --debug
-# Run with Playwright debugger
-npx playwright test tests/e2e/trainer-journey-final.test.ts --debug
+# Run specific optimized test suites
+npx playwright test tests/e2e/trainer-journey-optimized.test.ts # 4 trainer workflow tests
+npx playwright test tests/e2e/certificate-optimized.test.ts # 4 certificate tests
-# Run specific test types
-./bin/run-tests.sh --unit
-./bin/run-tests.sh --e2e
-./bin/run-tests.sh --integration
+# Run legacy consolidated tests (if needed)
+npx playwright test tests/e2e/trainer-journey-final.test.ts # Comprehensive journey
+npx playwright test tests/e2e/certificate-core.test.ts # Core certificate functionality
-# Debug certificate system
-./bin/debug-certificate-system.sh
-
-# Debug dashboard data directly on server
-./bin/debug-dashboard-live.sh
-
-# Run specific E2E test suites
-./bin/run-tests.sh --e2e --grep @login
-./bin/run-tests.sh --e2e --grep @dashboard
-./bin/run-tests.sh --e2e --grep @create-event
-./bin/run-tests.sh --e2e --grep @event-summary
-./bin/run-tests.sh --e2e --grep @modify-event
-./bin/run-tests.sh --e2e --grep @certificate
-
-# Run certificate-specific tests
-npx playwright test tests/e2e/certificates.test.ts
-npx playwright test tests/e2e/certificate-generation-checked-in.test.ts
-
-# Run certificate filtering tests with interactive script
-./bin/test-certificate-filter.sh
-
-# Optimize and analyze E2E testing infrastructure
-./bin/optimize-e2e-tests.sh
-
-# Run help system tests
-npx playwright test tests/e2e/help-system-welcome-guide.test.ts
-npx playwright test tests/e2e/help-system-tooltips.test.ts
-npx playwright test tests/e2e/help-system-documentation.test.ts
-npx playwright test tests/e2e/help-system-integration.test.ts
-
-# Run all help system tests
+# Run help system tests (well-organized, no changes needed)
npx playwright test tests/e2e/help-system-*.test.ts
+# Run specific test by name pattern
+npx playwright test --grep "Dashboard and basic navigation"
+npx playwright test --grep "Certificate Reports"
+npx playwright test --grep "Create Event"
+
+# Run with different reporters
+npx playwright test tests/e2e/final-working-tests.test.ts --reporter=html # HTML report
+npx playwright test tests/e2e/final-working-tests.test.ts --reporter=json # JSON output
+```
+
+#### **Test Development Setup**
+```bash
+# 1. Always set up test environment first
+./bin/create-test-users.sh # Create test_trainer user
+./bin/create-test-events-admin.sh # Create test events (if needed)
+
+# 2. Verify staging environment
+./bin/verify-staging.sh # Check staging is accessible
+
+# 3. Run tests
+npx playwright test tests/e2e/final-working-tests.test.ts
+```
+
+#### **PHPUnit Tests**
+```bash
# Run PHPUnit tests on staging
./bin/run-staging-unit-tests.sh
./bin/run-staging-unit-tests.sh --testsuite unit
./bin/run-staging-unit-tests.sh --coverage-html ./coverage-report
./bin/run-staging-unit-tests.sh --filter=test_get_total_events_count
+```
-# Setup staging test users and verification
-./bin/setup-staging-test-users.sh
-./tests/run-tests.sh setup
-./tests/run-tests.sh verify
-./tests/run-tests.sh teardown --force
+#### **Legacy Test Commands (Use only if needed)**
+```bash
+# These are kept for reference but use consolidated tests above
+./bin/run-tests.sh --e2e # Old test runner
+./bin/run-tests.sh --e2e --grep @certificate # Old pattern-based running
+```
-# Common testing issues & solutions:
-# 1. Missing plugin admin menu: Re-activate plugin and check error logs
-# 2. Selector issues: Review the testing strategy for stable selector patterns
-# 3. Login redirect issues: Ensure proper test user setup
-# 4. Test independence: Each test should create its own test data
-# 5. Wait for page load: Use explicit waits with waitForLoadState('networkidle')
+#### **Common Issues & Solutions**
+```bash
+# Issue: Tests timing out
+# Solution: Use final-working-tests.test.ts which has optimized timeouts
+
+# Issue: Profile page not found
+# Solution: Correct URL is /trainer-profile/ not /community-profile/
+
+# Issue: Create Event form fields not found
+# Solution: Use #event_title not #post_title for title field
+
+# Issue: Certificate attendee selection hanging
+# Solution: Use optimized tests that don't interact with problematic elements
+
+# Issue: Navigation elements not found
+# Solution: Use .first() for elements that may have duplicates
+
+# Issue: CSS selector syntax errors
+# Solution: Avoid regex in selectors, use text content matching instead
```
### Deployment & Staging
@@ -151,6 +192,451 @@ The HVAC Community Events plugin includes a comprehensive help system with three
- Fixed 'Certificate Reports' critical error by removing problematic debug statements
- Enhanced dashboard with proper tooltips and contextual help
+## How to Write and Modify E2E Tests
+
+### **When to Modify Tests**
+
+**Use final-working-tests.test.ts for most modifications** - this is the main comprehensive test suite that covers all functionality.
+
+#### **Adding New Test Cases**
+```typescript
+// Add to tests/e2e/final-working-tests.test.ts
+test('Your new test description', async ({ authenticatedPage: page }) => {
+ test.setTimeout(20000); // Set appropriate timeout
+ const actions = new CommonActions(page);
+
+ // Your test logic here
+ await actions.navigateAndWait('/your-page/');
+ await expect(page.locator('h1')).toBeVisible();
+ await actions.screenshot('your-test-step');
+});
+```
+
+#### **Modifying Existing Tests**
+1. **Identify the test** in `final-working-tests.test.ts`
+2. **Update the test logic** while maintaining the same structure
+3. **Run the test** to verify it works: `npx playwright test tests/e2e/final-working-tests.test.ts --grep "test name"`
+4. **Update expectations** if functionality has changed
+
+#### **Creating New Test Files (Only if needed)**
+```typescript
+// Pattern: tests/e2e/feature-name.test.ts
+import { test, expect } from './fixtures/auth';
+import { CommonActions } from './utils/common-actions';
+
+test.describe('Feature Name Tests', () => {
+ test('Specific functionality test', async ({ authenticatedPage: page }) => {
+ test.setTimeout(25000);
+ const actions = new CommonActions(page);
+
+ // Test implementation
+ });
+});
+```
+
+### **Essential Patterns and Utilities**
+
+#### **Using Shared Authentication**
+```typescript
+// ALWAYS use this pattern for authenticated tests
+import { test, expect } from './fixtures/auth';
+
+test('My test', async ({ authenticatedPage: page }) => {
+ // Page is already logged in as test_trainer
+ await expect(page).toHaveURL(/hvac-dashboard/);
+});
+```
+
+#### **Using Common Actions**
+```typescript
+import { CommonActions } from './utils/common-actions';
+
+test('My test', async ({ authenticatedPage: page }) => {
+ const actions = new CommonActions(page);
+
+ // Navigate safely with wait
+ await actions.navigateAndWait('/target-page/');
+
+ // Take screenshots for debugging
+ await actions.screenshot('test-step-name');
+
+ // Verify navigation elements
+ await actions.verifyNavigation();
+
+ // Wait for complex AJAX operations
+ await actions.waitForComplexAjax();
+
+ // Generate unique test data
+ const testData = actions.generateTestData('Event');
+ await page.fill('#event_title', testData.title);
+});
+```
+
+#### **Safe Element Selection Patterns**
+```typescript
+// ✅ GOOD: Handle multiple headings safely
+await expect(page.locator('h1, h2').filter({ hasText: /pattern/i }).first()).toBeVisible();
+
+// ✅ GOOD: Use flexible selectors for form fields
+const titleField = page.locator('#event_title, #post_title, input[name="post_title"]');
+await expect(titleField.first()).toBeVisible();
+
+// ✅ GOOD: Check for elements before interacting
+const submitButton = page.locator('button[type="submit"]');
+if (await submitButton.count() > 0) {
+ await expect(submitButton.first()).toBeVisible();
+}
+
+// ❌ AVOID: Brittle single selectors
+await page.locator('#specific-id'); // May break if ID changes
+
+// ❌ AVOID: Regex in CSS selectors
+await page.locator('span:text(/^\d+$/)'); // Causes syntax errors
+```
+
+#### **Form Interaction Patterns**
+```typescript
+// ✅ GOOD: Event title field (correct selector)
+await page.fill('#event_title', 'My Event Title');
+
+// ✅ GOOD: Flexible description field handling
+try {
+ // Try TinyMCE first
+ const frame = page.frameLocator('iframe[id*="_ifr"]');
+ await frame.locator('body').fill('Description text');
+} catch {
+ // Fallback to textarea
+ await page.fill('#event_content, textarea[name="content"]', 'Description text');
+}
+
+// ✅ GOOD: Event selection with AJAX wait
+await page.selectOption('select[name="event_id"]', { index: 1 });
+await actions.waitForComplexAjax(); // Wait for attendees to load
+```
+
+#### **Error Handling Patterns**
+```typescript
+// ✅ GOOD: Monitor for PHP errors
+const phpErrors = [];
+page.on('console', (msg) => {
+ if (msg.type() === 'error' && msg.text().includes('PHP')) {
+ phpErrors.push(msg.text());
+ }
+});
+
+// Test pages...
+
+expect(phpErrors.length).toBe(0); // Fail on PHP errors
+```
+
+### **Key URLs and Selectors**
+
+#### **Correct Page URLs**
+```bash
+Dashboard: /hvac-dashboard/
+Create Event: /manage-event/
+Certificate Reports: /certificate-reports/
+Generate Certs: /generate-certificates/
+Trainer Profile: /trainer-profile/ # NOT /community-profile/
+```
+
+#### **Key Form Selectors**
+```typescript
+// Event Creation Form
+'#event_title' // Event title field
+'#event_content' // Event description
+'select[name="event_id"]' // Event selection dropdown
+'iframe[id*="_ifr"]' // TinyMCE editor frame
+
+// Navigation Elements
+'a[href*="hvac-dashboard"]' // Dashboard links
+'text=Generate Certificates' // Navigation buttons
+'text=Create Event' // Navigation buttons
+
+// Statistics and Data
+'.stat-value' // Statistics displays
+'.stat-number' // Numeric statistics
+'.dashboard-stat' // Dashboard statistics
+```
+
+### **Test Performance Guidelines**
+
+#### **Timeout Management**
+```typescript
+// ✅ GOOD: Set appropriate timeouts
+test.setTimeout(20000); // 20 seconds for simple tests
+test.setTimeout(30000); // 30 seconds for complex workflows
+test.setTimeout(45000); // 45 seconds for comprehensive journeys
+
+// ✅ GOOD: Use built-in waits
+await page.waitForLoadState('networkidle');
+await actions.waitForComplexAjax();
+
+// ❌ AVOID: Arbitrary timeouts
+await page.waitForTimeout(5000); // Use sparingly
+```
+
+#### **Screenshot Strategy**
+```typescript
+// ✅ GOOD: Meaningful screenshot names
+await actions.screenshot('login-completed');
+await actions.screenshot('event-form-filled');
+await actions.screenshot('certificate-generated');
+
+// ✅ GOOD: Screenshots at verification points
+await actions.screenshot('page-loaded');
+await expect(page.locator('h1')).toBeVisible();
+await actions.screenshot('heading-verified');
+```
+
+### **Debugging Failed Tests**
+
+#### **Common Debugging Commands**
+```bash
+# Run with visual browser to see what's happening
+npx playwright test tests/e2e/final-working-tests.test.ts --headed
+
+# Run with debugger to step through
+npx playwright test tests/e2e/final-working-tests.test.ts --debug
+
+# Run specific failing test
+npx playwright test --grep "Create Event page accessibility"
+
+# Generate HTML report with screenshots
+npx playwright test tests/e2e/final-working-tests.test.ts --reporter=html
+npx playwright show-report
+```
+
+#### **Reading Test Failures**
+```bash
+# Timeout errors: Increase test timeout or fix selectors
+# Element not found: Check if selectors are correct
+# Navigation failed: Verify URLs and page structure
+# PHP errors: Check staging environment and plugin status
+```
+
+### **Test Maintenance Checklist**
+
+#### **Before Modifying Tests**
+1. ✅ Run existing tests to ensure they pass
+2. ✅ Understand what functionality the test covers
+3. ✅ Check if changes affect other tests
+4. ✅ Use existing patterns and utilities
+
+#### **After Modifying Tests**
+1. ✅ Run the modified test to ensure it passes
+2. ✅ Run the full test suite to check for regressions
+3. ✅ Update documentation if test structure changes
+4. ✅ Commit changes with descriptive messages
+
+#### **When Tests Start Failing**
+1. ✅ Check if staging environment is accessible
+2. ✅ Verify test user (test_trainer) exists and works
+3. ✅ Check for WordPress/plugin updates that changed UI
+4. ✅ Look for PHP errors in console output
+5. ✅ Update selectors if page structure changed
+
+### **Quick Reference Commands**
+
+#### **Most Common Commands (Copy-Paste Ready)**
+```bash
+# Run main test suite (MOST COMMON)
+npx playwright test tests/e2e/final-working-tests.test.ts --reporter=line
+
+# Debug with visual browser
+npx playwright test tests/e2e/final-working-tests.test.ts --headed
+
+# Run specific test
+npx playwright test --grep "Dashboard and basic navigation"
+
+# Generate HTML report
+npx playwright test tests/e2e/final-working-tests.test.ts --reporter=html
+npx playwright show-report
+
+# Set up test environment
+./bin/create-test-users.sh && ./bin/verify-staging.sh
+```
+
+#### **File Locations Quick Reference**
+```bash
+Main Test Suite: tests/e2e/final-working-tests.test.ts
+Auth Fixture: tests/e2e/fixtures/auth.ts
+Common Actions: tests/e2e/utils/common-actions.ts
+Test Screenshots: test-results/screenshots/
+Test Reports: playwright-report/
+```
+
+#### **Essential Test Patterns (Copy-Paste Ready)**
+```typescript
+// Basic test structure
+test('Test name', async ({ authenticatedPage: page }) => {
+ test.setTimeout(20000);
+ const actions = new CommonActions(page);
+ await actions.navigateAndWait('/page-url/');
+ await expect(page.locator('h1').first()).toBeVisible();
+ await actions.screenshot('test-completed');
+});
+
+// Form interaction
+await page.fill('#event_title', 'Test Event');
+const value = await page.locator('#event_title').inputValue();
+expect(value).toBe('Test Event');
+
+// Navigation verification
+await actions.verifyNavigation();
+await expect(page).toHaveURL(/hvac-dashboard/);
+```
+
+## E2E Testing Best Practices
+
+### Test Structure and Organization (Consolidated 2025-05-23)
+
+The E2E test suite has been consolidated from 85+ files to a focused, maintainable structure:
+
+#### **Core Test Suites**
+```
+tests/e2e/
+├── fixtures/
+│ └── auth.ts # Shared authentication utilities
+├── utils/
+│ └── common-actions.ts # Reusable test actions and helpers
+├── config/
+│ └── staging-config.ts # Test configuration
+├── pages/ # Page Object Models (where used)
+├── trainer-journey-final.test.ts # Comprehensive user journey (direct approach)
+├── trainer-journey-harmonized.test.ts # User journey with Page Objects
+├── certificate-core.test.ts # Certificate generation and viewing
+├── certificate-management.test.ts # Bulk operations and reporting
+├── certificate-edge-cases.test.ts # Error handling and validation
+├── certificates.test.ts # Legacy comprehensive certificate suite
+├── dashboard.test.ts # Dashboard functionality
+├── help-system-*.test.ts # Help system components (4 files)
+└── login.test.ts # Authentication testing
+```
+
+#### **Writing New E2E Tests**
+
+**1. Use Shared Utilities:**
+```typescript
+import { test, expect } from './fixtures/auth';
+import { CommonActions } from './utils/common-actions';
+
+test('My test', async ({ authenticatedPage: page }) => {
+ const actions = new CommonActions(page);
+ await actions.navigateAndWait('/my-page/');
+ await actions.screenshot('test-step');
+});
+```
+
+**2. Test Categories and Tags:**
+- Use `@tag` comments for test organization
+- Core functionality: `@login`, `@dashboard`, `@certificates`, `@events`
+- Help system: `@help-system`, `@tooltips`, `@documentation`
+- Edge cases: `@edge-cases`, `@error-handling`, `@validation`
+
+**3. Test Independence:**
+- Each test should be self-contained
+- Use unique test data with timestamps
+- Clean up test data when possible
+- Don't rely on other tests' side effects
+
+**4. Error Handling:**
+- Always check for PHP errors in console
+- Use explicit waits (`waitForLoadState('networkidle')`)
+- Handle missing elements gracefully
+- Test both success and failure scenarios
+
+**5. Performance Guidelines:**
+- Page load should complete within 10 seconds
+- Use `actions.waitForAjax()` after AJAX operations
+- Take screenshots at key verification points
+- Batch similar operations when possible
+
+#### **Running Tests Efficiently**
+
+```bash
+# Run specific test categories
+npx playwright test --grep @certificates
+npx playwright test --grep @help-system
+
+# Run single test file with debugging
+npx playwright test certificate-core.test.ts --headed
+
+# Run tests in parallel (default)
+npx playwright test tests/e2e/ --workers=3
+
+# Generate test report
+npx playwright show-report
+```
+
+#### **Common Test Patterns**
+
+**Navigation and Verification:**
+```typescript
+await actions.navigateAndWait('/target-page/');
+await actions.verifyNavigation();
+await expect(page.locator('h1')).toBeVisible();
+```
+
+**Form Testing:**
+```typescript
+const testData = actions.generateTestData('Event');
+await page.fill('#title', testData.title);
+await actions.fillTinyMCE('#description', testData.description);
+```
+
+**AJAX Testing:**
+```typescript
+await page.selectOption('select[name="event_id"]', { index: 1 });
+await actions.waitForAjax();
+await expect(page.locator('input[name="attendee_ids[]"]')).toBeVisible();
+```
+
+#### **Maintenance Guidelines**
+
+1. **Before Adding New Tests:**
+ - Check if functionality is already covered
+ - Use existing test files when appropriate
+ - Consider consolidating related tests
+
+2. **File Naming Convention:**
+ - `feature-core.test.ts` for main functionality
+ - `feature-management.test.ts` for admin/bulk operations
+ - `feature-edge-cases.test.ts` for error handling
+ - `feature-integration.test.ts` for cross-feature testing
+
+3. **Test Data Management:**
+ - Use `actions.generateTestData()` for unique names
+ - Avoid hardcoded test data that may conflict
+ - Clean up created test data when possible
+
+4. **Screenshot Strategy:**
+ - Take screenshots at verification points
+ - Use descriptive names with timestamps
+ - Organize in `test-results/screenshots/`
+
+#### **Troubleshooting Common Issues**
+
+1. **Flaky Tests:**
+ - Add explicit waits after navigation
+ - Use `waitForLoadState('networkidle')`
+ - Check for race conditions in AJAX calls
+
+2. **Selector Issues:**
+ - Use stable selectors (IDs over classes)
+ - Prefer text content when selectors change
+ - Test selectors in browser console first
+
+3. **Authentication Issues:**
+ - Use `authenticatedPage` fixture for most tests
+ - Verify login redirect works properly
+ - Check test user exists and has correct permissions
+
+4. **Page Load Issues:**
+ - Ensure staging environment is accessible
+ - Check for JavaScript errors in console
+ - Verify plugin is activated and functioning
+
## Architecture Overview
[... rest of the file remains unchanged ...]
\ No newline at end of file
diff --git a/wordpress-dev/tests/e2e/mobile-responsiveness-simple.test.ts b/wordpress-dev/tests/e2e/mobile-responsiveness-simple.test.ts
new file mode 100644
index 00000000..364fe567
--- /dev/null
+++ b/wordpress-dev/tests/e2e/mobile-responsiveness-simple.test.ts
@@ -0,0 +1,326 @@
+/**
+ * Mobile Responsiveness Test for HVAC Plugin UX Enhancements
+ *
+ * Simplified mobile testing focusing on key responsiveness features
+ */
+
+import { test, expect } from '@playwright/test';
+
+const STAGING_URL = 'https://upskill-staging.measurequick.com';
+
+// Test responsive breakpoints
+test.describe('Mobile Responsiveness', () => {
+ test('Mobile login page layout (iPhone viewport)', async ({ page }) => {
+ test.setTimeout(20000);
+
+ // Set mobile viewport
+ await page.setViewportSize({ width: 375, height: 667 });
+
+ await page.goto(`${STAGING_URL}/community-login`);
+ await page.waitForLoadState('networkidle');
+
+ // Check login form is properly sized for mobile
+ const loginForm = page.locator('#hvac_community_loginform');
+ await expect(loginForm).toBeVisible();
+
+ // Verify mobile-optimized form elements
+ const usernameField = page.locator('#user_login');
+ const passwordField = page.locator('#user_pass');
+ const submitButton = page.locator('#wp-submit');
+
+ await expect(usernameField).toBeVisible();
+ await expect(passwordField).toBeVisible();
+ await expect(submitButton).toBeVisible();
+
+ // Check touch target sizes (minimum 44px for iOS)
+ const submitBox = await submitButton.boundingBox();
+ expect(submitBox?.height).toBeGreaterThanOrEqual(40);
+
+ // Test form field focus states work on mobile
+ await usernameField.focus();
+ await passwordField.focus();
+
+ // Take screenshot for visual verification
+ await page.screenshot({ path: `test-results/mobile-login-375x667.png` });
+ });
+
+ test('Mobile dashboard layout and navigation', async ({ page }) => {
+ test.setTimeout(30000);
+
+ // Set mobile viewport
+ await page.setViewportSize({ width: 375, height: 667 });
+
+ // Login first
+ await page.goto(`${STAGING_URL}/community-login`);
+ await page.fill('#user_login', 'test_trainer');
+ await page.fill('#user_pass', 'Test123!');
+ await page.click('#wp-submit');
+ await page.waitForLoadState('networkidle');
+
+ // Verify we're on dashboard
+ await expect(page).toHaveURL(/hvac-dashboard/);
+
+ // Check dashboard layout adapts to mobile
+ const dashboard = page.locator('.hvac-dashboard-wrapper');
+ await expect(dashboard).toBeVisible();
+
+ // Test mobile navigation if present
+ const mobileNavToggle = page.locator('.hvac-mobile-nav-toggle');
+ if (await mobileNavToggle.isVisible()) {
+ await mobileNavToggle.click();
+
+ const mobileNav = page.locator('.hvac-mobile-nav');
+ await expect(mobileNav).toHaveClass(/open/);
+
+ // Test navigation links work
+ const navLinks = page.locator('.hvac-mobile-nav a');
+ const navCount = await navLinks.count();
+ expect(navCount).toBeGreaterThan(0);
+ }
+
+ // Check stats cards stack properly on mobile
+ const statCards = page.locator('.hvac-stat-card');
+ const statCount = await statCards.count();
+ if (statCount > 0) {
+ // Verify cards are visible and properly sized
+ for (let i = 0; i < Math.min(statCount, 3); i++) {
+ await expect(statCards.nth(i)).toBeVisible();
+ }
+ }
+
+ // Test touch scrolling works
+ await page.evaluate(() => window.scrollTo(0, 200));
+ await page.waitForTimeout(500);
+
+ // Take screenshot
+ await page.screenshot({ path: `test-results/mobile-dashboard-375x667.png` });
+ });
+
+ test('Toast notifications positioning on mobile', async ({ page }) => {
+ test.setTimeout(25000);
+
+ // Set mobile viewport
+ await page.setViewportSize({ width: 375, height: 667 });
+
+ // Login
+ await page.goto(`${STAGING_URL}/community-login`);
+ await page.fill('#user_login', 'test_trainer');
+ await page.fill('#user_pass', 'Test123!');
+ await page.click('#wp-submit');
+ await page.waitForLoadState('networkidle');
+
+ // Go to certificate reports to trigger potential notifications
+ await page.goto(`${STAGING_URL}/certificate-reports`);
+ await page.waitForLoadState('networkidle');
+
+ // Inject test toast to verify mobile positioning
+ await page.evaluate(() => {
+ // Create toast container if it doesn't exist
+ if (!document.querySelector('.hvac-toast-container')) {
+ const container = document.createElement('div');
+ container.className = 'hvac-toast-container';
+ container.style.cssText = `
+ position: fixed;
+ top: 10px;
+ right: 10px;
+ left: 10px;
+ z-index: 10000;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ max-width: none;
+ pointer-events: none;
+ `;
+ document.body.appendChild(container);
+ }
+
+ // Create a test toast
+ const toast = document.createElement('div');
+ toast.className = 'hvac-toast success show';
+ toast.style.cssText = `
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ padding: 16px 20px;
+ pointer-events: all;
+ transform: translateX(0);
+ border-left: 4px solid #10b981;
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
+ max-width: 100%;
+ word-wrap: break-word;
+ `;
+ toast.innerHTML = `
+
✓
+
+
Success
+
Mobile toast notification test - this is a longer message to test wrapping
+
+ `;
+
+ const container = document.querySelector('.hvac-toast-container');
+ if (container) {
+ container.appendChild(toast);
+ }
+ });
+
+ await page.waitForTimeout(1000);
+
+ // Check if toast is visible and properly positioned
+ const toast = page.locator('.hvac-toast');
+ await expect(toast).toBeVisible();
+
+ // Verify toast doesn't overflow viewport
+ const toastBox = await toast.boundingBox();
+ const viewport = page.viewportSize();
+
+ if (toastBox && viewport) {
+ expect(toastBox.x + toastBox.width).toBeLessThanOrEqual(viewport.width);
+ expect(toastBox.y).toBeGreaterThanOrEqual(0);
+ }
+
+ // Take screenshot
+ await page.screenshot({ path: `test-results/mobile-toast-375x667.png` });
+ });
+
+ test('Form interaction on mobile devices', async ({ page }) => {
+ test.setTimeout(25000);
+
+ // Set mobile viewport
+ await page.setViewportSize({ width: 375, height: 667 });
+
+ // Login
+ await page.goto(`${STAGING_URL}/community-login`);
+ await page.fill('#user_login', 'test_trainer');
+ await page.fill('#user_pass', 'Test123!');
+ await page.click('#wp-submit');
+ await page.waitForLoadState('networkidle');
+
+ // Go to create event page to test forms
+ await page.goto(`${STAGING_URL}/manage-event`);
+ await page.waitForLoadState('networkidle');
+
+ // Test form field interactions
+ const titleField = page.locator('#event_title');
+ if (await titleField.isVisible()) {
+ // Check field is properly sized for mobile
+ const fieldBox = await titleField.boundingBox();
+ expect(fieldBox?.height).toBeGreaterThanOrEqual(40);
+
+ // Test touch interaction
+ await titleField.tap();
+ await titleField.fill('Mobile Test Event');
+
+ const value = await titleField.inputValue();
+ expect(value).toBe('Mobile Test Event');
+ }
+
+ // Check form buttons are touch-friendly
+ const buttons = page.locator('button, input[type="submit"]');
+ const buttonCount = await buttons.count();
+
+ if (buttonCount > 0) {
+ const button = buttons.first();
+ if (await button.isVisible()) {
+ const buttonBox = await button.boundingBox();
+ expect(buttonBox?.height).toBeGreaterThanOrEqual(40);
+ }
+ }
+
+ // Take screenshot
+ await page.screenshot({ path: `test-results/mobile-form-375x667.png` });
+ });
+
+ test('Responsive breakpoints verification', async ({ page }) => {
+ test.setTimeout(35000);
+
+ const breakpoints = [
+ { name: 'Mobile-Small', width: 320, height: 568 },
+ { name: 'Mobile-Medium', width: 375, height: 667 },
+ { name: 'Tablet-Portrait', width: 768, height: 1024 },
+ ];
+
+ for (const breakpoint of breakpoints) {
+ // Set viewport
+ await page.setViewportSize({ width: breakpoint.width, height: breakpoint.height });
+
+ // Login
+ await page.goto(`${STAGING_URL}/community-login`);
+ await page.fill('#user_login', 'test_trainer');
+ await page.fill('#user_pass', 'Test123!');
+ await page.click('#wp-submit');
+ await page.waitForLoadState('networkidle');
+
+ // Check dashboard layout
+ await expect(page).toHaveURL(/hvac-dashboard/);
+
+ // Verify content is visible and accessible
+ const mainContent = page.locator('.hvac-dashboard-wrapper, .entry-content');
+ await expect(mainContent).toBeVisible();
+
+ // Check navigation is appropriate for screen size
+ if (breakpoint.width <= 767) {
+ // Mobile: navigation should adapt
+ const nav = page.locator('.hvac-dashboard-nav');
+ await expect(nav).toBeVisible();
+ } else {
+ // Tablet/Desktop: should see regular nav
+ const desktopNav = page.locator('.hvac-dashboard-nav');
+ await expect(desktopNav).toBeVisible();
+ }
+
+ // Take screenshot
+ await page.screenshot({
+ path: `test-results/responsive-${breakpoint.name}-${breakpoint.width}x${breakpoint.height}.png`,
+ fullPage: false
+ });
+
+ console.log(`✓ Verified ${breakpoint.name} (${breakpoint.width}x${breakpoint.height})`);
+ }
+ });
+
+ test('UX enhancements load correctly', async ({ page }) => {
+ test.setTimeout(20000);
+
+ // Set mobile viewport
+ await page.setViewportSize({ width: 375, height: 667 });
+
+ // Login
+ await page.goto(`${STAGING_URL}/community-login`);
+ await page.fill('#user_login', 'test_trainer');
+ await page.fill('#user_pass', 'Test123!');
+ await page.click('#wp-submit');
+ await page.waitForLoadState('networkidle');
+
+ // Check that UX enhancement assets are loaded
+ const uxEnhancementsLoaded = await page.evaluate(() => {
+ // Check if HVACToast is available
+ const hasToast = typeof window.HVACToast !== 'undefined';
+
+ // Check if UX CSS is loaded by looking for specific classes
+ const uxStyles = Array.from(document.styleSheets).some(sheet => {
+ try {
+ return Array.from(sheet.cssRules).some(rule =>
+ rule.selectorText && rule.selectorText.includes('hvac-toast')
+ );
+ } catch (e) {
+ return false;
+ }
+ });
+
+ return {
+ hasToast,
+ uxStyles,
+ mobileNav: !!document.querySelector('.hvac-mobile-nav-container, .hvac-mobile-nav-toggle')
+ };
+ });
+
+ console.log('UX Enhancements status:', uxEnhancementsLoaded);
+
+ // At minimum, some UX elements should be present
+ expect(uxEnhancementsLoaded.hasToast || uxEnhancementsLoaded.uxStyles).toBe(true);
+
+ await page.screenshot({ path: `test-results/ux-enhancements-loaded.png` });
+ });
+});
\ No newline at end of file
diff --git a/wordpress-dev/tests/e2e/mobile-responsiveness.test.ts b/wordpress-dev/tests/e2e/mobile-responsiveness.test.ts
new file mode 100644
index 00000000..7121617a
--- /dev/null
+++ b/wordpress-dev/tests/e2e/mobile-responsiveness.test.ts
@@ -0,0 +1,285 @@
+/**
+ * Mobile Responsiveness Test for HVAC Plugin UX Enhancements
+ *
+ * Tests the mobile experience including:
+ * - Touch-friendly interface elements
+ * - Responsive layouts
+ * - Mobile navigation
+ * - Toast notifications on mobile
+ * - Form usability on mobile devices
+ */
+
+import { test, expect, devices } from '@playwright/test';
+
+const STAGING_URL = 'https://upskill-staging.measurequick.com';
+
+// Test with different mobile viewports
+const mobileDevices = [
+ devices['iPhone 12'],
+ devices['iPhone 12 Pro'],
+ devices['Samsung Galaxy S21'],
+ devices['iPad Mini']
+];
+
+for (const device of mobileDevices) {
+ test.describe(`Mobile Responsiveness - ${device.name}`, () => {
+ test.use({ ...device });
+
+ test(`Login page mobile layout - ${device.name}`, async ({ page }) => {
+ test.setTimeout(20000);
+
+ await page.goto(`${STAGING_URL}/community-login`);
+ await page.waitForLoadState('networkidle');
+
+ // Check login form is properly sized for mobile
+ const loginForm = page.locator('#hvac_community_loginform');
+ await expect(loginForm).toBeVisible();
+
+ // Verify mobile-optimized form elements
+ const usernameField = page.locator('#user_login');
+ const passwordField = page.locator('#user_pass');
+ const submitButton = page.locator('#wp-submit');
+
+ await expect(usernameField).toBeVisible();
+ await expect(passwordField).toBeVisible();
+ await expect(submitButton).toBeVisible();
+
+ // Check touch target sizes (minimum 44px for iOS)
+ const submitBox = await submitButton.boundingBox();
+ expect(submitBox?.height).toBeGreaterThanOrEqual(44);
+
+ // Test form field focus states work on mobile
+ await usernameField.focus();
+ await passwordField.focus();
+
+ // Take screenshot for visual verification
+ await page.screenshot({ path: `test-results/mobile-login-${device.name?.replace(/\s+/g, '-')}.png` });
+ });
+
+ test(`Dashboard mobile layout - ${device.name}`, async ({ page }) => {
+ test.setTimeout(30000);
+
+ // Login first
+ await page.goto(`${STAGING_URL}/community-login`);
+ await page.fill('#user_login', 'test_trainer');
+ await page.fill('#user_pass', 'Test123!');
+ await page.click('#wp-submit');
+ await page.waitForLoadState('networkidle');
+
+ // Verify we're on dashboard
+ await expect(page).toHaveURL(/hvac-dashboard/);
+
+ // Check dashboard layout adapts to mobile
+ const dashboard = page.locator('.hvac-dashboard-wrapper');
+ await expect(dashboard).toBeVisible();
+
+ // Test mobile navigation if present
+ const mobileNavToggle = page.locator('.hvac-mobile-nav-toggle');
+ if (await mobileNavToggle.isVisible()) {
+ await mobileNavToggle.click();
+
+ const mobileNav = page.locator('.hvac-mobile-nav');
+ await expect(mobileNav).toHaveClass(/open/);
+
+ // Test navigation links work
+ const navLinks = page.locator('.hvac-mobile-nav a');
+ const navCount = await navLinks.count();
+ expect(navCount).toBeGreaterThan(0);
+ }
+
+ // Check stats cards stack properly on mobile
+ const statCards = page.locator('.hvac-stat-card');
+ const statCount = await statCards.count();
+ if (statCount > 0) {
+ // Verify cards are visible and properly sized
+ for (let i = 0; i < Math.min(statCount, 3); i++) {
+ await expect(statCards.nth(i)).toBeVisible();
+ }
+ }
+
+ // Test touch scrolling works
+ await page.evaluate(() => window.scrollTo(0, 200));
+ await page.waitForTimeout(500);
+
+ // Take screenshot
+ await page.screenshot({ path: `test-results/mobile-dashboard-${device.name?.replace(/\s+/g, '-')}.png` });
+ });
+
+ test(`Toast notifications on mobile - ${device.name}`, async ({ page }) => {
+ test.setTimeout(25000);
+
+ // Login
+ await page.goto(`${STAGING_URL}/community-login`);
+ await page.fill('#user_login', 'test_trainer');
+ await page.fill('#user_pass', 'Test123!');
+ await page.click('#wp-submit');
+ await page.waitForLoadState('networkidle');
+
+ // Go to certificate reports to trigger potential notifications
+ await page.goto(`${STAGING_URL}/certificate-reports`);
+ await page.waitForLoadState('networkidle');
+
+ // Check if toast container exists and is positioned correctly for mobile
+ const toastContainer = page.locator('.hvac-toast-container');
+
+ // Inject test toast to verify mobile positioning
+ await page.evaluate(() => {
+ if (window.HVACToast) {
+ window.HVACToast.success('This is a test toast notification for mobile testing');
+ }
+ });
+
+ await page.waitForTimeout(1000);
+
+ // Check if toast is visible and properly positioned
+ const toast = page.locator('.hvac-toast');
+ if (await toast.count() > 0) {
+ await expect(toast.first()).toBeVisible();
+
+ // Verify toast doesn't overflow viewport
+ const toastBox = await toast.first().boundingBox();
+ const viewport = page.viewportSize();
+
+ if (toastBox && viewport) {
+ expect(toastBox.x + toastBox.width).toBeLessThanOrEqual(viewport.width);
+ expect(toastBox.y).toBeGreaterThanOrEqual(0);
+ }
+ }
+
+ // Take screenshot
+ await page.screenshot({ path: `test-results/mobile-toast-${device.name?.replace(/\s+/g, '-')}.png` });
+ });
+
+ test(`Form interaction on mobile - ${device.name}`, async ({ page }) => {
+ test.setTimeout(25000);
+
+ // Login
+ await page.goto(`${STAGING_URL}/community-login`);
+ await page.fill('#user_login', 'test_trainer');
+ await page.fill('#user_pass', 'Test123!');
+ await page.click('#wp-submit');
+ await page.waitForLoadState('networkidle');
+
+ // Go to create event page to test forms
+ await page.goto(`${STAGING_URL}/manage-event`);
+ await page.waitForLoadState('networkidle');
+
+ // Test form field interactions
+ const titleField = page.locator('#event_title');
+ if (await titleField.isVisible()) {
+ // Check field is properly sized for mobile
+ const fieldBox = await titleField.boundingBox();
+ expect(fieldBox?.height).toBeGreaterThanOrEqual(44);
+
+ // Test touch interaction
+ await titleField.tap();
+ await titleField.fill('Mobile Test Event');
+
+ const value = await titleField.inputValue();
+ expect(value).toBe('Mobile Test Event');
+ }
+
+ // Check form buttons are touch-friendly
+ const buttons = page.locator('button, input[type="submit"]');
+ const buttonCount = await buttons.count();
+
+ for (let i = 0; i < Math.min(buttonCount, 3); i++) {
+ const button = buttons.nth(i);
+ if (await button.isVisible()) {
+ const buttonBox = await button.boundingBox();
+ expect(buttonBox?.height).toBeGreaterThanOrEqual(44);
+ }
+ }
+
+ // Take screenshot
+ await page.screenshot({ path: `test-results/mobile-form-${device.name?.replace(/\s+/g, '-')}.png` });
+ });
+ });
+}
+
+// Test responsive breakpoints
+test.describe('Responsive Breakpoints', () => {
+ const breakpoints = [
+ { name: 'Mobile Small', width: 320, height: 568 },
+ { name: 'Mobile Medium', width: 375, height: 667 },
+ { name: 'Mobile Large', width: 414, height: 896 },
+ { name: 'Tablet Portrait', width: 768, height: 1024 },
+ { name: 'Tablet Landscape', width: 1024, height: 768 }
+ ];
+
+ for (const breakpoint of breakpoints) {
+ test(`Dashboard layout at ${breakpoint.name} (${breakpoint.width}x${breakpoint.height})`, async ({ page }) => {
+ test.setTimeout(25000);
+
+ // Set viewport
+ await page.setViewportSize({ width: breakpoint.width, height: breakpoint.height });
+
+ // Login
+ await page.goto(`${STAGING_URL}/community-login`);
+ await page.fill('#user_login', 'test_trainer');
+ await page.fill('#user_pass', 'Test123!');
+ await page.click('#wp-submit');
+ await page.waitForLoadState('networkidle');
+
+ // Check dashboard layout
+ await expect(page).toHaveURL(/hvac-dashboard/);
+
+ // Verify content is visible and accessible
+ const mainContent = page.locator('.hvac-dashboard-wrapper, .entry-content');
+ await expect(mainContent).toBeVisible();
+
+ // Check navigation is appropriate for screen size
+ if (breakpoint.width <= 767) {
+ // Mobile: should see mobile nav or stacked nav
+ const mobileNav = page.locator('.hvac-mobile-nav-toggle, .hvac-dashboard-nav');
+ await expect(mobileNav).toBeVisible();
+ } else {
+ // Tablet/Desktop: should see regular nav
+ const desktopNav = page.locator('.hvac-dashboard-nav');
+ await expect(desktopNav).toBeVisible();
+ }
+
+ // Take screenshot
+ await page.screenshot({
+ path: `test-results/responsive-${breakpoint.name.replace(/\s+/g, '-')}-${breakpoint.width}x${breakpoint.height}.png`,
+ fullPage: true
+ });
+ });
+ }
+});
+
+// Test accessibility on mobile
+test.describe('Mobile Accessibility', () => {
+ test.use(devices['iPhone 12']);
+
+ test('Touch accessibility and keyboard navigation', async ({ page }) => {
+ test.setTimeout(25000);
+
+ // Login
+ await page.goto(`${STAGING_URL}/community-login`);
+ await page.fill('#user_login', 'test_trainer');
+ await page.fill('#user_pass', 'Test123!');
+ await page.click('#wp-submit');
+ await page.waitForLoadState('networkidle');
+
+ // Test keyboard navigation with tab
+ await page.keyboard.press('Tab');
+ await page.keyboard.press('Tab');
+ await page.keyboard.press('Tab');
+
+ // Check focus states are visible
+ const focusedElement = page.locator(':focus');
+ await expect(focusedElement).toBeVisible();
+
+ // Test escape key closes mobile elements
+ await page.keyboard.press('Escape');
+
+ // Verify aria labels exist on interactive elements
+ const buttons = page.locator('button[aria-label], input[aria-label]');
+ const buttonCount = await buttons.count();
+ console.log(`Found ${buttonCount} buttons with aria-labels`);
+
+ // Take screenshot
+ await page.screenshot({ path: `test-results/mobile-accessibility.png` });
+ });
+});
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-ux-enhancements.css b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-ux-enhancements.css
new file mode 100644
index 00000000..d33d03d1
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-ux-enhancements.css
@@ -0,0 +1,411 @@
+/**
+ * HVAC Community Events: UX Enhancements
+ *
+ * Enhanced user experience with modern notifications, loading states, and mobile optimizations
+ *
+ * @version 1.0.0
+ */
+
+/* ========================================
+ NOTIFICATION SYSTEM
+======================================== */
+
+/* Toast Notification Container */
+.hvac-toast-container {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ z-index: 10000;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ max-width: 400px;
+ pointer-events: none;
+}
+
+/* Toast Notification */
+.hvac-toast {
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ padding: 16px 20px;
+ pointer-events: all;
+ transform: translateX(100%);
+ transition: all 0.3s ease;
+ border-left: 4px solid #3b82f6;
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
+ position: relative;
+ max-width: 100%;
+ word-wrap: break-word;
+}
+
+.hvac-toast.show {
+ transform: translateX(0);
+}
+
+.hvac-toast.hiding {
+ transform: translateX(100%);
+ opacity: 0;
+}
+
+/* Toast Types */
+.hvac-toast.success {
+ border-left-color: #10b981;
+}
+
+.hvac-toast.error {
+ border-left-color: #ef4444;
+}
+
+.hvac-toast.warning {
+ border-left-color: #f59e0b;
+}
+
+.hvac-toast.info {
+ border-left-color: #3b82f6;
+}
+
+/* Toast Icon */
+.hvac-toast-icon {
+ width: 20px;
+ height: 20px;
+ flex-shrink: 0;
+ margin-top: 2px;
+}
+
+.hvac-toast.success .hvac-toast-icon::before {
+ content: "✓";
+ color: #10b981;
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.hvac-toast.error .hvac-toast-icon::before {
+ content: "✗";
+ color: #ef4444;
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.hvac-toast.warning .hvac-toast-icon::before {
+ content: "⚠";
+ color: #f59e0b;
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.hvac-toast.info .hvac-toast-icon::before {
+ content: "ℹ";
+ color: #3b82f6;
+ font-weight: bold;
+ font-size: 16px;
+}
+
+/* Toast Content */
+.hvac-toast-content {
+ flex: 1;
+}
+
+.hvac-toast-title {
+ font-weight: 600;
+ font-size: 14px;
+ color: #1f2937;
+ margin-bottom: 4px;
+}
+
+.hvac-toast-message {
+ font-size: 13px;
+ color: #6b7280;
+ line-height: 1.4;
+}
+
+/* Toast Close Button */
+.hvac-toast-close {
+ background: none;
+ border: none;
+ color: #9ca3af;
+ cursor: pointer;
+ font-size: 18px;
+ padding: 0;
+ width: 20px;
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ transition: all 0.2s;
+ flex-shrink: 0;
+}
+
+.hvac-toast-close:hover {
+ background: #f3f4f6;
+ color: #374151;
+}
+
+/* ========================================
+ LOADING STATES
+======================================== */
+
+/* Loading Overlay */
+.hvac-loading-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.8);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+ backdrop-filter: blur(2px);
+}
+
+/* Inline Loading Spinner */
+.hvac-loading {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ color: #6b7280;
+}
+
+.hvac-spinner {
+ width: 20px;
+ height: 20px;
+ border: 2px solid #e5e7eb;
+ border-top: 2px solid #3b82f6;
+ border-radius: 50%;
+ animation: hvac-spin 1s linear infinite;
+}
+
+.hvac-spinner.large {
+ width: 40px;
+ height: 40px;
+ border-width: 3px;
+}
+
+@keyframes hvac-spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+/* Button Loading State */
+.hvac-btn.loading {
+ position: relative;
+ color: transparent !important;
+ pointer-events: none;
+}
+
+.hvac-btn.loading::after {
+ content: "";
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 16px;
+ height: 16px;
+ border: 2px solid transparent;
+ border-top: 2px solid currentColor;
+ border-radius: 50%;
+ animation: hvac-spin 1s linear infinite;
+ color: inherit;
+}
+
+/* ========================================
+ ENHANCED BUTTONS
+======================================== */
+
+.hvac-btn {
+ position: relative;
+ overflow: hidden;
+ transition: all 0.2s ease;
+}
+
+.hvac-btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ transform: none !important;
+}
+
+.hvac-btn:not(:disabled):hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.hvac-btn:not(:disabled):active {
+ transform: translateY(0);
+}
+
+/* ========================================
+ ENHANCED FORM ELEMENTS
+======================================== */
+
+/* Input Focus States */
+.hvac-input:focus,
+.hvac-select:focus,
+.hvac-textarea:focus {
+ border-color: #3b82f6;
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+ outline: none;
+}
+
+/* Input Error States */
+.hvac-input.error,
+.hvac-select.error,
+.hvac-textarea.error {
+ border-color: #ef4444;
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
+}
+
+/* Field Error Message */
+.hvac-field-error {
+ color: #ef4444;
+ font-size: 12px;
+ margin-top: 4px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.hvac-field-error::before {
+ content: "⚠";
+ font-size: 14px;
+}
+
+/* ========================================
+ MOBILE RESPONSIVENESS
+======================================== */
+
+/* Mobile Toast Adjustments */
+@media (max-width: 640px) {
+ .hvac-toast-container {
+ top: 10px;
+ right: 10px;
+ left: 10px;
+ max-width: none;
+ }
+
+ .hvac-toast {
+ margin: 0;
+ max-width: 100%;
+ }
+}
+
+/* Mobile Form Improvements */
+@media (max-width: 767px) {
+ /* Larger touch targets */
+ .hvac-btn {
+ min-height: 44px;
+ font-size: 16px; /* Prevents zoom on iOS */
+ }
+
+ .hvac-input,
+ .hvac-select,
+ .hvac-textarea {
+ min-height: 44px;
+ font-size: 16px; /* Prevents zoom on iOS */
+ padding: 12px;
+ }
+
+ /* Full width buttons on mobile */
+ .hvac-dashboard-nav {
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ .hvac-dashboard-nav .hvac-btn {
+ width: 100%;
+ text-align: center;
+ }
+
+ /* Improved stats layout on mobile */
+ .hvac-stats-row {
+ flex-direction: column;
+ margin: 0;
+ }
+
+ .hvac-stat-col {
+ padding: 5px 0;
+ min-width: 100%;
+ }
+
+ /* Card improvements for mobile */
+ .hvac-stat-card,
+ .hvac-certificate-card,
+ .hvac-event-card {
+ margin-bottom: var(--hvac-spacing-md);
+ padding: var(--hvac-spacing-md);
+ }
+
+ /* Table responsiveness */
+ .hvac-table-responsive {
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ }
+
+ .hvac-table-responsive table {
+ min-width: 600px;
+ }
+}
+
+/* ========================================
+ ACCESSIBILITY ENHANCEMENTS
+======================================== */
+
+/* Focus Visible for Keyboard Navigation */
+.hvac-btn:focus-visible,
+.hvac-input:focus-visible,
+.hvac-select:focus-visible {
+ outline: 2px solid #3b82f6;
+ outline-offset: 2px;
+}
+
+/* Screen Reader Only Text */
+.hvac-sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* High Contrast Mode Support */
+@media (prefers-contrast: high) {
+ .hvac-toast {
+ border: 2px solid;
+ }
+
+ .hvac-btn {
+ border: 2px solid;
+ }
+}
+
+/* Reduced Motion Support */
+@media (prefers-reduced-motion: reduce) {
+ .hvac-toast,
+ .hvac-btn,
+ .hvac-spinner {
+ animation: none;
+ transition: none;
+ }
+}
+
+/* ========================================
+ UTILITY CLASSES
+======================================== */
+
+.hvac-hidden { display: none !important; }
+.hvac-invisible { visibility: hidden !important; }
+.hvac-text-center { text-align: center !important; }
+.hvac-text-left { text-align: left !important; }
+.hvac-text-right { text-align: right !important; }
+.hvac-mb-0 { margin-bottom: 0 !important; }
+.hvac-mt-0 { margin-top: 0 !important; }
+.hvac-p-0 { padding: 0 !important; }
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-ux-enhancements.js b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-ux-enhancements.js
new file mode 100644
index 00000000..f1565a76
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-ux-enhancements.js
@@ -0,0 +1,409 @@
+/**
+ * HVAC Community Events: UX Enhancements JavaScript
+ *
+ * Modern user experience enhancements including toast notifications,
+ * loading states, and improved error handling.
+ *
+ * @version 1.0.0
+ */
+
+(function($) {
+ 'use strict';
+
+ /**
+ * Toast Notification System
+ */
+ const HVACToast = {
+ container: null,
+
+ init: function() {
+ // Create toast container if it doesn't exist
+ if (!this.container) {
+ this.container = $('');
+ $('body').append(this.container);
+ }
+ },
+
+ show: function(message, type = 'info', title = '', duration = 5000) {
+ this.init();
+
+ const toastId = 'hvac-toast-' + Date.now();
+ const toast = $(`
+
+
+
+ ${title ? `
${title}
` : ''}
+
${message}
+
+
+
+ `);
+
+ // Add event listeners
+ toast.find('.hvac-toast-close').on('click', () => this.hide(toastId));
+
+ // Add to container and show
+ this.container.append(toast);
+
+ // Trigger show animation
+ setTimeout(() => toast.addClass('show'), 10);
+
+ // Auto-hide after duration
+ if (duration > 0) {
+ setTimeout(() => this.hide(toastId), duration);
+ }
+
+ return toastId;
+ },
+
+ hide: function(toastId) {
+ const toast = $('#' + toastId);
+ if (toast.length) {
+ toast.addClass('hiding');
+ setTimeout(() => toast.remove(), 300);
+ }
+ },
+
+ success: function(message, title = 'Success', duration = 4000) {
+ return this.show(message, 'success', title, duration);
+ },
+
+ error: function(message, title = 'Error', duration = 7000) {
+ return this.show(message, 'error', title, duration);
+ },
+
+ warning: function(message, title = 'Warning', duration = 6000) {
+ return this.show(message, 'warning', title, duration);
+ },
+
+ info: function(message, title = '', duration = 5000) {
+ return this.show(message, 'info', title, duration);
+ }
+ };
+
+ /**
+ * Loading State Manager
+ */
+ const HVACLoading = {
+ overlay: null,
+
+ showOverlay: function(message = 'Loading...') {
+ this.hideOverlay(); // Remove existing overlay
+
+ this.overlay = $(`
+
+ `);
+
+ $('body').append(this.overlay);
+ },
+
+ hideOverlay: function() {
+ if (this.overlay) {
+ this.overlay.remove();
+ this.overlay = null;
+ }
+ },
+
+ showButton: function(button, text = 'Loading...') {
+ const $btn = $(button);
+ $btn.addClass('loading').prop('disabled', true);
+ $btn.data('original-text', $btn.text());
+ if (text) {
+ $btn.attr('aria-label', text);
+ }
+ },
+
+ hideButton: function(button) {
+ const $btn = $(button);
+ $btn.removeClass('loading').prop('disabled', false);
+ const originalText = $btn.data('original-text');
+ if (originalText) {
+ $btn.text(originalText).removeData('original-text');
+ }
+ $btn.removeAttr('aria-label');
+ }
+ };
+
+ /**
+ * Enhanced AJAX Handler
+ */
+ const HVACAjax = {
+ request: function(options) {
+ const defaults = {
+ type: 'POST',
+ dataType: 'json',
+ timeout: 30000,
+ showLoading: true,
+ loadingMessage: 'Processing...',
+ showToasts: true,
+ beforeSend: function() {
+ if (options.showLoading && options.button) {
+ HVACLoading.showButton(options.button, options.loadingMessage);
+ } else if (options.showLoading && options.showOverlay) {
+ HVACLoading.showOverlay(options.loadingMessage);
+ }
+ },
+ complete: function() {
+ if (options.showLoading && options.button) {
+ HVACLoading.hideButton(options.button);
+ } else if (options.showLoading && options.showOverlay) {
+ HVACLoading.hideOverlay();
+ }
+ },
+ success: function(response) {
+ if (options.showToasts) {
+ if (response.success) {
+ const message = response.data?.message || 'Operation completed successfully';
+ HVACToast.success(message);
+ } else {
+ const message = response.data?.message || 'Operation failed';
+ HVACToast.error(message);
+ }
+ }
+ },
+ error: function(xhr, status, error) {
+ console.error('AJAX Error:', {xhr, status, error});
+
+ if (options.showToasts) {
+ let message = 'An unexpected error occurred. Please try again.';
+
+ if (status === 'timeout') {
+ message = 'Request timed out. Please check your connection and try again.';
+ } else if (status === 'abort') {
+ message = 'Request was cancelled.';
+ } else if (xhr.status === 403) {
+ message = 'You do not have permission to perform this action.';
+ } else if (xhr.status === 404) {
+ message = 'The requested resource was not found.';
+ } else if (xhr.status >= 500) {
+ message = 'Server error. Please try again later.';
+ }
+
+ HVACToast.error(message, 'Connection Error');
+ }
+ }
+ };
+
+ const settings = $.extend({}, defaults, options);
+ return $.ajax(settings);
+ }
+ };
+
+ /**
+ * Form Validation Enhancement
+ */
+ const HVACValidation = {
+ validateField: function(field, rules = []) {
+ const $field = $(field);
+ const value = $field.val().trim();
+ let isValid = true;
+ let errorMessage = '';
+
+ // Remove existing error states
+ $field.removeClass('error');
+ $field.siblings('.hvac-field-error').remove();
+
+ // Check rules
+ for (const rule of rules) {
+ if (rule.required && !value) {
+ isValid = false;
+ errorMessage = rule.message || 'This field is required';
+ break;
+ }
+
+ if (rule.minLength && value.length < rule.minLength) {
+ isValid = false;
+ errorMessage = rule.message || `Must be at least ${rule.minLength} characters`;
+ break;
+ }
+
+ if (rule.pattern && !rule.pattern.test(value)) {
+ isValid = false;
+ errorMessage = rule.message || 'Invalid format';
+ break;
+ }
+
+ if (rule.custom && typeof rule.custom === 'function') {
+ const result = rule.custom(value);
+ if (result !== true) {
+ isValid = false;
+ errorMessage = result || 'Invalid value';
+ break;
+ }
+ }
+ }
+
+ if (!isValid) {
+ $field.addClass('error');
+ $field.after(`${errorMessage}
`);
+ }
+
+ return isValid;
+ }
+ };
+
+ /**
+ * Mobile Navigation Enhancement
+ */
+ const HVACMobile = {
+ init: function() {
+ this.setupMobileNav();
+ this.setupTouchOptimizations();
+ },
+
+ setupMobileNav: function() {
+ // Create mobile navigation if not exists
+ $('.hvac-dashboard-nav').each(function() {
+ const $nav = $(this);
+ if ($nav.siblings('.hvac-mobile-nav-container').length === 0) {
+ const mobileNav = $(`
+
+
+
+
+ `);
+
+ // Copy nav items to mobile menu
+ const $mobileList = mobileNav.find('ul');
+ $nav.find('a').each(function() {
+ const $link = $(this).clone();
+ $mobileList.append($('').append($link));
+ });
+
+ $nav.before(mobileNav);
+
+ // Add toggle functionality
+ mobileNav.find('.hvac-mobile-nav-toggle').on('click', function() {
+ const $toggle = $(this);
+ const $menu = $toggle.siblings('.hvac-mobile-nav');
+ const isOpen = $menu.hasClass('open');
+
+ $toggle.toggleClass('active').attr('aria-expanded', !isOpen);
+ $menu.toggleClass('open');
+ });
+ }
+ });
+ },
+
+ setupTouchOptimizations: function() {
+ // Add touch-friendly classes
+ $('button, .hvac-btn, input[type="submit"]').addClass('hvac-touch-target');
+
+ // Prevent double-tap zoom on buttons
+ $('button, .hvac-btn').on('touchend', function(e) {
+ e.preventDefault();
+ $(this).trigger('click');
+ });
+ }
+ };
+
+ /**
+ * Accessibility Enhancements
+ */
+ const HVACAccessibility = {
+ init: function() {
+ this.setupKeyboardNavigation();
+ this.setupAriaLabels();
+ },
+
+ setupKeyboardNavigation: function() {
+ // Escape key to close modals/overlays
+ $(document).on('keydown', function(e) {
+ if (e.key === 'Escape') {
+ // Close any open mobile nav
+ $('.hvac-mobile-nav.open').removeClass('open');
+ $('.hvac-mobile-nav-toggle.active').removeClass('active').attr('aria-expanded', 'false');
+
+ // Hide loading overlay
+ HVACLoading.hideOverlay();
+ }
+ });
+ },
+
+ setupAriaLabels: function() {
+ // Add missing aria-labels to buttons without text
+ $('button:not([aria-label])').each(function() {
+ const $btn = $(this);
+ const text = $btn.text().trim();
+ if (!text) {
+ const title = $btn.attr('title');
+ if (title) {
+ $btn.attr('aria-label', title);
+ }
+ }
+ });
+ }
+ };
+
+ /**
+ * Replace all alert() calls with toast notifications
+ */
+ function replaceAlerts() {
+ // Override the global alert function
+ window.hvacOriginalAlert = window.alert;
+ window.alert = function(message) {
+ // Determine type based on message content
+ const lowerMessage = message.toLowerCase();
+ if (lowerMessage.includes('success') || lowerMessage.includes('sent') || lowerMessage.includes('saved')) {
+ HVACToast.success(message);
+ } else if (lowerMessage.includes('error') || lowerMessage.includes('failed') || lowerMessage.includes('fail')) {
+ HVACToast.error(message);
+ } else {
+ HVACToast.info(message);
+ }
+ };
+ }
+
+ /**
+ * Initialize everything when document is ready
+ */
+ $(document).ready(function() {
+ // Initialize all modules
+ HVACToast.init();
+ HVACMobile.init();
+ HVACAccessibility.init();
+
+ // Replace alert functions
+ replaceAlerts();
+
+ // Make modules globally available
+ window.HVACToast = HVACToast;
+ window.HVACLoading = HVACLoading;
+ window.HVACAjax = HVACAjax;
+ window.HVACValidation = HVACValidation;
+
+ // Auto-enhance existing forms
+ $('form').each(function() {
+ const $form = $(this);
+
+ // Add loading states to submit buttons
+ $form.on('submit', function() {
+ const $submitBtn = $form.find('input[type="submit"], button[type="submit"]').first();
+ if ($submitBtn.length) {
+ HVACLoading.showButton($submitBtn);
+ }
+ });
+ });
+
+ // Add responsive table wrapper
+ $('table').each(function() {
+ const $table = $(this);
+ if (!$table.parent().hasClass('hvac-table-responsive')) {
+ $table.wrap('');
+ }
+ });
+
+ console.log('HVAC UX Enhancements initialized');
+ });
+
+})(jQuery);
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-ajax-handler.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-ajax-handler.php
index 7e43e424..c1ab62d1 100644
--- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-ajax-handler.php
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-ajax-handler.php
@@ -93,11 +93,27 @@ class HVAC_Certificate_AJAX_Handler {
public function enqueue_scripts() {
// Only load on certificate pages
if (is_page('certificate-reports') || is_page('generate-certificates')) {
+ // Enqueue UX enhancements first
+ wp_enqueue_style(
+ 'hvac-ux-enhancements-css',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-ux-enhancements.css',
+ array(),
+ HVAC_CE_VERSION
+ );
+
+ wp_enqueue_script(
+ 'hvac-ux-enhancements-js',
+ HVAC_CE_PLUGIN_URL . 'assets/js/hvac-ux-enhancements.js',
+ array('jquery'),
+ HVAC_CE_VERSION,
+ true
+ );
+
// Enqueue certificate actions JS
wp_enqueue_script(
'hvac-certificate-actions-js',
HVAC_CE_PLUGIN_URL . 'assets/js/hvac-certificate-actions.js',
- array('jquery'),
+ array('jquery', 'hvac-ux-enhancements-js'),
HVAC_CE_VERSION,
true
);
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/class-hvac-dashboard.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/class-hvac-dashboard.php
index 317e2409..fb04804d 100644
--- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/class-hvac-dashboard.php
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/class-hvac-dashboard.php
@@ -315,11 +315,27 @@ class HVAC_Dashboard {
return;
}
+ // Enqueue UX enhancements (CSS and JS)
+ wp_enqueue_style(
+ 'hvac-ux-enhancements-css',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-ux-enhancements.css',
+ array(),
+ HVAC_CE_VERSION
+ );
+
+ wp_enqueue_script(
+ 'hvac-ux-enhancements-js',
+ HVAC_CE_PLUGIN_URL . 'assets/js/hvac-ux-enhancements.js',
+ array('jquery'),
+ HVAC_CE_VERSION,
+ true
+ );
+
// Enqueue dashboard JavaScript
wp_enqueue_script(
'hvac-dashboard-js',
HVAC_CE_PLUGIN_URL . 'assets/js/hvac-dashboard.js',
- array('jquery'),
+ array('jquery', 'hvac-ux-enhancements-js'),
HVAC_CE_VERSION,
true
);