PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.3
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.3
2.4.3 2.4.4 2.4.2 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 All 60 releases
← All changes | templates/main-template.php +207 -56 2.3.2 → 2.4.3 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 /**
@@ -84,22 +113,18 @@
84 113 * Filter: easy_invoice_admin_logo_html
85 114 * Args: ($default_html)
86 115 */
87 116 $default_logo_html = '<div class="flex items-center space-x-2 mb-1">'
88 - . '<div class="flex items-center justify-center w-8 h-8 bg-indigo-600 rounded ei-app-logo-mark">'
89 - . '<svg class="h-5 w-5 text-white" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">'
90 - . '<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"/>'
91 - . '<path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
92 - . '<path d="M16 13H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
93 - . '<path d="M16 17H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
94 - . '<path d="M10 9H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
95 - . '</svg></div>'
117 + . '<div class="flex items-center justify-center w-8 h-8 rounded ei-app-logo-mark">'
118 + . easy_invoice_logo_mark_svg(32)
119 + . '</div>'
96 120 . '<span class="text-lg font-semibold text-gray-900 ei-app-logo-label">' . esc_html__('Easy Invoice', 'easy-invoice') . '</span>'
97 121 . '</div>';
98 - echo apply_filters('easy_invoice_admin_logo_html', $default_logo_html);
122 + // wp_kses_post() drops <svg>; the logo list keeps the mark intact.
123 + echo easy_invoice_kses_logo(apply_filters('easy_invoice_admin_logo_html', $default_logo_html));
99 124 ?>
100 125 <?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">
126 + <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 127 <svg class="w-3 h-3 mr-1" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
103 128 <path d="M19 12H5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
104 129 <path d="M12 19L5 12L12 5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
105 130 </svg>
@@ -126,9 +151,9 @@
126 151 <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 152 <path d="M9 12L11 14L15 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
128 153 <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 154 </svg>
130 - <?php echo esc_html( sprintf( __( 'Pro v%s', 'easy-invoice' ), EASY_INVOICE_PRO_VERSION ) ); ?>
155 + <?php /* translators: %s: Pro plugin version. */ echo esc_html( sprintf( __( 'Pro v%s', 'easy-invoice' ), EASY_INVOICE_PRO_VERSION ) ); ?>
131 156 </span>
132 157 <?php endif; ?>
133 158 </div>
134 159 <?php endif; // easy_invoice_admin_show_version_badges ?>
@@ -136,10 +161,10 @@
136 161
137 162
138 163 <nav class="ei-app-sidebar-nav flex-1 min-h-0 overflow-y-auto mt-6 px-3 pb-4 space-y-2">
139 164
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">
165 + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice')); ?>"
166 + 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 167 <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 168 <path d="M3 13H9V19H3V13Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
144 169 <path d="M9 13H15V19H9V13Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
145 170 <path d="M15 13H21V19H15V13Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -150,10 +175,18 @@
150 175 <?php esc_html_e( 'Dashboard', 'easy-invoice' ); ?>
151 176 </a>
152 177
153 178
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">
179 + <?php
180 +// A sidebar entry is drawn only when the current user can open the page it
181 +// leads to: administrators can open everything; a team member with an EI role
182 +// (Pro Team Roles) is mapped through the same filter the menu uses.
183 +$ei_can_open = static function ($slug) {
184 + return current_user_can((string) apply_filters('easy_invoice_menu_capability', 'manage_options', $slug));
185 +};
186 +?>
187 +<a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-all')); ?>"
188 + 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 189 <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 190 <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 191 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
159 192 <path d="M12 11V19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -162,10 +195,11 @@
162 195 <?php esc_html_e( 'Invoices', 'easy-invoice' ); ?>
163 196 </a>
164 197
165 198
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">
199 + <?php if ($ei_can_open('easy-quote-all')) : ?>
200 + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-quote-all')); ?>"
201 + 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 202 <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 203 <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 204 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
171 205 <path d="M8.5 15.5L11 18L16 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -171,12 +205,14 @@
171 205 <path d="M8.5 15.5L11 18L16 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
172 206 </svg>
173 207 <?php esc_html_e( 'Quotes', 'easy-invoice' ); ?>
174 208 </a>
209 + <?php endif; ?>
175 210
176 211
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">
212 + <?php if ($ei_can_open('easy-invoice-payments')) : ?>
213 + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-payments')); ?>"
214 + 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 215 <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 216 <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 217 <line x1="1" y1="10" x2="23" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
182 218 </svg>
@@ -181,12 +217,14 @@
181 217 <line x1="1" y1="10" x2="23" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
182 218 </svg>
183 219 <?php esc_html_e( 'Payments', 'easy-invoice' ); ?>
184 220 </a>
221 + <?php endif; ?>
185 222
186 223
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">
224 + <?php if ($ei_can_open('easy-invoice-clients')) : ?>
225 + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-clients')); ?>"
226 + 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 227 <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 228 <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 229 <circle cx="9" cy="7" r="4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
192 230 <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 +231,9 @@
193 231 <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 232 </svg>
195 233 <?php esc_html_e( 'Clients', 'easy-invoice' ); ?>
196 234 </a>
235 + <?php endif; ?>
197 236
198 237 <?php
199 238 // Item Library + Template Builder sidebar entries were here.
200 239 // They've been moved BELOW the Addons menu — see the per-addon
@@ -233,10 +272,11 @@
233 272 </a>
234 273 <?php endif; ?>
235 274
236 275
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">
276 + <?php if ($ei_can_open('easy-invoice-settings')) : ?>
277 + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-settings')); ?>"
278 + 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 279 <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 280 <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
241 281 <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 282 </svg>
@@ -241,9 +281,21 @@
241 281 <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 282 </svg>
243 283 <?php esc_html_e( 'Settings', 'easy-invoice' ); ?>
244 284 </a>
285 + <?php endif; ?>
245 286
287 + <?php $is_import_active = ( 'easy-invoice-import' === $current_page ); ?>
288 + <?php if ($ei_can_open('easy-invoice-import')) : ?>
289 + <a href="<?php echo esc_url( admin_url( 'admin.php?page=easy-invoice-import' ) ); ?>"
290 + 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">
291 + <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">
292 + <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"/>
293 + </svg>
294 + <?php esc_html_e( 'Import', 'easy-invoice' ); ?>
295 + </a>
296 + <?php endif; ?>
297 +
246 298
247 299 <?php
248 300 // Check if Pro plugin is active and get license status
249 301 $show_license_badge = false;
@@ -280,10 +332,11 @@
280 332 $license_status = 'free';
281 333 $license_text = __( 'Free', 'easy-invoice' );
282 334 }
283 335 ?>
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">
336 + <?php if ($ei_can_open('easy-invoice-license')) : ?>
337 + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-license')); ?>"
338 + 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 339
287 340 <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 341 <circle cx="7.5" cy="15.5" r="5.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
289 342 <path d="M21 2L11.5 11.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -290,14 +343,15 @@
290 343 <path d="M15.5 7.5L18.5 10.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
291 344 </svg>
292 345 <span class="flex-1"><?php esc_html_e( 'License', 'easy-invoice' ); ?></span>
293 346 <?php if ($show_license_badge): ?>
294 - <span class="license-status-badge <?php echo $license_status; ?>">
347 + <span class="license-status-badge <?php echo esc_attr($license_status); ?>">
295 348 <span class="status-dot"></span>
296 349 <?php echo esc_html( $license_text ); ?>
297 350 </span>
298 351 <?php endif; ?>
299 352 </a>
353 + <?php endif; ?>
300 354
301 355
302 356 <?php
303 357 $show_free_vs_pro = !easy_invoice_has_pro();
@@ -303,10 +357,10 @@
303 357 $show_free_vs_pro = !easy_invoice_has_pro();
304 358
305 359 if ($show_free_vs_pro):
306 360 ?>
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">
361 + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-free-vs-pro')); ?>"
362 + 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 363 <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 364 <path d="M3 6L21 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
311 365 <path d="M3 12L21 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
312 366 <path d="M3 18L21 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -346,8 +400,9 @@
346 400 parse_str($ei_addon_parsed['query'], $ei_addon_query);
347 401 }
348 402 $ei_addon_slug = $ei_addon_query['page'] ?? '';
349 403 if ($ei_addon_slug === 'easy-invoice-settings') continue;
404 + if (!$ei_can_open($ei_addon_slug)) continue;
350 405
351 406 $ei_addon_active = ($current_page === $ei_addon_slug);
352 407 $ei_addon_icon = !empty($ei_addon['icon']) ? $ei_addon['icon'] : 'dashicons-admin-plugins';
353 408 $ei_addon_label = !empty($ei_addon['short_name']) ? $ei_addon['short_name'] : $ei_addon['name'];
@@ -363,10 +418,11 @@
363 418 endif;
364 419 ?>
365 420
366 421
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">
422 + <?php if ($ei_can_open('easy-invoice-addons')) : ?>
423 + <a href="<?php echo esc_url(admin_url('admin.php?page=easy-invoice-addons')); ?>"
424 + 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 425 <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 426 <rect x="3" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
371 427 <rect x="14" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
372 428 <rect x="3" y="14" width="7" height="7" rx="1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -376,10 +432,12 @@
376 432 <span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-700">
377 433 <?php esc_html_e('New', 'easy-invoice'); ?>
378 434 </span>
379 435 </a>
436 + <?php endif; ?>
380 437
381 438
439 + <?php if (apply_filters('easy_invoice_show_upgrade_prompts', true)) : ?>
382 440 <a href="<?php echo esc_url('https://www.facebook.com/groups/mantrabraincommunity'); ?>"
383 441 target="_blank"
384 442 rel="noopener noreferrer"
385 443 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 +448,21 @@
390 448 <svg class="flex-shrink-0 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
391 449 <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 450 </svg>
393 451 </a>
452 + <?php endif; ?>
394 453
395 454
396 - <a href="<?php echo esc_url('https://easy-invoice.matrixaddons.com/docs/'); ?>"
455 + <?php
456 + /**
457 + * Filter the sidebar help link. Pro's White Label addon points it at
458 + * the agency's own support page.
459 + *
460 + * @param string $url
461 + */
462 + $ei_help_url = apply_filters('easy_invoice_help_link_url', 'https://easy-invoice.matrixaddons.com/docs/');
463 + ?>
464 + <a href="<?php echo esc_url($ei_help_url); ?>"
397 465 target="_blank"
398 466 rel="noopener noreferrer"
399 467 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 468 <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 +484,9 @@
416 484 <div class="relative">
417 485 <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 486 <div class="flex-shrink-0">
419 487 <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>
488 + <span class="text-gray-600 font-medium text-sm"><?php echo esc_html(substr(wp_get_current_user()->display_name, 0, 1)); ?></span>
421 489 </div>
422 490 </div>
423 491 <div class="ml-3 flex-1">
424 492 <p class="text-sm font-medium text-gray-700"><?php echo esc_html(wp_get_current_user()->display_name); ?></p>
@@ -433,19 +501,19 @@
433 501
434 502
435 503 <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 504 <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">
505 + <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 506 <svg class="mr-3 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
439 507 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
440 508 </svg>
441 - <?php _e('Back to WordPress', 'easy-invoice'); ?>
509 + <?php esc_html_e('Back to WordPress', 'easy-invoice'); ?>
442 510 </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">
511 + <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 512 <svg class="mr-3 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
445 513 <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 514 </svg>
447 - <?php _e('Logout', 'easy-invoice'); ?>
515 + <?php esc_html_e('Logout', 'easy-invoice'); ?>
448 516 </a>
449 517 </div>
450 518 </div>
451 519 </div>
@@ -451,29 +519,28 @@
451 519 </div>
452 520 </div>
453 521 </div>
454 522
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>
523 +<?php
524 +/*
525 + * The old mobile menu button lived here. Removed in favour of #ei-sidebar-toggle
526 + * at the top of this file, because this one could not work:
527 + *
528 + * - Its handler bound to `document.querySelector('button[aria-expanded="false"]')`,
529 + * i.e. the first such button anywhere in the document — not reliably this one.
530 + * - It toggled a `hidden` class the sidebar never had. The sidebar had no
531 + * responsive rules at all, so it was always open; toggling `hidden` removed it
532 + * outright while the content kept its unconditional `pl-64`, leaving a 256px
533 + * empty gutter.
534 + * - It was `sm:hidden`, so it only appeared below 640px, while the layout
535 + * actually breaks below 1024px.
536 + */
537 +?>
462 538
463 539 <script>
464 540 document.addEventListener('DOMContentLoaded', function() {
465 - const mobileMenuButton = document.querySelector('button[aria-expanded="false"]');
466 - const sidebar = document.querySelector('.fixed.inset-y-0.left-0');
541 + /* Sidebar drawer is handled by the #ei-sidebar-toggle script above. */
467 542
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 543 // Handle Reports menu click for premium feature
477 544 const reportsMenuLink = document.getElementById('reports-menu-link');
478 545 if (reportsMenuLink) {
479 546 reportsMenuLink.addEventListener('click', function(e) {
@@ -531,8 +598,28 @@
531 598 body.easy-invoice-admin-body.admin-bar #wpbody-content {
532 599 padding-top: 0 !important;
533 600 }
534 601
602 +/* Keep any admin notice clear of the fixed sidebar.
603 + *
604 + * WordPress prints `admin_notices` output into #wpbody-content BEFORE this
605 + * template's markup, so a notice sits outside the `.lg:pl-64` content column and
606 + * runs edge to edge underneath the 16rem fixed sidebar — the first words of every
607 + * line get covered. disableAdminNoticesOnEasyInvoicePages() suppresses most
608 + * notices on our screens, but not all of them: WordPress core prints some
609 + * (php_update_notice, update-nag) outside that hook, and any notice we
610 + * deliberately keep would be clipped the same way. Indenting them costs nothing
611 + * when there are none to show. */
612 +@media (min-width: 1024px) {
613 + body.easy-invoice-admin-body #wpbody-content > .notice,
614 + body.easy-invoice-admin-body #wpbody-content > .updated,
615 + body.easy-invoice-admin-body #wpbody-content > .update-nag,
616 + body.easy-invoice-admin-body #wpbody-content > .error {
617 + margin-left: 17rem;
618 + margin-right: 1rem;
619 + }
620 +}
621 +
535 622 /* Full-height app shell — works WITH the global
536 623 `#wpbody-content { display: flex; flex-direction: column; min-height: 100vh }`
537 624 rule defined in easy-invoice.css. Because the parent is a flex
538 625 column, the wrapper can't rely on `height: 100%` alone (flex
@@ -546,8 +633,30 @@
546 633 flex: 1;
547 634 min-height: 100vh;
548 635 }
549 636
637 +/*
638 + * Below `lg` the sidebar collapses to a drawer and #ei-sidebar-toggle is pinned at
639 + * top-left. Every page header starts hard against the left edge, so without this
640 + * the toggle sat on top of the page title ("Invoices" rendered as "voices") and on
641 + * top of the builders' "Back to invoices" link.
642 + *
643 + * Reserve the toggle's width (44px) plus its offset so headers start clear of it.
644 + * Both header variants are covered: `.ei-app-page-header` is used by the eight
645 + * list/settings screens, `.ei-app-header-sync` by the invoice and quote builders.
646 + */
647 +@media (max-width: 1023px) {
648 + /* !important is needed here, not for weight games: five of these headers
649 + (invoice/quote listings, payments, clients, reports) set `padding-left`
650 + in an inline style attribute, which a stylesheet rule cannot otherwise
651 + beat. Those inline paddings pair with a negative margin to bleed the
652 + header full-width, so they cannot simply be removed. */
653 + .ei-app-page-header,
654 + .ei-app-header-sync {
655 + padding-left: 3.75rem !important;
656 + }
657 +}
658 +
550 659 /* Custom color for active icons */
551 660 .group:hover svg {
552 661 color: rgb(79 70 229) !important;
553 662 }
@@ -595,9 +704,11 @@
595 704
596 705 <div class="min-h-screen bg-white easy-invoice-admin">
597 706 <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 707
599 - <div id="easy-invoice-main" class="pl-64" tabindex="-1">
708 + <?php // pl-64 only from `lg` up — under that the sidebar is an off-canvas drawer
709 + // and reserving 256px of padding left phones with ~134px of usable width. ?>
710 + <div id="easy-invoice-main" class="lg:pl-64" tabindex="-1">
600 711 <?php
601 712
602 713 $page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : 'easy-invoice';
603 714 // Content will be inserted here
@@ -611,9 +722,49 @@
611 722 </div>
612 723
613 724
614 725 <script>
726 + /* Off-canvas sidebar for viewports below Tailwind's `lg` (1024px).
727 + Kept dependency-free and inline so it works on every Easy Invoice screen
728 + without touching the 13 individual page templates. */
729 + (function () {
730 + var toggle = document.getElementById('ei-sidebar-toggle');
731 + var sidebar = document.getElementById('ei-app-sidebar');
732 + var backdrop = document.getElementById('ei-sidebar-backdrop');
733 + if (!toggle || !sidebar || !backdrop) { return; }
734 +
735 + function setOpen(open) {
736 + sidebar.classList.toggle('-translate-x-full', !open);
737 + backdrop.classList.toggle('hidden', !open);
738 + toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
739 + /* Stop the page scrolling behind the drawer while it is open. */
740 + document.body.style.overflow = open ? 'hidden' : '';
741 + }
742 +
743 + toggle.addEventListener('click', function () {
744 + setOpen(sidebar.classList.contains('-translate-x-full'));
745 + });
746 + backdrop.addEventListener('click', function () { setOpen(false); });
747 +
748 + document.addEventListener('keydown', function (e) {
749 + if (e.key === 'Escape' && !sidebar.classList.contains('-translate-x-full')) {
750 + setOpen(false);
751 + toggle.focus();
752 + }
753 + });
754 +
755 + /* Closing on navigation keeps the drawer from being stuck open after a
756 + click-through, and resizing up to desktop must clear the inline
757 + overflow lock the drawer set. */
758 + sidebar.addEventListener('click', function (e) {
759 + if (e.target.closest('a') && window.matchMedia('(max-width: 1023px)').matches) { setOpen(false); }
760 + });
761 + window.matchMedia('(min-width: 1024px)').addEventListener('change', function (ev) {
762 + if (ev.matches) { setOpen(false); document.body.style.overflow = ''; }
763 + });
764 + })();
765 +
615 766 if (typeof ajaxurl === 'undefined') {
616 - var ajaxurl = '<?php echo admin_url('admin-ajax.php'); ?>';
767 + var ajaxurl = '<?php echo esc_url(admin_url('admin-ajax.php')); ?>';
617 768 }
618 769 </script>
619 770 </div>