freealberta/influence/ADMIN_INLINE_HANDLER_FIX.md

6.2 KiB

Response Wall Admin Panel - Inline Handler Fix Summary

Issue

Content Security Policy (CSP) violation when clicking buttons in the Response Moderation tab of the admin panel.

Error Messages

Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src-attr 'none'"
TypeError: window.apiClient.patch is not a function

Root Causes

1. Inline Event Handlers (CSP Violation)

The admin panel's Response Moderation tab was using inline onclick handlers:

<button onclick="adminPanel.approveResponse(${response.id})"> Approve</button>

This violates:

  • Browser Content Security Policy (CSP)
  • Project development guidelines in instruct.md: "No inline event handlers. Always use addEventListener in JS files."

2. Missing API Client Methods

The APIClient class only had get() and post() methods, but admin operations needed patch(), put(), and delete().

Solutions Implemented

Fix 1: Removed All Inline Handlers in admin.js

Before:

<button class="btn btn-success btn-sm" onclick="adminPanel.approveResponse(${response.id})">
   Approve
</button>

After:

<button class="btn btn-success btn-sm" 
        data-action="approve-response" 
        data-response-id="${response.id}">
   Approve
</button>

Fix 2: Added Event Delegation

Created new setupResponseActionListeners() method in AdminPanel class:

setupResponseActionListeners() {
    const container = document.getElementById('admin-responses-container');
    if (!container) return;

    // Remove old listener if exists to avoid duplicates
    const oldListener = container._responseActionListener;
    if (oldListener) {
        container.removeEventListener('click', oldListener);
    }

    // Create new listener with event delegation
    const listener = (e) => {
        const target = e.target;
        const action = target.dataset.action;
        const responseId = target.dataset.responseId;

        if (!action || !responseId) return;

        switch (action) {
            case 'approve-response':
                this.approveResponse(parseInt(responseId));
                break;
            case 'reject-response':
                this.rejectResponse(parseInt(responseId));
                break;
            case 'verify-response':
                const isVerified = target.dataset.verified === 'true';
                this.toggleVerified(parseInt(responseId), isVerified);
                break;
            case 'delete-response':
                this.deleteResponse(parseInt(responseId));
                break;
        }
    };

    // Store listener reference and add it
    container._responseActionListener = listener;
    container.addEventListener('click', listener);
}

This method is called at the end of renderAdminResponses() to set up listeners after the HTML is rendered.

Fix 3: Added Missing HTTP Methods to api-client.js

async put(endpoint, data) {
    return this.makeRequest(endpoint, {
        method: 'PUT',
        body: JSON.stringify(data)
    });
}

async patch(endpoint, data) {
    return this.makeRequest(endpoint, {
        method: 'PATCH',
        body: JSON.stringify(data)
    });
}

async delete(endpoint) {
    return this.makeRequest(endpoint, {
        method: 'DELETE'
    });
}

Buttons Fixed

All response moderation buttons now use proper event delegation:

  1. Approve - data-action="approve-response"
  2. Reject - data-action="reject-response"
  3. Mark as Verified - data-action="verify-response" data-verified="true"
  4. Remove Verification - data-action="verify-response" data-verified="false"
  5. Unpublish - data-action="reject-response" (reuses reject action)
  6. Delete - data-action="delete-response"

Files Modified

  1. app/public/js/admin.js

    • Modified renderAdminResponses() - Replaced inline onclick with data attributes
    • Added setupResponseActionListeners() - Event delegation implementation
    • Total changes: ~85 lines modified/added
  2. app/public/js/api-client.js

    • Added put() method
    • Added patch() method
    • Added delete() method
    • Total changes: ~18 lines added

Benefits of This Approach

1. Security

  • Complies with Content Security Policy (CSP)
  • Prevents script injection attacks
  • Follows modern web security best practices

2. Performance

  • Single event listener instead of N listeners (one per button)
  • Better memory usage
  • Faster page rendering

3. Maintainability

  • Follows project coding standards (instruct.md)
  • Centralized event handling logic
  • Easy to add new actions without HTML changes

4. Reliability

  • Prevents duplicate listeners
  • Clean listener removal/re-attachment
  • Works with dynamically rendered content

Testing Checklist

  • Load admin panel → Response Moderation tab
  • Click "Approve" button → Should approve response without CSP error
  • Click "Reject" button → Should reject response
  • Click "Mark as Verified" → Should add verification badge
  • Click "Remove Verification" → Should remove badge
  • Click "Delete" → Should delete response after confirmation
  • Filter responses by status → Should reload list
  • No console errors related to inline handlers
  • No CSP violations

Lessons Learned

  1. Always Follow Project Guidelines: The instruct.md file explicitly prohibits inline event handlers - following it from the start would have prevented this issue

  2. Complete API Client: When building a REST client, implement all HTTP methods (GET, POST, PUT, PATCH, DELETE) from the beginning

  3. Event Delegation for Dynamic Content: When rendering content dynamically with buttons/links, always use event delegation on a parent container

  4. CSP is Your Friend: Content Security Policy errors point to real security issues - fix them rather than disabling CSP

  • See instruct.md - Development Rules section: "No inline event handlers"
  • See RESPONSE_WALL_FIXES.md - Full list of all Response Wall bug fixes
  • See RESPONSE_WALL_USAGE.md - How to use the Response Wall feature