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 / main-template.php

main-template.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.0, at templates/main-template.php

775 lines 46.8 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 $current_page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : 'easy-invoice';
6
7 // Function to check if current page is a subpage of a main menu
8 function is_subpage_of($current_page, $main_page) {
9 // Check if current page starts with the main page slug
10 $current_page = $current_page ?? '';
11 $main_page = $main_page ?? '';
12 return strpos($current_page, $main_page) === 0;
13 }
14
15 // Function to get the active menu class
16 function get_menu_class($current_page, $menu_page, $is_active = false) {
17 if ($is_active) {
18 return 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600';
19 }
20 return 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700';
21 }
22
23 // Function to get the active icon class
24 function get_icon_class($current_page, $menu_page, $is_active = false) {
25 if ($is_active) {
26 return 'text-indigo-600';
27 }
28 return 'text-gray-400 group-hover:text-indigo-500';
29 }
30
31 // Determine which menu should be active
32 $is_dashboard_active = $current_page === 'easy-invoice';
33 $is_invoices_active = in_array($current_page, [
34 'easy-invoice-all', // All invoices
35 'easy-invoice-builder', // Invoice builder
36 'easy-invoice-preview' // Invoice preview
37 ]);
38 $is_quotes_active = in_array($current_page, [
39 'easy-quote-all', // All quotes
40 'easy-invoice-quote-builder', // Quote builder
41 'easy-quote-preview' // Quote preview
42 ]);
43 $is_payments_active = in_array($current_page, [
44 'easy-invoice-payments', // All payments
45 'easy-invoice-payment-new' // Add new payment
46 ]);
47 $is_clients_active = in_array($current_page, [
48 'easy-invoice-clients', // All clients
49 'easy-invoice-client-edit', // Edit client
50 'easy-invoice-client-view' // View client
51 ]);
52 $is_item_library_active = in_array($current_page, [
53 'easy-invoice-pro-item-library', // Item Library (Pro)
54 'easy-invoice-item-library' // Item Library (alternative)
55 ]);
56 $is_templates_active = in_array($current_page, [
57 'easy-invoice-templates', // Template Builder (Pro)
58 'easy-invoice-template-builder' // Template Builder page
59 ]);
60 $is_reports_active = $current_page === 'easy-invoice-reports';
61 $is_settings_active = in_array($current_page, [
62 'easy-invoice-settings', // Main settings
63 'easy-invoice-email-settings', // Email settings
64 'easy-invoice-email-settings-general',
65 'easy-invoice-email-settings-invoice',
66 'easy-invoice-email-settings-quote',
67 'easy-invoice-email-settings-payment',
68 'easy-invoice-email-settings-payment-reminder',
69 'easy-invoice-pro-settings', // Pro settings
70 'easy-invoice-pro-translations' // Pro translations
71 ]);
72 // The "Addons" sidebar entry is only active on the Addons grid itself.
73 // Per-addon settings pages (Time Tracking / Smart Reminders / White-Label /
74 // Team & Audit / Webhooks) are now top-level sidebar entries — each highlights
75 // itself; the parent Addons row should NOT also light up.
76 $is_addons_active = ($current_page === 'easy-invoice-addons');
77 ?>
78
79 <?php
80 /*
81 * The sidebar is 256px wide and fixed. It used to carry no responsive classes at
82 * all, and the content wrapper below was an unconditional `pl-64` — so on a 390px
83 * phone the sidebar ate 256px and left ~134px for the actual page. Stat cards
84 * became unreadable slivers, tables collapsed to a column of checkboxes, and every
85 * screen overflowed horizontally.
86 *
87 * It is now an off-canvas drawer under `lg` (slid out by default, toggled by the
88 * button below) and pinned open from `lg` up, which is the behaviour it always had
89 * on desktop. The toggle script near the bottom of this file adds and removes
90 * `-translate-x-full` on the sidebar to slide it; `lg:translate-x-0` overrides that
91 * from 1024px up, so the drawer state is irrelevant on desktop.
92 */
93 ?>
94 <button type="button" id="ei-sidebar-toggle"
95 class="lg:hidden fixed top-3 left-3 z-[60] inline-flex items-center justify-center w-11 h-11 rounded-md bg-white border border-gray-300 shadow-sm text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
96 aria-controls="ei-app-sidebar" aria-expanded="false">
97 <span class="sr-only"><?php esc_html_e('Toggle Easy Invoice navigation', 'easy-invoice'); ?></span>
98 <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
99 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
100 </svg>
101 </button>
102
103 <div id="ei-sidebar-backdrop" class="lg:hidden fixed inset-0 bg-gray-900 bg-opacity-50 z-40 hidden" aria-hidden="true"></div>
104
105 <div id="ei-app-sidebar" class="fixed inset-y-0 left-0 w-64 bg-white shadow-lg border-r border-gray-200 ei-app-sidebar flex flex-col z-50 transform -translate-x-full transition-transform duration-200 ease-in-out lg:translate-x-0">
106
107 <div class="ei-app-sidebar-top flex flex-col items-center justify-center px-4 py-2 border-b border-gray-200 box-border flex-shrink-0">
108 <?php
109 /**
110 * Default logo HTML. Addons (White-Label) can short-circuit this to
111 * inject a custom image/label without touching this template.
112 *
113 * Filter: easy_invoice_admin_logo_html
114 * Args: ($default_html)
115 */
116 $default_logo_html = '<div class="flex items-center space-x-2 mb-1">'
117 . '<div class="flex items-center justify-center w-8 h-8 bg-indigo-600 rounded ei-app-logo-mark">'
118 . '<svg class="h-5 w-5 text-white" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">'
119 . '<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"/>'
120 . '<path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
121 . '<path d="M16 13H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
122 . '<path d="M16 17H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
123 . '<path d="M10 9H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
124 . '</svg></div>'
125 . '<span class="text-lg font-semibold text-gray-900 ei-app-logo-label">' . esc_html__('Easy Invoice', 'easy-invoice') . '</span>'
126 . '</div>';
127 echo wp_kses_post(apply_filters('easy_invoice_admin_logo_html', $default_logo_html));
128 ?>
129 <?php if (apply_filters('easy_invoice_admin_show_back_to_wp', true)): ?>
130 <a href="<?php echo esc_url(admin_url()); ?>" target="_blank" class="inline-flex items-center text-xs text-gray-500 hover:text-gray-700 transition-colors duration-200">
131 <svg class="w-3 h-3 mr-1" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
132 <path d="M19 12H5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
133 <path d="M12 19L5 12L12 5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
134 </svg>
135 <?php esc_html_e( 'Back to WordPress', 'easy-invoice' ); ?>
136 </a>
137 <?php endif; ?>
138
139
140 <?php if (apply_filters('easy_invoice_admin_show_version_badges', true)): ?>
141 <div class="mt-1 flex flex-wrap items-center justify-center gap-1.5">
142
143 <span class="inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-700 border border-gray-200">
144 <svg class="w-2.5 h-2.5 mr-1 text-gray-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
145 <path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
146 <path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
147 <path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
148 </svg>
149 v<?php echo esc_html(EASY_INVOICE_VERSION); ?>
150 </span>
151
152 <?php if (defined('EASY_INVOICE_PRO_VERSION') && function_exists('is_plugin_active') && is_plugin_active('easy-invoice-pro/easy-invoice-pro.php')): ?>
153
154 <span class="inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-700 border border-green-200">
155 <svg class="w-2.5 h-2.5 mr-1 text-green-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
156 <path d="M9 12L11 14L15 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
157 <path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
158 </svg>
159 <?php /* translators: %s: Pro plugin version. */ echo esc_html( sprintf( __( 'Pro v%s', 'easy-invoice' ), EASY_INVOICE_PRO_VERSION ) ); ?>
160 </span>
161 <?php endif; ?>
162 </div>
163 <?php endif; // easy_invoice_admin_show_version_badges ?>
164 </div>
165
166
167 <nav class="ei-app-sidebar-nav flex-1 min-h-0 overflow-y-auto mt-6 px-3 pb-4 space-y-2">
168
169 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice')); ?>"
170 class="<?php echo esc_attr($is_dashboard_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700'); ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
171 <svg class="<?php echo $is_dashboard_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
172 <path d="M3 13H9V19H3V13Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
173 <path d="M9 13H15V19H9V13Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
174 <path d="M15 13H21V19H15V13Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
175 <path d="M3 5H9V11H3V5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
176 <path d="M9 5H15V11H9V5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
177 <path d="M15 5H21V11H15V5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
178 </svg>
179 <?php esc_html_e( 'Dashboard', 'easy-invoice' ); ?>
180 </a>
181
182
183 <?php
184 // A sidebar entry is drawn only when the current user can open the page it
185 // leads to: administrators can open everything; a team member with an EI role
186 // (Pro Team Roles) is mapped through the same filter the menu uses.
187 $ei_can_open = static function ($slug) {
188 return current_user_can((string) apply_filters('easy_invoice_menu_capability', 'manage_options', $slug));
189 };
190 ?>
191 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-all')); ?>"
192 class="<?php echo esc_attr($is_invoices_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700'); ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
193 <svg class="<?php echo $is_invoices_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
194 <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"/>
195 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
196 <path d="M12 11V19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
197 <path d="M14.5 13H10.75a1.75 1.75 0 0 0 0 3.5h2.5a1.75 1.75 0 0 1 0 3.5H9.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
198 </svg>
199 <?php esc_html_e( 'Invoices', 'easy-invoice' ); ?>
200 </a>
201
202
203 <?php if ($ei_can_open('easy-quote-all')) : ?>
204 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-quote-all')); ?>"
205 class="<?php echo esc_attr($is_quotes_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700'); ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
206 <svg class="<?php echo $is_quotes_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
207 <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"/>
208 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
209 <path d="M8.5 15.5L11 18L16 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
210 </svg>
211 <?php esc_html_e( 'Quotes', 'easy-invoice' ); ?>
212 </a>
213 <?php endif; ?>
214
215
216 <?php if ($ei_can_open('easy-invoice-payments')) : ?>
217 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-payments')); ?>"
218 class="<?php echo esc_attr($is_payments_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700'); ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
219 <svg class="<?php echo $is_payments_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
220 <rect x="1" y="4" width="22" height="16" rx="2" ry="2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
221 <line x1="1" y1="10" x2="23" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
222 </svg>
223 <?php esc_html_e( 'Payments', 'easy-invoice' ); ?>
224 </a>
225 <?php endif; ?>
226
227
228 <?php if ($ei_can_open('easy-invoice-clients')) : ?>
229 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-clients')); ?>"
230 class="<?php echo esc_attr($is_clients_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700'); ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
231 <svg class="<?php echo $is_clients_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
232 <path d="M17 21V19C17 17.9391 16.5786 16.9217 15.8284 16.1716C15.0783 15.4214 14.0609 15 13 15H5C3.93913 15 2.92172 15.4214 2.17157 16.1716C1.42143 16.9217 1 17.9391 1 19V21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
233 <circle cx="9" cy="7" r="4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
234 <path d="M23 21V19C23 18.1137 22.6485 17.2628 22.0237 16.6425C21.3989 16.0221 20.5471 15.6722 19.66 15.6722C18.7729 15.6722 17.9211 16.0221 17.2963 16.6425C16.6715 17.2628 16.32 18.1137 16.32 19V21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
235 <path d="M16.32 11.6722C18.36 11.6722 20 10.0322 20 7.99219C20 5.95219 18.36 4.31219 16.32 4.31219" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
236 </svg>
237 <?php esc_html_e( 'Clients', 'easy-invoice' ); ?>
238 </a>
239 <?php endif; ?>
240
241 <?php
242 // Item Library + Template Builder sidebar entries were here.
243 // They've been moved BELOW the Addons menu — see the per-addon
244 // sidebar loop further down in this file (lines ~365). The loop
245 // automatically renders a sidebar entry for every enabled addon
246 // whose registry entry has a non-empty settings_url, so the two
247 // explicit blocks are no longer needed (and were creating
248 // visual duplication with the loop).
249 ?>
250
251 <?php
252 // Reports — addon-gated (PERSONAL tier).
253 //
254 // When the addon is loaded, the per-addon sidebar loop further
255 // down in this file (after the Addons menu entry) renders the
256 // Reports link automatically — so this block only handles the
257 // Free-user upsell state. Showing it twice (here AND via the
258 // loop) was the cause of the "2 Reports pages" duplicate.
259 ?>
260 <?php if (!easy_invoice_has_pro()): ?>
261
262 <a href="#" id="reports-menu-link"
263 class="text-gray-600 hover:bg-indigo-50 hover:text-indigo-700 group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
264 <svg class="text-gray-400 group-hover:text-indigo-500 mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
265 <path d="M18 20V10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
266 <path d="M12 20V4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
267 <path d="M6 20V14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
268 </svg>
269 <?php esc_html_e( 'Reports', 'easy-invoice' ); ?>
270 <span class="ml-auto inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-purple-600 text-white shadow-sm">
271 <svg class="w-4 h-4 mr-1 text-white" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
272 <path d="M12 8L15 13.2L18 10.5L17.3 14H6.7L6 10.5L9 13.2L12 8M12 4L8.5 10L3 5L5 16H19L21 5L15.5 10L12 4Z"/>
273 </svg>
274 <?php esc_html_e( 'Pro', 'easy-invoice' ); ?>
275 </span>
276 </a>
277 <?php endif; ?>
278
279
280 <?php if ($ei_can_open('easy-invoice-settings')) : ?>
281 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-settings')); ?>"
282 class="<?php echo esc_attr($is_settings_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700'); ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
283 <svg class="<?php echo $is_settings_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
284 <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
285 <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
286 </svg>
287 <?php esc_html_e( 'Settings', 'easy-invoice' ); ?>
288 </a>
289 <?php endif; ?>
290
291 <?php $is_import_active = ( 'easy-invoice-import' === $current_page ); ?>
292 <?php if ($ei_can_open('easy-invoice-import')) : ?>
293 <a href="<?php echo esc_url( admin_url( 'admin.php?page=easy-invoice-import' ) ); ?>"
294 class="<?php echo esc_attr( $is_import_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700' ); ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
295 <svg class="<?php echo $is_import_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" aria-hidden="true">
296 <path d="M12 3v12m0 0l-4-4m4 4l4-4M4 17v2a2 2 0 002 2h12a2 2 0 002-2v-2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
297 </svg>
298 <?php esc_html_e( 'Import', 'easy-invoice' ); ?>
299 </a>
300 <?php endif; ?>
301
302
303 <?php
304 // Check if Pro plugin is active and get license status
305 $show_license_badge = false;
306 $license_status = 'inactive';
307 $license_text = __( 'Deactivated', 'easy-invoice' );
308
309 if (easy_invoice_has_pro()) {
310 $license_key = \EasyInvoicePro\Updater\License::get_license_key();
311 $is_valid = \EasyInvoicePro\Updater\License::has_valid_license();
312 $is_expired = \EasyInvoicePro\Updater\License::has_license_expired();
313
314 // Always show badge for Pro version
315 $show_license_badge = true;
316
317 if (!empty($license_key)) {
318 if ($is_valid && !$is_expired) {
319 $license_status = 'activated';
320 $license_text = __( 'Activated', 'easy-invoice' );
321 } elseif ($is_expired) {
322 $license_status = 'expired';
323 $license_text = __( 'Expired', 'easy-invoice' );
324 } else {
325 $license_status = 'inactive';
326 $license_text = __( 'Deactivated', 'easy-invoice' );
327 }
328 } else {
329 // No license key entered
330 $license_status = 'inactive';
331 $license_text = __( 'Inactive', 'easy-invoice' );
332 }
333 } else {
334 // Free version - show Free badge
335 $show_license_badge = true;
336 $license_status = 'free';
337 $license_text = __( 'Free', 'easy-invoice' );
338 }
339 ?>
340 <?php if ($ei_can_open('easy-invoice-license')) : ?>
341 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-license')); ?>"
342 class="<?php echo esc_attr(($current_page === 'easy-invoice-license') ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700'); ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
343
344 <svg class="<?php echo ($current_page === 'easy-invoice-license') ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
345 <circle cx="7.5" cy="15.5" r="5.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
346 <path d="M21 2L11.5 11.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
347 <path d="M15.5 7.5L18.5 10.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
348 </svg>
349 <span class="flex-1"><?php esc_html_e( 'License', 'easy-invoice' ); ?></span>
350 <?php if ($show_license_badge): ?>
351 <span class="license-status-badge <?php echo esc_attr($license_status); ?>">
352 <span class="status-dot"></span>
353 <?php echo esc_html( $license_text ); ?>
354 </span>
355 <?php endif; ?>
356 </a>
357 <?php endif; ?>
358
359
360 <?php
361 $show_free_vs_pro = !easy_invoice_has_pro();
362
363 if ($show_free_vs_pro):
364 ?>
365 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-free-vs-pro')); ?>"
366 class="<?php echo esc_attr(($current_page === 'easy-invoice-free-vs-pro') ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700'); ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
367 <svg class="<?php echo ($current_page === 'easy-invoice-free-vs-pro') ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
368 <path d="M3 6L21 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
369 <path d="M3 12L21 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
370 <path d="M3 18L21 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
371 <path d="M9 3L9 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
372 <path d="M15 3L15 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
373 </svg>
374 <span class="flex-1"><?php esc_html_e('Free vs Pro', 'easy-invoice'); ?></span>
375 <span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
376 <?php esc_html_e('Compare', 'easy-invoice'); ?>
377 </span>
378 </a>
379 <?php endif; ?>
380
381 <?php
382 // Per-addon sidebar entries. Show every enabled addon that has
383 // its own dedicated page (Smart Reminders, Time Tracking,
384 // White Label, etc. — anything whose settings_url is NOT a
385 // deep-link into the main Settings page). Addons whose settings
386 // live inside Settings → <section> (Secure Links → Advanced,
387 // Partial Payments → Invoice, etc.) are intentionally excluded:
388 // the main Settings page's section nav is already the
389 // navigation aid for those, and a sidebar entry would be a
390 // duplicate path to the same place. Users still reach those
391 // via the "Settings →" link on the addon card.
392 if (class_exists('\\EasyInvoice\\Addons\\AddonRegistry') && class_exists('\\EasyInvoice\\Addons\\AddonManager')):
393 foreach (\EasyInvoice\Addons\AddonRegistry::all() as $ei_addon):
394 if (empty($ei_addon['settings_url'])) continue;
395 if (!\EasyInvoice\Addons\AddonManager::isEnabled($ei_addon['id'])) continue;
396 if (!\EasyInvoice\Addons\AddonManager::userCanAccess($ei_addon['id'])) continue;
397
398 // Skip addons whose settings_url deep-links into the
399 // main Settings page (easy-invoice-settings). Those
400 // are accessed via the card's "Settings →" link only.
401 $ei_addon_parsed = wp_parse_url($ei_addon['settings_url']);
402 $ei_addon_query = [];
403 if (!empty($ei_addon_parsed['query'])) {
404 parse_str($ei_addon_parsed['query'], $ei_addon_query);
405 }
406 $ei_addon_slug = $ei_addon_query['page'] ?? '';
407 if ($ei_addon_slug === 'easy-invoice-settings') continue;
408 if (!$ei_can_open($ei_addon_slug)) continue;
409
410 $ei_addon_active = ($current_page === $ei_addon_slug);
411 $ei_addon_icon = !empty($ei_addon['icon']) ? $ei_addon['icon'] : 'dashicons-admin-plugins';
412 $ei_addon_label = !empty($ei_addon['short_name']) ? $ei_addon['short_name'] : $ei_addon['name'];
413 ?>
414 <a href="<?php echo esc_url($ei_addon['settings_url']); ?>"
415 class="<?php echo $ei_addon_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700'; ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200"
416 title="<?php echo esc_attr($ei_addon['name']); ?>">
417 <span class="dashicons <?php echo esc_attr($ei_addon_icon); ?> mr-3 flex-shrink-0 h-5 w-5 <?php echo $ei_addon_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-500'; ?>" style="font-size:20px;width:20px;height:20px;line-height:20px;"></span>
418 <span class="flex-1 truncate"><?php echo esc_html($ei_addon_label); ?></span>
419 </a>
420 <?php
421 endforeach;
422 endif;
423 ?>
424
425
426 <?php if ($ei_can_open('easy-invoice-addons')) : ?>
427 <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-addons')); ?>"
428 class="<?php echo esc_attr($is_addons_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700'); ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
429 <svg class="<?php echo $is_addons_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
430 <rect x="3" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
431 <rect x="14" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
432 <rect x="3" y="14" width="7" height="7" rx="1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
433 <path d="M17.5 14V21M14 17.5H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
434 </svg>
435 <span class="flex-1"><?php esc_html_e('Addons', 'easy-invoice'); ?></span>
436 <span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-700">
437 <?php esc_html_e('New', 'easy-invoice'); ?>
438 </span>
439 </a>
440 <?php endif; ?>
441
442
443 <?php if (apply_filters('easy_invoice_show_upgrade_prompts', true)) : ?>
444 <a href="<?php echo esc_url('https://www.facebook.com/groups/mantrabraincommunity'); ?>"
445 target="_blank"
446 rel="noopener noreferrer"
447 class="<?php echo ($current_page === 'easy-invoice-join-community') ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700'; ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
448 <svg class="<?php echo ($current_page === 'easy-invoice-join-community') ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
449 <path d="M18 2H15C13.6739 2 12.4021 2.52678 11.4645 3.46447C10.5268 4.40215 10 5.67392 10 7V10H7V14H10V22H14V14H17L18 10H14V7C14 6.73478 14.1054 6.48043 14.2929 6.29289C14.4804 6.10536 14.7348 6 15 6H18V2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
450 </svg>
451 <span class="flex-1"><?php esc_html_e('Join Community', 'easy-invoice'); ?></span>
452 <svg class="flex-shrink-0 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
453 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
454 </svg>
455 </a>
456 <?php endif; ?>
457
458
459 <?php
460 /**
461 * Filter the sidebar help link. Pro's White Label addon points it at
462 * the agency's own support page.
463 *
464 * @param string $url
465 */
466 $ei_help_url = apply_filters('easy_invoice_help_link_url', 'https://easy-invoice.matrixaddons.com/docs/');
467 ?>
468 <a href="<?php echo esc_url($ei_help_url); ?>"
469 target="_blank"
470 rel="noopener noreferrer"
471 class="text-gray-600 hover:bg-indigo-50 hover:text-indigo-700 group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
472 <svg class="text-gray-400 group-hover:text-indigo-500 mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
473 <path d="M4 19.5V4.5C4 3.4 4.9 2.5 6 2.5H19C19.5 2.5 20 3 20 3.5V20.5C20 21 19.5 21.5 19 21.5H6C4.9 21.5 4 20.6 4 19.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
474 <path d="M8 7H16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
475 <path d="M8 11H16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
476 <path d="M8 15H13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
477 </svg>
478 <span class="flex-1"><?php esc_html_e('Documentation', 'easy-invoice'); ?></span>
479 <svg class="flex-shrink-0 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
480 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
481 </svg>
482 </a>
483
484 </nav>
485
486
487 <div class="ei-app-sidebar-foot border-t border-gray-200 p-4 flex-shrink-0 bg-white">
488 <div class="relative">
489 <button type="button" id="user-dropdown-button" class="flex items-center w-full text-left hover:bg-gray-50 rounded-lg p-2 transition-colors duration-200">
490 <div class="flex-shrink-0">
491 <div class="h-8 w-8 rounded-full bg-gray-200 flex items-center justify-center">
492 <span class="text-gray-600 font-medium text-sm"><?php echo esc_html(substr(wp_get_current_user()->display_name, 0, 1)); ?></span>
493 </div>
494 </div>
495 <div class="ml-3 flex-1">
496 <p class="text-sm font-medium text-gray-700"><?php echo esc_html(wp_get_current_user()->display_name); ?></p>
497 <p class="text-xs text-gray-500"><?php echo esc_html(wp_get_current_user()->user_email); ?></p>
498 </div>
499 <div class="flex-shrink-0">
500 <svg class="h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
501 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
502 </svg>
503 </div>
504 </button>
505
506
507 <div id="user-dropdown-menu" class="hidden absolute bottom-full left-0 right-0 mb-2 bg-white border border-gray-200 rounded-lg shadow-lg z-50">
508 <div class="py-1">
509 <a href="<?php echo esc_url(admin_url()); ?>" target="_blank" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors duration-200">
510 <svg class="mr-3 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
511 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
512 </svg>
513 <?php esc_html_e('Back to WordPress', 'easy-invoice'); ?>
514 </a>
515 <a href="<?php echo esc_url(wp_logout_url()); ?>" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors duration-200">
516 <svg class="mr-3 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
517 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path>
518 </svg>
519 <?php esc_html_e('Logout', 'easy-invoice'); ?>
520 </a>
521 </div>
522 </div>
523 </div>
524 </div>
525 </div>
526
527 <?php
528 /*
529 * The old mobile menu button lived here. Removed in favour of #ei-sidebar-toggle
530 * at the top of this file, because this one could not work:
531 *
532 * - Its handler bound to `document.querySelector('button[aria-expanded="false"]')`,
533 * i.e. the first such button anywhere in the document — not reliably this one.
534 * - It toggled a `hidden` class the sidebar never had. The sidebar had no
535 * responsive rules at all, so it was always open; toggling `hidden` removed it
536 * outright while the content kept its unconditional `pl-64`, leaving a 256px
537 * empty gutter.
538 * - It was `sm:hidden`, so it only appeared below 640px, while the layout
539 * actually breaks below 1024px.
540 */
541 ?>
542
543 <script>
544 document.addEventListener('DOMContentLoaded', function() {
545 /* Sidebar drawer is handled by the #ei-sidebar-toggle script above. */
546
547 // Handle Reports menu click for premium feature
548 const reportsMenuLink = document.getElementById('reports-menu-link');
549 if (reportsMenuLink) {
550 reportsMenuLink.addEventListener('click', function(e) {
551 e.preventDefault();
552 // Show premium popup using the proper confirmation modal
553 if (typeof EasyInvoiceConfirmation !== 'undefined') {
554 EasyInvoiceConfirmation.showFeatureUpgrade(
555 <?php echo wp_json_encode( __( 'Detailed Reports', 'easy-invoice' ) ); ?>,
556 <?php echo wp_json_encode( __( 'Get comprehensive insights into your business with detailed reports including revenue analysis, payment statistics, invoice status tracking, and monthly revenue charts. Upgrade to Easy Invoice Pro to unlock advanced reporting features.', 'easy-invoice' ) ); ?>
557 );
558 } else {
559 // Fallback to alert if confirmation modal is not available
560 alert('<?php echo esc_js(__('Detailed Reports is a premium feature. Upgrade to Easy Invoice Pro to unlock advanced reporting features including revenue analysis, payment statistics, and monthly revenue charts.', 'easy-invoice')); ?>');
561 }
562 });
563 }
564
565 // Handle user dropdown
566 const userDropdownButton = document.getElementById('user-dropdown-button');
567 const userDropdownMenu = document.getElementById('user-dropdown-menu');
568
569 if (userDropdownButton && userDropdownMenu) {
570 userDropdownButton.addEventListener('click', function(e) {
571 e.preventDefault();
572 e.stopPropagation();
573 userDropdownMenu.classList.toggle('hidden');
574 });
575
576 // Close dropdown when clicking outside
577 document.addEventListener('click', function(e) {
578 if (!userDropdownButton.contains(e.target) && !userDropdownMenu.contains(e.target)) {
579 userDropdownMenu.classList.add('hidden');
580 }
581 });
582
583 // Close dropdown when pressing Escape key
584 document.addEventListener('keydown', function(e) {
585 if (e.key === 'Escape') {
586 userDropdownMenu.classList.add('hidden');
587 }
588 });
589 }
590 });
591 </script>
592
593 <style>
594 html.wp-toolbar {
595 padding-top: 0 !important;
596 }
597 /* Matches hideAdminUi: hidden admin bar must not reserve body padding or main content shifts vs fixed sidebar */
598 body.easy-invoice-admin-body.admin-bar {
599 padding-top: 0 !important;
600 }
601 body.easy-invoice-admin-body.admin-bar #wpbody,
602 body.easy-invoice-admin-body.admin-bar #wpbody-content {
603 padding-top: 0 !important;
604 }
605
606 /* Keep any admin notice clear of the fixed sidebar.
607 *
608 * WordPress prints `admin_notices` output into #wpbody-content BEFORE this
609 * template's markup, so a notice sits outside the `.lg:pl-64` content column and
610 * runs edge to edge underneath the 16rem fixed sidebar — the first words of every
611 * line get covered. disableAdminNoticesOnEasyInvoicePages() suppresses most
612 * notices on our screens, but not all of them: WordPress core prints some
613 * (php_update_notice, update-nag) outside that hook, and any notice we
614 * deliberately keep would be clipped the same way. Indenting them costs nothing
615 * when there are none to show. */
616 @media (min-width: 1024px) {
617 body.easy-invoice-admin-body #wpbody-content > .notice,
618 body.easy-invoice-admin-body #wpbody-content > .updated,
619 body.easy-invoice-admin-body #wpbody-content > .update-nag,
620 body.easy-invoice-admin-body #wpbody-content > .error {
621 margin-left: 17rem;
622 margin-right: 1rem;
623 }
624 }
625
626 /* Full-height app shell — works WITH the global
627 `#wpbody-content { display: flex; flex-direction: column; min-height: 100vh }`
628 rule defined in easy-invoice.css. Because the parent is a flex
629 column, the wrapper can't rely on `height: 100%` alone (flex
630 children stay at content height unless given `flex-grow`). We give
631 the wrapper `flex: 1` so it expands to fill whatever vertical space
632 the flex column offers, and a `min-height: 100vh` floor so short
633 pages still paint the bg-gray-50 all the way down to the viewport
634 bottom. Removing the previous html → body → wpwrap → wpcontent
635 chain that wasn't doing anything useful in the flex-column world. */
636 .easy-invoice-admin {
637 flex: 1;
638 min-height: 100vh;
639 }
640
641 /*
642 * Below `lg` the sidebar collapses to a drawer and #ei-sidebar-toggle is pinned at
643 * top-left. Every page header starts hard against the left edge, so without this
644 * the toggle sat on top of the page title ("Invoices" rendered as "voices") and on
645 * top of the builders' "Back to invoices" link.
646 *
647 * Reserve the toggle's width (44px) plus its offset so headers start clear of it.
648 * Both header variants are covered: `.ei-app-page-header` is used by the eight
649 * list/settings screens, `.ei-app-header-sync` by the invoice and quote builders.
650 */
651 @media (max-width: 1023px) {
652 /* !important is needed here, not for weight games: five of these headers
653 (invoice/quote listings, payments, clients, reports) set `padding-left`
654 in an inline style attribute, which a stylesheet rule cannot otherwise
655 beat. Those inline paddings pair with a negative margin to bleed the
656 header full-width, so they cannot simply be removed. */
657 .ei-app-page-header,
658 .ei-app-header-sync {
659 padding-left: 3.75rem !important;
660 }
661 }
662
663 /* Custom color for active icons */
664 .group:hover svg {
665 color: rgb(79 70 229) !important;
666 }
667
668 /* Active state icon color */
669 .bg-indigo-50 svg {
670 color: rgb(79 70 229) !important;
671 }
672
673 /* Hover state for non-active items */
674 .group:hover:not(.bg-indigo-50) svg {
675 color: rgb(79 70 229) !important;
676 }
677
678 /* Sidebar scrollbar — slim, only visible on hover/scroll. Firefox uses scrollbar-* */
679 .ei-app-sidebar-nav {
680 scrollbar-width: thin;
681 scrollbar-color: rgba(156, 163, 175, 0) transparent;
682 transition: scrollbar-color 0.2s ease;
683 }
684 .ei-app-sidebar-nav:hover {
685 scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
686 }
687 /* WebKit (Chrome / Safari / Edge) */
688 .ei-app-sidebar-nav::-webkit-scrollbar {
689 width: 8px;
690 }
691 .ei-app-sidebar-nav::-webkit-scrollbar-track {
692 background: transparent;
693 }
694 .ei-app-sidebar-nav::-webkit-scrollbar-thumb {
695 background-color: transparent;
696 border-radius: 4px;
697 border: 2px solid transparent;
698 background-clip: content-box;
699 transition: background-color 0.2s ease;
700 }
701 .ei-app-sidebar-nav:hover::-webkit-scrollbar-thumb {
702 background-color: rgba(156, 163, 175, 0.45);
703 }
704 .ei-app-sidebar-nav::-webkit-scrollbar-thumb:hover {
705 background-color: rgba(107, 114, 128, 0.7);
706 }
707 </style>
708
709 <div class="min-h-screen bg-white easy-invoice-admin">
710 <a href="#easy-invoice-main" class="easy-invoice-skip-link screen-reader-text"><?php esc_html_e( 'Skip to main content', 'easy-invoice' ); ?></a>
711
712 <?php // pl-64 only from `lg` up — under that the sidebar is an off-canvas drawer
713 // and reserving 256px of padding left phones with ~134px of usable width. ?>
714 <div id="easy-invoice-main" class="lg:pl-64" tabindex="-1">
715 <?php
716
717 $page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : 'easy-invoice';
718 // Content will be inserted here
719 do_action('easy_invoice_admin_before_main_content', $page);
720
721 do_action('easy_invoice_admin_main_content', $page);
722
723 do_action('easy_invoice_admin_after_main_content', $page);
724
725 ?>
726 </div>
727
728
729 <script>
730 /* Off-canvas sidebar for viewports below Tailwind's `lg` (1024px).
731 Kept dependency-free and inline so it works on every Easy Invoice screen
732 without touching the 13 individual page templates. */
733 (function () {
734 var toggle = document.getElementById('ei-sidebar-toggle');
735 var sidebar = document.getElementById('ei-app-sidebar');
736 var backdrop = document.getElementById('ei-sidebar-backdrop');
737 if (!toggle || !sidebar || !backdrop) { return; }
738
739 function setOpen(open) {
740 sidebar.classList.toggle('-translate-x-full', !open);
741 backdrop.classList.toggle('hidden', !open);
742 toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
743 /* Stop the page scrolling behind the drawer while it is open. */
744 document.body.style.overflow = open ? 'hidden' : '';
745 }
746
747 toggle.addEventListener('click', function () {
748 setOpen(sidebar.classList.contains('-translate-x-full'));
749 });
750 backdrop.addEventListener('click', function () { setOpen(false); });
751
752 document.addEventListener('keydown', function (e) {
753 if (e.key === 'Escape' && !sidebar.classList.contains('-translate-x-full')) {
754 setOpen(false);
755 toggle.focus();
756 }
757 });
758
759 /* Closing on navigation keeps the drawer from being stuck open after a
760 click-through, and resizing up to desktop must clear the inline
761 overflow lock the drawer set. */
762 sidebar.addEventListener('click', function (e) {
763 if (e.target.closest('a') && window.matchMedia('(max-width: 1023px)').matches) { setOpen(false); }
764 });
765 window.matchMedia('(min-width: 1024px)').addEventListener('change', function (ev) {
766 if (ev.matches) { setOpen(false); document.body.style.overflow = ''; }
767 });
768 })();
769
770 if (typeof ajaxurl === 'undefined') {
771 var ajaxurl = '<?php echo esc_url(admin_url('admin-ajax.php')); ?>';
772 }
773 </script>
774 </div>
775