PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.2.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.2.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 / settings-page.php

settings-page.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.2.0, at templates/settings-page.php

1,300 lines 117.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Settings page template for Easy Invoice
4 *
5 * @package Easy_Invoice
6 * @since 1.0.0
7 */
8
9 // Exit if accessed directly
10 if (!defined('ABSPATH')) {
11 exit;
12 }
13
14 /**
15 * Helper function to render a single settings field.
16 *
17 * @param string $option_key The option key.
18 * @param array $field_config The field configuration.
19 * @param mixed $current_value The current value of the field.
20 */
21 function easy_invoice_render_field($option_key, $field_config, $current_value) {
22 $label = esc_html($field_config['label'] ?? '');
23 $type = $field_config['type'] ?? 'text';
24 $default = $field_config['default'] ?? '';
25 $value = $current_value ?? $default;
26 $description_text = $field_config['description'] ?? '';
27 $field_id = esc_attr($option_key);
28 $field_name = 'settings[' . esc_attr($option_key) . ']';
29 $col_span_class = esc_attr($field_config['col_span'] ?? 'sm:col-span-6');
30 $placeholder = isset($field_config['placeholder']) ? esc_attr($field_config['placeholder']) : '';
31 $required = !empty($field_config['required']) ? 'required' : '';
32 $aria_describedby = !empty($description_text) ? 'aria-describedby="' . $field_id . '-description"' : '';
33
34 echo '<div class=" ' . $col_span_class . '">'; // Added 'form-group' class for JS show/hide functionality
35
36 // Special layout for checkboxes with descriptions (e.g., Invoice Numbering type)
37 if ($type === 'checkbox' && !empty($description_text) && ($option_key === 'easy_invoice_invoice_numbering')) {
38 echo '<div class="flex items-start space-x-3 p-4 bg-gray-50 rounded-lg border border-gray-200">';
39 echo ' <div class="flex items-center h-5 mt-0.5">';
40 echo ' <input type="checkbox" name="' . $field_name . '" id="' . $field_id . '" value="yes" ' . checked($value, 'yes', false) . ' class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded" ' . $required . '>';
41 echo ' </div>';
42 echo ' <div class="flex-1">';
43 echo ' <label for="' . $field_id . '" class="block text-sm font-medium text-gray-700 mb-1">' . $label . '</label>';
44 echo ' <p id="' . $field_id . '-description" class="text-sm text-gray-400 leading-relaxed">' . wp_kses($description_text, array(
45 'a' => array(
46 'href' => array(),
47 'target' => array(),
48 'class' => array()
49 )
50 )) . '</p>';
51 echo ' </div>';
52 echo '</div>';
53 } else {
54 // Standard label for other types, or checkboxes without the special description structure
55 if ($type !== 'checkbox') {
56 echo '<label for="' . $field_id . '" class="block text-sm font-medium text-gray-700 mb-2">' . $label . '</label>';
57 }
58
59 $input_class = 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm';
60 $select_class = 'mt-1 block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md';
61
62
63 switch ($type) {
64 case 'text':
65 case 'email':
66 case 'url':
67 case 'tel':
68 echo '<input type="' . esc_attr($type) . '" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '" class="' . $input_class . '" placeholder="' . $placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
69 break;
70 case 'number':
71 $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : '';
72 $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : '';
73 $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : '';
74 echo '<input type="number" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '"' . $min . $max . $step . ' class="' . $input_class . '" placeholder="' . $placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
75 break;
76 case 'textarea':
77 echo '<textarea id="' . $field_id . '" name="' . $field_name . '" rows="3" class="' . $input_class . '" placeholder="' . $placeholder . '" ' . $required . ' ' . $aria_describedby . '>' . wp_kses_post($value) . '</textarea>';
78 break;
79 case 'select':
80 echo '<select id="' . $field_id . '" name="' . $field_name . '" class="' . $select_class . '" ' . $required . ' ' . $aria_describedby . '>';
81 if (!empty($field_config['options']) && is_array($field_config['options'])) {
82 foreach ($field_config['options'] as $opt_val => $opt_label) {
83 echo '<option value="' . esc_attr($opt_val) . '" ' . selected($value, $opt_val, false) . '>' . esc_html($opt_label) . '</option>';
84 }
85 }
86 echo '</select>';
87 break;
88 case 'multiselect':
89 echo '<select id="' . $field_id . '" name="' . $field_name . '[]" class="' . $select_class . '" multiple="multiple" aria-label="' . esc_attr($label) . '">';
90 if (!empty($field_config['options']) && is_array($field_config['options'])) {
91 foreach ($field_config['options'] as $opt_val => $opt_label) {
92 $selected = is_array($value) && in_array($opt_val, $value) ? 'selected="selected"' : '';
93 echo '<option value="' . esc_attr($opt_val) . '" ' . $selected . '>' . esc_html($opt_label) . '</option>';
94 }
95 }
96 echo '</select>';
97 break;
98 case 'readonly':
99 echo '<input type="text" id="' . $field_id . '" value="' . esc_attr($value) . '" class="' . $input_class . ' bg-gray-50" readonly ' . $aria_describedby . '>';
100 echo '<input type="hidden" name="' . $field_name . '" value="' . esc_attr($value) . '">';
101 break;
102 case 'checkbox': // Checkboxes that don't use the special description layout
103 echo '<div class="flex items-center">'; // Original 'tax_enabled' was just this simple structure
104 echo ' <input type="checkbox" name="' . $field_name . '" id="' . $field_id . '" value="yes" ' . checked($value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" ' . $required . ' ' . $aria_describedby . '>';
105 echo ' <label for="' . $field_id . '" class="ml-2 block text-sm text-gray-900">' . $label . '</label>';
106 echo '</div>';
107 if (!empty($description_text)) { // Show description below the field
108 echo '<p id="' . $field_id . '-description" class="mt-2 text-sm text-gray-400 leading-relaxed">' . wp_kses($description_text, array(
109 'a' => array(
110 'href' => array(),
111 'target' => array(),
112 'class' => array()
113 )
114 )) . '</p>';
115 }
116 break;
117 case 'image':
118 echo '<div class="mt-1 flex items-center">';
119 echo ' <span class="inline-block h-12 w-12 rounded-full overflow-hidden bg-gray-100">';
120 echo ' <img id="' . $field_id . '-preview" src="' . esc_url($value) . '" alt="' . esc_attr($label) . '" class="h-full w-full object-cover' . (empty($value) ? ' hidden' : '') . '">';
121 echo ' </span>';
122 echo ' <button type="button" id="upload_image_button_' . $field_id . '" class="upload-logo-button ml-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-uploader_title="' . esc_attr__('Choose Logo', 'easy-invoice') . '" data-uploader_button_text="' . esc_attr__('Select Logo', 'easy-invoice') . '">';
123 echo esc_html__('Change', 'easy-invoice');
124 echo '</button>';
125 echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '" ' . $required . ' ' . $aria_describedby . '>';
126 echo '</div>';
127 break;
128 case 'wp_editor':
129 // For wp_editor, the label is usually handled before calling it.
130 // The col_span div wraps this.
131 wp_editor( $value, $field_id, ['textarea_name' => $field_name, 'teeny' => true, 'media_buttons' => false, 'textarea_rows' => 7, 'editor_class' => 'mt-1'] );
132 // Handle description for wp_editor specifically
133 if (!empty($description_text)) {
134 echo '<p id="' . $field_id . '-description" class="mt-1 text-sm text-gray-400">' . wp_kses($description_text, array(
135 'a' => array(
136 'href' => array(),
137 'target' => array(),
138 'class' => array()
139 )
140 )) . '</p>';
141 }
142 break;
143 case 'test_email':
144 echo '<div class="mt-1">';
145 echo ' <div class="flex items-center space-x-3">';
146 echo ' <input type="email" id="' . $field_id . '_email" placeholder="' . esc_attr__('Enter email address to test', 'easy-invoice') . '" class="mt-1 block w-[200px] border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" value="' . esc_attr(get_option('admin_email')) . '">';
147 echo ' <button type="button" id="' . $field_id . '_button" class="test-email-button inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 h-[38px]">';
148 echo ' <i class="fas fa-paper-plane mr-2" aria-hidden="true"></i>';
149 echo ' ' . esc_html__('Send Test Email', 'easy-invoice');
150 echo ' </button>';
151 echo ' </div>';
152 echo ' <div id="' . $field_id . '_result" class="mt-2" style="display: none;"></div>';
153 echo '</div>';
154 break;
155 case 'color':
156 echo '<input type="color" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '" class="h-10 w-20 border border-gray-300 rounded">';
157 break;
158 case 'range':
159 $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : '';
160 $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : '';
161 $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : '';
162 echo '<input type="range" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '"' . $min . $max . $step . ' class="w-full">';
163 echo '<span class="text-sm text-gray-400">' . esc_html($value) . '%</span>';
164 break;
165 case 'button':
166 $button_text = $field_config['button_text'] ?? $label;
167 $button_class = $field_config['button_class'] ?? 'button button-secondary';
168 $button_class_name = 'regenerate-invoice-numbers-button';
169 if ($option_key === 'easy_invoice_regenerate_quote_numbers') {
170 $button_class_name = 'regenerate-quote-numbers-button';
171 }
172 echo '<button type="button" id="' . $field_id . '" class="' . esc_attr($button_class) . ' ' . $button_class_name . '">';
173 echo esc_html($button_text);
174 echo '</button>';
175 break;
176 case 'image_upload':
177 echo '<div class="image-upload-wrap">';
178 echo '<div class="flex items-center space-x-3">';
179 echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '">';
180 echo ' <button type="button" class="upload-image-button bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-target="' . $field_id . '">';
181 echo esc_html__('Upload Image', 'easy-invoice');
182 echo '</button>';
183 echo '</div>';
184 echo '<div class="image-preview mt-2' . (empty($value) ? ' hidden' : '') . '">';
185 if (!empty($value)) {
186 echo '<img src="' . esc_url($value) . '" alt="Preview" style="max-width: 200px; max-height: 100px;">';
187 }
188 echo '</div>';
189 echo '</div>';
190 break;
191 default:
192 // Special handling for regenerate invoice numbers button
193 if ($option_key === 'easy_invoice_regenerate_invoice_numbers') {
194 $button_text = $field_config['button_text'] ?? $label;
195 $button_class = $field_config['button_class'] ?? 'button button-secondary';
196 echo '<button type="button" id="' . $field_id . '" class="' . esc_attr($button_class) . ' regenerate-invoice-numbers-button">';
197 echo esc_html($button_text);
198 echo '</button>';
199 } elseif ($option_key === 'easy_invoice_regenerate_quote_numbers') {
200 $button_text = $field_config['button_text'] ?? $label;
201 $button_class = $field_config['button_class'] ?? 'button button-secondary';
202 echo '<button type="button" id="' . $field_id . '" class="' . esc_attr($button_class) . ' regenerate-quote-numbers-button">';
203 echo esc_html($button_text);
204 echo '</button>';
205 } else {
206 echo '<input type="text" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($value) . '" class="' . $input_class . '" placeholder="' . $placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
207 }
208 break;
209 }
210 // General description for non-checkbox and non-wp_editor fields (if it exists and not handled by special layouts)
211 if ($type !== 'checkbox' && $type !== 'wp_editor' && !empty($description_text)) {
212 echo '<p id="' . $field_id . '-description" class="mt-1 text-sm text-gray-400">' . wp_kses($description_text, array(
213 'a' => array(
214 'href' => array(),
215 'target' => array(),
216 'class' => array()
217 )
218 )) . '</p>';
219 }
220 }
221 echo '</div>'; // End col_span div
222 }
223
224 /**
225 * Filter settings config to allow extensions to modify it
226 * Note: This filter is already applied in the controller, so this is redundant
227 */
228 // $settings_config = apply_filters('easy_invoice_settings_fields_config', $settings_config);
229
230 $active_section = $_GET['section'] ?? key($settings_config);
231 if (!isset($settings_config[$active_section])) {
232 $active_section = key($settings_config);
233 }
234 ?>
235
236 <div class="p-8">
237 <!-- Header with Actions -->
238 <div class="ei-app-page-header bg-white border-b border-gray-200 px-6 easy-invoice-admin-page-header">
239 <div class="flex items-center justify-between">
240 <div>
241 <h1 class="text-2xl font-bold text-gray-900"><?php esc_html_e('Settings', 'easy-invoice'); ?></h1>
242 <p class="mt-1 text-sm text-gray-400"><?php esc_html_e('Configure your invoice management system', 'easy-invoice'); ?></p>
243 </div>
244 <div class="flex items-center space-x-3">
245 <button type="button" id="save-settings" 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" aria-label="<?php esc_attr_e('Save Settings', 'easy-invoice'); ?>">
246 <i class="fas fa-save mr-2" aria-hidden="true"></i>
247 <?php esc_html_e('Save Changes', 'easy-invoice'); ?>
248 </button>
249 </div>
250 </div>
251 </div>
252
253 <!-- Main Content -->
254 <div class="max-w-7xl mx-auto">
255 <div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mt-8">
256 <!-- Left Sidebar - Navigation -->
257 <div class="lg:col-span-1">
258 <div class="bg-white shadow-lg rounded-lg sticky top-24 border border-gray-200">
259 <nav class="space-y-2 p-4" aria-label="<?php esc_attr_e('Settings Navigation', 'easy-invoice'); ?>">
260 <?php foreach ($settings_config as $section_id => $section_data) : ?>
261 <div class="space-y-1">
262 <a href="?page=easy-invoice-settings&section=<?php echo esc_attr($section_id); ?>"
263 class="nav-link group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200 <?php echo ($section_id === $active_section) ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?>"
264 aria-selected="<?php echo $section_id === $active_section ? 'true' : 'false'; ?>"
265 role="tab">
266
267 <?php if ($section_id === 'company'): ?>
268 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
269 <path d="M3 9L12 2L21 9V20C21 20.5304 20.7893 21.0391 20.4142 21.4142C20.0391 21.7893 19.5304 22 19 22H5C4.46957 22 3.96086 21.7893 3.58579 21.4142C3.21071 21.0391 3 20.5304 3 20V9Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
270 <polyline points="9,22 9,12 15,12 15,22" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
271 </svg>
272 <?php elseif ($section_id === 'invoice'): ?>
273 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
274 <path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
275 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
276 <path d="M16 13H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
277 <path d="M16 17H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
278 <path d="M10 9H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
279 </svg>
280 <?php elseif ($section_id === 'quote'): ?>
281 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
282 <path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
283 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
284 <path d="M9 9H15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
285 <path d="M9 13H15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
286 <path d="M9 17H13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
287 </svg>
288 <?php elseif ($section_id === 'currency'): ?>
289 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
290 <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
291 <path d="M12 1V23" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
292 <path d="M17 5H9.5C8.57174 5 7.6815 5.36875 7.02513 6.02513C6.36875 6.6815 6 7.57174 6 8.5C6 9.42826 6.36875 10.3185 7.02513 10.9749C7.6815 11.6313 8.57174 12 9.5 12H14.5C15.4283 12 16.3185 12.3687 16.9749 13.0251C17.6313 13.6815 18 14.5717 18 15.5C18 16.4283 17.6313 17.3185 16.9749 17.9749C16.3185 18.6313 15.4283 19 14.5 19H6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
293 </svg>
294 <?php elseif ($section_id === 'tax'): ?>
295 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
296 <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
297 <path d="M8 14L16 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
298 <path d="M8 8L16 14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
299 <path d="M12 6V18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
300 </svg>
301 <?php elseif ($section_id === 'payment'): ?>
302 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
303 <rect x="1" y="4" width="22" height="16" rx="2" ry="2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
304 <line x1="1" y1="10" x2="23" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
305 </svg>
306 <?php elseif ($section_id === 'email'): ?>
307 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
308 <path d="M4 4H20C21.1 4 22 4.9 22 6V18C22 19.1 21.1 20 20 20H4C2.9 20 2 19.1 2 18V6C2 4.9 2.9 4 4 4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
309 <polyline points="22,6 12,13 2,6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
310 </svg>
311 <?php elseif ($section_id === 'advanced'): ?>
312 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
313 <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
314 <path d="M19.4 15A1.65 1.65 0 0 0 18 14A6 6 0 0 0 6 14A1.65 1.65 0 0 0 4.6 15A2 2 0 0 0 6 19H18A2 2 0 0 0 19.4 15Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
315 </svg>
316 <?php elseif ($section_id === 'text_settings'): ?>
317 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
318 <path d="M4 7V4C4 3.44772 4.44772 3 5 3H19C19.5523 3 20 3.44772 20 4V7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
319 <path d="M4 7V20C4 20.5523 4.44772 21 5 21H19C19.5523 21 20 20.5523 20 20V7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
320 <path d="M8 11H16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
321 <path d="M8 15H12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
322 <path d="M8 19H14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
323 <path d="M12 11L14 13L12 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
324 </svg>
325 <?php else: ?>
326 <svg class="<?php echo ($section_id === $active_section) ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
327 <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
328 <path d="M19.4 15A1.65 1.65 0 0 0 18 14A6 6 0 0 0 6 14A1.65 1.65 0 0 0 4.6 15A2 2 0 0 0 6 19H18A2 2 0 0 0 19.4 15Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
329 </svg>
330 <?php endif; ?>
331
332 <span class="flex-1"><?php echo esc_html($section_data['title']); ?></span>
333 <?php if ($section_id === 'email' && !empty($section_data['subsections'])): ?>
334 <svg class="flex-shrink-0 h-4 w-4 text-gray-400 group-hover:text-gray-500 transition-transform duration-200" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
335 <polyline points="6,9 12,15 18,9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
336 </svg>
337 <?php endif; ?>
338 </a>
339
340 <?php if ($section_id === 'email' && !empty($section_data['subsections'])): ?>
341 <div class="ml-6 space-y-1 email-settings-submenu" style="display: none;">
342 <?php
343 // Only show active subsection if we're actually on an email settings page
344 $active_email_subsection = null;
345 if ($active_section === 'email') {
346 if (isset($_GET['subsection']) && isset($section_data['subsections'][$_GET['subsection']])) {
347 $active_email_subsection = $_GET['subsection'];
348 } elseif (!empty($_GET['subsection']) && isset($section_data['subsections'][$_GET['subsection']])) {
349 $active_email_subsection = $_GET['subsection'];
350 } else {
351 // Default to 'general' only if we're on email section but no specific subsection
352 $active_email_subsection = 'general';
353 }
354 }
355 ?>
356 <?php foreach ($section_data['subsections'] as $subsection_id => $subsection_data): ?>
357 <?php $is_active_subsection = ($active_email_subsection && $subsection_id === $active_email_subsection); ?>
358 <a href="?page=easy-invoice-settings&section=email&subsection=<?php echo esc_attr($subsection_id); ?>"
359 class="email-submenu-link group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-all duration-200 <?php echo $is_active_subsection ? 'text-indigo-700 bg-indigo-50 border-l-2 border-indigo-500' : 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'; ?>"
360 data-subsection="<?php echo esc_attr($subsection_id); ?>"
361 data-section="<?php echo esc_attr($section_id); ?>">
362 <svg class="mr-2 flex-shrink-0 h-4 w-4 <?php echo $is_active_subsection ? 'text-indigo-500' : 'text-gray-400 group-hover:text-gray-500'; ?>" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
363 <polyline points="9,18 15,12 9,6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
364 </svg>
365 <?php
366 // Display shorter names for better UX
367 $display_name = '';
368 switch ($subsection_id) {
369 case 'general':
370 $display_name = 'General';
371 break;
372 case 'invoice_available':
373 $display_name = 'Invoice Available';
374 break;
375 case 'quote_available':
376 $display_name = 'Quote Available';
377 break;
378 case 'payment_received':
379 $display_name = 'Payment Received';
380 break;
381 default:
382 $display_name = $subsection_data['title'];
383 break;
384 }
385 echo esc_html($display_name);
386 ?>
387 </a>
388 <?php endforeach; ?>
389 </div>
390 <?php endif; ?>
391 </div>
392 <?php endforeach; ?>
393 </nav>
394 </div>
395 </div>
396
397 <!-- Right Content - Settings -->
398 <div class="lg:col-span-2">
399 <form id="settings-form" method="post" action="">
400 <?php wp_nonce_field('easy_invoice_settings', 'easy_invoice_settings_nonce'); ?>
401 <input type="hidden" name="action" value="easy_invoice_save_settings">
402
403 <?php foreach ($settings_config as $section_id => $section_data) : ?>
404 <div id="<?php echo esc_attr($section_id); ?>"
405 class="settings-section bg-white shadow rounded-lg <?php echo ($section_id !== $active_section) ? 'hidden' : ''; ?>"
406 role="tabpanel"
407 aria-labelledby="tab-<?php echo esc_attr($section_id); ?>">
408 <div class="px-6 py-5 border-b border-gray-200">
409 <div class="flex items-center">
410 <div class="flex-shrink-0 bg-indigo-100 rounded-lg p-3">
411 <?php if (isset($section_data['icon']) && !empty($section_data['icon'])): ?>
412 <?php if (strpos($section_data['icon'], '<svg') === 0): ?>
413 <?php echo $section_data['icon']; ?>
414 <?php else: ?>
415 <i class="<?php echo esc_attr($section_data['icon']); ?> text-indigo-600 text-xl"></i>
416 <?php endif; ?>
417 <?php else: ?>
418 <svg class="text-indigo-600 text-xl h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
419 <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
420 <path d="M19.4 15A1.65 1.65 0 0 0 18 14A6 6 0 0 0 6 14A1.65 1.65 0 0 0 4.6 15A2 2 0 0 0 6 19H18A2 2 0 0 0 19.4 15Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
421 </svg>
422 <?php endif; ?>
423 </div>
424 <div class="ml-4">
425 <h2 class="text-lg font-medium text-gray-900"><?php echo esc_html($section_data['title']); ?></h2>
426 <p class="mt-1 text-sm text-gray-400"><?php echo esc_html($section_data['description']); ?></p>
427 </div>
428 </div>
429 </div>
430 <div class="px-6 py-5">
431 <div class="email-settings">
432 <?php if ($section_id === 'email' && !empty($section_data['subsections'])): ?>
433 <?php
434 // Determine the active subsection from the hash or default to 'general'
435 $active_email_subsection = 'general';
436 if (isset($_GET['subsection']) && isset($section_data['subsections'][$_GET['subsection']])) {
437 $active_email_subsection = $_GET['subsection'];
438 } elseif (!empty($_GET['subsection']) && isset($section_data['subsections'][$_GET['subsection']])) {
439 $active_email_subsection = $_GET['subsection'];
440 }
441 if (!isset($section_data['subsections'][$active_email_subsection])) {
442 $active_email_subsection = array_key_first($section_data['subsections']);
443 }
444 $subsection_data = $section_data['subsections'][$active_email_subsection];
445 ?>
446 <?php
447 // Render all email subsections so JS can show/hide them
448 foreach ($section_data['subsections'] as $subsection_key => $subsection_data) {
449 $is_active = ($subsection_key === $active_email_subsection) ? '' : 'hidden';
450 ?>
451 <div id="email--<?php echo esc_attr($subsection_key); ?>-content" class="email-subsection-content <?php echo $is_active; ?>">
452 <?php
453 // Display subsection title
454 $subsection_title = '';
455 switch ($subsection_key) {
456 case 'general':
457 $subsection_title = 'General Email Settings';
458 break;
459 case 'invoice_available':
460 $subsection_title = 'Invoice Available Email Settings';
461 break;
462 case 'quote_available':
463 $subsection_title = 'Quote Available Email Settings';
464 break;
465 case 'payment_received':
466 $subsection_title = 'Payment Received Email Settings';
467 break;
468 default:
469 $subsection_title = $subsection_data['title'];
470 break;
471 }
472 ?>
473 <div class="mb-6">
474 <h3 class="text-lg font-medium text-gray-900 mb-2"><?php echo esc_html($subsection_title); ?></h3>
475 <?php if (!empty($subsection_data['description'])): ?>
476 <p class="text-sm text-gray-400"><?php echo esc_html($subsection_data['description']); ?></p>
477 <?php endif; ?>
478 </div>
479 <div class="space-y-6">
480 <?php if (!empty($subsection_data['fields'])) {
481 foreach ($subsection_data['fields'] as $option_key => $field_config) {
482 $current_value = $settings[$option_key] ?? ($field_config['default'] ?? '');
483 $field_id = esc_attr($option_key);
484 $field_type = $field_config['type'] ?? 'text';
485 $field_label = esc_html($field_config['label'] ?? '');
486 $field_placeholder = isset($field_config['placeholder']) ? esc_attr($field_config['placeholder']) : '';
487 $field_description = $field_config['description'] ?? '';
488 $field_name = 'settings[' . esc_attr($option_key) . ']';
489 $input_class = 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm';
490 $select_class = 'mt-1 block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md';
491 $col_span_class = esc_attr($field_config['col_span'] ?? 'sm:col-span-6');
492 echo '<div class="field-wrapper form-group">';
493 if ($field_type !== 'checkbox') {
494 echo '<label for="' . $field_id . '" class="block text-sm font-medium text-gray-700">' . $field_label . '</label>';
495 }
496
497 // Define required and aria_describedby variables
498 $required = !empty($field_config['required']) ? 'required' : '';
499 $aria_describedby = !empty($field_description) ? 'aria-describedby="' . $field_id . '-description"' : '';
500
501 switch ($field_type) {
502 case 'text':
503 case 'email':
504 case 'url':
505 case 'tel':
506 echo '<input type="' . esc_attr($field_type) . '" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
507 break;
508 case 'number':
509 $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : '';
510 $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : '';
511 $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : '';
512 echo '<input type="number" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
513 break;
514 case 'textarea':
515 echo '<textarea id="' . $field_id . '" name="' . $field_name . '" rows="3" class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>' . wp_kses_post($current_value) . '</textarea>';
516 break;
517 case 'select':
518 echo '<select id="' . $field_id . '" name="' . $field_name . '" class="' . $select_class . '" ' . $required . ' ' . $aria_describedby . '>';
519 if (!empty($field_config['options']) && is_array($field_config['options'])) {
520 foreach ($field_config['options'] as $opt_val => $opt_label) {
521 echo '<option value="' . esc_attr($opt_val) . '" ' . selected($current_value, $opt_val, false) . '>' . esc_html($opt_label) . '</option>';
522 }
523 }
524 echo '</select>';
525 break;
526 case 'multiselect':
527 echo '<select id="' . $field_id . '" name="' . $field_name . '[]" class="' . $select_class . '" multiple="multiple" aria-label="' . esc_attr($field_label) . '">';
528 if (!empty($field_config['options']) && is_array($field_config['options'])) {
529 foreach ($field_config['options'] as $opt_val => $opt_label) {
530 $selected = is_array($current_value) && in_array($opt_val, $current_value) ? 'selected="selected"' : '';
531 echo '<option value="' . esc_attr($opt_val) . '" ' . $selected . '>' . esc_html($opt_label) . '</option>';
532 }
533 }
534 echo '</select>';
535 break;
536 case 'readonly':
537 echo '<input type="text" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . ' bg-gray-50" readonly>';
538 echo '<input type="hidden" name="' . $field_name . '" value="' . esc_attr($current_value) . '">';
539 break;
540 case 'checkbox':
541 echo '<div class="flex items-center">';
542 echo ' <input type="checkbox" name="' . $field_name . '" id="' . $field_id . '" value="yes" ' . checked($current_value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">';
543 echo ' <label for="' . $field_id . '" class="ml-2 block text-sm text-gray-900">' . $field_label . '</label>';
544 echo '</div>';
545 if (!empty($field_description)) {
546 echo '<p id="' . $field_id . '-description" class="mt-2 text-sm text-gray-400 leading-relaxed">' . esc_html($field_description) . '</p>';
547 }
548 break;
549 case 'image':
550 echo '<div class="mt-1 flex items-center">';
551 echo ' <span class="inline-block h-12 w-12 rounded-full overflow-hidden bg-gray-100">';
552 echo ' <img id="' . $field_id . '-preview" src="' . esc_url($current_value) . '" alt="' . esc_attr($field_label) . '" class="h-full w-full object-cover' . (empty($current_value) ? ' hidden' : '') . '">';
553 echo ' </span>';
554 echo ' <button type="button" id="upload_image_button_' . $field_id . '" class="upload-logo-button ml-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-uploader_title="' . esc_attr__('Choose Logo', 'easy-invoice') . '" data-uploader_button_text="' . esc_attr__('Select Logo', 'easy-invoice') . '">';
555 echo esc_html__('Change', 'easy-invoice');
556 echo '</button>';
557 echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '">';
558 echo '</div>';
559 break;
560 case 'wp_editor':
561 wp_editor( $current_value, $field_id, ['textarea_name' => $field_name, 'teeny' => true, 'media_buttons' => false, 'textarea_rows' => 7, 'editor_class' => 'mt-1'] );
562 // Handle description for wp_editor specifically
563 if (!empty($field_description)) {
564 echo '<p class="mt-1 text-sm text-gray-400">' . esc_html($field_description) . '</p>';
565 }
566 break;
567 case 'test_email':
568 echo '<div class="mt-1">';
569 echo ' <div class="flex items-center space-x-3">';
570 echo ' <input type="email" id="' . $field_id . '_email" placeholder="' . esc_attr__('Enter email address to test', 'easy-invoice') . '" class="mt-1 block w-[200px] border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" value="' . esc_attr(get_option('admin_email')) . '">';
571 echo ' <button type="button" id="' . $field_id . '_button" class="test-email-button inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 h-[38px]">';
572 echo ' <i class="fas fa-paper-plane mr-2" aria-hidden="true"></i>';
573 echo ' ' . esc_html__('Send Test Email', 'easy-invoice');
574 echo ' </button>';
575 echo ' </div>';
576 echo ' <div id="' . $field_id . '_result" class="mt-2" style="display: none;"></div>';
577 echo '</div>';
578 break;
579 case 'test_template_email':
580 $template_type = $field_config['template_type'] ?? '';
581 $is_payment_test = ($field_id === 'easy_invoice_payment_test_template');
582 $width_class = $is_payment_test ? 'w-[200px]' : 'w-64';
583 echo '<div class="mt-1">';
584 echo ' <div class="flex items-center space-x-3">';
585 echo ' <input type="email" id="' . $field_id . '_email" placeholder="' . esc_attr__('Enter email address to test', 'easy-invoice') . '" class="mt-1 block ' . $width_class . ' border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" value="' . esc_attr(get_option('admin_email')) . '">';
586 echo ' <button type="button" id="' . $field_id . '_button" class="test-template-email-button inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 h-[38px]" data-template-type="' . esc_attr($template_type) . '">';
587 echo ' <i class="fas fa-envelope mr-2" aria-hidden="true"></i>';
588 echo ' ' . esc_html__('Test Template', 'easy-invoice');
589 echo ' </button>';
590 echo ' </div>';
591 echo ' <div id="' . $field_id . '_result" class="mt-2" style="display: none;"></div>';
592 echo '</div>';
593 break;
594 case 'test_payment_reminder_email':
595 echo '<div class="mt-1">';
596 echo ' <div class="flex items-center space-x-3">';
597 echo ' <input type="email" id="' . $field_id . '_email" placeholder="' . esc_attr__('Enter email address to test', 'easy-invoice') . '" class="mt-1 block w-64 border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" value="' . esc_attr(get_option('admin_email')) . '">';
598 echo ' <button type="button" id="' . $field_id . '_button" class="test-payment-reminder-email-button inline-flex items-center px-3 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-orange-600 hover:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 h-[38px]">';
599 echo ' <i class="fas fa-bell mr-2" aria-hidden="true"></i>';
600 echo ' ' . esc_html__('Test Reminder', 'easy-invoice');
601 echo ' </button>';
602 echo ' </div>';
603 echo ' <div id="' . $field_id . '_result" class="mt-2" style="display: none;"></div>';
604 echo '</div>';
605 break;
606 default:
607 echo '<input type="text" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . $field_placeholder . '">';
608 break;
609 }
610 if ($field_type !== 'checkbox' && $field_type !== 'wp_editor' && !empty($field_description)) {
611 echo '<p class="mt-1 text-sm text-gray-400">' . esc_html($field_description) . '</p>';
612 }
613 echo '</div>'; // Close the field wrapper div
614 }
615 }
616 ?>
617
618 <?php
619 // Show placeholders only for specific email subsections (not General)
620 if (in_array($subsection_key, ['invoice_available', 'quote_available', 'payment_received', 'payment_reminder'])): ?>
621 <div class="mt-6">
622 <div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
623 <p class="text-sm font-medium text-blue-900 mb-3"><?php esc_html_e('Available Placeholders:', 'easy-invoice'); ?></p>
624 <div class="grid grid-cols-2 md:grid-cols-3 gap-2">
625 <?php if (in_array($subsection_key, ['invoice_available', 'payment_received', 'payment_reminder'])): ?>
626 <!-- Invoice-specific placeholders -->
627 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{invoice_number}}</div>
628 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{due_date}}</div>
629 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{invoice_url}}</div>
630 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{payment_url}}</div>
631 <?php endif; ?>
632
633 <?php if (in_array($subsection_key, ['quote_available'])): ?>
634 <!-- Quote-specific placeholders -->
635 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{quote_number}}</div>
636 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{expiry_date}}</div>
637 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{quote_url}}</div>
638 <?php endif; ?>
639
640 <!-- Common placeholders for all templates -->
641 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_name}}</div>
642 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_first_name}}</div>
643 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_last_name}}</div>
644 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_email}}</div>
645 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{client_address}}</div>
646 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{company_name}}</div>
647 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{total_amount}}</div>
648 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{issue_date}}</div>
649 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{company_email}}</div>
650 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{company_phone}}</div>
651 <div class="bg-white border border-blue-200 rounded px-3 py-2 text-xs text-blue-700 font-mono">{{payment_terms}}</div>
652 <?php do_action('easy_invoice_settings_email_placeholders', $settings); ?>
653 </div>
654 </div>
655 </div>
656 <?php endif; ?>
657 </div>
658 </div>
659 <?php } ?>
660 <?php elseif ($section_id === 'payment' && !empty($all_gateways_sorted)): ?>
661 <div class="space-y-5">
662 <?php if (count($all_gateways_sorted) > 1): ?>
663 <div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
664 <div class="flex items-center">
665 <i class="fas fa-info-circle text-blue-500 mr-2"></i>
666 <p class="text-sm text-blue-700">
667 <?php _e('Drag and drop payment methods to reorder them. The order will be reflected on payment forms.', 'easy-invoice'); ?>
668 </p>
669 </div>
670 </div>
671 <?php endif; ?>
672
673 <?php
674 /**
675 * Pro-gateway teaser block (renders only when Pro is NOT active).
676 *
677 * Showing the names of Pro gateways here drives conversion — users
678 * see exactly what they unlock without leaving the settings page.
679 * Each row links to the pricing page in a new tab. Free gateways
680 * (PayPal, Manual) remain enabled below this block as usual.
681 */
682 if (!easy_invoice_has_pro()) :
683 $pro_gateway_teasers = [
684 'stripe' => [
685 'title' => __('Stripe', 'easy-invoice'),
686 'desc' => __('Cards, Apple Pay, Google Pay, Link. SCA / 3-D Secure built in.', 'easy-invoice'),
687 ],
688 'square' => [
689 'title' => __('Square', 'easy-invoice'),
690 'desc' => __('US / CA / UK / AU / JP card processing with webhook reconciliation.', 'easy-invoice'),
691 ],
692 'authorizenet' => [
693 'title' => __('Authorize.Net', 'easy-invoice'),
694 'desc' => __('Long-standing US gateway — credit cards + eChecks via AIM.', 'easy-invoice'),
695 ],
696 'mollie' => [
697 'title' => __('Mollie', 'easy-invoice'),
698 'desc' => __('Europe-friendly: SEPA, iDEAL, Bancontact, Sofort, Klarna, card.', 'easy-invoice'),
699 ],
700 'paystack' => [
701 'title' => __('Paystack', 'easy-invoice'),
702 'desc' => __('Africa-first (NGN / GHS / ZAR / KES / USD): card, bank, USSD, mobile money, QR.', 'easy-invoice'),
703 ],
704 'moneris' => [
705 'title' => __('Moneris', 'easy-invoice'),
706 'desc' => __('Canada\'s largest card processor — Visa, Mastercard, AMEX, Interac.', 'easy-invoice'),
707 ],
708 'bank_transfer'=> [
709 'title' => __('Bank Transfer', 'easy-invoice'),
710 'desc' => __('Show your IBAN / SWIFT on invoices; mark paid on wire arrival.', 'easy-invoice'),
711 ],
712 'cheque' => [
713 'title' => __('Cheque', 'easy-invoice'),
714 'desc' => __('Display your mailing address; mark paid when the cheque clears.', 'easy-invoice'),
715 ],
716 'cash' => [
717 'title' => __('Cash', 'easy-invoice'),
718 'desc' => __('In-person cash with custom collection instructions.', 'easy-invoice'),
719 ],
720 ];
721
722 // Hide the ones we already register in Free / are already shown above.
723 $already_registered = array_keys($all_gateways_sorted);
724 foreach ($already_registered as $id) {
725 unset($pro_gateway_teasers[$id]);
726 }
727 ?>
728 <?php if (!empty($pro_gateway_teasers)): ?>
729 <div class="ei-pro-gateways mt-2 mb-6 rounded-xl border border-gray-200 bg-white shadow-sm overflow-hidden">
730 <!-- Header -->
731 <div class="flex items-center justify-between px-5 py-4 border-b border-gray-100 bg-gradient-to-r from-indigo-50/60 to-transparent">
732 <div class="flex items-center min-w-0">
733 <span class="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 text-white shadow-sm mr-3 flex-shrink-0">
734 <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
735 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
736 </svg>
737 </span>
738 <div class="min-w-0">
739 <h3 class="text-sm font-semibold text-gray-900 truncate">
740 <?php
741 printf(
742 /* translators: %d is the number of additional gateways */
743 esc_html__('Unlock %d more payment gateways with Easy Invoice Pro', 'easy-invoice'),
744 count($pro_gateway_teasers)
745 );
746 ?>
747 </h3>
748 <p class="mt-0.5 text-xs text-gray-500"><?php esc_html_e('Stripe, Square, Mollie, Paystack and more — installed alongside PayPal &amp; Manual.', 'easy-invoice'); ?></p>
749 </div>
750 </div>
751 <a href="https://matrixaddons.com/plugins/easy-invoice/#pricing" target="_blank" rel="noopener"
752 class="ml-4 shrink-0 inline-flex items-center px-3 py-1.5 rounded-md text-xs font-semibold text-white bg-indigo-600 hover:bg-indigo-700 transition-colors">
753 <?php esc_html_e('View pricing', 'easy-invoice'); ?>
754 <svg class="w-3 h-3 ml-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
755 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 5l7 7-7 7"/>
756 </svg>
757 </a>
758 </div>
759
760 <!-- Grid -->
761 <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 divide-y sm:divide-y-0 sm:divide-x divide-gray-100 lg:divide-x">
762 <?php
763 $teaser_idx = 0;
764 $teaser_count = count($pro_gateway_teasers);
765 foreach ($pro_gateway_teasers as $teaser_id => $teaser):
766 $teaser_idx++;
767 // Mark items that start a new row so the row-divider rules look correct on every grid breakpoint.
768 $row_break_sm = ($teaser_idx > 2); // 2 cols
769 $row_break_lg = ($teaser_idx > 3); // 3 cols
770 $initial = strtoupper(mb_substr($teaser['title'], 0, 1));
771 ?>
772 <a href="https://matrixaddons.com/plugins/easy-invoice/#pricing" target="_blank" rel="noopener"
773 class="ei-pro-gateway-card group relative flex items-center gap-3 px-4 py-3.5 hover:bg-indigo-50/40 transition-colors <?php echo $row_break_sm ? 'sm:border-t sm:border-gray-100' : ''; ?> <?php echo $row_break_lg ? 'lg:border-t lg:border-gray-100' : ''; ?>"
774 data-gateway-id="<?php echo esc_attr($teaser_id); ?>">
775 <!-- Brand initial chip -->
776 <span class="flex-shrink-0 inline-flex items-center justify-center w-9 h-9 rounded-md bg-gradient-to-br from-gray-50 to-gray-100 border border-gray-200 text-sm font-bold text-gray-700 group-hover:from-indigo-50 group-hover:to-indigo-100 group-hover:border-indigo-200 group-hover:text-indigo-700 transition-all">
777 <?php echo esc_html($initial); ?>
778 </span>
779 <div class="min-w-0 flex-1">
780 <div class="text-sm font-semibold text-gray-900 truncate"><?php echo esc_html($teaser['title']); ?></div>
781 <p class="mt-0.5 text-xs text-gray-500 leading-snug line-clamp-2"><?php echo esc_html($teaser['desc']); ?></p>
782 </div>
783 <svg class="w-4 h-4 text-gray-300 group-hover:text-indigo-500 group-hover:translate-x-0.5 flex-shrink-0 transition-all" fill="none" stroke="currentColor" viewBox="0 0 24 24">
784 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
785 </svg>
786 </a>
787 <?php endforeach; ?>
788 </div>
789 </div>
790 <?php endif; ?>
791 <?php endif; ?>
792
793 <div id="sortable-gateways" class="space-y-5">
794 <?php foreach ($all_gateways_sorted as $gateway_id => $gateway) : ?>
795 <div class="gateway-item bg-gray-50 p-5 rounded-lg border border-gray-200 cursor-move" data-gateway-id="<?php echo esc_attr($gateway_id); ?>">
796 <div class="flex items-center mb-3 space-x-3">
797 <!-- Drag Handle -->
798 <div class="gateway-handle text-gray-400 hover:text-gray-600 cursor-move mr-2">
799 <i class="fas fa-grip-vertical"></i>
800 </div>
801
802 <input type="checkbox"
803 class="gateway-enable-checkbox h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
804 name="settings[easy_invoice_payment_methods][]"
805 id="gateway-enable-<?php echo esc_attr($gateway_id); ?>"
806 value="<?php echo esc_attr($gateway_id); ?>"
807 <?php checked(!empty($payment_methods_enabled) && in_array($gateway_id, $payment_methods_enabled)); ?>>
808
809 <div class="flex-1">
810 <label for="gateway-enable-<?php echo esc_attr($gateway_id); ?>" class="font-medium text-gray-900">
811 <?php echo esc_html(method_exists($gateway, 'getTitle') ? $gateway->getTitle() : $gateway_id); ?>
812 </label>
813 <?php if (method_exists($gateway, 'getDescription') && $gateway->getDescription()): ?>
814 <span class="block text-sm text-gray-400"><?php echo esc_html($gateway->getDescription()); ?></span>
815 <?php endif; ?>
816 </div>
817 </div>
818
819 <!-- Custom Display Name Field -->
820 <div class="gateway-settings <?php echo empty($payment_methods_enabled) || !in_array($gateway_id, $payment_methods_enabled) ? 'hidden' : ''; ?>">
821 <div class="mb-4">
822 <label for="gateway-display-name-<?php echo esc_attr($gateway_id); ?>" class="block text-sm font-medium text-gray-700">
823 <?php _e('Display Name', 'easy-invoice'); ?>
824 </label>
825 <input type="text"
826 id="gateway-display-name-<?php echo esc_attr($gateway_id); ?>"
827 name="settings[easy_invoice_gateway_display_name_<?php echo esc_attr($gateway_id); ?>]"
828 value="<?php echo esc_attr($settings['easy_invoice_gateway_display_name_' . $gateway_id] ?? (method_exists($gateway, 'getTitle') ? $gateway->getTitle() : ucfirst(str_replace('_', ' ', $gateway_id)))); ?>"
829 placeholder="<?php echo esc_attr(method_exists($gateway, 'getTitle') ? $gateway->getTitle() : ucfirst(str_replace('_', ' ', $gateway_id))); ?>"
830 class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
831 <p class="mt-1 text-xs text-gray-500">
832 <?php _e('Leave blank to use the default name. This name will appear on invoices and payment forms.', 'easy-invoice'); ?>
833 </p>
834 </div>
835
836 <?php
837 $gateway_config = method_exists($gateway, 'getSettingsConfig') ? $gateway->getSettingsConfig() : [];
838 if (!empty($gateway_config['fields'])): ?>
839 <div class="grid grid-cols-1 md:grid-cols-6 gap-4">
840 <?php foreach ($gateway_config['fields'] as $option_key => $field_config): ?>
841 <?php
842 $current_value = $settings[$option_key] ?? ($field_config['default'] ?? '');
843 easy_invoice_render_field($option_key, $field_config, $current_value);
844 ?>
845 <?php endforeach; ?>
846 </div>
847 <?php endif; ?>
848 </div>
849
850 <input type="hidden" class="gateway-order-input" name="settings[easy_invoice_gateway_order][]" value="<?php echo esc_attr($gateway_id); ?>">
851 </div>
852 <?php endforeach; ?>
853 </div>
854 </div>
855 <?php elseif (!empty($section_data['fields'])): ?>
856 <?php
857 // Check if this section has subsections
858 if (!empty($section_data['subsections'])) {
859 // Handle sections with subsections (like invoice, quote, etc.)
860 foreach ($section_data['subsections'] as $subsection_id => $subsection_data) {
861 $subsection_title = $subsection_data['title'] ?? '';
862 $subsection_description = $subsection_data['description'] ?? '';
863 ?>
864 <div class="subsection-content mb-8">
865 <div class="mb-4">
866 <h3 class="text-lg font-medium text-gray-900"><?php echo esc_html($subsection_title); ?></h3>
867 <?php if (!empty($subsection_description)): ?>
868 <p class="text-sm text-gray-400"><?php echo esc_html($subsection_description); ?></p>
869 <?php endif; ?>
870 </div>
871 <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
872 <?php if (!empty($subsection_data['fields'])) {
873 foreach ($subsection_data['fields'] as $option_key => $field_config) {
874 $current_value = $settings[$option_key] ?? ($field_config['default'] ?? '');
875 easy_invoice_render_field($option_key, $field_config, $current_value);
876 }
877 }
878 ?>
879 </div>
880 </div>
881 <?php
882 }
883 } else {
884 // Handle regular sections without subsections
885 if ($section_id === 'tax') {
886 // Render Enable Tax checkbox full width
887 $enable_tax_key = 'easy_invoice_tax_enabled';
888 $enable_tax_field = $section_data['fields'][$enable_tax_key];
889 $current_value = $settings[$enable_tax_key] ?? ($enable_tax_field['default'] ?? '');
890 echo '<div class="mb-4">';
891 echo '<div class="flex items-center">';
892 echo ' <input type="checkbox" name="settings[' . esc_attr($enable_tax_key) . ']" id="' . esc_attr($enable_tax_key) . '" value="yes" ' . checked($current_value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">';
893 echo ' <label for="' . esc_attr($enable_tax_key) . '" class="ml-2 block text-sm text-gray-900">' . esc_html($enable_tax_field['label']) . '</label>';
894 echo '</div>';
895 if (!empty($enable_tax_field['description'])) {
896 echo '<p id="' . $field_id . '-description" class="text-gray-400 mt-1 text-xs">' . esc_html($enable_tax_field['description']) . '</p>';
897 }
898 echo '</div>';
899 // Render the other three fields in a 3-column grid
900 echo '<div class="grid grid-cols-1 md:grid-cols-3 gap-6">';
901 $tax_keys = ['easy_invoice_tax_entry_method', 'easy_invoice_tax_rate', 'easy_invoice_tax_name'];
902 $select_class = 'mt-1 block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md';
903 $input_class = 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm';
904 foreach ($tax_keys as $tax_key) {
905 $field_config = $section_data['fields'][$tax_key];
906 $current_value = $settings[$tax_key] ?? ($field_config['default'] ?? '');
907 echo '<div>';
908 if ($field_config['type'] !== 'checkbox') {
909 echo '<label for="' . esc_attr($tax_key) . '" class="block text-sm font-medium text-gray-700">' . esc_html($field_config['label']) . '</label>';
910 }
911 switch ($field_config['type']) {
912 case 'text':
913 case 'email':
914 case 'url':
915 case 'tel':
916 echo '<input type="' . esc_attr($field_config['type']) . '" name="settings[' . esc_attr($tax_key) . ']" id="' . esc_attr($tax_key) . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . esc_attr($field_config['placeholder'] ?? '') . '">';
917 break;
918 case 'number':
919 $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : '';
920 $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : '';
921 $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : '';
922 echo '<input type="number" name="settings[' . esc_attr($tax_key) . ']" id="' . esc_attr($tax_key) . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="' . $input_class . '" placeholder="' . esc_attr($field_config['placeholder'] ?? '') . '">';
923 break;
924 case 'select':
925 echo '<select id="' . esc_attr($tax_key) . '" name="settings[' . esc_attr($tax_key) . ']" class="' . $select_class . '">';
926 if (!empty($field_config['options']) && is_array($field_config['options'])) {
927 foreach ($field_config['options'] as $opt_val => $opt_label) {
928 echo '<option value="' . esc_attr($opt_val) . '" ' . selected($current_value, $opt_val, false) . '>' . esc_html($opt_label) . '</option>';
929 }
930 }
931 echo '</select>';
932 break;
933 }
934 if (!empty($field_config['description'])) {
935 echo '<p class="mt-1 text-xs text-gray-400">' . esc_html($field_config['description']) . '</p>';
936 }
937 echo '</div>';
938 }
939 echo '</div>';
940
941 // Allow additional fields to be added to the tax section
942 $additional_tax_fields = apply_filters('easy_invoice_tax_section_additional_fields', [], $settings);
943 if (!empty($additional_tax_fields)) {
944 echo '<div class="mt-6 pt-6 border-t border-gray-200">';
945 echo '<h4 class="text-sm font-medium text-gray-900 mb-4">' . __('Additional Tax Settings', 'easy-invoice-pro') . '</h4>';
946 echo '<div class="grid grid-cols-1 md:grid-cols-2 gap-6">';
947 foreach ($additional_tax_fields as $field_key => $field_config) {
948 $current_value = $settings[$field_key] ?? ($field_config['default'] ?? '');
949 easy_invoice_render_field($field_key, $field_config, $current_value);
950 }
951 echo '</div>';
952 echo '</div>';
953 }
954 } else {
955 // Use a two-column grid for currency and company settings
956 $is_grid = ($section_id === 'currency' || $section_id === 'company');
957 // Use a 6-column grid for invoice and quote settings to allow specific field pairing
958 $is_invoice_grid = ($section_id === 'invoice');
959 $is_quote_grid = ($section_id === 'quote');
960 // Use a specific grid for advanced settings to improve spacing
961 $is_advanced_grid = ($section_id === 'advanced');
962 // Use a 3-column grid for text settings
963 $is_text_settings_grid = ($section_id === 'text_settings');
964 if ($is_grid) echo '<div class="grid grid-cols-1 md:grid-cols-2 gap-6">';
965 if ($is_invoice_grid) echo '<div class="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">';
966 if ($is_quote_grid) echo '<div class="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">';
967 if ($is_advanced_grid) echo '<div class="grid grid-cols-1 gap-6">';
968 if ($is_text_settings_grid) echo '<div class="grid grid-cols-1 md:grid-cols-3 gap-6">';
969 foreach ($section_data['fields'] as $option_key => $field_config) {
970 $current_value = $settings[$option_key] ?? ($field_config['default'] ?? '');
971 $field_id = esc_attr($option_key);
972 $field_type = $field_config['type'] ?? 'text';
973 $field_label = esc_html($field_config['label'] ?? '');
974 $field_placeholder = isset($field_config['placeholder']) ? esc_attr($field_config['placeholder']) : '';
975 $field_description = $field_config['description'] ?? '';
976 $field_name = 'settings[' . esc_attr($option_key) . ']';
977 $input_class = 'mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm';
978 $select_class = 'mt-1 block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md';
979 $col_span_class = esc_attr($field_config['col_span'] ?? 'sm:col-span-6');
980 $form_group_class = '';
981 if ($is_grid) {
982 $form_group_class = '';
983 } elseif ($is_invoice_grid || $is_quote_grid || $is_advanced_grid || $is_text_settings_grid) {
984 $form_group_class = $col_span_class;
985 } else {
986 $form_group_class = $col_span_class;
987 }
988
989 // Handle conditional display
990 $conditional_class = '';
991 if (isset($field_config['depends_on']) && is_array($field_config['depends_on'])) {
992 $conditional_class = ' conditional-field';
993 foreach ($field_config['depends_on'] as $depends_key => $depends_value) {
994 $depends_field_name = 'settings[' . esc_attr($depends_key) . ']';
995 $conditional_class .= ' depends-on-' . esc_attr($depends_key) . '-' . esc_attr($depends_value);
996 }
997 }
998
999 echo '<div class="form-group ' . $form_group_class . $conditional_class . '">';
1000 if ($field_type !== 'checkbox') {
1001 echo '<label for="' . $field_id . '" class="block text-sm font-medium text-gray-700">' . $field_label . '</label>';
1002 }
1003
1004 // Define required and aria_describedby variables
1005 $required = !empty($field_config['required']) ? 'required' : '';
1006 $aria_describedby = !empty($field_description) ? 'aria-describedby="' . $field_id . '-description"' : '';
1007
1008 switch ($field_type) {
1009 case 'text':
1010 case 'email':
1011 case 'url':
1012 case 'tel':
1013 echo '<input type="' . esc_attr($field_type) . '" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
1014 break;
1015 case 'number':
1016 $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : '';
1017 $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : '';
1018 $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : '';
1019 echo '<input type="number" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>';
1020 break;
1021 case 'textarea':
1022 echo '<textarea id="' . $field_id . '" name="' . $field_name . '" rows="3" class="' . $input_class . '" placeholder="' . $field_placeholder . '" ' . $required . ' ' . $aria_describedby . '>' . wp_kses_post($current_value) . '</textarea>';
1023 break;
1024 case 'select':
1025 echo '<select id="' . $field_id . '" name="' . $field_name . '" class="' . $select_class . '" ' . $required . ' ' . $aria_describedby . '>';
1026 if (!empty($field_config['options']) && is_array($field_config['options'])) {
1027 foreach ($field_config['options'] as $opt_val => $opt_label) {
1028 echo '<option value="' . esc_attr($opt_val) . '" ' . selected(strtolower($current_value), strtolower($opt_val), false) . '>' . esc_html($opt_label) . '</option>';
1029 }
1030 }
1031 echo '</select>';
1032 break;
1033 case 'multiselect':
1034 echo '<select id="' . $field_id . '" name="' . $field_name . '[]" class="' . $select_class . '" multiple="multiple" aria-label="' . esc_attr($field_label) . '">';
1035 if (!empty($field_config['options']) && is_array($field_config['options'])) {
1036 foreach ($field_config['options'] as $opt_val => $opt_label) {
1037 $selected = is_array($current_value) && in_array($opt_val, $current_value) ? 'selected="selected"' : '';
1038 echo '<option value="' . esc_attr($opt_val) . '" ' . $selected . '>' . esc_html($opt_label) . '</option>';
1039 }
1040 }
1041 echo '</select>';
1042 break;
1043 case 'readonly':
1044 echo '<input type="text" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . ' bg-gray-50" readonly>';
1045 echo '<input type="hidden" name="' . $field_name . '" value="' . esc_attr($current_value) . '">';
1046 break;
1047 case 'checkbox':
1048 echo '<div class="flex items-center">';
1049 echo ' <input type="checkbox" name="' . $field_name . '" id="' . $field_id . '" value="yes" ' . checked($current_value, 'yes', false) . ' class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">';
1050 echo ' <label for="' . $field_id . '" class="ml-2 block text-sm text-gray-900">' . $field_label . '</label>';
1051 echo '</div>';
1052 if (!empty($field_description)) {
1053 echo '<p id="' . $field_id . '-description" class="mt-2 text-sm text-gray-400 leading-relaxed">' . esc_html($field_description) . '</p>';
1054 }
1055 break;
1056 case 'image':
1057 echo '<div class="mt-1 flex items-center">';
1058 echo ' <span class="inline-block h-12 w-12 rounded-full overflow-hidden bg-gray-100">';
1059 echo ' <img id="' . $field_id . '-preview" src="' . esc_url($current_value) . '" alt="' . esc_attr($field_label) . '" class="h-full w-full object-cover' . (empty($current_value) ? ' hidden' : '') . '">';
1060 echo ' </span>';
1061 echo ' <button type="button" id="upload_image_button_' . $field_id . '" class="upload-logo-button ml-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-uploader_title="' . esc_attr__('Choose Logo', 'easy-invoice') . '" data-uploader_button_text="' . esc_attr__('Select Logo', 'easy-invoice') . '">';
1062 echo esc_html__('Change', 'easy-invoice');
1063 echo '</button>';
1064 echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '">';
1065 echo '</div>';
1066 break;
1067 case 'wp_editor':
1068 wp_editor( $current_value, $field_id, ['textarea_name' => $field_name, 'teeny' => true, 'media_buttons' => false, 'textarea_rows' => 7, 'editor_class' => 'mt-1'] );
1069 // Handle description for wp_editor specifically
1070 if (!empty($field_description)) {
1071 echo '<p class="mt-1 text-sm text-gray-400">' . esc_html($field_description) . '</p>';
1072 }
1073 break;
1074 case 'color':
1075 echo '<input type="color" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="h-10 w-20 border border-gray-300 rounded">';
1076 break;
1077 case 'range':
1078 $min = isset($field_config['min']) ? ' min="' . esc_attr($field_config['min']) . '"' : '';
1079 $max = isset($field_config['max']) ? ' max="' . esc_attr($field_config['max']) . '"' : '';
1080 $step = isset($field_config['step']) ? ' step="' . esc_attr($field_config['step']) . '"' : '';
1081 echo '<input type="range" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '"' . $min . $max . $step . ' class="w-full">';
1082 echo '<span class="text-sm text-gray-400">' . esc_html($current_value) . '%</span>';
1083 break;
1084 case 'image_upload':
1085 echo '<div class="image-upload-wrap">';
1086 echo '<div class="flex items-center space-x-3">';
1087 echo ' <input type="hidden" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '">';
1088 echo ' <button type="button" class="upload-image-button bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" data-target="' . $field_id . '">';
1089 echo esc_html__('Upload Image', 'easy-invoice');
1090 echo '</button>';
1091 echo '</div>';
1092 echo '<div class="image-preview mt-2' . (empty($current_value) ? ' hidden' : '') . '">';
1093 if (!empty($current_value)) {
1094 echo '<img src="' . esc_url($current_value) . '" alt="Preview" style="max-width: 200px; max-height: 100px;">';
1095 }
1096 echo '</div>';
1097 echo '</div>';
1098 break;
1099 case 'button':
1100 $button_text = $field_config['button_text'] ?? $field_label;
1101 $button_class = $field_config['button_class'] ?? 'button button-secondary';
1102 $button_class_name = 'regenerate-invoice-numbers-button';
1103 if ($option_key === 'easy_invoice_regenerate_quote_numbers') {
1104 $button_class_name = 'regenerate-quote-numbers-button';
1105 }
1106 echo '<button type="button" id="' . $field_id . '" class="' . esc_attr($button_class) . ' ' . $button_class_name . '">';
1107 echo esc_html($button_text);
1108 echo '</button>';
1109 break;
1110 default:
1111 echo '<input type="text" name="' . $field_name . '" id="' . $field_id . '" value="' . esc_attr($current_value) . '" class="' . $input_class . '" placeholder="' . $field_placeholder . '">';
1112 break;
1113 }
1114 if ($field_type !== 'checkbox' && $field_type !== 'wp_editor' && !empty($field_description)) {
1115 echo '<p class="mt-1 text-sm text-gray-400">' . esc_html($field_description) . '</p>';
1116 }
1117 echo '</div>';
1118 }
1119 if ($is_grid) echo '</div>';
1120 if ($is_invoice_grid) echo '</div>';
1121 if ($is_quote_grid) echo '</div>';
1122 if ($is_advanced_grid) echo '</div>';
1123 if ($is_text_settings_grid) echo '</div>';
1124 }
1125 }
1126 ?>
1127 <?php endif; ?>
1128 </div>
1129 </div>
1130 </div>
1131 <?php endforeach; ?>
1132
1133 <!-- Bottom Save Button -->
1134 <div class="mt-8 pt-6 border-t border-gray-200">
1135 <div class="flex items-center justify-start space-x-3">
1136 <button type="button" id="save-settings-bottom" 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 transition-colors duration-200" aria-label="<?php esc_attr_e('Save Settings', 'easy-invoice'); ?>">
1137 <i class="fas fa-save mr-2" aria-hidden="true"></i>
1138 <?php esc_html_e('Save Changes', 'easy-invoice'); ?>
1139 </button>
1140 </div>
1141 </div>
1142
1143 </form>
1144 </div>
1145 </div>
1146 </div>
1147 </div>
1148
1149 <script>
1150 document.addEventListener('DOMContentLoaded', function() {
1151 // Enter key functionality to save settings
1152 const settingsForm = document.getElementById('settings-form');
1153 const saveButton = document.getElementById('save-settings-bottom');
1154
1155 if (settingsForm && saveButton) {
1156 // Get both save buttons
1157 const topSaveButton = document.getElementById('save-settings');
1158 const bottomSaveButton = document.getElementById('save-settings-bottom');
1159
1160 // Function to trigger save
1161 function triggerSave() {
1162 // Try bottom button first, then top button
1163 if (bottomSaveButton) {
1164 bottomSaveButton.click();
1165 } else if (topSaveButton) {
1166 topSaveButton.click();
1167 }
1168 }
1169
1170 // Listen for Enter key on form inputs
1171 settingsForm.addEventListener('keydown', function(e) {
1172 // Check if Enter key is pressed and not in a textarea or select
1173 if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'SELECT') {
1174 e.preventDefault();
1175 triggerSave();
1176 }
1177 });
1178
1179 // Also listen for Enter key on the entire form
1180 settingsForm.addEventListener('keypress', function(e) {
1181 if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'SELECT') {
1182 e.preventDefault();
1183 triggerSave();
1184 }
1185 });
1186 }
1187
1188 // Email Settings submenu hover behavior for settings page
1189 const emailNavLink = document.querySelector('.nav-link[href*="section=email"]');
1190 const emailSubmenu = document.querySelector('.email-settings-submenu');
1191
1192 if (emailNavLink && emailSubmenu) {
1193 // Check if any Email Settings subsection is active
1194 const activeSubmenuLink = document.querySelector('.email-submenu-link.text-indigo-700');
1195 const isEmailSectionActive = emailNavLink.classList.contains('text-indigo-700');
1196 const isAnySubsectionActive = activeSubmenuLink !== null;
1197
1198 // If any subsection is active, show submenu permanently and disable hover effects
1199 if (isAnySubsectionActive || isEmailSectionActive) {
1200 emailSubmenu.style.display = 'block';
1201 emailSubmenu.style.opacity = '1';
1202 emailSubmenu.style.visibility = 'visible';
1203 emailSubmenu.style.maxHeight = '200px';
1204 emailSubmenu.classList.add('show');
1205 return; // Exit early, no hover effects needed
1206 }
1207
1208 // Only add hover effects if no subsection is active
1209 // Show submenu on hover
1210 emailNavLink.addEventListener('mouseenter', function() {
1211 emailSubmenu.style.display = 'block';
1212 emailSubmenu.classList.add('show');
1213 setTimeout(() => {
1214 emailSubmenu.style.opacity = '1';
1215 emailSubmenu.style.visibility = 'visible';
1216 emailSubmenu.style.maxHeight = '200px';
1217 }, 10);
1218 });
1219
1220 // Hide submenu when mouse leaves
1221 emailNavLink.addEventListener('mouseleave', function() {
1222 // Only hide if not hovering over submenu
1223 setTimeout(() => {
1224 if (!emailSubmenu.matches(':hover')) {
1225 emailSubmenu.style.opacity = '0';
1226 emailSubmenu.style.visibility = 'hidden';
1227 emailSubmenu.style.maxHeight = '0';
1228 emailSubmenu.classList.remove('show');
1229 setTimeout(() => {
1230 emailSubmenu.style.display = 'none';
1231 }, 200);
1232 }
1233 }, 100);
1234 });
1235
1236 // Keep submenu open when hovering over submenu items
1237 emailSubmenu.addEventListener('mouseenter', function() {
1238 emailSubmenu.style.opacity = '1';
1239 emailSubmenu.style.visibility = 'visible';
1240 emailSubmenu.style.maxHeight = '200px';
1241 emailSubmenu.classList.add('show');
1242 });
1243
1244 emailSubmenu.addEventListener('mouseleave', function() {
1245 emailSubmenu.style.opacity = '0';
1246 emailSubmenu.style.visibility = 'hidden';
1247 emailSubmenu.style.maxHeight = '0';
1248 emailSubmenu.classList.remove('show');
1249 setTimeout(() => {
1250 emailSubmenu.style.display = 'none';
1251 }, 200);
1252 });
1253 }
1254 });
1255 </script>
1256
1257 <style>
1258 #screen-meta-links{
1259 display:none;
1260 }
1261 /* Email Settings Submenu Styles for Settings Page */
1262 .email-settings-submenu {
1263 transition: all 0.3s ease;
1264 overflow: hidden;
1265 }
1266
1267 .email-settings-submenu.show {
1268 display: block !important;
1269 }
1270
1271 .email-settings-submenu.show ~ .nav-link .fas.fa-chevron-down,
1272 .email-settings-submenu:hover ~ .nav-link .fas.fa-chevron-down {
1273 transform: rotate(180deg);
1274 }
1275
1276 /* Custom color for settings navigation icons */
1277 .nav-link:hover svg {
1278 color: rgb(79 70 229) !important;
1279 }
1280
1281 /* Active state icon color */
1282 .bg-indigo-50 svg {
1283 color: rgb(79 70 229) !important;
1284 }
1285
1286 /* Hover state for non-active items */
1287 .nav-link:hover:not(.bg-indigo-50) svg {
1288 color: rgb(79 70 229) !important;
1289 }
1290
1291 /* Email submenu icon colors */
1292 .email-submenu-link:hover svg {
1293 color: rgb(79 70 229) !important;
1294 }
1295
1296 .email-submenu-link.bg-indigo-50 svg {
1297 color: rgb(79 70 229) !important;
1298 }
1299 </style>
1300