| @@ -1,5 +1,9 @@ | ||
| 1 | -<div class="bg-white shadow rounded-lg" style="padding: 10px;"> | |
| 1 | +<?php | |
| 2 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 3 | + exit; | |
| 4 | +} | |
| 5 | +?><div class="bg-white shadow rounded-lg" style="padding: 10px;"> | |
| 2 | 6 | |
| 3 | 7 | <div class="px-6 py-5 border-b border-gray-200" style="margin-bottom: 10px;"> |
| 4 | 8 | <div class="flex justify-between items-center"> |
| 5 | 9 | <div class="flex items-center"> |
| @@ -6,14 +10,14 @@ | ||
| 6 | 10 | <div class="flex-shrink-0 bg-indigo-100 rounded-lg p-3"> |
| 7 | 11 | <i class="fas fa-eye text-indigo-600 text-xl"></i> |
| 8 | 12 | </div> |
| 9 | 13 | <div class="ml-4"> |
| 10 | - <h2 class="text-lg font-medium text-gray-900">Quote Preview</h2> | |
| 11 | - <p class="mt-1 text-sm text-gray-500">Live preview of your quote</p> | |
| 14 | + <h2 class="text-lg font-medium text-gray-900"><?php esc_html_e('Quote Preview', 'easy-invoice'); ?></h2> | |
| 15 | + <p class="mt-1 text-sm text-gray-500"><?php esc_html_e('Live preview of your quote', 'easy-invoice'); ?></p> | |
| 12 | 16 | </div> |
| 13 | 17 | </div> |
| 14 | 18 | <?php if($quote_id > 0 ){ ?> |
| 15 | - <a href="<?php echo get_permalink($quote_id); ?>" target="_blank" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> | |
| 19 | + <a href="<?php echo esc_url(get_permalink($quote_id)); ?>" target="_blank" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> | |
| 16 | 20 | <i class="fas fa-external-link-alt mr-2"></i> |
| 17 | 21 | Full Preview |
| 18 | 22 | </a> |
| 19 | 23 | <?php } ?> |
| @@ -31,15 +35,13 @@ | ||
| 31 | 35 | // Initialize formatter for the quote templates |
| 32 | 36 | $formatter = new \EasyInvoice\Helpers\QuoteFormatter($quote); |
| 33 | 37 | ?> |
| 34 | 38 | |
| 35 | - <div id="quote-container" class="quote-container quote-preview <?php echo $template_class; ?>" style="position: relative;"> | |
| 39 | + <div id="quote-container" class="quote-container quote-preview <?php echo esc_attr($template_class); ?>" style="position: relative;"> | |
| 36 | 40 | <?php |
| 37 | 41 | // Dynamically load the selected template from quote templates directory |
| 38 | 42 | // Dynamically load the selected template |
| 39 | - $template_file = EASY_INVOICE_PLUGIN_DIR . 'templates/quote-templates/' . $current_template . '.php'; | |
| 40 | - | |
| 41 | - $template_file = file_exists($template_file) ? $template_file : EASY_INVOICE_PLUGIN_DIR . 'templates/quote-templates/default.php'; | |
| 43 | + $template_file = easy_invoice_design_template( 'quote', (string) $current_template ); | |
| 42 | 44 | if (file_exists($template_file)) { |
| 43 | 45 | include_once $template_file; |
| 44 | 46 | } else { |
| 45 | 47 | // Show template not found message |
| @@ -47,11 +49,11 @@ | ||
| 47 | 49 | <div class="template-not-found"> |
| 48 | 50 | <div class="template-not-found-icon"> |
| 49 | 51 | <i class="fas fa-exclamation-circle"></i> |
| 50 | 52 | </div> |
| 51 | - <h3>Template Not Found</h3> | |
| 53 | + <h3><?php esc_html_e('Template Not Found', 'easy-invoice'); ?></h3> | |
| 52 | 54 | <p>The selected template "<?php echo esc_html($current_template); ?>" does not exist.</p> |
| 53 | - <p>Please select a different template or contact support if you believe this is an error.</p> | |
| 55 | + <p><?php esc_html_e('Please select a different template or contact support if you believe this is an error.', 'easy-invoice'); ?></p> | |
| 54 | 56 | </div> |
| 55 | 57 | <style> |
| 56 | 58 | .template-not-found { |
| 57 | 59 | text-align: center; |
| @@ -112,10 +114,10 @@ | ||
| 112 | 114 | // Localize easyInvoice object for this page |
| 113 | 115 | // Localize easyInvoice object for this page |
| 114 | 116 | window.easyInvoiceQ = window.easyInvoice || {}; |
| 115 | 117 | window.easyInvoice.isPro = <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>; |
| 116 | -window.easyInvoice.ajaxUrl = '<?php echo admin_url('admin-ajax.php'); ?>'; | |
| 117 | -window.easyInvoice.nonce = '<?php echo wp_create_nonce('easy_invoice_nonce'); ?>'; | |
| 118 | +window.easyInvoice.ajaxUrl = '<?php echo esc_url(admin_url('admin-ajax.php')); ?>'; | |
| 119 | +window.easyInvoice.nonce = '<?php echo esc_attr(wp_create_nonce('easy_invoice_nonce')); ?>'; | |
| 118 | 120 | |
| 119 | 121 | // Function to update the live preview using AJAX |
| 120 | 122 | function updateLivePreview(templateId) { |
| 121 | 123 | $('#quote-container').addClass('loading'); |
| @@ -124,9 +126,10 @@ | ||
| 124 | 126 | type: 'POST', |
| 125 | 127 | data: { |
| 126 | 128 | action: 'easy_invoice_load_quote_template', |
| 127 | 129 | template: templateId, |
| 128 | - quote_id: <?php echo $quote ? $quote->getId() : 0; ?>, | |
| 130 | + form_data: jQuery('#quote-form').length ? jQuery('#quote-form').serialize() : '', | |
| 131 | + quote_id: <?php echo (int) ($quote ? $quote->getId() : 0); ?>, | |
| 129 | 132 | nonce: window.easyInvoice.nonce |
| 130 | 133 | }, |
| 131 | 134 | success: function(response) { |
| 132 | 135 | if (response.success) { |
| @@ -136,9 +139,9 @@ | ||
| 136 | 139 | } |
| 137 | 140 | $('#quote-container').removeClass('loading'); |
| 138 | 141 | }, |
| 139 | 142 | error: function(xhr, status, error) { |
| 140 | - $('#quote-container').html('<div class="error">Failed to load template. Please try again.</div>'); | |
| 143 | + $('#quote-container').html('<div class="error"><?php esc_html_e('Failed to load template. Please try again.', 'easy-invoice'); ?></div>'); | |
| 141 | 144 | $('#quote-container').removeClass('loading'); |
| 142 | 145 | } |
| 143 | 146 | }); |
| 144 | 147 | } |
| @@ -153,11 +156,11 @@ | ||
| 153 | 156 | |
| 154 | 157 | if (isPremium && isFreeVersion) { |
| 155 | 158 | // Show premium upgrade modal instead of selecting the template |
| 156 | 159 | if (typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) { |
| 157 | - EasyInvoiceConfirmation.showFeatureUpgrade('<?php _e('Premium Templates', 'easy-invoice'); ?>', '<?php _e('Access all premium templates including Modern, Professional, Classic, and more to give your quotes a unique and professional look.', 'easy-invoice'); ?>'); | |
| 160 | + EasyInvoiceConfirmation.showFeatureUpgrade('<?php echo esc_js(__('Premium Templates', 'easy-invoice')); ?>', '<?php echo esc_js(__('Access all premium templates including Modern, Professional, Classic, and more to give your quotes a unique and professional look.', 'easy-invoice')); ?>'); | |
| 158 | 161 | } else { |
| 159 | - alert('<?php _e('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice'); ?>'); | |
| 162 | + alert('<?php echo esc_js(__('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice')); ?>'); | |
| 160 | 163 | } |
| 161 | 164 | return; |
| 162 | 165 | } |
| 163 | 166 | |
| @@ -167,6 +170,45 @@ | ||
| 167 | 170 | |
| 168 | 171 | |
| 169 | 172 | |
| 170 | 173 | |
| 174 | + // Live means live: re-render as the builder changes, not only when the | |
| 175 | + // template changes. Debounced so typing a description does not fire a | |
| 176 | + // request per keystroke. | |
| 177 | + (function () { | |
| 178 | + var $form = $('#quote-form'); if (!$form.length) { return; } | |
| 179 | + var timer = null; | |
| 180 | + var current = function () { return $form.find('input[name="quote_template"]').val() || $('.template-card.selected, .template-card.active').first().data('template-id') || 'standard'; }; | |
| 181 | + var schedule = function () { clearTimeout(timer); timer = setTimeout(function () { updateLivePreview(current()); }, 700); }; | |
| 182 | + $form.on('input change', 'input, textarea, select', schedule); | |
| 183 | + $(document).on('click', '.add-item-button, .remove-item, .client-option, #remove-selected-client, .template-card', schedule); | |
| 184 | + $(document).on('easy_invoice_items_changed easy_invoice_client_changed', schedule); | |
| 185 | + })(); | |
| 186 | + | |
| 171 | 187 | }); |
| 188 | +</script> | |
| 189 | + | |
| 190 | +<script> | |
| 191 | +// Scale the A4 design to the preview pane instead of letting it reflow: the | |
| 192 | +// preview then shows the sheet the client will see, only smaller. | |
| 193 | +(function () { | |
| 194 | + var box = document.getElementById('quote-container'); | |
| 195 | + if (!box || !window.ResizeObserver) { return; } | |
| 196 | + var SHEET = 794; | |
| 197 | + function fit() { | |
| 198 | + var sheet = box.querySelector('.template, [class*="template-"]'); | |
| 199 | + if (!sheet) { box.style.height = ''; return; } | |
| 200 | + var avail = box.clientWidth; | |
| 201 | + if (avail <= 0) { return; } | |
| 202 | + var k = Math.min(1, avail / SHEET); | |
| 203 | + sheet.style.width = SHEET + 'px'; | |
| 204 | + sheet.style.maxWidth = 'none'; | |
| 205 | + sheet.style.transformOrigin = 'top left'; | |
| 206 | + sheet.style.transform = k < 1 ? 'scale(' + k + ')' : ''; | |
| 207 | + box.style.overflow = 'hidden'; | |
| 208 | + box.style.height = k < 1 ? Math.ceil(sheet.offsetHeight * k) + 'px' : ''; | |
| 209 | + } | |
| 210 | + new ResizeObserver(fit).observe(box); | |
| 211 | + new MutationObserver(fit).observe(box, { childList: true, subtree: true }); | |
| 212 | + fit(); | |
| 213 | +})(); | |
| 172 | 214 | </script> |