PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
← All changes | templates/quotes/builder.php +146 -173 2.1.102.4.0 View file →
@@ -8,20 +8,13 @@
8 8
9 9 // Enqueue CSS and JS files for the builder page
10 10 wp_enqueue_style('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/css/invoice-form.css', array(), EASY_INVOICE_VERSION);
11 11 wp_enqueue_script('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/js/invoice-form.js', array('jquery'), EASY_INVOICE_VERSION, true);
12 -wp_enqueue_script('easy-quote-save', plugin_dir_url(__FILE__) . '../../assets/js/quote-save.js', array('jquery'), EASY_INVOICE_VERSION, true);
13 12 wp_enqueue_script('easy-client-manager', plugin_dir_url(__FILE__) . '../../assets/js/client-manager.js', array('jquery'), EASY_INVOICE_VERSION, true);
14 13
15 14 // Create nonce for AJAX calls
16 15 $ajax_nonce = wp_create_nonce('easy_invoice_nonce');
17 16
18 -// Localize script for AJAX URL and nonce
19 -wp_localize_script('easy-quote-save', 'easyInvoice', array(
20 - 'ajaxUrl' => admin_url('admin-ajax.php'),
21 - 'nonce' => $ajax_nonce
22 -));
23 -
24 17 // Localize client manager script
25 18 wp_localize_script('easy-client-manager', 'easyInvoice', array(
26 19 'ajaxUrl' => admin_url('admin-ajax.php'),
27 20 'nonce' => $ajax_nonce
@@ -31,9 +24,9 @@
31 24 $quote_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
32 25 if ($quote_id) {
33 26 $post = get_post($quote_id);
34 27 if (!$post || $post->post_type !== \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE) {
35 - wp_die(__('Invalid post type. This builder can only be used for quotes.', 'easy-invoice'), __('Invalid Post Type', 'easy-invoice'), array('back_link' => true));
28 + wp_die(esc_html__('That quote does not exist or has been deleted.', 'easy-invoice'), esc_html__('Quote not found', 'easy-invoice'), array('back_link' => true, 'response' => 404));
36 29 }
37 30 }
38 31 $quote = null;
39 32
@@ -55,10 +48,10 @@
55 48 $quote_declined_message = get_option('easy_invoice_quote_declined_message', __('Thank you for your consideration.', 'easy-invoice'));
56 49
57 50 $quote_data = array(
58 51 'number' => $quote_number_service ? $quote_number_service->getNextNumber() : 'QT-1',
59 - 'date' => date('Y-m-d'),
60 - 'expiry_date' => date('Y-m-d', strtotime('+30 days')),
52 + 'date' => current_time('Y-m-d'),
53 + 'expiry_date' => wp_date('Y-m-d', strtotime('+30 days')),
61 54 'client_id' => 0,
62 55 'client_name' => '',
63 56 'client_email' => '',
64 57 'client_phone' => '',
@@ -86,9 +79,11 @@
86 79 );
87 80
88 81 // Get clients from repository
89 82 $client_repository = ClientServiceProvider::getClientRepository();
90 -$clients = $client_repository->all();
83 +// The hidden #select-client mirror only needs the document's own client; the picker
84 +// searches over AJAX. Loading every client here built a model per user on each open.
85 +$clients = [];
91 86
92 87 // Load client data directly in PHP if quote has a client
93 88 $client_data = null;
94 89 if ($quote && $quote->getClientId()) {
@@ -210,212 +205,190 @@
210 205 // $template_htmls[$template_id] = '<div class="template-not-found"><h3>Template Not Found</h3><p>The template "' . esc_html($template_id) . '" does not exist.</p></div>';
211 206 // }
212 207 // }
213 208
209 +if ( $quote_id && $quote ) {
210 + $ei_header_display_title = $quote->title ?: $quote->number ?: __( 'Untitled quote', 'easy-invoice' );
211 +} else {
212 + $ei_header_display_title = __( 'Create new quote', 'easy-invoice' );
213 +}
214 +
214 215 ?>
215 216
216 217 <script>
217 -// Flag to indicate this is a quote form
218 218 window.isQuoteForm = true;
219 +</script>
219 220
220 -// Handle Send Quote button
221 -jQuery(document).ready(function($) {
222 - $('#send-quote-btn').on('click', function(e) {
223 - e.preventDefault();
221 +<script>
222 +jQuery(function ($) {
223 + window.easyInvoice = $.extend(true, {}, typeof window.easyInvoice === 'object' ? window.easyInvoice : {}, {
224 + fieldConfig: <?php echo wp_json_encode( $quote_field_config ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
225 + clientData: <?php echo wp_json_encode( $client_data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>,
226 + ajaxUrl: '<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>',
227 + nonce: '<?php echo esc_js( wp_create_nonce( 'easy_invoice_nonce' ) ); ?>',
228 + isPro: <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>
229 + });
224 230
225 - // First save the quote if it's not saved yet
226 - if ($('#quote-id').val() == '0') {
227 - // Quote not saved yet, save it first
228 - $('#save-quote-btn').click();
231 + function t(key, fb) {
232 + return (window.easyInvoice && easyInvoice.i18n && easyInvoice.i18n[key]) ? easyInvoice.i18n[key] : fb;
233 + }
234 + var spin = '<svg class="ei-btn-spinner h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
235 + var pendingSend = false;
229 236
230 - // Wait for save to complete, then send
231 - setTimeout(function() {
232 - if ($('#quote-id').val() != '0') {
233 - sendQuoteEmail();
234 - } else {
235 - if (typeof EasyInvoiceToast !== 'undefined') {
236 - EasyInvoiceToast.warning('Please save the quote first before sending.');
237 - } else {
238 - console.log('Please save the quote first before sending.');
239 - }
240 - }
241 - }, 2000);
242 - } else {
243 - // Quote already saved, send directly
237 + $(document).on('easy-quote-saved', function (e, response) {
238 + if (!pendingSend || !response || !response.success) {
239 + return;
240 + }
241 + pendingSend = false;
242 + if ($('#quote-id').val() !== '0') {
244 243 sendQuoteEmail();
245 244 }
246 245 });
246 + $(document).on('easy-quote-save-failed', function () {
247 + pendingSend = false;
248 + });
247 249
250 + $('#send-quote-btn').on('click', function (e) {
251 + e.preventDefault();
252 + if ($('#quote-id').val() === '0') {
253 + pendingSend = true;
254 + $('#save-quote-btn').trigger('click');
255 + return;
256 + }
257 + sendQuoteEmail();
258 + });
259 +
248 260 function sendQuoteEmail() {
249 - const quoteId = $('#quote-id').val();
250 -
251 - if (quoteId == '0') {
261 + var quoteId = $('#quote-id').val();
262 + if (quoteId === '0') {
252 263 if (typeof EasyInvoiceToast !== 'undefined') {
253 - EasyInvoiceToast.warning('Please save the quote first before sending.');
254 - } else {
255 - console.log('Please save the quote first before sending.');
264 + EasyInvoiceToast.warning(t('save_first_quote', 'Please save the quote before sending.'));
256 265 }
257 266 return;
258 267 }
268 + var nonce = $('#send-quote-btn').data('send-nonce') || '<?php echo esc_js( wp_create_nonce( 'easy_invoice_send_quote_email' ) ); ?>';
259 269
260 - // Use the confirmation system instead of alert
261 - if (typeof EasyInvoiceConfirmation !== 'undefined') {
262 - EasyInvoiceConfirmation.confirmAction('send email', 'this quote', function() {
263 - // Show loading state
264 - const $btn = $('#send-quote-btn');
265 - const originalText = $btn.html();
266 - $btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin mr-2"></i>Sending...');
267 -
268 - // Send AJAX request
269 - $.ajax({
270 - url: window.easyInvoice.ajaxUrl,
271 - type: 'POST',
272 - data: {
273 - action: 'easy_invoice_send_quote_email',
274 - quote_id: quoteId,
275 - nonce: '<?php echo wp_create_nonce('easy_invoice_send_quote_email'); ?>'
276 - },
277 - success: function(response) {
278 - if (response.success) {
279 - if (typeof EasyInvoiceToast !== 'undefined') {
280 - EasyInvoiceToast.success('Quote email sent successfully!');
281 - } else {
282 - console.log('Quote email sent successfully!');
283 - }
284 - } else {
285 - if (typeof EasyInvoiceToast !== 'undefined') {
286 - // Handle both response.data.message and response.data (direct string)
287 - const errorMessage = (response.data && response.data.message) ? response.data.message : (response.data || 'Error sending email');
288 - EasyInvoiceToast.error(errorMessage);
289 - } else {
290 - const errorMessage = (response.data && response.data.message) ? response.data.message : (response.data || 'Unknown error');
291 - console.log('Error sending email: ' + errorMessage);
292 - }
270 + var runAjax = function () {
271 + var $btn = $('#send-quote-btn');
272 + var original = $btn.html();
273 + $btn.prop('disabled', true).attr('aria-busy', 'true').html('<span class="inline-flex items-center gap-2">' + spin + '<span>' + t('sending', 'Sending…') + '</span></span>');
274 + $.ajax({
275 + url: window.easyInvoice.ajaxUrl,
276 + type: 'POST',
277 + data: {
278 + action: 'easy_invoice_send_quote_email',
279 + quote_id: quoteId,
280 + nonce: nonce
281 + },
282 + success: function (response) {
283 + if (response.success) {
284 + if (typeof EasyInvoiceToast !== 'undefined') {
285 + EasyInvoiceToast.success(t('email_sent_quote', 'Quote email sent successfully.'));
293 286 }
294 - },
295 - error: function() {
287 + } else {
288 + var msg = (response.data && response.data.message) ? response.data.message : (response.data || t('email_error', 'Error sending email.'));
296 289 if (typeof EasyInvoiceToast !== 'undefined') {
297 - EasyInvoiceToast.error('Error connecting to server');
298 - } else {
299 - console.log('Error connecting to server');
290 + EasyInvoiceToast.error(msg);
300 291 }
301 - },
302 - complete: function() {
303 - // Reset button
304 - $btn.prop('disabled', false).html(originalText);
305 292 }
306 - });
293 + },
294 + error: function () {
295 + if (typeof EasyInvoiceToast !== 'undefined') {
296 + EasyInvoiceToast.error(t('network_error', 'Error connecting to server.'));
297 + }
298 + },
299 + complete: function () {
300 + $btn.prop('disabled', false).removeAttr('aria-busy').html(original);
301 + }
307 302 });
308 - } else {
309 - // Fallback to browser confirm if confirmation system not available
310 - if (confirm('Send this quote via email?')) {
311 - // Show loading state
312 - const $btn = $('#send-quote-btn');
313 - const originalText = $btn.html();
314 - $btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin mr-2"></i>Sending...');
303 + };
315 304
316 - // Send AJAX request
317 - $.ajax({
318 - url: window.easyInvoice.ajaxUrl,
319 - type: 'POST',
320 - data: {
321 - action: 'easy_invoice_send_quote_email',
322 - quote_id: quoteId,
323 - nonce: '<?php echo wp_create_nonce('easy_invoice_send_quote_email'); ?>'
324 - },
325 - success: function(response) {
326 - if (response.success) {
327 - alert('Quote email sent successfully!');
328 - } else {
329 - const errorMessage = (response.data && response.data.message) ? response.data.message : (response.data || 'Error sending email');
330 - alert(errorMessage);
331 - }
332 - },
333 - error: function() {
334 - alert('Error connecting to server');
335 - },
336 - complete: function() {
337 - // Reset button
338 - $btn.prop('disabled', false).html(originalText);
339 - }
340 - });
341 - }
305 + if (typeof EasyInvoiceConfirmation !== 'undefined') {
306 + EasyInvoiceConfirmation.confirmSendDocument('quote', runAjax);
307 + } else if (window.confirm(t('send_quote_confirm_browser', 'Send this quote by email?'))) {
308 + runAjax();
342 309 }
343 310 }
344 311 });
345 312 </script>
346 313
347 -<script>
348 -// Quote field configuration and pre-loaded data
349 -window.easyInvoice = window.easyInvoice || {};
350 -window.easyInvoice.fieldConfig = <?php echo json_encode($quote_field_config); ?>;
351 -window.easyInvoice.clientData = <?php echo json_encode($client_data); ?>;
352 -window.easyInvoice.ajaxUrl = '<?php echo admin_url('admin-ajax.php'); ?>';
353 -window.easyInvoice.nonce = '<?php echo wp_create_nonce('easy_invoice_nonce'); ?>';
354 -window.easyInvoice.isPro = <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>;
355 -</script>
356 -
357 314 <form id="quote-form" method="post">
358 - <input type="hidden" id="quote-id" name="quote_id" value="<?php echo $quote_id; ?>">
359 - <input type="hidden" id="quote_nonce" name="quote_nonce" value="<?php echo wp_create_nonce('easy_invoice_nonce'); ?>">
315 + <input type="hidden" id="quote-id" name="quote_id" value="<?php echo (int) $quote_id; ?>">
316 + <input type="hidden" id="quote_nonce" name="quote_nonce" value="<?php echo esc_attr(wp_create_nonce('easy_invoice_nonce')); ?>">
360 317
361 - <div id="easy-invoice-content" class="h-screen flex flex-col">
362 - <!-- Header -->
363 - <div class="bg-white shadow-sm w-full z-10">
364 - <div class="max-w-full mx-auto px-5">
365 - <div class="py-3 flex items-center justify-between">
366 - <div class="flex items-center">
367 - <a href="<?php echo admin_url('admin.php?page=easy-quote-all'); ?>"
368 - class="inline-flex items-center text-gray-600 hover:text-gray-900">
369 - <i class="fas fa-arrow-left mr-2"></i>
370 - <span>Back to Quotes</span>
318 + <div id="easy-invoice-content" class="h-screen flex flex-col bg-gray-50">
319 + <header class="ei-quote-builder-header ei-app-header-sync bg-white border-b border-gray-200 shadow-sm w-full z-10 box-border">
320 + <div class="max-w-full mx-auto h-full px-4 sm:px-5">
321 + <div class="ei-app-header-inner flex flex-col gap-3 py-3 sm:gap-4 lg:flex-row lg:items-center lg:justify-between lg:gap-6 lg:py-0 lg:h-full">
322 + <div class="ei-app-header-leading flex min-w-0 flex-1 flex-col gap-3 sm:flex-row sm:items-center sm:gap-4">
323 + <a href="<?php echo esc_url( admin_url( 'admin.php?page=easy-quote-all' ) ); ?>"
324 + class="inline-flex shrink-0 items-center gap-2 self-start rounded-md text-sm font-medium text-gray-600 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
325 + <svg class="h-4 w-4 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
326 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
327 + </svg>
328 + <span><?php esc_html_e( 'Back to quotes', 'easy-invoice' ); ?></span>
371 329 </a>
330 + <div class="min-w-0 flex-1 border-l-0 sm:border-l sm:border-gray-200 sm:pl-4">
331 + <p class="text-xs font-medium uppercase tracking-wide text-gray-500"><?php esc_html_e( 'Quote', 'easy-invoice' ); ?></p>
332 + <h1 id="ei-builder-header-title" class="mt-0.5 truncate text-lg font-semibold leading-tight text-gray-900 sm:text-xl" data-ei-default="<?php echo esc_attr( $ei_header_display_title ); ?>">
333 + <?php echo esc_html( $ei_header_display_title ); ?>
334 + </h1>
335 + <p class="sr-only"><?php esc_html_e( 'The title above matches the document title field in the editor.', 'easy-invoice' ); ?></p>
336 + </div>
372 337 </div>
373 - <h1 class="text-lg font-semibold text-gray-800">
374 - <?php
375 - if ($quote_id && $quote) {
376 - $title = $quote->title ?: $quote->number ?: 'Untitled Quote';
377 - echo esc_html($title);
378 - } else {
379 - echo 'Create New Quote';
380 - }
381 - ?>
382 - </h1>
383 - <div class="flex items-center space-x-4">
384 - <button type="button" id="save-quote-btn" class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
385 - <i class="fas fa-save mr-2"></i>
386 - <span><?php echo (isset($_GET['id']) ? __('Update Quote', 'easy-invoice') : __('Save Quote', 'easy-invoice')); ?></span>
338 + <div class="ei-builder-header-actions flex flex-wrap items-center gap-2 lg:shrink-0">
339 + <button type="button" id="save-quote-btn" class="inline-flex items-center gap-2 px-3 sm:px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" aria-busy="false">
340 + <svg class="h-4 w-4 text-gray-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
341 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path>
342 + </svg>
343 + <span><?php echo isset( $_GET['id'] ) ? esc_html__( 'Update quote', 'easy-invoice' ) : esc_html__( 'Save quote', 'easy-invoice' ); ?></span>
387 344 </button>
388 - <button type="button" id="send-quote-btn" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
389 - <i class="fas fa-paper-plane mr-2"></i>
390 - <span>Send Quote</span>
345 + <button type="button" id="send-quote-btn" class="inline-flex items-center gap-2 px-3 sm:px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-send-nonce="<?php echo esc_attr( wp_create_nonce( 'easy_invoice_send_quote_email' ) ); ?>">
346 + <svg class="h-4 w-4 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
347 + <path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"></path>
348 + </svg>
349 + <span><?php esc_html_e( 'Send quote', 'easy-invoice' ); ?></span>
391 350 </button>
392 351 </div>
393 352 </div>
394 353 </div>
395 - </div>
354 + </header>
396 355
397 - <!-- Main Content -->
398 - <div class="flex-grow overflow-y-auto">
399 - <div class="max-w-full mx-auto px-5 py-5">
400 - <div id="ei-quote-builder-grid" class="grid grid-cols-1 lg:grid-cols-2 gap-8">
401 - <!-- Left Panel - Editable Fields -->
402 -
403 - <?php
404 - include_once EASY_INVOICE_PLUGIN_DIR . 'templates/quotes/form.php';
405 -
406 - include_once EASY_INVOICE_PLUGIN_DIR . 'templates/quotes/live-preview.php';
407 - ?>
356 + <div class="flex-grow overflow-y-auto min-h-0">
357 + <div class="max-w-full mx-auto px-4 sm:px-5 py-4 sm:py-5">
358 + <div class="ei-builder-tabs lg:hidden flex rounded-lg bg-gray-100 p-1 gap-1 mb-4" role="tablist" aria-label="<?php echo esc_attr__( 'Editor panels', 'easy-invoice' ); ?>">
359 + <button type="button" role="tab" class="ei-builder-tab flex-1 rounded-md py-2 text-sm font-medium transition bg-white shadow-sm text-indigo-700" data-tab="editor" aria-selected="true"><?php esc_html_e( 'Editor', 'easy-invoice' ); ?></button>
360 + <button type="button" role="tab" class="ei-builder-tab flex-1 rounded-md py-2 text-sm font-medium transition text-gray-600 hover:text-gray-800" data-tab="preview" aria-selected="false"><?php esc_html_e( 'Preview', 'easy-invoice' ); ?></button>
408 361 </div>
362 + <div id="ei-builder-shell-hint" class="ei-builder-shell-hint mb-4 hidden rounded-md border border-indigo-100 bg-indigo-50/90 p-3 text-sm text-gray-700 shadow-sm lg:hidden" role="status" hidden>
363 + <div class="flex items-start gap-3">
364 + <span class="mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-indigo-100 text-indigo-700" aria-hidden="true">
365 + <svg class="h-3.5 w-3.5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
366 + </span>
367 + <p class="min-w-0 flex-1 leading-snug"><?php esc_html_e( 'On smaller screens, use the tabs to switch between the editor and the live preview.', 'easy-invoice' ); ?></p>
368 + <button type="button" id="ei-builder-shell-hint-dismiss" class="shrink-0 rounded-md px-2 py-1 text-xs font-medium text-indigo-700 hover:bg-indigo-100 focus:outline-none focus:ring-2 focus:ring-indigo-500">
369 + <?php esc_html_e( 'Got it', 'easy-invoice' ); ?>
370 + </button>
371 + </div>
372 + </div>
373 + <div id="ei-quote-builder-grid" class="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-8">
374 + <div id="ei-builder-editor" class="min-w-0 ei-builder-panel-editor">
375 + <?php include_once EASY_INVOICE_PLUGIN_DIR . 'templates/quotes/form.php'; ?>
376 + </div>
377 + <div id="ei-builder-preview" class="min-w-0 ei-builder-panel-preview hidden lg:block">
378 + <?php include_once EASY_INVOICE_PLUGIN_DIR . 'templates/quotes/live-preview.php'; ?>
379 + </div>
380 + </div>
409 381
410 - <!-- Add Client Modal -->
411 - <div id="add_client_modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full z-50">
382 + <div id="add_client_modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full z-50" role="dialog" aria-modal="true" aria-labelledby="ei-quote-add-client-title">
412 383 <div class="relative top-20 mx-auto p-5 border w-11/12 md:w-2/3 lg:w-1/2 shadow-lg rounded-md bg-white">
413 384 <div class="flex justify-between items-center mb-4">
414 - <h3 class="text-lg font-medium text-gray-900">Add New Client</h3>
415 - <button type="button" class="close-modal text-gray-400 hover:text-gray-500">
416 - <span class="sr-only">Close</span>
417 - <i class="fas fa-times"></i>
385 + <h3 id="ei-quote-add-client-title" class="text-lg font-medium text-gray-900"><?php esc_html_e( 'Add new client', 'easy-invoice' ); ?></h3>
386 + <button type="button" class="close-modal rounded-md p-1 text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500">
387 + <span class="sr-only"><?php esc_html_e( 'Close', 'easy-invoice' ); ?></span>
388 + <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
389 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
390 + </svg>
418 391 </button>
419 392 </div>
420 393
421 394 <?php