PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.4
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.4
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 +463 -125 2.1.1 → 2.4.4 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) {
@@ -13,9 +16,9 @@
13 16 function get_menu_class($current_page, $menu_page, $is_active = false) {
14 17 if ($is_active) {
15 18 return 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600';
16 19 }
17 - return 'text-gray-600 hover:bg-gray-50 hover:text-gray-900';
20 + return 'text-gray-600 hover:bg-indigo-50 hover:text-indigo-700';
18 21 }
19 22
20 23 // Function to get the active icon class
21 24 function get_icon_class($current_page, $menu_page, $is_active = false) {
@@ -21,9 +24,9 @@
21 24 function get_icon_class($current_page, $menu_page, $is_active = false) {
22 25 if ($is_active) {
23 26 return 'text-indigo-600';
24 27 }
25 - return 'text-gray-400 group-hover:text-gray-500';
28 + return 'text-gray-400 group-hover:text-indigo-500';
26 29 }
27 30
28 31 // Determine which menu should be active
29 32 $is_dashboard_active = $current_page === 'easy-invoice';
@@ -45,8 +48,16 @@
45 48 'easy-invoice-clients', // All clients
46 49 'easy-invoice-client-edit', // Edit client
47 50 'easy-invoice-client-view' // View client
48 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 +]);
49 60 $is_reports_active = $current_page === 'easy-invoice-reports';
50 61 $is_settings_active = in_array($current_page, [
51 62 'easy-invoice-settings', // Main settings
52 63 'easy-invoice-email-settings', // Email settings
@@ -57,38 +68,75 @@
57 68 'easy-invoice-email-settings-payment-reminder',
58 69 'easy-invoice-pro-settings', // Pro settings
59 70 'easy-invoice-pro-translations' // Pro translations
60 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');
61 77 ?>
62 78
63 -<!-- Sidebar -->
64 -<div class="fixed inset-y-0 left-0 w-64 bg-white shadow-lg border-r border-gray-200">
65 - <!-- Logo and Back Button -->
66 - <div class="flex flex-col items-center justify-center h-24 px-4 pt-3 pb-2 border-b border-gray-200">
67 - <div class="flex items-center space-x-2 mb-2">
68 - <div class="flex items-center justify-center w-8 h-8 bg-indigo-600 rounded">
69 - <!-- Invoice Logo SVG -->
70 - <svg class="h-5 w-5 text-white" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
71 - <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"/>
72 - <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
73 - <path d="M16 13H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
74 - <path d="M16 17H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
75 - <path d="M10 9H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
76 - </svg>
77 - </div>
78 - <span class="text-lg font-semibold text-gray-900"><?php esc_html_e('Easy Invoice', 'easy-invoice'); ?></span>
79 - </div>
80 - <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">
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 rounded ei-app-logo-mark">'
118 + . easy_invoice_logo_mark_svg(32)
119 + . '</div>'
120 + . '<span class="text-lg font-semibold text-gray-900 ei-app-logo-label">' . esc_html__('Easy Invoice', 'easy-invoice') . '</span>'
121 + . '</div>';
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));
124 + ?>
125 + <?php if (apply_filters('easy_invoice_admin_show_back_to_wp', true)): ?>
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">
81 127 <svg class="w-3 h-3 mr-1" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
82 128 <path d="M19 12H5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
83 129 <path d="M12 19L5 12L12 5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
84 130 </svg>
85 - Back to WordPress
131 + <?php esc_html_e( 'Back to WordPress', 'easy-invoice' ); ?>
86 132 </a>
133 + <?php endif; ?>
87 134
88 - <!-- Version Information -->
89 - <div class="mt-2 mb-2 flex items-center justify-center space-x-2">
90 - <!-- Free Version Badge -->
135 +
136 + <?php if (apply_filters('easy_invoice_admin_show_version_badges', true)): ?>
137 + <div class="mt-1 flex flex-wrap items-center justify-center gap-1.5">
138 +
91 139 <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">
92 140 <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">
93 141 <path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
94 142 <path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -97,26 +145,27 @@
97 145 v<?php echo esc_html(EASY_INVOICE_VERSION); ?>
98 146 </span>
99 147
100 148 <?php if (defined('EASY_INVOICE_PRO_VERSION') && function_exists('is_plugin_active') && is_plugin_active('easy-invoice-pro/easy-invoice-pro.php')): ?>
101 - <!-- Pro Version Badge -->
149 +
102 150 <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">
103 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">
104 152 <path d="M9 12L11 14L15 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
105 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"/>
106 154 </svg>
107 - Pro v<?php echo esc_html(EASY_INVOICE_PRO_VERSION); ?>
155 + <?php /* translators: %s: Pro plugin version. */ echo esc_html( sprintf( __( 'Pro v%s', 'easy-invoice' ), EASY_INVOICE_PRO_VERSION ) ); ?>
108 156 </span>
109 157 <?php endif; ?>
110 158 </div>
159 + <?php endif; // easy_invoice_admin_show_version_badges ?>
111 160 </div>
112 161
113 - <!-- Navigation Links -->
114 - <nav class="mt-6 px-3 space-y-2">
115 - <!-- Dashboard -->
116 - <a href="<?php echo admin_url('admin.php?page=easy-invoice'); ?>"
117 - class="<?php echo $is_dashboard_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
118 - <svg class="<?php echo $is_dashboard_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
162 +
163 + <nav class="ei-app-sidebar-nav flex-1 min-h-0 overflow-y-auto mt-6 px-3 pb-4 space-y-2">
164 +
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">
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">
119 168 <path d="M3 13H9V19H3V13Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
120 169 <path d="M9 13H15V19H9V13Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
121 170 <path d="M15 13H21V19H15V13Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
122 171 <path d="M3 5H9V11H3V5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -122,104 +171,137 @@
122 171 <path d="M3 5H9V11H3V5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
123 172 <path d="M9 5H15V11H9V5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
124 173 <path d="M15 5H21V11H15V5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
125 174 </svg>
126 - Dashboard
175 + <?php esc_html_e( 'Dashboard', 'easy-invoice' ); ?>
127 176 </a>
128 177
129 - <!-- Invoices -->
130 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-all'); ?>"
131 - class="<?php echo $is_invoices_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
132 - <svg class="<?php echo $is_invoices_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
178 +
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">
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">
133 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"/>
134 191 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
135 - <path d="M16 13H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
136 - <path d="M16 17H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
137 - <path d="M10 9H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
192 + <path d="M12 11V19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
193 + <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"/>
138 194 </svg>
139 - Invoices
195 + <?php esc_html_e( 'Invoices', 'easy-invoice' ); ?>
140 196 </a>
141 197
142 - <!-- Quotes -->
143 - <a href="<?php echo admin_url('admin.php?page=easy-quote-all'); ?>"
144 - class="<?php echo $is_quotes_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
145 - <svg class="<?php echo $is_quotes_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
198 +
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">
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">
146 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"/>
147 204 <path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
148 - <path d="M9 9H15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
149 - <path d="M9 13H15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
150 - <path d="M9 17H13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
205 + <path d="M8.5 15.5L11 18L16 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
151 206 </svg>
152 - Quotes
207 + <?php esc_html_e( 'Quotes', 'easy-invoice' ); ?>
153 208 </a>
209 + <?php endif; ?>
154 210
155 - <!-- Payments -->
156 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-payments'); ?>"
157 - class="<?php echo $is_payments_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
158 - <svg class="<?php echo $is_payments_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
211 +
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">
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">
159 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"/>
160 217 <line x1="1" y1="10" x2="23" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
161 218 </svg>
162 - Payments
219 + <?php esc_html_e( 'Payments', 'easy-invoice' ); ?>
163 220 </a>
221 + <?php endif; ?>
164 222
165 - <!-- Clients -->
166 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-clients'); ?>"
167 - class="<?php echo $is_clients_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
168 - <svg class="<?php echo $is_clients_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
223 +
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">
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">
169 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"/>
170 229 <circle cx="9" cy="7" r="4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
171 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"/>
172 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"/>
173 232 </svg>
174 - Clients
233 + <?php esc_html_e( 'Clients', 'easy-invoice' ); ?>
175 234 </a>
235 + <?php endif; ?>
176 236
177 - <!-- Reports -->
237 + <?php
238 + // Item Library + Template Builder sidebar entries were here.
239 + // They've been moved BELOW the Addons menu — see the per-addon
240 + // sidebar loop further down in this file (lines ~365). The loop
241 + // automatically renders a sidebar entry for every enabled addon
242 + // whose registry entry has a non-empty settings_url, so the two
243 + // explicit blocks are no longer needed (and were creating
244 + // visual duplication with the loop).
245 + ?>
246 +
247 + <?php
248 + // Reports — addon-gated (PERSONAL tier).
249 + //
250 + // When the addon is loaded, the per-addon sidebar loop further
251 + // down in this file (after the Addons menu entry) renders the
252 + // Reports link automatically — so this block only handles the
253 + // Free-user upsell state. Showing it twice (here AND via the
254 + // loop) was the cause of the "2 Reports pages" duplicate.
255 + ?>
178 256 <?php if (!easy_invoice_has_pro()): ?>
257 +
179 258 <a href="#" id="reports-menu-link"
180 - class="text-gray-600 hover:bg-gray-50 hover:text-gray-900 group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
181 - <svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
259 + 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">
260 + <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">
182 261 <path d="M18 20V10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
183 262 <path d="M12 20V4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
184 263 <path d="M6 20V14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
185 264 </svg>
186 - Reports
265 + <?php esc_html_e( 'Reports', 'easy-invoice' ); ?>
187 266 <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">
188 267 <svg class="w-4 h-4 mr-1 text-white" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
189 268 <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"/>
190 269 </svg>
191 - Pro
270 + <?php esc_html_e( 'Pro', 'easy-invoice' ); ?>
192 271 </span>
193 272 </a>
194 - <?php else: ?>
195 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-reports'); ?>"
196 - class="<?php echo $is_reports_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
197 - <svg class="<?php echo $is_reports_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
198 - <path d="M18 20V10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
199 - <path d="M12 20V4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
200 - <path d="M6 20V14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
273 + <?php endif; ?>
274 +
275 +
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">
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">
280 + <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
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"/>
201 282 </svg>
202 - Reports
283 + <?php esc_html_e( 'Settings', 'easy-invoice' ); ?>
203 284 </a>
204 285 <?php endif; ?>
205 286
206 - <!-- Settings -->
207 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-settings'); ?>"
208 - class="<?php echo $is_settings_active ? 'bg-indigo-50 text-indigo-700 border-r-2 border-indigo-600' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'; ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
209 - <svg class="<?php echo $is_settings_active ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
210 - <circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
211 - <path d="M19.4 15A1.65 1.65 0 0 0 18 14A6 6 0 0 0 6 14A1.65 1.65 0 0 0 4.6 15A2 2 0 0 0 6 19H18A2 2 0 0 0 19.4 15Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
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"/>
212 293 </svg>
213 - Settings
294 + <?php esc_html_e( 'Import', 'easy-invoice' ); ?>
214 295 </a>
296 + <?php endif; ?>
215 297
216 - <!-- License -->
298 +
217 299 <?php
218 300 // Check if Pro plugin is active and get license status
219 301 $show_license_badge = false;
220 302 $license_status = 'inactive';
221 - $license_text = 'Deactivated';
303 + $license_text = __( 'Deactivated', 'easy-invoice' );
222 304
223 305 if (easy_invoice_has_pro()) {
224 306 $license_key = \EasyInvoicePro\Updater\License::get_license_key();
225 307 $is_valid = \EasyInvoicePro\Updater\License::has_valid_license();
@@ -230,51 +312,56 @@
230 312
231 313 if (!empty($license_key)) {
232 314 if ($is_valid && !$is_expired) {
233 315 $license_status = 'activated';
234 - $license_text = 'Activated';
316 + $license_text = __( 'Activated', 'easy-invoice' );
235 317 } elseif ($is_expired) {
236 318 $license_status = 'expired';
237 - $license_text = 'Expired';
319 + $license_text = __( 'Expired', 'easy-invoice' );
238 320 } else {
239 321 $license_status = 'inactive';
240 - $license_text = 'Deactivated';
322 + $license_text = __( 'Deactivated', 'easy-invoice' );
241 323 }
242 324 } else {
243 325 // No license key entered
244 326 $license_status = 'inactive';
245 - $license_text = 'Inactive';
327 + $license_text = __( 'Inactive', 'easy-invoice' );
246 328 }
247 329 } else {
248 330 // Free version - show Free badge
249 331 $show_license_badge = true;
250 332 $license_status = 'free';
251 - $license_text = 'Free';
333 + $license_text = __( 'Free', 'easy-invoice' );
252 334 }
253 335 ?>
254 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-license'); ?>"
255 - 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-gray-50 hover:text-gray-900'; ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
256 - <svg class="<?php echo ($current_page === 'easy-invoice-license') ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
257 - <path d="M12 2L13.09 8.26L20 9L13.09 9.74L12 16L10.91 9.74L4 9L10.91 8.26L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
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">
339 +
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">
341 + <circle cx="7.5" cy="15.5" r="5.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
342 + <path d="M21 2L11.5 11.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
343 + <path d="M15.5 7.5L18.5 10.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
258 344 </svg>
259 - <span class="flex-1">License</span>
345 + <span class="flex-1"><?php esc_html_e( 'License', 'easy-invoice' ); ?></span>
260 346 <?php if ($show_license_badge): ?>
261 - <span class="license-status-badge <?php echo $license_status; ?>">
347 + <span class="license-status-badge <?php echo esc_attr($license_status); ?>">
262 348 <span class="status-dot"></span>
263 - <?php echo $license_text; ?>
349 + <?php echo esc_html( $license_text ); ?>
264 350 </span>
265 351 <?php endif; ?>
266 352 </a>
353 + <?php endif; ?>
267 354
268 - <!-- Free vs Pro - only show in free version -->
355 +
269 356 <?php
270 357 $show_free_vs_pro = !easy_invoice_has_pro();
271 358
272 359 if ($show_free_vs_pro):
273 360 ?>
274 - <a href="<?php echo admin_url('admin.php?page=easy-invoice-free-vs-pro'); ?>"
275 - 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-gray-50 hover:text-gray-900'; ?> group flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200">
276 - <svg class="<?php echo ($current_page === 'easy-invoice-free-vs-pro') ? 'text-indigo-600' : 'text-gray-400 group-hover:text-gray-500'; ?> mr-3 flex-shrink-0 h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
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">
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">
277 364 <path d="M3 6L21 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
278 365 <path d="M3 12L21 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
279 366 <path d="M3 18L21 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
280 367 <path d="M9 3L9 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -286,17 +373,120 @@
286 373 </span>
287 374 </a>
288 375 <?php endif; ?>
289 376
377 + <?php
378 + // Per-addon sidebar entries. Show every enabled addon that has
379 + // its own dedicated page (Smart Reminders, Time Tracking,
380 + // White Label, etc. — anything whose settings_url is NOT a
381 + // deep-link into the main Settings page). Addons whose settings
382 + // live inside Settings → <section> (Secure Links → Advanced,
383 + // Partial Payments → Invoice, etc.) are intentionally excluded:
384 + // the main Settings page's section nav is already the
385 + // navigation aid for those, and a sidebar entry would be a
386 + // duplicate path to the same place. Users still reach those
387 + // via the "Settings →" link on the addon card.
388 + if (class_exists('\\EasyInvoice\\Addons\\AddonRegistry') && class_exists('\\EasyInvoice\\Addons\\AddonManager')):
389 + foreach (\EasyInvoice\Addons\AddonRegistry::all() as $ei_addon):
390 + if (empty($ei_addon['settings_url'])) continue;
391 + if (!\EasyInvoice\Addons\AddonManager::isEnabled($ei_addon['id'])) continue;
392 + if (!\EasyInvoice\Addons\AddonManager::userCanAccess($ei_addon['id'])) continue;
393 +
394 + // Skip addons whose settings_url deep-links into the
395 + // main Settings page (easy-invoice-settings). Those
396 + // are accessed via the card's "Settings →" link only.
397 + $ei_addon_parsed = wp_parse_url($ei_addon['settings_url']);
398 + $ei_addon_query = [];
399 + if (!empty($ei_addon_parsed['query'])) {
400 + parse_str($ei_addon_parsed['query'], $ei_addon_query);
401 + }
402 + $ei_addon_slug = $ei_addon_query['page'] ?? '';
403 + if ($ei_addon_slug === 'easy-invoice-settings') continue;
404 + if (!$ei_can_open($ei_addon_slug)) continue;
405 +
406 + $ei_addon_active = ($current_page === $ei_addon_slug);
407 + $ei_addon_icon = !empty($ei_addon['icon']) ? $ei_addon['icon'] : 'dashicons-admin-plugins';
408 + $ei_addon_label = !empty($ei_addon['short_name']) ? $ei_addon['short_name'] : $ei_addon['name'];
409 + ?>
410 + <a href="<?php echo esc_url($ei_addon['settings_url']); ?>"
411 + 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"
412 + title="<?php echo esc_attr($ei_addon['name']); ?>">
413 + <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>
414 + <span class="flex-1 truncate"><?php echo esc_html($ei_addon_label); ?></span>
415 + </a>
416 + <?php
417 + endforeach;
418 + endif;
419 + ?>
420 +
421 +
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">
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">
426 + <rect x="3" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
427 + <rect x="14" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
428 + <rect x="3" y="14" width="7" height="7" rx="1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
429 + <path d="M17.5 14V21M14 17.5H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
430 + </svg>
431 + <span class="flex-1"><?php esc_html_e('Addons', 'easy-invoice'); ?></span>
432 + <span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-700">
433 + <?php esc_html_e('New', 'easy-invoice'); ?>
434 + </span>
435 + </a>
436 + <?php endif; ?>
437 +
438 +
439 + <?php if (apply_filters('easy_invoice_show_upgrade_prompts', true)) : ?>
440 + <a href="<?php echo esc_url('https://www.facebook.com/groups/mantrabraincommunity'); ?>"
441 + target="_blank"
442 + rel="noopener noreferrer"
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">
444 + <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">
445 + <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"/>
446 + </svg>
447 + <span class="flex-1"><?php esc_html_e('Join Community', 'easy-invoice'); ?></span>
448 + <svg class="flex-shrink-0 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
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"/>
450 + </svg>
451 + </a>
452 + <?php endif; ?>
453 +
454 +
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); ?>"
465 + target="_blank"
466 + rel="noopener noreferrer"
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">
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">
469 + <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"/>
470 + <path d="M8 7H16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
471 + <path d="M8 11H16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
472 + <path d="M8 15H13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
473 + </svg>
474 + <span class="flex-1"><?php esc_html_e('Documentation', 'easy-invoice'); ?></span>
475 + <svg class="flex-shrink-0 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
476 + <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"/>
477 + </svg>
478 + </a>
479 +
290 480 </nav>
291 481
292 - <!-- User Profile -->
293 - <div class="absolute bottom-0 w-full border-t border-gray-200 p-4">
482 +
483 + <div class="ei-app-sidebar-foot border-t border-gray-200 p-4 flex-shrink-0 bg-white">
294 484 <div class="relative">
295 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">
296 486 <div class="flex-shrink-0">
297 487 <div class="h-8 w-8 rounded-full bg-gray-200 flex items-center justify-center">
298 - <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>
299 489 </div>
300 490 </div>
301 491 <div class="ml-3 flex-1">
302 492 <p class="text-sm font-medium text-gray-700"><?php echo esc_html(wp_get_current_user()->display_name); ?></p>
@@ -308,22 +498,22 @@
308 498 </svg>
309 499 </div>
310 500 </button>
311 501
312 - <!-- Dropdown Menu -->
502 +
313 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">
314 504 <div class="py-1">
315 - <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">
316 506 <svg class="mr-3 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
317 507 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
318 508 </svg>
319 - <?php _e('Back to WordPress', 'easy-invoice'); ?>
509 + <?php esc_html_e('Back to WordPress', 'easy-invoice'); ?>
320 510 </a>
321 - <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">
322 512 <svg class="mr-3 h-4 w-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
323 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>
324 514 </svg>
325 - <?php _e('Logout', 'easy-invoice'); ?>
515 + <?php esc_html_e('Logout', 'easy-invoice'); ?>
326 516 </a>
327 517 </div>
328 518 </div>
329 519 </div>
@@ -329,28 +519,28 @@
329 519 </div>
330 520 </div>
331 521 </div>
332 522
333 -<!-- Mobile menu button -->
334 -<div class="fixed top-0 left-0 z-50 p-4 sm:hidden">
335 - <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">
336 - <i class="fas fa-bars"></i>
337 - </button>
338 -</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 +?>
339 538
340 539 <script>
341 540 document.addEventListener('DOMContentLoaded', function() {
342 - const mobileMenuButton = document.querySelector('button[aria-expanded="false"]');
343 - const sidebar = document.querySelector('.fixed.inset-y-0.left-0');
541 + /* Sidebar drawer is handled by the #ei-sidebar-toggle script above. */
344 542
345 - if (mobileMenuButton && sidebar) {
346 - mobileMenuButton.addEventListener('click', function() {
347 - const expanded = this.getAttribute('aria-expanded') === 'true';
348 - this.setAttribute('aria-expanded', !expanded);
349 - sidebar.classList.toggle('hidden');
350 - });
351 - }
352 -
353 543 // Handle Reports menu click for premium feature
354 544 const reportsMenuLink = document.getElementById('reports-menu-link');
355 545 if (reportsMenuLink) {
356 546 reportsMenuLink.addEventListener('click', function(e) {
@@ -356,9 +546,12 @@
356 546 reportsMenuLink.addEventListener('click', function(e) {
357 547 e.preventDefault();
358 548 // Show premium popup using the proper confirmation modal
359 549 if (typeof EasyInvoiceConfirmation !== 'undefined') {
360 - EasyInvoiceConfirmation.showFeatureUpgrade('Detailed Reports', '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.');
550 + EasyInvoiceConfirmation.showFeatureUpgrade(
551 + <?php echo wp_json_encode( __( 'Detailed Reports', 'easy-invoice' ) ); ?>,
552 + <?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' ) ); ?>
553 + );
361 554 } else {
362 555 // Fallback to alert if confirmation modal is not available
363 556 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')); ?>');
364 557 }
@@ -396,9 +589,74 @@
396 589 <style>
397 590 html.wp-toolbar {
398 591 padding-top: 0 !important;
399 592 }
593 +/* Matches hideAdminUi: hidden admin bar must not reserve body padding or main content shifts vs fixed sidebar */
594 +body.easy-invoice-admin-body.admin-bar {
595 + padding-top: 0 !important;
596 +}
597 +body.easy-invoice-admin-body.admin-bar #wpbody,
598 +body.easy-invoice-admin-body.admin-bar #wpbody-content {
599 + padding-top: 0 !important;
600 +}
400 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 +
622 +/* Full-height app shell — works WITH the global
623 + `#wpbody-content { display: flex; flex-direction: column; min-height: 100vh }`
624 + rule defined in easy-invoice.css. Because the parent is a flex
625 + column, the wrapper can't rely on `height: 100%` alone (flex
626 + children stay at content height unless given `flex-grow`). We give
627 + the wrapper `flex: 1` so it expands to fill whatever vertical space
628 + the flex column offers, and a `min-height: 100vh` floor so short
629 + pages still paint the bg-gray-50 all the way down to the viewport
630 + bottom. Removing the previous html → body → wpwrap → wpcontent
631 + chain that wasn't doing anything useful in the flex-column world. */
632 +.easy-invoice-admin {
633 + flex: 1;
634 + min-height: 100vh;
635 +}
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 +
401 659 /* Custom color for active icons */
402 660 .group:hover svg {
403 661 color: rgb(79 70 229) !important;
404 662 }
@@ -411,13 +669,46 @@
411 669 /* Hover state for non-active items */
412 670 .group:hover:not(.bg-indigo-50) svg {
413 671 color: rgb(79 70 229) !important;
414 672 }
673 +
674 +/* Sidebar scrollbar — slim, only visible on hover/scroll. Firefox uses scrollbar-* */
675 +.ei-app-sidebar-nav {
676 + scrollbar-width: thin;
677 + scrollbar-color: rgba(156, 163, 175, 0) transparent;
678 + transition: scrollbar-color 0.2s ease;
679 +}
680 +.ei-app-sidebar-nav:hover {
681 + scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
682 +}
683 +/* WebKit (Chrome / Safari / Edge) */
684 +.ei-app-sidebar-nav::-webkit-scrollbar {
685 + width: 8px;
686 +}
687 +.ei-app-sidebar-nav::-webkit-scrollbar-track {
688 + background: transparent;
689 +}
690 +.ei-app-sidebar-nav::-webkit-scrollbar-thumb {
691 + background-color: transparent;
692 + border-radius: 4px;
693 + border: 2px solid transparent;
694 + background-clip: content-box;
695 + transition: background-color 0.2s ease;
696 +}
697 +.ei-app-sidebar-nav:hover::-webkit-scrollbar-thumb {
698 + background-color: rgba(156, 163, 175, 0.45);
699 +}
700 +.ei-app-sidebar-nav::-webkit-scrollbar-thumb:hover {
701 + background-color: rgba(107, 114, 128, 0.7);
702 +}
415 703 </style>
416 704
417 -<div class="min-h-screen bg-gray-50 easy-invoice-admin">
418 - <!-- Main Content -->
419 - <div class="pl-64">
705 +<div class="min-h-screen bg-white easy-invoice-admin">
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>
707 +
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">
420 711 <?php
421 712
422 713 $page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : 'easy-invoice';
423 714 // Content will be inserted here
@@ -428,5 +719,52 @@
428 719 do_action('easy_invoice_admin_after_main_content', $page);
429 720
430 721 ?>
431 722 </div>
723 +
724 +
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 +
766 + if (typeof ajaxurl === 'undefined') {
767 + var ajaxurl = '<?php echo esc_url(admin_url('admin-ajax.php')); ?>';
768 + }
769 + </script>
432 770 </div>