upskill-event-manager/assets/js/hvac-modal-forms.js
ben 91873c6a9c feat: implement comprehensive featured image system for events, organizers, and venues
- Add featured image field to main event creation form with WordPress media uploader
- Implement featured image upload in organizer and venue creation modals
- Update AJAX handlers to process and validate featured image attachments
- Add comprehensive media upload UI with preview and removal functionality
- Include proper permission validation for administrator, trainer, and master trainer roles
- Create authoritative documentation for complete event creation page functionality

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-26 20:24:31 -03:00

405 lines
No EOL
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* HVAC Modal Forms
*
* Handles modal forms for creating new organizers, categories, and venues
* with role-based permissions and AJAX submission.
*/
(function($) {
'use strict';
class HVACModalForms {
constructor() {
this.init();
}
init() {
this.bindEvents();
this.createModalContainer();
}
bindEvents() {
// Listen for create new modal trigger
$(document).on('hvac:create-new-modal', (e, data) => {
this.showCreateModal(data.type, data.callback);
});
// Modal close events
$(document).on('click', '.hvac-modal-overlay, .hvac-modal-close', (e) => {
e.preventDefault();
this.closeModal();
});
// Prevent modal close when clicking inside modal content
$(document).on('click', '.hvac-modal-content', (e) => {
e.stopPropagation();
});
// Form submission
$(document).on('submit', '.hvac-modal-form', (e) => {
e.preventDefault();
this.handleFormSubmission(e.target);
});
// Escape key to close modal
$(document).on('keydown', (e) => {
if (e.keyCode === 27) { // ESC key
this.closeModal();
}
});
// Media upload handlers
$(document).on('click', '.hvac-modal .select-image-btn', (e) => {
e.preventDefault();
this.openMediaUploader(e.target);
});
$(document).on('click', '.hvac-modal .remove-image', (e) => {
e.preventDefault();
this.removeImage(e.target);
});
}
createModalContainer() {
if ($('#hvac-modal-container').length) {
return;
}
const modalHtml = `
<div id="hvac-modal-container" class="hvac-modal-overlay" style="display: none;">
<div class="hvac-modal-content">
<div class="hvac-modal-header">
<h3 class="hvac-modal-title"></h3>
<button type="button" class="hvac-modal-close">&times;</button>
</div>
<div class="hvac-modal-body">
<!-- Form content will be inserted here -->
</div>
</div>
</div>
`;
$('body').append(modalHtml);
}
showCreateModal(type, callback) {
this.currentCallback = callback;
const config = this.getModalConfig(type);
if (!config) {
console.error(`Unknown modal type: ${type}`);
return;
}
// Set modal title
$('.hvac-modal-title').text(config.title);
// Generate form HTML
const formHtml = this.generateFormHtml(type, config);
$('.hvac-modal-body').html(formHtml);
// Show modal
$('#hvac-modal-container').fadeIn(300);
// Focus first input
setTimeout(() => {
$('.hvac-modal-form input:first').focus();
}, 350);
}
getModalConfig(type) {
const configs = {
organizer: {
title: 'Add New Organizer',
fields: [
{ name: 'organizer_name', label: 'Organizer Name', type: 'text', required: true },
{ name: 'organizer_email', label: 'Email', type: 'email', required: false },
{ name: 'organizer_website', label: 'Website', type: 'url', required: false },
{ name: 'organizer_phone', label: 'Phone', type: 'tel', required: false },
{ name: 'organizer_featured_image', label: 'Featured Image', type: 'media', required: false }
],
action: 'hvac_create_organizer'
},
category: {
title: 'Add New Category',
fields: [
{ name: 'category_name', label: 'Category Name', type: 'text', required: true },
{ name: 'category_description', label: 'Description', type: 'textarea', required: false }
],
action: 'hvac_create_category',
permission_check: true
},
venue: {
title: 'Add New Venue',
fields: [
{ name: 'venue_name', label: 'Venue Name', type: 'text', required: true },
{ name: 'venue_address', label: 'Address', type: 'text', required: false },
{ name: 'venue_city', label: 'City', type: 'text', required: false },
{ name: 'venue_state', label: 'State/Province', type: 'text', required: false },
{ name: 'venue_zip', label: 'Zip/Postal Code', type: 'text', required: false },
{ name: 'venue_country', label: 'Country', type: 'text', required: false },
{ name: 'venue_website', label: 'Website', type: 'url', required: false },
{ name: 'venue_phone', label: 'Phone', type: 'tel', required: false },
{ name: 'venue_featured_image', label: 'Featured Image', type: 'media', required: false }
],
action: 'hvac_create_venue'
}
};
return configs[type] || null;
}
generateFormHtml(type, config) {
// Check for category permission
if (config.permission_check && !hvacModalForms.canCreateCategories) {
return `
<div class="hvac-permission-error">
<p><strong>Permission Denied</strong></p>
<p>You don't have permission to create new categories. Please contact a master trainer for assistance.</p>
<div class="hvac-modal-actions">
<button type="button" class="hvac-btn hvac-btn-secondary hvac-modal-close">Close</button>
</div>
</div>
`;
}
let formHtml = `
<form class="hvac-modal-form" data-action="${config.action}">
<div class="hvac-form-fields">
`;
config.fields.forEach(field => {
formHtml += this.generateFieldHtml(field);
});
formHtml += `
</div>
<div class="hvac-modal-actions">
<button type="button" class="hvac-btn hvac-btn-secondary hvac-modal-close">Cancel</button>
<button type="submit" class="hvac-btn hvac-btn-primary">Create ${this.capitalizeFirst(type)}</button>
</div>
</form>
`;
return formHtml;
}
generateFieldHtml(field) {
const required = field.required ? 'required' : '';
const requiredMark = field.required ? '<span class="required">*</span>' : '';
if (field.type === 'textarea') {
return `
<div class="hvac-form-field">
<label for="${field.name}">${field.label}${requiredMark}</label>
<textarea id="${field.name}" name="${field.name}" ${required} rows="3"></textarea>
</div>
`;
}
if (field.type === 'media') {
return `
<div class="hvac-form-field hvac-media-field" data-field-name="${field.name}">
<label>${field.label}${requiredMark}</label>
<div class="media-upload-container">
<div class="image-preview-container" style="margin-bottom: 10px;">
<div class="image-preview" style="display: none; position: relative; max-width: 200px;">
<img src="" alt="Preview" style="max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px;">
<button type="button" class="remove-image" style="position: absolute; top: 5px; right: 5px; background: #d63638; color: white; border: none; border-radius: 50%; width: 20px; height: 20px; cursor: pointer; font-size: 12px;">×</button>
</div>
</div>
<div class="upload-controls">
<button type="button" class="select-image-btn hvac-btn hvac-btn-secondary">
<span class="dashicons dashicons-format-image" style="vertical-align: middle; margin-right: 5px;"></span>
Select Image
</button>
<input type="hidden" name="${field.name}" class="image-id-input" value="">
<input type="hidden" name="${field.name}_url" class="image-url-input" value="">
</div>
<p class="description" style="margin-top: 5px; font-size: 12px; color: #666;">
Recommended: 300x300 pixels or larger
</p>
</div>
</div>
`;
}
return `
<div class="hvac-form-field">
<label for="${field.name}">${field.label}${requiredMark}</label>
<input type="${field.type}" id="${field.name}" name="${field.name}" ${required}>
</div>
`;
}
async handleFormSubmission(form) {
const $form = $(form);
const $submitBtn = $form.find('button[type="submit"]');
const action = $form.data('action');
// Disable submit button and show loading
$submitBtn.prop('disabled', true).text('Creating...');
try {
const formData = new FormData(form);
formData.append('action', action);
formData.append('nonce', hvacModalForms.nonce);
const response = await fetch(hvacModalForms.ajaxUrl, {
method: 'POST',
body: formData
});
const result = await response.json();
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
if (!result.success) {
throw new Error(result.data || 'Request failed');
}
// Success - call callback with new item
if (this.currentCallback) {
this.currentCallback(result.data);
}
this.closeModal();
this.showSuccessMessage(`Successfully created ${result.data.title}`);
} catch (error) {
console.error('Form submission error:', error);
this.showErrorMessage(error.message || 'Failed to create item');
} finally {
// Re-enable submit button
$submitBtn.prop('disabled', false).text($submitBtn.text().replace('Creating...', 'Create'));
}
}
closeModal() {
$('#hvac-modal-container').fadeOut(300);
this.currentCallback = null;
}
showSuccessMessage(message) {
// Create temporary success notification
const $notification = $(`
<div class="hvac-notification hvac-success">
<span class="dashicons dashicons-yes-alt"></span>
${this.escapeHtml(message)}
</div>
`);
$('body').append($notification);
setTimeout(() => {
$notification.addClass('show');
}, 100);
setTimeout(() => {
$notification.removeClass('show');
setTimeout(() => $notification.remove(), 300);
}, 3000);
}
showErrorMessage(message) {
// Create temporary error notification
const $notification = $(`
<div class="hvac-notification hvac-error">
<span class="dashicons dashicons-warning"></span>
${this.escapeHtml(message)}
</div>
`);
$('body').append($notification);
setTimeout(() => {
$notification.addClass('show');
}, 100);
setTimeout(() => {
$notification.removeClass('show');
setTimeout(() => $notification.remove(), 300);
}, 5000);
}
openMediaUploader(button) {
const $button = $(button);
const $fieldContainer = $button.closest('.hvac-media-field');
const $imagePreview = $fieldContainer.find('.image-preview');
const $previewImg = $fieldContainer.find('.image-preview img');
const $imageIdInput = $fieldContainer.find('.image-id-input');
const $imageUrlInput = $fieldContainer.find('.image-url-input');
// Create WordPress media frame
const mediaUploader = wp.media({
title: 'Select Image',
button: {
text: 'Select Image'
},
multiple: false,
library: {
type: 'image'
}
});
// When an image is selected
mediaUploader.on('select', () => {
const attachment = mediaUploader.state().get('selection').first().toJSON();
// Update hidden inputs
$imageIdInput.val(attachment.id);
$imageUrlInput.val(attachment.url);
// Update preview
$previewImg.attr('src', attachment.url);
$previewImg.attr('alt', attachment.alt || attachment.title || 'Selected image');
$imagePreview.show();
// Update button text
$button.html('<span class="dashicons dashicons-format-image" style="vertical-align: middle; margin-right: 5px;"></span>Change Image');
});
// Open the media modal
mediaUploader.open();
}
removeImage(button) {
const $button = $(button);
const $fieldContainer = $button.closest('.hvac-media-field');
const $imagePreview = $fieldContainer.find('.image-preview');
const $previewImg = $fieldContainer.find('.image-preview img');
const $imageIdInput = $fieldContainer.find('.image-id-input');
const $imageUrlInput = $fieldContainer.find('.image-url-input');
const $selectBtn = $fieldContainer.find('.select-image-btn');
// Clear inputs
$imageIdInput.val('');
$imageUrlInput.val('');
// Hide preview
$imagePreview.hide();
$previewImg.attr('src', '');
// Reset button text
$selectBtn.html('<span class="dashicons dashicons-format-image" style="vertical-align: middle; margin-right: 5px;"></span>Select Image');
}
capitalizeFirst(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// Initialize modal forms when document is ready
$(document).ready(function() {
new HVACModalForms();
});
})(jQuery);