Backup pre-corrección namespaces: mejoras schemas y componentes

Cambios incluidos:
- Actualización de copy/textos en 7 schemas JSON
- Mejoras en AdminAjaxHandler con mapeos adicionales
- Refactorización de FormBuilders y Renderers
- Correcciones en dashboard admin JS
- Nuevo ContactFormRenderer funcional

NOTA: Este commit sirve como respaldo antes de corregir
inconsistencias de case en namespaces (API→Api, WordPress→Wordpress)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
FrankZamora
2025-11-26 17:59:01 -06:00
parent 0846a3bf03
commit 4c807e1cf2
26 changed files with 717 additions and 79 deletions

View File

@@ -50,6 +50,21 @@ final class ContactFormRenderer implements RendererInterface
return sprintf("<style>%s</style>\n%s\n<script>%s</script>", $css, $html, $js);
}
/**
* Renderiza el modal de contacto para el boton Let's Talk
* Usa la misma configuracion y webhook que el formulario de seccion
*/
public function renderModal(Component $component): string
{
$data = $component->getData();
$css = $this->generateModalCSS($data);
$html = $this->buildModalHTML($data);
$js = $this->buildModalJS($data);
return sprintf("<style>%s</style>\n%s\n<script>%s</script>", $css, $html, $js);
}
public function supports(string $componentType): bool
{
return $componentType === 'contact-form';
@@ -454,6 +469,307 @@ final class ContactFormRenderer implements RendererInterface
});
});
})();
JS;
return $js;
}
/**
* Generar CSS para el modal
*/
private function generateModalCSS(array $data): string
{
$colors = $data['colors'] ?? [];
$effects = $data['visual_effects'] ?? [];
$cssRules = [];
// Modal header con gradiente del tema
$cssRules[] = $this->cssGenerator->generate('#contactModal .modal-header', [
'background' => 'linear-gradient(135deg, #0E2337 0%, #1e3a5f 100%)',
'border-bottom' => 'none',
'padding' => '1.5rem',
]);
$cssRules[] = $this->cssGenerator->generate('#contactModal .modal-title', [
'color' => '#ffffff',
'font-weight' => '600',
]);
$cssRules[] = $this->cssGenerator->generate('#contactModal .btn-close', [
'filter' => 'brightness(0) invert(1)',
]);
// Modal body
$cssRules[] = $this->cssGenerator->generate('#contactModal .modal-body', [
'padding' => '2rem',
]);
// Form inputs
$inputBorderColor = $colors['input_border_color'] ?? '#dee2e6';
$inputFocusBorder = $colors['input_focus_border'] ?? '#FF8600';
$inputBorderRadius = $effects['input_border_radius'] ?? '6px';
$transitionDuration = $effects['transition_duration'] ?? '0.3s';
$cssRules[] = $this->cssGenerator->generate('#contactModal .form-control', [
'border-color' => $inputBorderColor,
'border-radius' => $inputBorderRadius,
'transition' => "all {$transitionDuration} ease",
]);
$cssRules[] = $this->cssGenerator->generate('#contactModal .form-control:focus', [
'border-color' => $inputFocusBorder,
'box-shadow' => '0 0 0 0.2rem rgba(255, 134, 0, 0.25)',
'outline' => 'none',
]);
// Submit button
$buttonBgColor = $colors['button_bg_color'] ?? '#FF8600';
$buttonTextColor = $colors['button_text_color'] ?? '#ffffff';
$buttonHoverBg = $colors['button_hover_bg'] ?? '#e67a00';
$buttonBorderRadius = $effects['button_border_radius'] ?? '6px';
$buttonPadding = $effects['button_padding'] ?? '0.75rem 2rem';
$cssRules[] = $this->cssGenerator->generate('#contactModal .btn-modal-submit', [
'background-color' => $buttonBgColor,
'color' => $buttonTextColor,
'font-weight' => '600',
'padding' => $buttonPadding,
'border' => 'none',
'border-radius' => $buttonBorderRadius,
'transition' => "all {$transitionDuration} ease",
]);
$cssRules[] = $this->cssGenerator->generate('#contactModal .btn-modal-submit:hover', [
'background-color' => $buttonHoverBg,
'color' => $buttonTextColor,
]);
$cssRules[] = $this->cssGenerator->generate('#contactModal .btn-modal-submit:disabled', [
'opacity' => '0.7',
'cursor' => 'not-allowed',
]);
// Success/Error messages
$successBgColor = $colors['success_bg_color'] ?? '#d1e7dd';
$successTextColor = $colors['success_text_color'] ?? '#0f5132';
$errorBgColor = $colors['error_bg_color'] ?? '#f8d7da';
$errorTextColor = $colors['error_text_color'] ?? '#842029';
$cssRules[] = $this->cssGenerator->generate('#contactModal .alert-success', [
'background-color' => $successBgColor,
'color' => $successTextColor,
'border-color' => $successBgColor,
]);
$cssRules[] = $this->cssGenerator->generate('#contactModal .alert-danger', [
'background-color' => $errorBgColor,
'color' => $errorTextColor,
'border-color' => $errorBgColor,
]);
return implode("\n", $cssRules);
}
/**
* Generar HTML del modal
*/
private function buildModalHTML(array $data): string
{
$content = $data['content'] ?? [];
$formLabels = $data['form_labels'] ?? [];
$effects = $data['visual_effects'] ?? [];
// Content
$sectionTitle = $content['section_title'] ?? '¿Tienes alguna pregunta?';
$submitText = $content['submit_button_text'] ?? 'Enviar Mensaje';
$submitIcon = $content['submit_button_icon'] ?? 'bi-send-fill';
// Form labels/placeholders
$fullnamePlaceholder = $formLabels['fullname_placeholder'] ?? 'Nombre completo *';
$companyPlaceholder = $formLabels['company_placeholder'] ?? 'Empresa';
$whatsappPlaceholder = $formLabels['whatsapp_placeholder'] ?? 'WhatsApp *';
$emailPlaceholder = $formLabels['email_placeholder'] ?? 'Correo electrónico *';
$messagePlaceholder = $formLabels['message_placeholder'] ?? '¿En qué podemos ayudarte?';
$textareaRows = $effects['textarea_rows'] ?? '4';
// Nonce for AJAX security
$nonce = wp_create_nonce('roi_contact_form_nonce');
$html = '<div class="modal fade" id="contactModal" tabindex="-1" aria-labelledby="contactModalLabel" aria-hidden="true">';
$html .= '<div class="modal-dialog modal-dialog-centered modal-lg">';
$html .= '<div class="modal-content">';
// Modal Header
$html .= '<div class="modal-header">';
$html .= '<h5 class="modal-title" id="contactModalLabel">';
$html .= '<i class="bi bi-chat-dots-fill me-2" style="color: #FF8600;"></i>';
$html .= esc_html($sectionTitle);
$html .= '</h5>';
$html .= '<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>';
$html .= '</div>';
// Modal Body
$html .= '<div class="modal-body">';
$html .= sprintf('<form id="roiContactModalForm" data-nonce="%s">', esc_attr($nonce));
$html .= '<div class="row g-3">';
// Full name field
$html .= '<div class="col-md-6">';
$html .= sprintf(
'<input type="text" class="form-control" id="roiModalFullName" name="fullName" placeholder="%s" required>',
esc_attr($fullnamePlaceholder)
);
$html .= '</div>';
// Company field
$html .= '<div class="col-md-6">';
$html .= sprintf(
'<input type="text" class="form-control" id="roiModalCompany" name="company" placeholder="%s">',
esc_attr($companyPlaceholder)
);
$html .= '</div>';
// WhatsApp field
$html .= '<div class="col-md-6">';
$html .= sprintf(
'<input type="tel" class="form-control" id="roiModalWhatsapp" name="whatsapp" placeholder="%s" required>',
esc_attr($whatsappPlaceholder)
);
$html .= '</div>';
// Email field
$html .= '<div class="col-md-6">';
$html .= sprintf(
'<input type="email" class="form-control" id="roiModalEmail" name="email" placeholder="%s" required>',
esc_attr($emailPlaceholder)
);
$html .= '</div>';
// Message field
$html .= '<div class="col-12">';
$html .= sprintf(
'<textarea class="form-control" id="roiModalMessage" name="message" rows="%s" placeholder="%s"></textarea>',
esc_attr($textareaRows),
esc_attr($messagePlaceholder)
);
$html .= '</div>';
// Submit button
$html .= '<div class="col-12">';
$html .= '<button type="submit" class="btn btn-modal-submit w-100">';
$html .= sprintf('<i class="%s me-2"></i>', esc_attr($submitIcon));
$html .= esc_html($submitText);
$html .= '</button>';
$html .= '</div>';
// Message container
$html .= '<div id="roiContactModalMessage" class="col-12 mt-2 alert" style="display: none;"></div>';
$html .= '</div>'; // .row g-3
$html .= '</form>';
$html .= '</div>'; // .modal-body
$html .= '</div>'; // .modal-content
$html .= '</div>'; // .modal-dialog
$html .= '</div>'; // .modal
return $html;
}
/**
* Generar JS para el modal
*/
private function buildModalJS(array $data): string
{
$messages = $data['messages'] ?? [];
$content = $data['content'] ?? [];
$successMessage = $messages['success_message'] ?? '¡Gracias por contactarnos! Te responderemos pronto.';
$errorMessage = $messages['error_message'] ?? 'Hubo un error al enviar el mensaje. Por favor intenta de nuevo.';
$sendingMessage = $messages['sending_message'] ?? 'Enviando...';
// AJAX URL for WordPress
$ajaxUrl = admin_url('admin-ajax.php');
$js = <<<JS
(function() {
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('roiContactModalForm');
if (!form) return;
form.addEventListener('submit', async function(e) {
e.preventDefault();
const submitBtn = form.querySelector('button[type="submit"]');
const messageDiv = document.getElementById('roiContactModalMessage');
const originalBtnHtml = submitBtn.innerHTML;
const nonce = form.dataset.nonce;
// Disable button and show sending state
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>' + '{$sendingMessage}';
messageDiv.style.display = 'none';
// Collect form data
const formData = new FormData(form);
formData.append('action', 'roi_contact_form_submit');
formData.append('nonce', nonce);
formData.append('pageUrl', window.location.href);
formData.append('pageTitle', document.title);
try {
const response = await fetch('{$ajaxUrl}', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
messageDiv.className = 'col-12 mt-2 alert alert-success';
messageDiv.textContent = '{$successMessage}';
messageDiv.style.display = 'block';
form.reset();
// Cerrar modal despues de 2 segundos en exito
setTimeout(function() {
const modal = bootstrap.Modal.getInstance(document.getElementById('contactModal'));
if (modal) {
modal.hide();
}
messageDiv.style.display = 'none';
}, 2000);
} else {
messageDiv.className = 'col-12 mt-2 alert alert-danger';
messageDiv.textContent = result.data?.message || '{$errorMessage}';
messageDiv.style.display = 'block';
}
} catch (error) {
console.error('Contact modal form error:', error);
messageDiv.className = 'col-12 mt-2 alert alert-danger';
messageDiv.textContent = '{$errorMessage}';
messageDiv.style.display = 'block';
} finally {
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnHtml;
}
});
// Limpiar formulario cuando se cierra el modal
const contactModal = document.getElementById('contactModal');
if (contactModal) {
contactModal.addEventListener('hidden.bs.modal', function() {
form.reset();
const messageDiv = document.getElementById('roiContactModalMessage');
if (messageDiv) {
messageDiv.style.display = 'none';
}
});
}
});
})();
JS;
return $js;

View File

@@ -194,17 +194,27 @@ final class CtaBoxSidebarRenderer implements RendererInterface
esc_html($description)
);
// Button
// Button/Link
$iconHtml = !empty($buttonIcon)
? sprintf('<i class="%s"></i>', esc_attr($buttonIcon))
: '';
$html .= sprintf(
'<button class="btn btn-cta-box" %s>%s%s</button>',
$buttonAttributes,
$iconHtml,
esc_html($buttonText)
);
// Use <a> for link action, <button> for modal/scroll
if ($buttonAction === 'link') {
$html .= sprintf(
'<a href="%s" class="btn btn-cta-box">%s%s</a>',
esc_url($buttonLink),
$iconHtml,
esc_html($buttonText)
);
} else {
$html .= sprintf(
'<button class="btn btn-cta-box" %s>%s%s</button>',
$buttonAttributes,
$iconHtml,
esc_html($buttonText)
);
}
$html .= '</div>';

View File

@@ -75,20 +75,31 @@ final class CtaPostRenderer implements RendererInterface
{
$colors = $data['colors'] ?? [];
$effects = $data['visual_effects'] ?? [];
$spacing = $data['spacing'] ?? [];
$visibility = $data['visibility'] ?? [];
$cssRules = [];
// Container values
$gradientStart = $colors['gradient_start'] ?? '#FF8600';
$gradientEnd = $colors['gradient_end'] ?? '#FFB800';
$gradientAngle = $effects['gradient_angle'] ?? '135deg';
$borderRadius = $effects['border_radius'] ?? '12px';
$boxShadow = $effects['box_shadow'] ?? '0 8px 24px rgba(255, 133, 0, 0.3)';
$containerPadding = $spacing['container_padding'] ?? '2rem';
// Button values
$buttonBgColor = $colors['button_bg_color'] ?? '#FF8600';
$buttonTextColor = $colors['button_text_color'] ?? '#ffffff';
$buttonHoverBgColor = $colors['button_hover_bg_color'] ?? '#e67a00';
$buttonHoverBgColor = $colors['button_hover_bg'] ?? '#e67a00';
$buttonBorderRadius = $effects['button_border_radius'] ?? '8px';
// Container - gradient background
// Container - gradient background with box-shadow and border-radius
$cssRules[] = $this->cssGenerator->generate('.cta-post-container', [
'background' => "linear-gradient({$gradientAngle}, {$gradientStart} 0%, {$gradientEnd} 100%)",
'box-shadow' => $boxShadow,
'border-radius' => $borderRadius,
'padding' => $containerPadding,
]);
// Button styles (matching template .cta-button) - Using !important to override Bootstrap btn-light
@@ -98,8 +109,8 @@ final class CtaPostRenderer implements RendererInterface
font-weight: 600;
padding: 0.75rem 2rem;
border: none !important;
border-radius: 8px;
transition: 0.3s;
border-radius: {$buttonBorderRadius};
transition: all 0.3s ease;
text-decoration: none;
display: inline-block;
}";
@@ -149,7 +160,7 @@ final class CtaPostRenderer implements RendererInterface
$buttonUrl = $content['button_url'] ?? '#';
$buttonIcon = $content['button_icon'] ?? 'bi-arrow-right';
$html = '<div class="my-5 p-4 rounded cta-post-container">';
$html = '<div class="my-5 cta-post-container">';
$html .= ' <div class="row align-items-center">';
// Left column - Content
@@ -169,7 +180,7 @@ final class CtaPostRenderer implements RendererInterface
// Right column - Button
$html .= ' <div class="col-md-4 text-md-end mt-3 mt-md-0">';
$html .= sprintf(
' <a href="%s" class="btn btn-light btn-lg cta-button">%s',
' <a href="%s" class="cta-button">%s',
esc_url($buttonUrl),
esc_html($buttonText)
);

View File

@@ -58,8 +58,11 @@ final class NewsletterAjaxHandler
return;
}
// 3. Validar email
// 3. Validar y sanitizar campos
$email = sanitize_email($_POST['email'] ?? '');
$name = sanitize_text_field($_POST['name'] ?? '');
$whatsapp = sanitize_text_field($_POST['whatsapp'] ?? '');
if (empty($email) || !is_email($email)) {
wp_send_json_error([
'message' => __('Por favor ingresa un email valido.', 'roi-theme')
@@ -94,12 +97,20 @@ final class NewsletterAjaxHandler
// 5. Preparar payload
$payload = [
'email' => $email,
'name' => $name,
'whatsapp' => $whatsapp,
'source' => 'newsletter-footer',
'pageUrl' => sanitize_url($_POST['pageUrl'] ?? ''),
'pageTitle' => sanitize_text_field($_POST['pageTitle'] ?? ''),
'timestamp' => current_time('c'),
'timezone' => wp_timezone_string(),
'siteName' => get_bloginfo('name'),
'siteUrl' => home_url(),
];
// Debug: Log payload enviado
error_log('ROI Theme Newsletter: Enviando a webhook - ' . wp_json_encode($payload));
// 6. Enviar a webhook
$result = $this->sendToWebhook($webhookUrl, $payload);
@@ -120,6 +131,8 @@ final class NewsletterAjaxHandler
*/
private function sendToWebhook(string $url, array $payload): array
{
error_log('ROI Theme Newsletter: Webhook URL - ' . $url);
$response = wp_remote_post($url, [
'timeout' => 30,
'headers' => [
@@ -130,6 +143,7 @@ final class NewsletterAjaxHandler
]);
if (is_wp_error($response)) {
error_log('ROI Theme Newsletter: WP Error - ' . $response->get_error_message());
return [
'success' => false,
'error' => $response->get_error_message()
@@ -137,6 +151,10 @@ final class NewsletterAjaxHandler
}
$statusCode = wp_remote_retrieve_response_code($response);
$responseBody = wp_remote_retrieve_body($response);
error_log('ROI Theme Newsletter: Response Code - ' . $statusCode);
error_log('ROI Theme Newsletter: Response Body - ' . $responseBody);
if ($statusCode >= 200 && $statusCode < 300) {
return ['success' => true, 'error' => ''];

View File

@@ -156,6 +156,11 @@ final class FooterRenderer implements RendererInterface
'color' => $linkHoverColor,
]);
// Widget 1B spacing
$cssRules[] = $this->cssGenerator->generate('.roi-footer .footer-widget-1b', [
'margin-top' => '1.5rem',
]);
// Newsletter description
$cssRules[] = $this->cssGenerator->generate('.roi-footer .newsletter-description', [
'color' => $textColor,
@@ -248,6 +253,7 @@ final class FooterRenderer implements RendererInterface
private function generateHTML(array $data): string
{
$widget1 = $data['widget_1'] ?? [];
$widget1b = $data['widget_1b'] ?? [];
$widget2 = $data['widget_2'] ?? [];
$widget3 = $data['widget_3'] ?? [];
$newsletter = $data['newsletter'] ?? [];
@@ -259,12 +265,15 @@ final class FooterRenderer implements RendererInterface
$newsletterVisible = $this->toBool($newsletter['newsletter_visible'] ?? true);
$widget1Title = esc_html($widget1['widget_1_title'] ?? 'Recursos');
$widget1bTitle = esc_html($widget1b['widget_1b_title'] ?? 'Bases de datos');
$widget2Title = esc_html($widget2['widget_2_title'] ?? 'Soporte');
$widget3Title = esc_html($widget3['widget_3_title'] ?? 'Empresa');
$newsletterTitle = esc_html($newsletter['newsletter_title'] ?? 'Suscribete al Newsletter');
$newsletterDesc = esc_html($newsletter['newsletter_description'] ?? 'Recibe las ultimas actualizaciones.');
$newsletterPlaceholder = esc_attr($newsletter['newsletter_placeholder'] ?? 'Email');
$newsletterNamePlaceholder = esc_attr($newsletter['newsletter_name_placeholder'] ?? 'Nombre');
$newsletterEmailPlaceholder = esc_attr($newsletter['newsletter_email_placeholder'] ?? 'Email');
$newsletterWhatsappPlaceholder = esc_attr($newsletter['newsletter_whatsapp_placeholder'] ?? 'WhatsApp');
$newsletterBtnText = esc_html($newsletter['newsletter_button_text'] ?? 'Suscribirse');
$copyrightText = esc_html($footerBottom['copyright_text'] ?? date('Y') . ' Todos los derechos reservados.');
@@ -276,12 +285,25 @@ final class FooterRenderer implements RendererInterface
$html .= '<div class="container">';
$html .= '<div class="footer-grid">';
// Widget 1
// Columna 1: Widget 1 + Widget 1B
if ($widget1Visible) {
$html .= '<div class="footer-column footer-column-1">';
// Widget 1
$html .= '<div class="footer-widget footer-widget-menu">';
$html .= '<h5 class="widget-title">' . $widget1Title . '</h5>';
$html .= $this->renderMenu('footer_menu_1');
$html .= '</div>';
// Widget 1B - Solo si tiene menu asignado
if (has_nav_menu('footer_menu_4')) {
$html .= '<div class="footer-widget footer-widget-menu footer-widget-1b">';
$html .= '<h5 class="widget-title">' . $widget1bTitle . '</h5>';
$html .= $this->renderMenu('footer_menu_4');
$html .= '</div>';
}
$html .= '</div>';
}
// Widget 2
@@ -308,7 +330,9 @@ final class FooterRenderer implements RendererInterface
$html .= '<form id="roi-newsletter-form" class="newsletter-form">';
$html .= '<input type="hidden" name="action" value="roi_newsletter_subscribe">';
$html .= '<input type="hidden" name="nonce" value="' . esc_attr($nonce) . '">';
$html .= '<input type="email" name="email" class="newsletter-input" placeholder="' . $newsletterPlaceholder . '" required>';
$html .= '<input type="text" name="name" class="newsletter-input" placeholder="' . $newsletterNamePlaceholder . '">';
$html .= '<input type="email" name="email" class="newsletter-input" placeholder="' . $newsletterEmailPlaceholder . '" required>';
$html .= '<input type="tel" name="whatsapp" class="newsletter-input" placeholder="' . $newsletterWhatsappPlaceholder . '">';
$html .= '<button type="submit" class="newsletter-btn">' . $newsletterBtnText . '</button>';
$html .= '<div class="newsletter-message"></div>';
$html .= '</form>';
@@ -384,6 +408,8 @@ final class FooterRenderer implements RendererInterface
try {
const formData = new FormData(form);
formData.append('pageUrl', window.location.href);
formData.append('pageTitle', document.title);
const response = await fetch('{$ajaxUrl}', {
method: 'POST',