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