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
easy-invoice / templates / invoices / form.php

form.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.0, at templates/invoices/form.php

1,401 lines 97.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 exit;
4 }
5 // Enqueue CSS and JS files
6 wp_enqueue_style('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/css/invoice-form.css', array(), '1.0.0');
7 wp_enqueue_script('easy-invoice-form', plugin_dir_url(__FILE__) . '../../assets/js/invoice-form.js', array('jquery'), '1.0.0', true);
8
9 // Initialize the form manager
10 $form_manager = new EasyInvoice\Forms\Invoice\InvoiceFormManager();
11
12 // Get field configuration for JavaScript
13 $invoice_field_config = $form_manager->getFieldConfigForJavaScript();
14
15 // Trigger form initialization to allow pro version to register new elements
16 do_action('easy_invoice_form_init');
17
18 $tabs = $form_manager->getTabs();
19 $templates = $form_manager->getTemplates();
20
21 // Create a separate array for browse templates modal with all available templates
22 $all_templates = apply_filters('easy_invoice_invoice_templates', [
23 'standard' => [
24 'name' => __('Standard', 'easy-invoice'),
25 'icon' => 'fas fa-file-invoice',
26 'description' => __('Clean and professional standard template', 'easy-invoice'),
27 'order' => 1,
28 'is_free' => true
29 ],
30 'legacy' => [
31 'name' => __('Legacy', 'easy-invoice'),
32 'icon' => 'fas fa-file-alt',
33 'description' => __('Clean, minimalist design with traditional business layout', 'easy-invoice'),
34 'order' => 2,
35 'is_free' => true
36 ],
37 'professional' => [
38 'name' => __('Professional', 'easy-invoice'),
39 'icon' => 'fas fa-briefcase',
40 'description' => __('Professional business template with modern design', 'easy-invoice'),
41 'order' => 3,
42 'is_free' => false
43 ],
44 'modern' => [
45 'name' => __('Modern', 'easy-invoice'),
46 'icon' => 'fas fa-rocket',
47 'description' => __('Modern and sleek design template', 'easy-invoice'),
48 'order' => 4,
49 'is_free' => false
50 ],
51 'classic' => [
52 'name' => __('Classic', 'easy-invoice'),
53 'icon' => 'fas fa-landmark',
54 'description' => __('Traditional business template', 'easy-invoice'),
55 'order' => 5,
56 'is_free' => false
57 ],
58 'minimal' => [
59 'name' => __('Minimal', 'easy-invoice'),
60 'icon' => 'fas fa-minus',
61 'description' => __('Simple and clean minimal template', 'easy-invoice'),
62 'order' => 6,
63 'is_free' => false
64 ],
65
66 'corporate' => [
67 'name' => __('Corporate', 'easy-invoice'),
68 'icon' => 'fas fa-building',
69 'description' => __('Professional corporate template', 'easy-invoice'),
70 'order' => 7,
71 'is_free' => false
72 ],
73 'elegant' => [
74 'name' => __('Elegant', 'easy-invoice'),
75 'icon' => 'fas fa-gem',
76 'description' => __('Elegant and sophisticated design', 'easy-invoice'),
77 'order' => 8,
78 'is_free' => false
79 ],
80 'creative' => [
81 'name' => __('Creative', 'easy-invoice'),
82 'icon' => 'fas fa-palette',
83 'description' => __('Creative and artistic design template', 'easy-invoice'),
84 'order' => 9,
85 'is_free' => false
86 ]
87 ]);
88
89
90
91 // Load client data directly in PHP if invoice has a client
92 $client_data = null;
93 if ($invoice && $invoice->getClientId()) {
94 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
95 if ($client) {
96 $client_data = array(
97 'id' => $client->getId(),
98 'name' => $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()),
99 'email' => $client->getEmail() ?: '',
100 'phone' => $client->getExtraInfo() ?: '',
101 'company' => $client->getBusinessClientName() ?: '',
102 'address' => $client->getAddress() ?: '',
103 'website' => $client->getWebsite() ?: '',
104 );
105 }
106 }
107 ?>
108
109 <style>
110 /* Premium Template Styling */
111 .premium-template-blurred {
112 position: relative;
113 opacity: 0.8;
114 transition: all 0.4s ease;
115 cursor: pointer;
116 border: 2px solid #e5e7eb;
117 background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
118 transform: scale(0.98);
119 }
120
121 .premium-template-blurred::before {
122 content: '';
123 position: absolute;
124 top: 0;
125 left: 0;
126 right: 0;
127 bottom: 0;
128 background: linear-gradient(135deg, rgba(139, 92, 246, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%);
129 border-radius: 0.5rem;
130 z-index: 1;
131 pointer-events: none;
132 }
133
134 .premium-template-blurred::after {
135 content: 'PRO';
136 position: absolute;
137 top: 8px;
138 right: 8px;
139 background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%);
140 color: white;
141 font-size: 10px;
142 font-weight: 700;
143 padding: 2px 6px;
144 border-radius: 4px;
145 z-index: 2;
146 box-shadow: 0 2px 4px rgba(139, 92, 246, 0.3);
147 letter-spacing: 0.5px;
148 }
149
150 .premium-template-blurred:hover {
151 opacity: 0.95;
152 transform: scale(1.02);
153 border-color: #8b5cf6;
154 box-shadow: 0 8px 25px rgba(139, 92, 246, 0.15);
155 }
156
157 .premium-template-blurred:hover::after {
158 background: linear-gradient(135deg, #7c3aed 0%, #9333ea 100%);
159 box-shadow: 0 4px 8px rgba(139, 92, 246, 0.4);
160 }
161
162 .premium-template-blurred .template-radio {
163 pointer-events: none;
164 }
165
166 .premium-template-blurred > div {
167 position: relative;
168 z-index: 3;
169 }
170 </style>
171
172 <div class="bg-white shadow rounded-lg">
173
174 <div class="border-b border-gray-200">
175 <nav class="flex -mb-px" aria-label="<?php esc_attr_e( 'Invoice form sections', 'easy-invoice' ); ?>">
176 <?php foreach ($tabs as $tab): ?>
177 <button type="button"
178 class="tab-button whitespace-nowrap py-3 px-6 border-b-2 <?php echo $tab['active'] ? 'border-indigo-500 font-medium text-sm text-indigo-600' : 'border-transparent font-medium text-sm text-gray-500 hover:text-gray-700 hover:border-gray-300'; ?> flex flex-col items-center"
179 data-tab="<?php echo esc_attr($tab['id']); ?>">
180 <i class="<?php echo esc_attr($tab['icon']); ?> mb-1 text-lg"></i>
181 <span><?php echo esc_html($tab['label']); ?></span>
182 </button>
183 <?php endforeach; ?>
184 </nav>
185 </div>
186 <?php
187 // Lifecycle-stage lock check. Addons (PartialPayments)
188 // can return false here to mark this invoice as
189 // immutable — currently used to freeze paid deposit
190 // invoices so admins don't accidentally invalidate
191 // the linked balance pair. Default: true (no listener).
192 $ei_can_edit = ! $invoice_id || apply_filters('easy_invoice_can_edit_invoice', true, (int) $invoice_id);
193 if (! $ei_can_edit && $invoice_id):
194 $ei_balance_id = (int) get_post_meta((int) $invoice_id, '_balance_invoice_id', true);
195 $ei_paid_date = (string) get_post_meta((int) $invoice_id, '_easy_invoice_payment_date', true);
196 ?>
197 <div class="mx-6 mt-4 mb-4 rounded-md border border-amber-200 bg-amber-50 p-4">
198 <div class="flex items-start">
199 <i class="fas fa-lock text-amber-500 mt-0.5 mr-3"></i>
200 <div class="flex-1 text-sm text-amber-800">
201 <p class="font-semibold mb-1"><?php esc_html_e('This deposit invoice is locked.', 'easy-invoice'); ?></p>
202 <p class="mb-2">
203 <?php
204 if ($ei_paid_date) {
205 printf(
206 /* translators: %s: payment date */
207 esc_html__('A payment was recorded on %s. Items and totals are frozen so the deposit receipt the customer already saw stays accurate. To add new charges, open the linked balance invoice instead.', 'easy-invoice'),
208 esc_html($ei_paid_date)
209 );
210 } else {
211 esc_html_e('A payment has been recorded against this deposit. Items and totals are frozen so the deposit receipt the customer already saw stays accurate. To add new charges, open the linked balance invoice instead.', 'easy-invoice');
212 }
213 ?>
214 </p>
215 <?php if ($ei_balance_id > 0): ?>
216 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-builder&invoice_id=' . $ei_balance_id)); ?>"
217 class="inline-flex items-center px-3 py-1.5 rounded text-xs font-medium bg-amber-600 text-white hover:bg-amber-700">
218 <i class="fas fa-arrow-right mr-1"></i>
219 <?php esc_html_e('Open the balance invoice', 'easy-invoice'); ?>
220 </a>
221 <?php endif; ?>
222 </div>
223 </div>
224 </div>
225 <?php endif; ?>
226 <form id="invoice-form"<?php echo $ei_can_edit ? '' : ' data-ei-locked="1"'; ?>>
227 <input type="hidden" id="invoice-id" name="invoice_id" value="<?php echo $invoice_id ? esc_attr($invoice_id) : ''; ?>">
228
229 <input type="hidden" id="invoice_nonce" name="invoice_nonce" value="<?php echo esc_attr(wp_create_nonce('easy_invoice_nonce')); ?>">
230
231 <?php foreach ($tabs as $tab): ?>
232 <div class="tab-content <?php echo $tab['active'] ? 'active' : 'hidden'; ?> px-6 py-5" id="<?php echo esc_attr($tab['id']); ?>">
233 <?php if ($tab['id'] === 'invoice-tab'): ?>
234 <div class="mb-8 pb-6 border-b border-gray-200">
235 <div class="flex justify-between items-center mb-4">
236 <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Invoice Template', 'easy-invoice'); ?></h3>
237 <button type="button" id="browse-all-templates" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
238 <span class="mr-1"><?php esc_html_e('Browse Invoice Templates', 'easy-invoice'); ?></span>
239 <i class="fas fa-chevron-right"></i>
240 </button>
241 </div>
242
243 <?php
244 // Get the current template (default to 'standard')
245 $current_template = $invoice ? $invoice->getTemplate() : 'standard';
246
247 if (empty($current_template)) {
248 $current_template = 'standard';
249 }
250
251 // Always include the hidden input for the template field
252 ?>
253 <input type="hidden" name="invoice_template" value="<?php echo esc_attr($current_template); ?>">
254 <?php
255
256 // Check if this is the free version
257 $is_free_version = !easy_invoice_has_pro();
258
259
260
261 // Show all templates grid
262 ?>
263 <div class="grid grid-cols-2 md:grid-cols-3 gap-4" id="main-templates-grid">
264 <?php
265 // Build the main grid templates array - always include current template plus 2 others
266 $main_grid_templates = [];
267
268 // First, add the current template if it exists in all_templates
269 if (isset($all_templates[$current_template])) {
270 // For free users, if current template is premium, default to standard
271 if ($is_free_version && isset($all_templates[$current_template]['is_free']) && !$all_templates[$current_template]['is_free']) {
272 $current_template = 'standard';
273 $main_grid_templates['standard'] = $all_templates['standard'];
274 } else {
275 $main_grid_templates[$current_template] = $all_templates[$current_template];
276 }
277 }
278
279 // Add other templates to fill up to 3, prioritizing standard templates
280 $default_templates = ['standard', 'legacy', 'professional'];
281 foreach ($default_templates as $template_id) {
282 if (count($main_grid_templates) >= 3) break;
283 if ($template_id !== $current_template && isset($all_templates[$template_id])) {
284 $main_grid_templates[$template_id] = $all_templates[$template_id];
285 }
286 }
287
288 // If we still don't have 3 templates, add any remaining templates
289 foreach ($all_templates as $template_id => $template) {
290 if (count($main_grid_templates) >= 3) break;
291 if (!isset($main_grid_templates[$template_id])) {
292 $main_grid_templates[$template_id] = $template;
293 }
294 }
295
296 // Display the main grid templates
297 foreach ($main_grid_templates as $template_id => $template) :
298 $checked = ($current_template === $template_id) ? 'checked' : '';
299 $selected_class = ($current_template === $template_id) ? 'selected' : '';
300
301 // Check if this template is premium (not free)
302 $is_premium = isset($template['is_free']) && !$template['is_free'];
303
304 // Add premium overlay classes
305 $premium_classes = '';
306 if ($is_free_version && $is_premium) {
307 $premium_classes = 'premium-template-blurred';
308 }
309 ?>
310 <div class="template-card <?php echo esc_attr($selected_class . ' ' . $premium_classes); ?>" data-template-id="<?php echo esc_attr($template_id); ?>" data-is-premium="<?php echo esc_attr($is_premium ? 'true' : 'false'); ?>">
311 <?php // aria-label: the visible template name sits in sibling markup, not in a
312 // <label for>, so assistive tech announced these radios as unnamed. ?>
313 <input type="radio" id="template-<?php /* translators: %s: template name. */ echo esc_attr($template_id); ?>" name="invoice_template" value="<?php echo esc_attr($template_id); ?>" aria-label="<?php echo esc_attr(sprintf(__('Use the %s invoice template', 'easy-invoice'), $template['name'])); ?>" class="template-radio" <?php echo esc_html($checked); ?> <?php echo esc_attr(($is_free_version && $is_premium) ? 'disabled' : ''); ?>>
314 <div class="p-4 border rounded-lg">
315 <div class="flex items-center mb-2">
316 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
317 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
318 </div>
319 <p class="text-sm text-gray-600"><?php echo esc_html($template['description']); ?></p>
320 <div class="mt-2 flex-grow">
321 <div class="template-preview bg-gray-100 rounded h-16 flex items-center justify-center <?php echo esc_attr($template_id); ?>">
322 </div>
323 </div>
324 </div>
325 </div>
326 <?php endforeach; ?>
327 </div>
328 </div>
329
330 <div class="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
331 <?php
332 $main_fields = $form_manager->getFields('invoice-tab');
333 foreach ($main_fields as $field):
334 // Skip fields that have a section (like notes) - they're rendered separately
335 // Also skip the template field as it's rendered manually in the template selection section
336 if (!isset($field['section']) && $field['name'] !== 'invoice_template') {
337 echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
338 }
339 endforeach;
340 ?>
341 </div>
342
343 <?php if (!$invoice): ?>
344 <div class="mt-4 p-3 bg-blue-50 border border-blue-200 rounded-md">
345 <div class="flex">
346 <div class="flex-shrink-0">
347 <i class="fas fa-info-circle text-blue-400"></i>
348 </div>
349 <div class="ml-3">
350 <p class="text-sm text-blue-700">
351 <?php esc_html_e('Invoice number will be automatically generated when you save the invoice.', 'easy-invoice'); ?>
352 </p>
353 </div>
354 </div>
355 </div>
356 <?php endif; ?>
357
358 <?php if (easy_invoice_has_pro()): ?>
359 <?php
360 $recurring_fields = $form_manager->getFieldsBySection('invoice-tab', 'recurring');
361 if (!empty($recurring_fields)):
362 ?>
363 <div class="mt-8 border-t border-gray-200 pt-6">
364 <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Recurring Invoice Settings', 'easy-invoice'); ?></h3>
365 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
366 <?php
367 foreach ($recurring_fields as $field):
368 echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
369 endforeach;
370 ?>
371 </div>
372 </div>
373 <?php endif; ?>
374 <?php else: ?>
375 <div class="mt-6 border-t border-gray-200 pt-4">
376 <div class="bg-gradient-to-r from-purple-50 to-indigo-50 border border-purple-200 rounded-lg p-4">
377 <div class="flex items-start">
378 <div class="flex-shrink-0">
379 <div class="w-8 h-8 bg-gradient-to-r from-purple-500 to-indigo-600 rounded-lg flex items-center justify-center">
380 <i class="fas fa-sync-alt text-white text-sm"></i>
381 </div>
382 </div>
383 <div class="ml-3 flex-1">
384 <h3 class="text-base font-semibold text-gray-900 mb-1"><?php esc_html_e('Recurring Invoices', 'easy-invoice'); ?></h3>
385 <p class="text-sm text-gray-600 mb-3"><?php esc_html_e('Automate your billing with recurring invoices. Set up weekly, monthly, or custom intervals to automatically generate and send invoices to your clients.', 'easy-invoice'); ?></p>
386 <div class="flex items-center space-x-3 mb-3">
387 <div class="flex items-center text-xs text-gray-500">
388 <i class="fas fa-check text-green-500 mr-1"></i>
389 <span><?php esc_html_e('Automated billing cycles', 'easy-invoice'); ?></span>
390 </div>
391 <div class="flex items-center text-xs text-gray-500">
392 <i class="fas fa-check text-green-500 mr-1"></i>
393 <span><?php esc_html_e('Custom intervals', 'easy-invoice'); ?></span>
394 </div>
395 <div class="flex items-center text-xs text-gray-500">
396 <i class="fas fa-check text-green-500 mr-1"></i>
397 <span><?php esc_html_e('Auto-send notifications', 'easy-invoice'); ?></span>
398 </div>
399 </div>
400 <button type="button" class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md text-white bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200" onclick="if(typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) { EasyInvoiceConfirmation.showFeatureUpgrade('<?php echo esc_js(__('Recurring Invoices', 'easy-invoice')); ?>', '<?php echo esc_js(__('Automate your billing with recurring invoices. Set up weekly, monthly, or custom intervals to automatically generate and send invoices to your clients.', 'easy-invoice')); ?>'); } else { alert('<?php echo esc_js(__('This is a premium feature. Please upgrade to Pro to access recurring invoices.', 'easy-invoice')); ?>'); }">
401 <i class="fas fa-crown mr-1.5"></i>
402 <?php esc_html_e('Upgrade to Pro', 'easy-invoice'); ?>
403 </button>
404 </div>
405 </div>
406 </div>
407 </div>
408 <?php endif; ?>
409
410 <?php if (easy_invoice_has_pro()): ?>
411 <?php
412 // Check if subscription is actually enabled
413 $subscription_enabled = get_option('easy_invoice_pro_subscription_enabled', '0');
414 if ($subscription_enabled):
415 $subscription_fields = $form_manager->getFieldsBySection('invoice-tab', 'subscription');
416 if (!empty($subscription_fields)):
417 ?>
418 <div class="mt-8 border-t border-gray-200 pt-6">
419 <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Subscription Settings', 'easy-invoice'); ?></h3>
420 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
421 <?php
422 foreach ($subscription_fields as $field):
423 echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
424 endforeach;
425 ?>
426 </div>
427 </div>
428 <?php endif; ?>
429 <?php endif; ?>
430 <?php else: ?>
431 <div class="mt-6 border-t border-gray-200 pt-4">
432 <div class="bg-gradient-to-r from-purple-50 to-indigo-50 border border-purple-200 rounded-lg p-4">
433 <div class="flex items-start">
434 <div class="flex-shrink-0">
435 <div class="w-8 h-8 bg-gradient-to-r from-purple-500 to-indigo-600 rounded-lg flex items-center justify-center">
436 <i class="fas fa-calendar-alt text-white text-sm"></i>
437 </div>
438 </div>
439 <div class="ml-3 flex-1">
440 <h3 class="text-base font-semibold text-gray-900 mb-1"><?php esc_html_e('Partial Payment', 'easy-invoice'); ?></h3>
441 <p class="text-sm text-gray-600 mb-3"><?php esc_html_e('Accept partial payments from clients with flexible payment schedules, payment tracking, and outstanding balance management.', 'easy-invoice'); ?></p>
442 <div class="flex items-center space-x-3 mb-3">
443 <div class="flex items-center text-xs text-gray-500">
444 <i class="fas fa-check text-green-500 mr-1"></i>
445 <span><?php esc_html_e('Flexible payment schedules', 'easy-invoice'); ?></span>
446 </div>
447 <div class="flex items-center text-xs text-gray-500">
448 <i class="fas fa-check text-green-500 mr-1"></i>
449 <span><?php esc_html_e('Payment tracking', 'easy-invoice'); ?></span>
450 </div>
451 <div class="flex items-center text-xs text-gray-500">
452 <i class="fas fa-check text-green-500 mr-1"></i>
453 <span><?php esc_html_e('Balance management', 'easy-invoice'); ?></span>
454 </div>
455 </div>
456 <button type="button" class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md text-white bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200" onclick="if(typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) { EasyInvoiceConfirmation.showFeatureUpgrade('<?php echo esc_js(__('Partial Payment', 'easy-invoice')); ?>', '<?php echo esc_js(__('Accept partial payments from clients with flexible payment schedules, payment tracking, and outstanding balance management.', 'easy-invoice')); ?>'); } else { alert('<?php echo esc_js(__('This is a premium feature. Please upgrade to Pro to access partial payment functionality.', 'easy-invoice')); ?>'); }">
457 <i class="fas fa-crown mr-1.5"></i>
458 <?php esc_html_e('Upgrade to Pro', 'easy-invoice'); ?>
459 </button>
460 </div>
461 </div>
462 </div>
463 </div>
464 <?php endif; ?>
465
466 <div class="mt-8 border-t border-gray-200 pt-6">
467 <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Notes & Terms', 'easy-invoice'); ?></h3>
468 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:col-span-6">
469 <?php
470 // Only render notes and terms fields, not all invoice tab fields
471 $notes_fields = $form_manager->getFieldsBySection('invoice-tab', 'notes');
472 foreach ($notes_fields as $field):
473 echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
474 endforeach;
475 ?>
476 </div>
477 </div>
478
479 <?php elseif ($tab['id'] === 'items-tab'): ?>
480 <div class="flex justify-between items-center mb-6">
481 <h2 class="text-lg font-medium text-gray-900"><?php esc_html_e('Invoice Items', 'easy-invoice'); ?></h2>
482 <button type="button" id="collapse-all-items" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center">
483 <i class="fas fa-chevron-down mr-1"></i>
484 <span><?php esc_html_e('Collapse All', 'easy-invoice'); ?></span>
485 </button>
486 </div>
487
488 <div class="space-y-4 invoice-items-container">
489 <?php
490 // If editing an existing invoice, directly output invoice items
491 if ($invoice && $invoice->getItems()) :
492 $index = 0;
493 foreach ($invoice->getItems() as $item) :
494 // Generate a unique ID for the item
495 $item_id = 'item_' . time() . '_' . $index . '_' . wp_rand(1000, 9999);
496
497 // Get display title for header using dynamic field access
498 $title = $item->getName();
499
500
501 if (empty($title) && method_exists($item, 'getData')) {
502 $item_data = $item->getData();
503 if (is_array($item_data) && isset($item_data['title'])) {
504 $title = $item_data['title'];
505 }
506 }
507
508 /* translators: %d: title. */
509 $display_title = !empty($title) ? $title : sprintf(__('Item #%d', 'easy-invoice'), $index + 1);
510 $short_title = strlen($display_title) > 30 ? substr($display_title, 0, 30) . '...' : $display_title;
511 ?>
512 <div class="invoice-item p-6 rounded-xl shadow-sm border border-blue-100 hover:shadow-md transition-all duration-200 mb-6" style="background-color: #f8fbff;" id="<?php echo (int) $item_id; ?>">
513 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
514 <div class="flex items-center">
515 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
516 <i class="fas fa-chevron-down text-sm"></i>
517 </button>
518 <h3 class="text-base font-normal text-gray-800 flex-1">
519 <?php
520 echo !empty($title) ? esc_html($title) : esc_html__('New Item', 'easy-invoice');
521 ?>
522 </h3>
523 </div>
524 <div class="flex items-center gap-3">
525 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
526 <span class="quantity-summary"><?php echo esc_html($item->getQuantity()); ?></span> × $<span class="price-summary"><?php echo esc_html(number_format(floatval($item->getPrice()), 2)); ?></span>
527 = $<span class="total-summary font-medium"><?php echo esc_html(number_format($item->getAmount(), 2)); ?></span>
528 </div>
529 <button type="button" class="fill-sample-data-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Fill this item with sample data', 'easy-invoice')); ?>">
530 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
531 <path d="M2 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H2zm0 1h16v14H2V3z"/>
532 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
533 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
534 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
535 </svg>
536 </button>
537 <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
538 <button type="button" class="item-library-btn text-gray-400 hover:text-blue-600 p-2 rounded-lg hover:bg-blue-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Select from Item Library', 'easy-invoice')); ?>">
539 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
540 <path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"/>
541 </svg>
542 </button>
543 <button type="button" class="save-to-library-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Save to Item Library', 'easy-invoice')); ?>" data-item-index="<?php echo esc_attr($index); ?>" data-nonce="<?php echo esc_attr(wp_create_nonce('easy_invoice_pro_item_library')); ?>">
544 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
545 <path d="M7.707 10.293a1 1 0 10-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 11.586V6h5a2 2 0 012 2v7a2 2 0 01-2 2H4a2 2 0 01-2-2V8a2 2 0 012-2h5v5.586l-1.293-1.293zM9 4a1 1 0 012 0v2H9V4z"/>
546 </svg>
547 </button>
548 <?php endif; ?>
549 <button type="button" class="remove-item text-gray-400 hover:text-red-600 hover:bg-red-50 p-2 rounded-lg transition-all duration-200 text-sm font-medium">
550 <i class="fas fa-trash-alt mr-1"></i> <?php esc_html_e('Remove', 'easy-invoice'); ?>
551 </button>
552 </div>
553 </div>
554 <div class="item-content">
555 <?php echo $form_manager->generateExistingItemHtml($item, $index); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- ItemFieldRenderer escapes every value it emits.
556 ?>
557 </div>
558 </div>
559 <?php
560 $index++;
561 endforeach;
562 else:
563 // For new invoices, render one empty item by default
564 $index = 0;
565 // Get the item fields config
566 $item_fields = $form_manager->getItemFields();
567 $title_placeholder = '';
568 foreach ($item_fields as $field) {
569 if ($field['name'] === 'title') {
570 $title_placeholder = $field['placeholder'] ?? __('Item', 'easy-invoice');
571 break;
572 }
573 }
574 // Try to get the value from POST (if form was submitted and errored)
575 $item_title = isset($_POST['items'][0]['title']) ? trim($_POST['items'][0]['title']) : '';
576 ?>
577 <div class="invoice-item p-6 rounded-xl shadow-sm border border-blue-100 hover:shadow-md transition-all duration-200 mb-6" style="background-color: #f8fbff;">
578 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
579 <div class="flex items-center">
580 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
581 <i class="fas fa-chevron-down text-sm"></i>
582 </button>
583 <h3 class="text-base font-normal text-gray-800 flex-1">
584 <?php
585 echo !empty($item_title) ? esc_html($item_title) : esc_html($title_placeholder);
586 ?>
587 </h3>
588 </div>
589 <div class="flex items-center gap-3">
590 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
591 <span class="quantity-summary">0</span> × $<span class="price-summary">0.00</span>
592 = $<span class="total-summary">0.00</span>
593 </div>
594 <button type="button" class="fill-sample-data-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Fill this item with sample data', 'easy-invoice')); ?>">
595 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
596 <path d="M2 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H2zm0 1h16v14H2V3z"/>
597 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
598 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
599 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
600 </svg>
601 </button>
602 <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
603 <button type="button" class="item-library-btn text-gray-400 hover:text-blue-600 p-2 rounded-lg hover:bg-blue-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Select from Item Library', 'easy-invoice')); ?>">
604 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
605 <path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"/>
606 </svg>
607 </button>
608 <button type="button" class="save-to-library-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Save to Item Library', 'easy-invoice')); ?>" data-item-index="0" data-nonce="<?php echo esc_attr(wp_create_nonce('easy_invoice_pro_item_library')); ?>">
609 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
610 <path d="M7.707 10.293a1 1 0 10-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 11.586V6h5a2 2 0 012 2v7a2 2 0 01-2 2H4a2 2 0 01-2-2V8a2 2 0 012-2h5v5.586l-1.293-1.293zM9 4a1 1 0 012 0v2H9V4z"/>
611 </svg>
612 </button>
613 <?php endif; ?>
614 <button type="button" class="remove-item text-gray-400 hover:text-red-600 hover:bg-red-50 p-2 rounded-lg transition-all duration-200 text-sm font-medium">
615 <i class="fas fa-trash-alt mr-1"></i> <?php esc_html_e('Remove', 'easy-invoice'); ?>
616 </button>
617 </div>
618 </div>
619 <div class="item-content">
620 <?php echo $form_manager->generateItemTemplateHtml(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- form markup built by ItemFieldRenderer, which escapes every value. ?>
621 </div>
622 </div>
623 <?php endif; ?>
624 </div>
625
626 <div class="mt-6">
627 <button type="button" class="add-item-button w-full flex justify-center items-center px-6 py-4 bg-white border border-blue-100 rounded-xl text-indigo-700 hover:bg-blue-50 hover:border-blue-200 hover:shadow-md transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 group">
628 <div class="flex items-center">
629 <div class="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center mr-3 group-hover:bg-indigo-200 transition-colors">
630 <i class="fas fa-plus text-indigo-600 text-lg"></i>
631 </div>
632 <div class="text-left">
633 <span class="font-semibold text-base"><?php esc_html_e('Add Another Item', 'easy-invoice'); ?></span>
634 <p class="text-sm text-indigo-600 mt-0.5"><?php esc_html_e('Click to add a new line item', 'easy-invoice'); ?></p>
635 </div>
636 </div>
637 </button>
638 </div>
639 <?php elseif ($tab['id'] === 'client-tab'): ?>
640 <div class="mb-6">
641 <label class="block text-sm font-medium text-gray-700 mb-3"><?php esc_html_e('Select Existing Client', 'easy-invoice'); ?></label>
642
643 <div class="relative">
644 <div class="flex items-center space-x-3">
645 <div class="relative flex-grow">
646 <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
647 <i class="fas fa-search text-gray-400"></i>
648 </div>
649 <input
650 type="text"
651 id="client-search-input"
652 class="block w-full rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm py-2 px-3 pl-10"
653 placeholder="<?php echo esc_js(__('Search clients by name, email, or company...', 'easy-invoice')); ?>"
654 autocomplete="off"
655 >
656 </div>
657 <button type="button" id="add-new-client-btn" class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
658 <i class="fas fa-plus -ml-1 mr-2"></i>
659 <?php esc_html_e('New Client', 'easy-invoice'); ?>
660 </button>
661 </div>
662
663 <div id="client-search-results" class="absolute z-50 w-full mt-2 bg-white border border-gray-200 rounded-lg shadow-xl max-h-80 overflow-y-auto" style="display: none;">
664 <div id="client-search-loading" class="p-6 text-center text-gray-500" style="display: none;">
665 <i class="fas fa-spinner fa-spin mr-2"></i>
666 <?php esc_html_e('Searching...', 'easy-invoice'); ?>
667 </div>
668 <div id="client-search-empty" class="p-6 text-center text-gray-500" style="display: none;">
669 <i class="fas fa-search mr-2"></i>
670 <?php esc_html_e('No clients found', 'easy-invoice'); ?>
671 </div>
672 <div id="client-search-list" class="py-2">
673 </div>
674 </div>
675
676 <div id="selected-client-display" class="mt-4 p-4 bg-gradient-to-r from-indigo-50 to-blue-50 border border-indigo-200 rounded-lg shadow-sm <?php echo ($invoice && $invoice->getClientId()) ? '' : 'hidden'; ?>">
677 <div class="flex items-center justify-between">
678 <div class="flex items-center">
679 <div class="flex-shrink-0">
680 <div class="w-10 h-10 bg-gradient-to-br from-indigo-500 to-indigo-600 rounded-full flex items-center justify-center shadow-sm">
681 <i class="fas fa-user text-white text-sm"></i>
682 </div>
683 </div>
684 <div class="ml-4">
685 <p id="selected-client-name" class="text-sm font-semibold text-gray-900">
686 <?php
687 if ($invoice && $invoice->getClientId()) {
688 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
689 if ($client) {
690 echo esc_html($client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()));
691 } else {
692 echo esc_html__('', 'easy-invoice');
693 }
694 } else {
695 echo esc_html__('', 'easy-invoice');
696 }
697 ?>
698 </p>
699 <p id="selected-client-email" class="text-sm text-gray-600">
700 <?php
701 if ($invoice && $invoice->getClientId()) {
702 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
703 if ($client) {
704 echo esc_html($client->getEmail() ?: esc_html__('', 'easy-invoice'));
705 } else {
706 echo esc_html__('', 'easy-invoice');
707 }
708 } else {
709 echo esc_html__('', 'easy-invoice');
710 }
711 ?>
712 </p>
713 </div>
714 </div>
715 <button type="button" id="clear-client-selection" class="text-gray-400 hover:text-gray-600 transition-colors p-2 rounded-full hover:bg-white hover:shadow-sm">
716 <i class="fas fa-times"></i>
717 </button>
718 </div>
719 </div>
720 </div>
721
722 <select id="select-client" class="hidden">
723 <option value=""><?php esc_html_e('-- Select a client --', 'easy-invoice'); ?></option>
724 <?php
725 // Ensure $clients is defined
726 if (!isset($clients) || empty($clients)) {
727 $clients = [];
728 $ei_selected_client_id = ($invoice && method_exists($invoice, 'getClientId')) ? (int) $invoice->getClientId() : 0;
729 if ($ei_selected_client_id > 0) {
730 $ei_selected_client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($ei_selected_client_id);
731 if ($ei_selected_client) { $clients = [ $ei_selected_client ]; }
732 }
733 }
734
735 // Output client options
736 if (!empty($clients)) {
737 foreach ($clients as $client) {
738 // Get the WordPress user data directly
739 $user = get_user_by('id', $client->getId());
740 if (!$user) {
741 continue;
742 }
743
744 // Use Client model properties first, fallback to WordPress user fields
745 $business_name = $client->business_client_name ?: '';
746 $first_name = $client->first_name ?: $user->first_name ?: '';
747 $last_name = $client->last_name ?: $user->last_name ?: '';
748 $client_email = $client->email ?: $user->user_email ?: '';
749
750 // Create display name
751 $client_name = $business_name ?: ($first_name . ' ' . $last_name);
752 if (empty(trim($client_name))) {
753 $client_name = $user->display_name ?: 'User ' . $client->getId();
754 }
755
756 $selected = $invoice && $invoice->getClientId() == $client->getId() ? 'selected' : '';
757 printf(
758 '<option value="%s" %s data-name="%s" data-email="%s">%s (%s)</option>',
759 esc_attr($client->getId()),
760 $selected, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output of selected( ..., false ).
761 esc_attr($client_name),
762 esc_attr($client_email),
763 esc_html($client_name),
764 esc_html($client_email)
765 );
766 }
767 }
768 ?>
769 </select>
770 </div>
771
772 <div class="border-t border-gray-200 pt-4 mt-4">
773 <input type="hidden" name="client_id" id="client-id" value="<?php echo $invoice ? esc_attr($invoice->getClientId()) : ''; ?>">
774
775 <div id="client-info-display" class="mt-6 <?php echo $invoice && $invoice->getClientId() ? '' : 'hidden'; ?>">
776 <div class="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
777 <div class="flex items-center justify-between pb-4 border-b border-gray-200 mb-4">
778 <h4 class="text-lg font-semibold text-gray-800 flex items-center">
779 <i class="fas fa-user-check text-indigo-500 mr-3"></i>
780 <span><?php esc_html_e('Client Information', 'easy-invoice'); ?></span>
781 </h4>
782 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-client-edit&client_id=' . ($invoice && $invoice->getClientId() ? $invoice->getClientId() : ''))); ?>"
783 id="edit-selected-client"
784 class="text-sm text-indigo-600 hover:text-indigo-800 font-medium<?php echo ($invoice && $invoice->getClientId()) ? '' : ' hidden'; ?>"
785 target="_blank">
786 <i class="fas fa-pen mr-1"></i>
787 <?php esc_html_e('Edit', 'easy-invoice'); ?>
788 </a>
789 </div>
790
791 <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 pt-2">
792 <div class="flex items-start group">
793 <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-user-tie text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div>
794 <div class="ml-3">
795 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Name', 'easy-invoice'); ?></label>
796 <p id="display-client-name" class="text-sm font-semibold text-gray-800 mt-1">
797 <?php
798 if ($invoice && $invoice->getClientId()) {
799 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
800 if ($client) {
801 echo esc_html($client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()));
802 } else {
803 echo esc_html__('', 'easy-invoice');
804 }
805 } else {
806 echo esc_html__('', 'easy-invoice');
807 }
808 ?>
809 </p>
810 </div>
811 </div>
812
813 <div class="flex items-start group">
814 <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-building text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div>
815 <div class="ml-3">
816 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Company', 'easy-invoice'); ?></label>
817 <p id="display-client-company" class="text-sm font-semibold text-gray-800 mt-1">
818 <?php
819 if ($invoice && $invoice->getClientId()) {
820 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
821 if ($client) {
822 echo esc_html($client->getBusinessClientName() ?: esc_html__('', 'easy-invoice'));
823 } else {
824 echo esc_html__('', 'easy-invoice');
825 }
826 } else {
827 echo esc_html__('', 'easy-invoice');
828 }
829 ?>
830 </p>
831 </div>
832 </div>
833
834 <div class="flex items-start group">
835 <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-envelope text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div>
836 <div class="ml-3">
837 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Email', 'easy-invoice'); ?></label>
838 <p id="display-client-email" class="text-sm text-gray-800 mt-1 break-all">
839 <?php
840 if ($invoice && $invoice->getClientId()) {
841 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
842 if ($client) {
843 echo esc_html($client->getEmail() ?: esc_html__('', 'easy-invoice'));
844 } else {
845 echo esc_html__('', 'easy-invoice');
846 }
847 } else {
848 echo esc_html__('', 'easy-invoice');
849 }
850 ?>
851 </p>
852 </div>
853 </div>
854
855 <div class="flex items-start group">
856 <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-phone-alt text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div>
857 <div class="ml-3">
858 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Phone', 'easy-invoice'); ?></label>
859 <p id="display-client-phone" class="text-sm text-gray-800 mt-1">
860 <?php
861 if ($invoice && $invoice->getClientId()) {
862 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
863 if ($client) {
864 echo esc_html($client->getExtraInfo() ?: esc_html__('', 'easy-invoice'));
865 } else {
866 echo esc_html__('', 'easy-invoice');
867 }
868 } else {
869 echo esc_html__('', 'easy-invoice');
870 }
871 ?>
872 </p>
873 </div>
874 </div>
875
876 <div class="flex items-start group md:col-span-2">
877 <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-globe-americas text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div>
878 <div class="ml-3">
879 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Website', 'easy-invoice'); ?></label>
880 <p id="display-client-website" class="text-sm text-gray-800 mt-1">
881 <?php
882 if ($invoice && $invoice->getClientId()) {
883 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
884 if ($client) {
885 echo esc_html($client->getWebsite() ?: esc_html__('', 'easy-invoice'));
886 } else {
887 echo esc_html__('', 'easy-invoice');
888 }
889 } else {
890 echo esc_html__('', 'easy-invoice');
891 }
892 ?>
893 </p>
894 </div>
895 </div>
896
897 <div class="flex items-start group md:col-span-2">
898 <div class="flex-shrink-0 w-8 text-center pt-1"><i class="fas fa-map-marker-alt text-gray-400 group-hover:text-indigo-500 transition-colors"></i></div>
899 <div class="ml-3">
900 <label class="block text-xs font-medium text-gray-500 uppercase tracking-wider"><?php esc_html_e('Address', 'easy-invoice'); ?></label>
901 <p id="display-client-address" class="text-sm text-gray-800 mt-1 whitespace-pre-wrap">
902 <?php
903 if ($invoice && $invoice->getClientId()) {
904 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
905 if ($client) {
906 echo esc_html($client->getAddress() ?: esc_html__('', 'easy-invoice'));
907 } else {
908 echo esc_html__('', 'easy-invoice');
909 }
910 } else {
911 echo esc_html__('', 'easy-invoice');
912 }
913 ?>
914 </p>
915 </div>
916 </div>
917 </div>
918 </div>
919 </div>
920
921 <div id="no-client-message" class="mt-4 text-center py-6 border-2 border-dashed border-gray-300 rounded-lg <?php echo $invoice && $invoice->getClientId() ? 'hidden' : ''; ?>">
922 <p class="text-gray-600"><?php esc_html_e('Select a client from the dropdown above to view their information.', 'easy-invoice'); ?></p>
923 </div>
924 </div>
925 <?php elseif ($tab['id'] === 'discounts-taxes-tab'): ?>
926 <div>
927 <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Discount & Tax Settings', 'easy-invoice'); ?></h3>
928 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
929 <?php
930 // Get all discount and tax fields
931 $discount_fields = $form_manager->getFieldsBySection('discounts-taxes-tab', 'discount');
932 $tax_fields = $form_manager->getFieldsBySection('discounts-taxes-tab', 'tax');
933 $additional_tax_fields = $form_manager->getFieldsBySection('discounts-taxes-tab', 'additional_tax');
934
935 // Combine and sort by order
936 $all_fields = array_merge($discount_fields, $tax_fields, $additional_tax_fields);
937 usort($all_fields, function($a, $b) {
938 return ($a['order'] ?? 0) - ($b['order'] ?? 0);
939 });
940
941 foreach ($all_fields as $field):
942 echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
943 endforeach;
944 ?>
945 </div>
946 </div>
947 <?php elseif ($tab['id'] === 'payments-tab'): ?>
948 <div>
949 <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Payment Information', 'easy-invoice'); ?></h3>
950 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
951 <?php
952 $payment_fields = $form_manager->getFieldsBySection('payments-tab', 'payment');
953 foreach ($payment_fields as $field):
954 echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
955 endforeach;
956 ?>
957 </div>
958
959 <div class="mt-8 border-t border-gray-200 pt-6">
960 <h3 class="text-lg font-medium text-gray-900"><?php esc_html_e('Currency', 'easy-invoice'); ?></h3>
961 <div class="mt-4 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
962 <?php
963 $currency_fields = $form_manager->getFieldsBySection('payments-tab', 'currency');
964 foreach ($currency_fields as $field):
965 echo $form_manager->renderField($field, $invoice); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FieldRenderer escapes every attribute and label it emits.
966 endforeach;
967 ?>
968 </div>
969 </div>
970 </div>
971 <?php endif; ?>
972 </div>
973 <?php endforeach; ?>
974 </form>
975 </div>
976
977 <div id="template-gallery-modal" style="display: none;" class="fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full z-50 flex items-center justify-center">
978 <div class="mx-auto p-6 border w-full md:w-4/5 max-w-4xl shadow-lg rounded-md bg-white max-h-[80vh] overflow-y-auto">
979 <div class="flex justify-between items-center mb-4">
980 <h3 class="text-xl font-medium text-gray-900"><?php esc_html_e('Browse Invoice Templates', 'easy-invoice'); ?></h3>
981 <button type="button" class="close-gallery-modal text-gray-400 hover:text-gray-500 focus:outline-none">
982 <span class="sr-only"><?php esc_html_e('Close', 'easy-invoice'); ?></span>
983 <i class="fas fa-times text-xl"></i>
984 </button>
985 </div>
986
987 <div class="templates-grid grid grid-cols-2 md:grid-cols-3 gap-4 mb-4 max-h-[45vh] overflow-y-auto p-2">
988 <?php
989 // Display all template options in the modal
990 foreach ($all_templates as $template_id => $template) :
991 $is_current = ($current_template === $template_id);
992 $selected_class = $is_current ? 'selected' : '';
993 ?>
994 <div class="gallery-template-card <?php echo esc_attr($selected_class); ?>" data-template-id="<?php echo esc_attr($template_id); ?>" data-is-premium="<?php echo isset($template['is_free']) && !$template['is_free'] ? 'true' : 'false'; ?>">
995 <div class="p-4 border rounded-lg">
996 <div class="flex items-center mb-2">
997 <i class="<?php echo esc_attr($template['icon']); ?> text-indigo-600 mr-2"></i>
998 <span class="text-sm font-medium"><?php echo esc_html($template['name']); ?></span>
999 </div>
1000 <p class="text-sm text-gray-600"><?php echo esc_html($template['description']); ?></p>
1001 <div class="mt-2 flex-grow">
1002 <div class="template-preview bg-gray-100 rounded h-16 flex items-center justify-center <?php echo esc_attr($template_id); ?>">
1003 </div>
1004 </div>
1005 </div>
1006 </div>
1007 <?php endforeach; ?>
1008 </div>
1009
1010 <div class="flex justify-end mt-4 space-x-3">
1011 <button type="button" id="apply-selected-template" class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
1012 <?php esc_html_e('Apply', 'easy-invoice'); ?>
1013 </button>
1014 <button type="button" class="gallery-close-btn 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">
1015 <?php esc_html_e('Close', 'easy-invoice'); ?>
1016 </button>
1017 </div>
1018 </div>
1019 </div>
1020
1021 <template id="invoice-item-template" style="display: none;">
1022 <div class="invoice-item p-6 rounded-xl shadow-sm border border-blue-100 hover:shadow-md transition-all duration-200 mb-6" style="background-color: #f8fbff;">
1023 <div class="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
1024 <div class="flex items-center">
1025 <button type="button" class="item-collapse-toggle mr-3 text-gray-400 hover:text-indigo-600 focus:outline-none transition-colors">
1026 <i class="fas fa-chevron-down text-sm"></i>
1027 </button>
1028 <h3 class="text-base font-normal text-gray-800 flex-1">
1029 <?php
1030 echo !empty($title) ? esc_html($title) : esc_html__('New Item', 'easy-invoice');
1031 ?>
1032 </h3>
1033 </div>
1034 <div class="flex items-center gap-3">
1035 <div class="item-collapsed-summary hidden text-sm text-gray-600 bg-gray-50 px-3 py-1 rounded-full">
1036 <span class="quantity-summary">0</span> × $<span class="price-summary">0.00</span>
1037 = $<span class="total-summary">0.00</span>
1038 </div>
1039 <button type="button" class="fill-sample-data-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Fill this item with sample data', 'easy-invoice')); ?>">
1040 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
1041 <path d="M2 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H2zm0 1h16v14H2V3z"/>
1042 <path d="M4 5h12v1.5H4V5zm0 3h10v1.5H4V8zm0 3h11v1.5H4V11zm0 3h8v1.5H4V14z"/>
1043 <path d="M14 14l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
1044 <path d="M14 17h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
1045 </svg>
1046 </button>
1047 <?php if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')): ?>
1048 <button type="button" class="item-library-btn text-gray-400 hover:text-blue-600 p-2 rounded-lg hover:bg-blue-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Select from Item Library', 'easy-invoice')); ?>">
1049 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
1050 <path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"/>
1051 </svg>
1052 </button>
1053 <button type="button" class="save-to-library-btn text-gray-400 hover:text-green-600 p-2 rounded-lg hover:bg-green-50 transition-all duration-200" data-tooltip="<?php echo esc_js(__('Save to Item Library', 'easy-invoice')); ?>" data-nonce="<?php echo esc_attr(wp_create_nonce('easy_invoice_pro_item_library')); ?>">
1054 <svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" class="inline-block">
1055 <path d="M7.707 10.293a1 1 0 10-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 11.586V6h5a2 2 0 012 2v7a2 2 0 01-2 2H4a2 2 0 01-2-2V8a2 2 0 012-2h5v5.586l-1.293-1.293zM9 4a1 1 0 012 0v2H9V4z"/>
1056 </svg>
1057 </button>
1058 <?php endif; ?>
1059 <button type="button" class="remove-item text-gray-400 hover:text-red-600 hover:bg-red-50 p-2 rounded-lg transition-all duration-200 text-sm font-medium">
1060 <i class="fas fa-trash-alt mr-1"></i> <?php esc_html_e('Remove', 'easy-invoice'); ?>
1061 </button>
1062 </div>
1063 </div>
1064 <div class="item-content">
1065 <?php echo $form_manager->generateItemTemplateHtml(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- form markup built by ItemFieldRenderer, which escapes every value. ?>
1066 </div>
1067 </div>
1068 </template>
1069
1070 <?php
1071 // The "Add New Client" modal used to be duplicated here. This file is
1072 // included by templates/invoices/builder.php, which renders its own
1073 // copy of the modal a few lines later — so #add_client_modal existed
1074 // twice in the DOM. Every `$('#add_client_modal')` binding matched the
1075 // first one, which was this one: the older markup, missing the
1076 // role="dialog" / aria-modal / aria-labelledby attributes the builder's
1077 // copy carries. The accessible modal was inert dead markup.
1078 //
1079 // Removed here, keeping the builder's version — which is exactly how
1080 // the quote side is already structured (quotes/form.php has no modal;
1081 // quotes/builder.php owns the only one).
1082 ?>
1083
1084 <?php
1085
1086 // Always localize script with AJAX URL and nonce
1087 wp_localize_script('easy-invoice-form', 'easyInvoice', array(
1088 'ajaxUrl' => admin_url('admin-ajax.php'),
1089 'nonce' => wp_create_nonce('easy_invoice_nonce'),
1090 'clientData' => $client_data ?: array()
1091 ));
1092 ?>
1093
1094 <script>
1095 jQuery(document).ready(function($) {
1096
1097
1098 // Client search functionality for invoice form
1099 var searchTimeout;
1100
1101 // Initialize existing client data
1102 var existingClientId = '<?php echo $invoice ? esc_js($invoice->getClientId()) : ''; ?>';
1103 var existingClientName = '<?php
1104 if ($invoice && $invoice->getClientId()) {
1105 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
1106 if ($client) {
1107 echo esc_js($client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()));
1108 }
1109 }
1110 ?>';
1111 var existingClientEmail = '<?php
1112 if ($invoice && $invoice->getClientId()) {
1113 $client = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository()->find($invoice->getClientId());
1114 if ($client) {
1115 echo esc_js($client->getEmail() ?: '');
1116 }
1117 }
1118 ?>';
1119 var existingClientData = <?php echo json_encode($client_data); ?>;
1120
1121 if (existingClientId && existingClientName && existingClientName !== '-') {
1122 // Show the selected client display and client info display
1123 $('#selected-client-display').show();
1124 $('#client-info-display').show();
1125 $('#no-client-message').hide();
1126
1127 // Set the select client dropdown value
1128 $('#select-client').val(existingClientId);
1129
1130 // Populate the search input with the existing client name
1131 $('#client-search-input').val(existingClientName);
1132
1133
1134 }
1135
1136 // Client search input functionality
1137 $('#client-search-input').on('input', function() {
1138 const query = $(this).val().trim();
1139 clearTimeout(searchTimeout);
1140
1141 // Don't hide results immediately for short queries - let the search function handle it
1142 if (query.length === 0) {
1143 // Show all clients when input is cleared
1144 searchClients('');
1145 return;
1146 }
1147
1148 searchTimeout = setTimeout(function() {
1149 searchClients(query);
1150 }, 300);
1151 });
1152
1153 // Focus on search input - ensure client tab is active
1154 $('#client-search-input').on('focus', function() {
1155 // Switch to client tab first to ensure the search results are visible
1156 if (window.switchToTab) {
1157 window.switchToTab('client-tab');
1158 } else {
1159 // Fallback: manually switch tabs
1160 $('.tab-button').removeClass('border-indigo-500 text-indigo-600').addClass('border-transparent text-gray-500');
1161 $('.tab-content').addClass('hidden');
1162 $('.tab-button[data-tab="client-tab"]').addClass('border-indigo-500 text-indigo-600');
1163 $('#client-tab').removeClass('hidden');
1164 }
1165 });
1166
1167 // Click on search input to show all clients
1168 $("#client-search-input").on("click", function() {
1169 // Show the dropdown and call AJAX to populate it
1170 $('#client-search-results').show();
1171 $('#client-search-loading').show();
1172 $('#client-search-empty').hide();
1173
1174 // Call the AJAX method to populate the dropdown
1175 if (typeof easyInvoice !== 'undefined' && easyInvoice.ajaxUrl) {
1176 searchClients('');
1177 } else {
1178 $('#client-search-loading').hide();
1179 $('#client-search-empty').show();
1180 }
1181 });
1182
1183 // Client selection
1184 $(document).on('click', '.client-option', function(e) {
1185 e.preventDefault();
1186 e.stopPropagation();
1187
1188 const clientId = $(this).data('client-id');
1189 const clientName = $(this).data('client-name');
1190 const clientEmail = $(this).data('client-email');
1191 const clientCompany = $(this).data('client-company');
1192 const clientPhone = $(this).data('client-phone');
1193 const clientWebsite = $(this).data('client-website');
1194 const clientAddress = $(this).data('client-address');
1195
1196 // Create client data object with all available information
1197 const clientData = {
1198 name: clientName,
1199 email: clientEmail,
1200 company: clientCompany,
1201 phone: clientPhone,
1202 website: clientWebsite,
1203 address: clientAddress
1204 };
1205
1206 selectClient(clientId, clientName, clientEmail, clientData);
1207 $('#client-search-results').hide();
1208 $('#client-search-input').val('');
1209 });
1210
1211 // Alternative click handler for client options
1212 $('#client-search-list').on('click', '.client-option', function(e) {
1213 e.preventDefault();
1214 e.stopPropagation();
1215
1216 const clientId = $(this).data('client-id');
1217 const clientName = $(this).data('client-name');
1218 const clientEmail = $(this).data('client-email');
1219 const clientCompany = $(this).data('client-company');
1220 const clientPhone = $(this).data('client-phone');
1221 const clientWebsite = $(this).data('client-website');
1222 const clientAddress = $(this).data('client-address');
1223
1224 // Create client data object with all available information
1225 const clientData = {
1226 name: clientName,
1227 email: clientEmail,
1228 company: clientCompany,
1229 phone: clientPhone,
1230 website: clientWebsite,
1231 address: clientAddress
1232 };
1233
1234 selectClient(clientId, clientName, clientEmail, clientData);
1235 $('#client-search-results').hide();
1236 $('#client-search-input').val('');
1237 });
1238
1239 // Clear client selection
1240 $('#clear-client-selection').on('click', function() {
1241 clearClientSelection();
1242 });
1243
1244 // Add new client button
1245 $('#add-new-client-btn').on('click', function() {
1246 $('#add_client_modal').show();
1247 });
1248
1249 // Close modal
1250 $('.close-modal').on('click', function() {
1251 $('#add_client_modal').hide();
1252 });
1253
1254 // Functions
1255 function searchClients(query) {
1256 $('#client-search-loading').show();
1257 $('#client-search-empty').hide();
1258 $('#client-search-list').empty();
1259
1260 // If query is empty or less than 2 characters, show all clients
1261 if (!query || query.trim().length < 2) {
1262 query = ''; // Set to empty string to get all clients
1263 }
1264
1265 $.ajax({
1266 url: easyInvoice.ajaxUrl,
1267 type: 'POST',
1268 data: {
1269 action: 'easy_invoice_search_clients',
1270 query: query,
1271 nonce: easyInvoice.nonce
1272 },
1273 success: function(response) {
1274 $('#client-search-loading').hide();
1275
1276 if (response.success && response.data.length > 0) {
1277 response.data.forEach(function(client) {
1278 // Built with jQuery, never by string interpolation: the name, company
1279 // and address are what the client typed into their portal profile.
1280 const option = $('<div>', {
1281 'class': 'client-option px-4 py-3 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0',
1282 'data-client-id': client.id,
1283 'data-client-name': client.name || '',
1284 'data-client-email': client.email || '',
1285 'data-client-company': client.company || '',
1286 'data-client-phone': client.phone || '',
1287 'data-client-website': client.website || '',
1288 'data-client-address': client.address || ''
1289 }).on('click', function() { selectClientFromOption(this); })
1290 .append($('<div>', { 'class': 'font-medium text-gray-900' }).text(client.name || ''))
1291 .append($('<div>', { 'class': 'text-sm text-gray-600' }).text(client.email || ''));
1292
1293 $('#client-search-list').append(option);
1294 });
1295 $('#client-search-results').show();
1296 } else {
1297 $('#client-search-empty').show();
1298 $('#client-search-results').show();
1299 }
1300 },
1301 error: function(xhr, status, error) {
1302 $('#client-search-loading').hide();
1303 $('#client-search-empty').show();
1304 $('#client-search-results').show();
1305 }
1306 });
1307 }
1308
1309 function selectClient(clientId, clientName, clientEmail, clientData = null) {
1310
1311 $('#select-client').val(clientId);
1312 $('#client-id').val(clientId);
1313 $('#selected-client-name').text(clientName);
1314 $('#selected-client-email').text(clientEmail);
1315
1316 $('#selected-client-display').show().removeClass('hidden');
1317 $('#client-info-display').show().removeClass('hidden');
1318 $('#no-client-message').hide().removeClass('hidden');
1319
1320 // Update the edit client button URL
1321 $('#edit-selected-client').attr('href', '<?php echo esc_url(admin_url('admin.php?page=easy-invoice-client-edit&client_id=')); ?>' + clientId);
1322 $('#edit-selected-client').toggleClass('hidden', !clientId);
1323
1324 // If we have full client data, populate all fields directly
1325 if (clientData) {
1326 $('#display-client-name').text(clientData.name || clientName || '-');
1327 $('#display-client-company').text(clientData.company || '-');
1328 $('#display-client-email').text(clientData.email || clientEmail || '-');
1329 $('#display-client-phone').text(clientData.phone || '-');
1330 $('#display-client-website').text(clientData.website || '-');
1331 $('#display-client-address').text(clientData.address || '-');
1332 } else {
1333 // Fallback to just the basic info we have
1334 $('#display-client-name').text(clientName);
1335 $('#display-client-email').text(clientEmail);
1336 $('#display-client-company').text('-');
1337 $('#display-client-phone').text('-');
1338 $('#display-client-website').text('-');
1339 $('#display-client-address').text('-');
1340 }
1341 }
1342
1343 function clearClientSelection() {
1344 $('#select-client').val('');
1345 $('#client-id').val('');
1346 $('#selected-client-display').hide();
1347 $('#client-info-display').hide();
1348 $('#no-client-message').show();
1349
1350 // Clear display fields
1351 $('#display-client-name, #display-client-email, #display-client-company, #display-client-phone, #display-client-website, #display-client-address').text('-');
1352 }
1353
1354 // Global function for inline onclick
1355 window.selectClientFromOption = function(element) {
1356 const $element = $(element);
1357 const clientId = $element.data('client-id');
1358 const clientName = $element.data('client-name');
1359 const clientEmail = $element.data('client-email');
1360 const clientCompany = $element.data('client-company');
1361 const clientPhone = $element.data('client-phone');
1362 const clientWebsite = $element.data('client-website');
1363 const clientAddress = $element.data('client-address');
1364
1365 // Create client data object with all available information
1366 const clientData = {
1367 name: clientName,
1368 email: clientEmail,
1369 company: clientCompany,
1370 phone: clientPhone,
1371 website: clientWebsite,
1372 address: clientAddress
1373 };
1374
1375 selectClient(clientId, clientName, clientEmail, clientData);
1376 $('#client-search-results').hide();
1377 $('#client-search-input').val('');
1378 };
1379 });
1380 </script>
1381
1382 <script>
1383 // Invoice field configuration and pre-loaded data
1384 window.easyInvoice = window.easyInvoice || {};
1385 <?php
1386 $json_field_config = wp_json_encode($invoice_field_config);
1387 ?>
1388 window.easyInvoice.fieldConfig = <?php echo $json_field_config; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_json_encode() output. ?>;
1389 window.easyInvoice.adminUrl = '<?php echo esc_url(admin_url()); ?>';
1390 window.easyInvoice.isPro = <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>;
1391 window.easyInvoice.translations = {
1392 premiumTemplates: '<?php echo esc_js(__('Premium Templates', 'easy-invoice')); ?>',
1393 premiumTemplatesDescription: '<?php echo esc_js(__('Access all premium templates including Modern, Professional, Classic, and more to give your invoices a unique and professional look.', 'easy-invoice')); ?>',
1394 premiumFeatureMessage: '<?php echo esc_js(__('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice')); ?>',
1395 selectTemplateFirst: '<?php echo esc_js(__('Please select a template first', 'easy-invoice')); ?>'
1396 };
1397 <?php if ($client_data): ?>
1398 window.easyInvoice.clientData = <?php echo json_encode($client_data); ?>;
1399 <?php endif; ?>
1400 </script>
1401