PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
← All changes | templates/main-template.php +203 -48 2.3.6 → 2.4.0 View file →
@@ -1,5 +1,8 @@
1 1 <?php
2 +if ( ! defined( 'ABSPATH' ) ) {
3 + exit;
4 +}
2 5 $current_page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : 'easy-invoice';
3 6
4 7 // Function to check if current page is a subpage of a main menu
5 8 function is_subpage_of($current_page, $main_page) {
@@ -72,9 +75,35 @@
72 75 // itself; the parent Addons row should NOT also light up.
73 76 $is_addons_active = ($current_page === 'easy-invoice-addons');
74 77 ?>
75 78
76 -<div class="fixed inset-y-0 left-0 w-64 bg-white shadow-lg border-r border-gray-200 ei-app-sidebar flex flex-col">
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">
77 106
78 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">
79 108 <?php
80 109 /**
@@ -94,12 +123,12 @@
94 123 . '<path d="M10 9H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
95 124 . '</svg></div>'
96 125 . '<span class="text-lg font-semibold text-gray-900 ei-app-logo-label">' . esc_html__('Easy Invoice', 'easy-invoice') . '</span>'
97 126 . '</div>';
98 - echo apply_filters('easy_invoice_admin_logo_html', $default_logo_html);
127 + echo wp_kses_post(apply_filters('easy_invoice_admin_logo_html', $default_logo_html));
99 128 ?>
100 129 <?php if (apply_filters('easy_invoice_admin_show_back_to_wp', true)): ?>
101 - <a href="<?php echo admin_url(); ?>" target="_blank" class="inline-flex items-center text-xs text-gray-500 hover:text-gray-700 transition-colors duration-200">
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">
102 131 <svg class="w-3 h-3 mr-1" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
103 132 <path d="M19 12H5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
104 133 <path d="M12 19L5 12L12 5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
105 134 </svg>
@@ -126,9 +155,9 @@
126 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">
127 156 <path d="M9 12L11 14L15 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
128 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"/>
129 158 </svg>
130 - <?php echo esc_html( sprintf( __( 'Pro v%s', 'easy-invoice' ), EASY_INVOICE_PRO_VERSION ) ); ?>
159 + <?php /* translators: %s: Pro plugin version. */ echo esc_html( sprintf( __( 'Pro v%s', 'easy-invoice' ), EASY_INVOICE_PRO_VERSION ) ); ?>
131 160 </span>
132 161 <?php endif; ?>
133 162 </div>
134 163 <?php endif; // easy_invoice_admin_show_version_badges ?>
@@ -136,10 +165,10 @@
136 165
137 166
138 167 <nav class="ei-app-sidebar-nav flex-1 min-h-0 overflow-y-auto mt-6 px-3 pb-4 space-y-2">
139 168
140 - <a href="<?php echo admin_url('admin.php?page=easy-invoice'); ?>"
141 - class="<?php echo $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">
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">
142 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">
143 172 <path d="M3 13H9V19H3V13Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
144 173 <path d="M9 13H15V19H9V13Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
145 174 <path d="M15 13H21V19H15V13Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -150,10 +179,18 @@
150 179 <?php esc_html_e( 'Dashboard', 'easy-invoice' ); ?>
151 180 </a>
152 181
153 182
154 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-all'); ?>"
155 - class="<?php echo $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">
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">
156 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">
157 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"/>
158 195 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
159 196 <path d="M12 11V19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -162,10 +199,11 @@
162 199 <?php esc_html_e( 'Invoices', 'easy-invoice' ); ?>
163 200 </a>
164 201
165 202
166 - <a href="<?php echo admin_url('admin.php?page=easy-quote-all'); ?>"
167 - class="<?php echo $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">
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">
168 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">
169 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"/>
170 208 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
171 209 <path d="M8.5 15.5L11 18L16 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -171,12 +209,14 @@
171 209 <path d="M8.5 15.5L11 18L16 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
172 210 </svg>
173 211 <?php esc_html_e( 'Quotes', 'easy-invoice' ); ?>
174 212 </a>
213 + <?php endif; ?>
175 214
176 215
177 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-payments'); ?>"
178 - class="<?php echo $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">
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">
179 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">
180 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"/>
181 221 <line x1="1" y1="10" x2="23" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
182 222 </svg>
@@ -181,12 +221,14 @@
181 221 <line x1="1" y1="10" x2="23" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
182 222 </svg>
183 223 <?php esc_html_e( 'Payments', 'easy-invoice' ); ?>
184 224 </a>
225 + <?php endif; ?>
185 226
186 227
187 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-clients'); ?>"
188 - class="<?php echo $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">
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">
189 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">
190 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"/>
191 233 <circle cx="9" cy="7" r="4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
192 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"/>
@@ -193,8 +235,9 @@
193 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"/>
194 236 </svg>
195 237 <?php esc_html_e( 'Clients', 'easy-invoice' ); ?>
196 238 </a>
239 + <?php endif; ?>
197 240
198 241 <?php
199 242 // Item Library + Template Builder sidebar entries were here.
200 243 // They've been moved BELOW the Addons menu — see the per-addon
@@ -233,10 +276,11 @@
233 276 </a>
234 277 <?php endif; ?>
235 278
236 279
237 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-settings'); ?>"
238 - class="<?php echo $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">
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">
239 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">
240 284 <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
241 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"/>
242 286 </svg>
@@ -241,9 +285,21 @@
241 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"/>
242 286 </svg>
243 287 <?php esc_html_e( 'Settings', 'easy-invoice' ); ?>
244 288 </a>
289 + <?php endif; ?>
245 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 +
246 302
247 303 <?php
248 304 // Check if Pro plugin is active and get license status
249 305 $show_license_badge = false;
@@ -280,10 +336,11 @@
280 336 $license_status = 'free';
281 337 $license_text = __( 'Free', 'easy-invoice' );
282 338 }
283 339 ?>
284 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-license'); ?>"
285 - class="<?php echo ($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">
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">
286 343
287 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">
288 345 <circle cx="7.5" cy="15.5" r="5.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
289 346 <path d="M21 2L11.5 11.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -290,14 +347,15 @@
290 347 <path d="M15.5 7.5L18.5 10.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
291 348 </svg>
292 349 <span class="flex-1"><?php esc_html_e( 'License', 'easy-invoice' ); ?></span>
293 350 <?php if ($show_license_badge): ?>
294 - <span class="license-status-badge <?php echo $license_status; ?>">
351 + <span class="license-status-badge <?php echo esc_attr($license_status); ?>">
295 352 <span class="status-dot"></span>
296 353 <?php echo esc_html( $license_text ); ?>
297 354 </span>
298 355 <?php endif; ?>
299 356 </a>
357 + <?php endif; ?>
300 358
301 359
302 360 <?php
303 361 $show_free_vs_pro = !easy_invoice_has_pro();
@@ -303,10 +361,10 @@
303 361 $show_free_vs_pro = !easy_invoice_has_pro();
304 362
305 363 if ($show_free_vs_pro):
306 364 ?>
307 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-free-vs-pro'); ?>"
308 - class="<?php echo ($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">
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">
309 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">
310 368 <path d="M3 6L21 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
311 369 <path d="M3 12L21 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
312 370 <path d="M3 18L21 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -346,8 +404,9 @@
346 404 parse_str($ei_addon_parsed['query'], $ei_addon_query);
347 405 }
348 406 $ei_addon_slug = $ei_addon_query['page'] ?? '';
349 407 if ($ei_addon_slug === 'easy-invoice-settings') continue;
408 + if (!$ei_can_open($ei_addon_slug)) continue;
350 409
351 410 $ei_addon_active = ($current_page === $ei_addon_slug);
352 411 $ei_addon_icon = !empty($ei_addon['icon']) ? $ei_addon['icon'] : 'dashicons-admin-plugins';
353 412 $ei_addon_label = !empty($ei_addon['short_name']) ? $ei_addon['short_name'] : $ei_addon['name'];
@@ -363,10 +422,11 @@
363 422 endif;
364 423 ?>
365 424
366 425
367 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-addons'); ?>"
368 - class="<?php echo $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">
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">
369 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">
370 430 <rect x="3" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
371 431 <rect x="14" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
372 432 <rect x="3" y="14" width="7" height="7" rx="1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -376,10 +436,12 @@
376 436 <span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-700">
377 437 <?php esc_html_e('New', 'easy-invoice'); ?>
378 438 </span>
379 439 </a>
440 + <?php endif; ?>
380 441
381 442
443 + <?php if (apply_filters('easy_invoice_show_upgrade_prompts', true)) : ?>
382 444 <a href="<?php echo esc_url('https://www.facebook.com/groups/mantrabraincommunity'); ?>"
383 445 target="_blank"
384 446 rel="noopener noreferrer"
385 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">
@@ -390,11 +452,21 @@
390 452 <svg class="flex-shrink-0 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
391 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"/>
392 454 </svg>
393 455 </a>
456 + <?php endif; ?>
394 457
395 458
396 - <a href="<?php echo esc_url('https://easy-invoice.matrixaddons.com/docs/'); ?>"
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); ?>"
397 469 target="_blank"
398 470 rel="noopener noreferrer"
399 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">
400 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">
@@ -416,9 +488,9 @@
416 488 <div class="relative">
417 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">
418 490 <div class="flex-shrink-0">
419 491 <div class="h-8 w-8 rounded-full bg-gray-200 flex items-center justify-center">
420 - <span class="text-gray-600 font-medium text-sm"><?php echo substr(wp_get_current_user()->display_name, 0, 1); ?></span>
492 + <span class="text-gray-600 font-medium text-sm"><?php echo esc_html(substr(wp_get_current_user()->display_name, 0, 1)); ?></span>
421 493 </div>
422 494 </div>
423 495 <div class="ml-3 flex-1">
424 496 <p class="text-sm font-medium text-gray-700"><?php echo esc_html(wp_get_current_user()->display_name); ?></p>
@@ -433,19 +505,19 @@
433 505
434 506
435 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">
436 508 <div class="py-1">
437 - <a href="<?php echo 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">
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">
438 510 <svg class="mr-3 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
439 511 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
440 512 </svg>
441 - <?php _e('Back to WordPress', 'easy-invoice'); ?>
513 + <?php esc_html_e('Back to WordPress', 'easy-invoice'); ?>
442 514 </a>
443 - <a href="<?php echo wp_logout_url(); ?>" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors duration-200">
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">
444 516 <svg class="mr-3 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
445 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>
446 518 </svg>
447 - <?php _e('Logout', 'easy-invoice'); ?>
519 + <?php esc_html_e('Logout', 'easy-invoice'); ?>
448 520 </a>
449 521 </div>
450 522 </div>
451 523 </div>
@@ -451,29 +523,28 @@
451 523 </div>
452 524 </div>
453 525 </div>
454 526
455 -<div class="fixed top-0 left-0 z-50 p-4 sm:hidden">
456 - <button type="button" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500" aria-expanded="false" aria-label="<?php echo esc_attr__( 'Open menu', 'easy-invoice' ); ?>">
457 - <svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
458 - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
459 - </svg>
460 - </button>
461 -</div>
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 +?>
462 542
463 543 <script>
464 544 document.addEventListener('DOMContentLoaded', function() {
465 - const mobileMenuButton = document.querySelector('button[aria-expanded="false"]');
466 - const sidebar = document.querySelector('.fixed.inset-y-0.left-0');
545 + /* Sidebar drawer is handled by the #ei-sidebar-toggle script above. */
467 546
468 - if (mobileMenuButton && sidebar) {
469 - mobileMenuButton.addEventListener('click', function() {
470 - const expanded = this.getAttribute('aria-expanded') === 'true';
471 - this.setAttribute('aria-expanded', !expanded);
472 - sidebar.classList.toggle('hidden');
473 - });
474 - }
475 -
476 547 // Handle Reports menu click for premium feature
477 548 const reportsMenuLink = document.getElementById('reports-menu-link');
478 549 if (reportsMenuLink) {
479 550 reportsMenuLink.addEventListener('click', function(e) {
@@ -531,8 +602,28 @@
531 602 body.easy-invoice-admin-body.admin-bar #wpbody-content {
532 603 padding-top: 0 !important;
533 604 }
534 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 +
535 626 /* Full-height app shell — works WITH the global
536 627 `#wpbody-content { display: flex; flex-direction: column; min-height: 100vh }`
537 628 rule defined in easy-invoice.css. Because the parent is a flex
538 629 column, the wrapper can't rely on `height: 100%` alone (flex
@@ -546,8 +637,30 @@
546 637 flex: 1;
547 638 min-height: 100vh;
548 639 }
549 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 +
550 663 /* Custom color for active icons */
551 664 .group:hover svg {
552 665 color: rgb(79 70 229) !important;
553 666 }
@@ -595,9 +708,11 @@
595 708
596 709 <div class="min-h-screen bg-white easy-invoice-admin">
597 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>
598 711
599 - <div id="easy-invoice-main" class="pl-64" tabindex="-1">
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">
600 715 <?php
601 716
602 717 $page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : 'easy-invoice';
603 718 // Content will be inserted here
@@ -611,9 +726,49 @@
611 726 </div>
612 727
613 728
614 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 +
615 770 if (typeof ajaxurl === 'undefined') {
616 - var ajaxurl = '<?php echo admin_url('admin-ajax.php'); ?>';
771 + var ajaxurl = '<?php echo esc_url(admin_url('admin-ajax.php')); ?>';
617 772 }
618 773 </script>
619 774 </div>