Skip to main content

max / makenotwork

Consolidate tabs and add overflow mechanism - Footer: 11 → 5 links (Pricing, Creators, Docs, Legal, Changelog) - Library: 7 → 3 tabs (Purchases+Subscriptions, Feed, Collections+Wishlists) plus conditional Communities/Contacts - Project dashboard: 10 → 5 tabs (Overview, Content+Blog, Analytics, Monetization (tiers+promos+team), Settings) plus conditional Code/Sync - User dashboard: 10 → 5 tabs (Projects, Payments, Analytics, Settings with sub-nav, Support) - Tab overflow JS: moves excess tabs into a "More" dropdown on resize - Bump to v0.5.14
Co-Authored-By
Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-11 00:59 UTC
Commit: cb328fadd7d6d66a73b01559b113007d8532e927
Parent: 2983c9d
21 files changed, +611 insertions, -204 deletions
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "makenotwork"
3 - version = "0.5.13"
3 + version = "0.5.14"
4 4 edition = "2024"
5 5 license-file = "LICENSE"
6 6
@@ -58,7 +58,9 @@
58 58 =========================================== */
59 59
60 60 function setActiveTab(btn) {
61 - btn.closest('.tabs').querySelectorAll('.tab').forEach(function(tab) {
61 + var container = btn.closest('.tabs');
62 + if (!container) return;
63 + container.querySelectorAll('.tab').forEach(function(tab) {
62 64 tab.classList.remove('active');
63 65 tab.setAttribute('aria-selected', 'false');
64 66 });
@@ -67,9 +69,152 @@
67 69 var panel = document.getElementById('tab-content');
68 70 if (panel) panel.setAttribute('aria-labelledby', btn.id);
69 71 if (btn.id) history.replaceState(null, '', '#' + btn.id);
72 + var menu = btn.closest('.tab-overflow-menu');
73 + if (menu) menu.style.display = 'none';
74 + tabOverflow.updateHighlight(container);
70 75 }
71 76
77 + /* ===========================================
78 + TAB OVERFLOW
79 + Moves tabs that don't fit into a "More" dropdown.
80 + No wrapper divs — tabs are moved directly.
81 + =========================================== */
82 +
83 + var tabOverflow = (function() {
84 + var containers = [];
85 +
86 + function init() {
87 + containers = Array.from(document.querySelectorAll('.tabs[role="tablist"]'));
88 + containers.forEach(setup);
89 + window.addEventListener('resize', debounce(reflowAll, 150));
90 + }
91 +
92 + function setup(tabsEl) {
93 + if (tabsEl.dataset.overflowInit) return;
94 + tabsEl.dataset.overflowInit = '1';
95 +
96 + var moreWrap = document.createElement('div');
97 + moreWrap.className = 'tab-more-wrap';
98 + moreWrap.style.display = 'none';
99 +
100 + var moreBtn = document.createElement('button');
101 + moreBtn.className = 'tab tab-more-btn';
102 + moreBtn.type = 'button';
103 + moreBtn.textContent = 'More';
104 + moreBtn.setAttribute('aria-haspopup', 'true');
105 + moreBtn.setAttribute('aria-expanded', 'false');
106 + moreBtn.addEventListener('click', function(e) {
107 + e.stopPropagation();
108 + var m = moreWrap.querySelector('.tab-overflow-menu');
109 + var open = m.style.display === 'block';
110 + m.style.display = open ? 'none' : 'block';
111 + moreBtn.setAttribute('aria-expanded', open ? 'false' : 'true');
112 + });
113 +
114 + var menu = document.createElement('div');
115 + menu.className = 'tab-overflow-menu';
116 + menu.style.display = 'none';
117 +
118 + moreWrap.appendChild(moreBtn);
119 + moreWrap.appendChild(menu);
120 +
121 + // Insert before spinner if present, otherwise append
122 + var spinner = tabsEl.querySelector('.htmx-indicator');
123 + if (spinner) {
124 + tabsEl.insertBefore(moreWrap, spinner);
125 + } else {
126 + tabsEl.appendChild(moreWrap);
127 + }
128 +
129 + reflow(tabsEl);
130 + }
131 +
132 + function reflow(tabsEl) {
133 + var moreWrap = tabsEl.querySelector('.tab-more-wrap');
134 + if (!moreWrap) return;
135 + var menu = moreWrap.querySelector('.tab-overflow-menu');
136 +
137 + // Move all tabs back from menu into the row (before moreWrap)
138 + Array.from(menu.children).forEach(function(t) {
139 + tabsEl.insertBefore(t, moreWrap);
140 + });
141 + moreWrap.style.display = 'none';
142 +
143 + // Collect all tab buttons (exclude the More button itself)
144 + var tabs = Array.from(tabsEl.querySelectorAll(':scope > .tab'));
145 + if (tabs.length === 0) return;
146 +
147 + // Check if everything fits without More
148 + var available = tabsEl.clientWidth;
149 + var totalWidth = 0;
150 + tabs.forEach(function(t) { totalWidth += t.offsetWidth; });
151 + if (totalWidth <= available) return;
152 +
153 + // Find the cutoff point (reserve space for More button)
154 + var moreBtnWidth = 90;
155 + var used = 0;
156 + var cutoff = tabs.length;
157 +
158 + for (var i = 0; i < tabs.length; i++) {
159 + used += tabs[i].offsetWidth;
160 + if (used + moreBtnWidth > available) {
161 + cutoff = i;
162 + break;
163 + }
164 + }
165 +
166 + // Ensure at least 1 tab stays visible
167 + if (cutoff < 1) cutoff = 1;
168 +
169 + // Move tabs from cutoff onward into the menu
170 + for (var j = cutoff; j < tabs.length; j++) {
171 + menu.appendChild(tabs[j]);
172 + }
173 + moreWrap.style.display = '';
174 + updateHighlight(tabsEl);
175 + }
176 +
177 + function reflowAll() {
178 + containers.forEach(reflow);
179 + }
180 +
181 + function updateHighlight(tabsEl) {
182 + var moreWrap = tabsEl.querySelector('.tab-more-wrap');
183 + if (!moreWrap) return;
184 + var moreBtn = moreWrap.querySelector('.tab-more-btn');
185 + var menu = moreWrap.querySelector('.tab-overflow-menu');
186 + if (!moreBtn || !menu) return;
187 + var hasActive = menu.querySelector('.tab.active');
188 + moreBtn.classList.toggle('active', !!hasActive);
189 + }
190 +
191 + function debounce(fn, ms) {
192 + var timer;
193 + return function() {
194 + clearTimeout(timer);
195 + timer = setTimeout(fn, ms);
196 + };
197 + }
198 +
199 + return { init: init, updateHighlight: updateHighlight };
200 + })();
201 +
72 202 document.addEventListener('DOMContentLoaded', function() {
203 + // Tab preloading on hover
204 + document.querySelectorAll('.tab').forEach(function(btn) {
205 + btn.addEventListener('mouseenter', function() {
206 + if (this.dataset.preloaded) return;
207 + var url = this.getAttribute('hx-get');
208 + if (!url) return;
209 + this.dataset.preloaded = '1';
210 + fetch(url, { headers: { 'HX-Request': 'true' } }).catch(function() {});
211 + });
212 + });
213 +
214 + // Initialize tab overflow
215 + tabOverflow.init();
216 +
217 + // Restore hash-based tab selection (after overflow init so tabs are placed)
73 218 var hash = location.hash.replace('#', '');
74 219 if (hash) {
75 220 var tab = document.getElementById(hash);
@@ -78,13 +223,13 @@
78 223 }
79 224 }
80 225
81 - document.querySelectorAll('.tab').forEach(function(btn) {
82 - btn.addEventListener('mouseenter', function() {
83 - if (this.dataset.preloaded) return;
84 - var url = this.getAttribute('hx-get');
85 - if (!url) return;
86 - this.dataset.preloaded = '1';
87 - fetch(url, { headers: { 'HX-Request': 'true' } }).catch(function() {});
226 + // Close More dropdown on outside click
227 + document.addEventListener('click', function() {
228 + document.querySelectorAll('.tab-overflow-menu').forEach(function(m) {
229 + m.style.display = 'none';
230 + });
231 + document.querySelectorAll('.tab-more-btn').forEach(function(b) {
232 + b.setAttribute('aria-expanded', 'false');
88 233 });
89 234 });
90 235 });
@@ -429,6 +429,7 @@
429 429
430 430 .tabs {
431 431 display: flex;
432 + flex-wrap: nowrap;
432 433 gap: 0;
433 434 margin-bottom: 0;
434 435 }
@@ -445,6 +446,8 @@
445 446 background 0.2s ease,
446 447 opacity 0.2s ease;
447 448 opacity: 0.6;
449 + white-space: nowrap;
450 + flex-shrink: 0;
448 451 }
449 452
450 453 .tab.active {
@@ -456,6 +459,38 @@
456 459 opacity: 1;
457 460 }
458 461
462 + .tab-more-wrap {
463 + position: relative;
464 + flex-shrink: 0;
465 + }
466 +
467 + .tab-overflow-menu {
468 + position: absolute;
469 + top: 100%;
470 + right: 0;
471 + z-index: 10;
472 + background: var(--background);
473 + border: 1px solid var(--border);
474 + min-width: 180px;
475 + box-shadow: 0 2px 8px rgba(0,0,0,0.1);
476 + }
477 +
478 + .tab-overflow-menu .tab {
479 + display: block;
480 + width: 100%;
481 + text-align: left;
482 + padding: 0.6rem 1rem;
483 + opacity: 0.7;
484 + }
485 +
486 + .tab-overflow-menu .tab:hover {
487 + opacity: 1;
488 + }
489 +
490 + .tab-overflow-menu .tab.active {
491 + opacity: 1;
492 + }
493 +
459 494 .tab-content {
460 495 display: none;
461 496 background: var(--light-background);
@@ -3744,10 +3779,6 @@
3744 3779 grid-template-columns: 1fr;
3745 3780 }
3746 3781
3747 - .tabs {
3748 - flex-wrap: wrap;
3749 - }
3750 -
3751 3782 .tab {
3752 3783 padding: 0.6rem 1.25rem;
3753 3784 font-size: 0.9rem;
@@ -18,15 +18,9 @@
18 18 <footer class="site-footer">
19 19 <div class="site-footer-links">
20 20 <a href="/pricing">Pricing</a>
21 - <a href="/use-cases">Use Cases</a>
22 21 <a href="/creators">Creators</a>
23 22 <a href="/docs">Docs</a>
24 - <a href="/fan-plus">Fan+</a>
25 - <a href="/docs/faq">FAQ</a>
26 - <a href="/policy">Policy</a>
27 - <a href="/docs/terms-of-service">Terms</a>
28 - <a href="/docs/privacy-policy">Privacy</a>
29 - <a href="/health">Status</a>
23 + <a href="/policy">Legal</a>
30 24 <a href="/changelog">Changelog</a>
31 25 </div>
32 26 <p>&copy; 2026 Makenotwork</p>
@@ -35,8 +29,8 @@
35 29 <!-- Toast notification container -->
36 30 <div id="notifications" class="toast-container" role="alert" aria-live="polite"></div>
37 31
38 - <script src="/static/mnw.js?v=0513"></script>
39 - <script src="/static/collections.js?v=0513"></script>
32 + <script src="/static/mnw.js?v=0514"></script>
33 + <script src="/static/collections.js?v=0514"></script>
40 34 {% block scripts %}{% endblock %}
41 35 </body>
42 36 </html>
@@ -120,6 +120,7 @@
120 120 ExportContentReadyTemplate,
121 121 TransactionsTableTemplate,
122 122 UserProfileTabTemplate,
123 + UserSettingsTabTemplate,
123 124 UserAccountTabTemplate,
124 125 UserSshKeysTabTemplate,
125 126 UserPaymentsTabTemplate,
@@ -135,6 +136,7 @@
135 136 ProjectBlogTabTemplate,
136 137 ProjectSubscriptionsTabTemplate,
137 138 ProjectMembersTabTemplate,
139 + ProjectMonetizationTabTemplate,
138 140 ItemEditRowTemplate,
139 141 // Admin partials
140 142 AdminWaitlistEntriesTemplate,
@@ -164,9 +166,7 @@
164 166 // Library tabs
165 167 LibraryPurchasesTabTemplate,
166 168 LibraryFeedTabTemplate,
167 - LibrarySubscriptionsTabTemplate,
168 169 LibraryCollectionsTabTemplate,
169 - LibraryWishlistsTabTemplate,
170 170 LibraryContactsTabTemplate,
171 171 LibraryCommunitiesTabTemplate,
172 172 // Follow button
@@ -174,6 +174,20 @@
174 174 pub custom_domain: Option<CustomDomainInfo>,
175 175 }
176 176
177 + /// Dashboard settings meta-tab with sub-navigation (profile, account, plan, etc.)
178 + #[derive(Template)]
179 + #[template(path = "partials/tabs/user_settings.html")]
180 + pub struct UserSettingsTabTemplate {
181 + pub user: User,
182 + pub custom_links: Vec<CustomLinkWithId>,
183 + pub feed_url: String,
184 + pub can_create_projects: bool,
185 + pub custom_domain: Option<CustomDomainInfo>,
186 + pub has_media: bool,
187 + pub git_enabled: bool,
188 + pub has_mt_memberships: bool,
189 + }
190 +
177 191 /// Dashboard tab: account mechanics — security, sessions, notifications, data.
178 192 #[derive(Template)]
179 193 #[template(path = "partials/tabs/user_account.html")]
@@ -320,6 +334,8 @@
320 334 pub items: Vec<ContentItem>,
321 335 pub deleted_items: Vec<DeletedItemRow>,
322 336 pub project_slug: String,
337 + pub project_id: String,
338 + pub posts: Vec<BlogPostDashboardRow>,
323 339 }
324 340
325 341 /// Dashboard tab: project analytics with stats, chart, and top items.
@@ -413,6 +429,21 @@
413 429 pub owner_split: i64,
414 430 }
415 431
432 + /// Combined monetization tab: tiers, promo codes, and team splits.
433 + #[derive(Template)]
434 + #[template(path = "partials/tabs/project_monetization.html")]
435 + pub struct ProjectMonetizationTabTemplate {
436 + pub project_id: String,
437 + pub project_slug: String,
438 + pub tiers: Vec<SubscriptionTier>,
439 + pub subscriber_count: i64,
440 + pub stripe_connected: bool,
441 + pub promo_codes: Vec<crate::types::PromoCodeRow>,
442 + pub items: Vec<ContentItem>,
443 + pub members: Vec<ProjectMemberRow>,
444 + pub owner_split: i64,
445 + }
446 +
416 447 /// SyncKit tab in the user dashboard for managing sync apps.
417 448 #[derive(Template)]
418 449 #[template(path = "partials/tabs/user_synckit.html")]
@@ -476,12 +507,6 @@
476 507 #[template(path = "partials/tabs/library_purchases.html")]
477 508 pub struct LibraryPurchasesTabTemplate {
478 509 pub purchases: Vec<crate::db::DbPurchaseRow>,
479 - }
480 -
481 - /// Library subscriptions tab.
482 - #[derive(Template)]
483 - #[template(path = "partials/tabs/library_subscriptions.html")]
484 - pub struct LibrarySubscriptionsTabTemplate {
485 510 pub subscriptions: Vec<UserSubscription>,
486 511 }
487 512
@@ -491,12 +516,6 @@
491 516 pub struct LibraryCollectionsTabTemplate {
492 517 pub collections: Vec<Collection>,
493 518 pub username: String,
494 - }
495 -
496 - /// Library wishlists tab.
497 - #[derive(Template)]
498 - #[template(path = "partials/tabs/library_wishlists.html")]
499 - pub struct LibraryWishlistsTabTemplate {
500 519 pub wishlists: Vec<crate::db::wishlists::WishlistItem>,
501 520 }
502 521
@@ -48,6 +48,8 @@
48 48 pub csrf_token: CsrfTokenOption,
49 49 pub session_user: Option<SessionUser>,
50 50 pub purchases: Vec<crate::db::DbPurchaseRow>,
51 + pub subscriptions: Vec<UserSubscription>,
52 + pub has_mt_memberships: bool,
51 53 }
52 54
53 55 /// Shopping cart page with items grouped by seller.
@@ -58,7 +58,7 @@
58 58 aria-selected="false"
59 59 aria-controls="tab-content"
60 60 id="tab-content-btn"
61 - title="Manage items, uploads, and versions"
61 + title="Items, files, and blog posts"
62 62 hx-get="/dashboard/project/{{ project.slug }}/tabs/content"
63 63 hx-target="#tab-content"
64 64 hx-swap="innerHTML"
@@ -79,46 +79,13 @@
79 79 role="tab"
80 80 aria-selected="false"
81 81 aria-controls="tab-content"
82 - id="tab-blog"
83 - title="Write and publish blog posts for this project"
84 - hx-get="/dashboard/project/{{ project.slug }}/tabs/blog"
82 + id="tab-monetization"
83 + title="Tiers, promo codes, and revenue splits"
84 + hx-get="/dashboard/project/{{ project.slug }}/tabs/monetization"
85 85 hx-target="#tab-content"
86 86 hx-swap="innerHTML"
87 87 hx-indicator="#tab-spinner"
88 - onclick="setActiveTab(this)">Blog</button>
89 - <button class="tab"
90 - role="tab"
91 - aria-selected="false"
92 - aria-controls="tab-content"
93 - id="tab-promotions"
94 - title="Promo codes and discount campaigns"
95 - hx-get="/dashboard/project/{{ project.slug }}/tabs/promotions"
96 - hx-target="#tab-content"
97 - hx-swap="innerHTML"
98 - hx-indicator="#tab-spinner"
99 - onclick="setActiveTab(this)">Promo Codes</button>
100 - <button class="tab"
101 - role="tab"
102 - aria-selected="false"
103 - aria-controls="tab-content"
104 - id="tab-subscriptions"
105 - title="Recurring membership tiers for fans"
106 - hx-get="/dashboard/project/{{ project.slug }}/tabs/subscriptions"
107 - hx-target="#tab-content"
108 - hx-swap="innerHTML"
109 - hx-indicator="#tab-spinner"
110 - onclick="setActiveTab(this)">Membership Tiers</button>
111 - <button class="tab"
112 - role="tab"
113 - aria-selected="false"
114 - aria-controls="tab-content"
115 - id="tab-members"
116 - title="Collaborators and team access"
117 - hx-get="/dashboard/project/{{ project.slug }}/tabs/members"
118 - hx-target="#tab-content"
119 - hx-swap="innerHTML"
120 - hx-indicator="#tab-spinner"
121 - onclick="setActiveTab(this)">Team</button>
88 + onclick="setActiveTab(this)">Monetization</button>
122 89 {% if git_enabled %}
123 90 <button class="tab"
124 91 role="tab"
@@ -155,103 +155,27 @@
155 155 role="tab"
156 156 aria-selected="false"
157 157 aria-controls="tab-content"
158 - id="tab-profile"
159 - title="Display name, bio, avatar, and public links"
160 - hx-get="/dashboard/tabs/profile"
158 + id="tab-settings"
159 + title="Profile, account, plan, and integrations"
160 + hx-get="/dashboard/tabs/settings"
161 161 hx-target="#tab-content"
162 162 hx-swap="innerHTML"
163 163 hx-indicator="#tab-spinner"
164 - onclick="setActiveTab(this)">Profile</button>
165 - <button class="tab"
166 - role="tab"
167 - aria-selected="false"
168 - aria-controls="tab-content"
169 - id="tab-account"
170 - title="Email, password, security, and data export"
171 - hx-get="/dashboard/tabs/account"
172 - hx-target="#tab-content"
173 - hx-swap="innerHTML"
174 - hx-indicator="#tab-spinner"
175 - onclick="setActiveTab(this)">Account</button>
176 - <button class="tab"
177 - role="tab"
178 - aria-selected="false"
179 - aria-controls="tab-content"
180 - id="tab-plan"
181 - title="Your creator subscription tier and usage"
182 - hx-get="/dashboard/tabs/creator"
183 - hx-target="#tab-content"
184 - hx-swap="innerHTML"
185 - hx-indicator="#tab-spinner"
186 - onclick="setActiveTab(this)">Creator Plan</button>
187 -
188 - {% if let Some(su) = session_user %}{% if su.can_create_projects && !projects.is_empty() %}
189 - <div class="tab-overflow" style="position: relative; display: inline-block;">
190 - <button class="tab" onclick="var m=this.nextElementSibling; m.style.display=m.style.display==='block'?'none':'block';" type="button" title="Media, SSH Keys, Forums, Support">More &darr;</button>
191 - <div class="tab-overflow-menu" style="display: none; position: absolute; top: 100%; left: 0; z-index: 10; background: var(--background); border: 1px solid var(--border); min-width: 160px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
192 - <div style="padding: 0.4rem 1rem 0.2rem; font-size: 0.75rem; opacity: 0.5; text-transform: uppercase; letter-spacing: 0.05em;">Content</div>
193 - <button class="tab" style="display: block; width: 100%; text-align: left; padding: 0.5rem 1rem;"
194 - title="Uploaded images and avatars"
195 - hx-get="/dashboard/tabs/media"
196 - hx-target="#tab-content"
197 - hx-swap="innerHTML"
198 - hx-indicator="#tab-spinner"
199 - onclick="setActiveTab(this); this.closest('.tab-overflow-menu').style.display='none';">Media</button>
200 - {% if git_enabled || has_mt_memberships %}
201 - <div style="padding: 0.4rem 1rem 0.2rem; font-size: 0.75rem; opacity: 0.5; text-transform: uppercase; letter-spacing: 0.05em; border-top: 1px solid var(--border); margin-top: 0.25rem;">Integration</div>
202 - {% endif %}
203 - {% if git_enabled %}
204 - <button class="tab" style="display: block; width: 100%; text-align: left; padding: 0.5rem 1rem;"
205 - title="Public keys for Git authentication"
206 - hx-get="/dashboard/tabs/ssh-keys"
207 - hx-target="#tab-content"
208 - hx-swap="innerHTML"
209 - hx-indicator="#tab-spinner"
210 - onclick="setActiveTab(this); this.closest('.tab-overflow-menu').style.display='none';">SSH Keys</button>
211 - {% endif %}
212 - {% if has_mt_memberships %}
213 - <button class="tab" style="display: block; width: 100%; text-align: left; padding: 0.5rem 1rem;"
214 - title="Your forum community memberships"
215 - hx-get="/dashboard/tabs/forums"
216 - hx-target="#tab-content"
217 - hx-swap="innerHTML"
218 - hx-indicator="#tab-spinner"
219 - onclick="setActiveTab(this); this.closest('.tab-overflow-menu').style.display='none';">Forums</button>
220 - {% endif %}
221 - <div style="padding: 0.4rem 1rem 0.2rem; font-size: 0.75rem; opacity: 0.5; text-transform: uppercase; letter-spacing: 0.05em; border-top: 1px solid var(--border); margin-top: 0.25rem;">Support</div>
222 - <button class="tab" style="display: block; width: 100%; text-align: left; padding: 0.5rem 1rem;"
223 - title="Contact support or report an issue"
224 - hx-get="/dashboard/tabs/support"
225 - hx-target="#tab-content"
226 - hx-swap="innerHTML"
227 - hx-indicator="#tab-spinner"
228 - onclick="setActiveTab(this); this.closest('.tab-overflow-menu').style.display='none';">Support</button>
229 - </div>
230 - </div>
231 - {% else %}
164 + onclick="setActiveTab(this)">Settings</button>
232 165 <button class="tab"
233 166 role="tab"
234 167 aria-selected="false"
235 168 aria-controls="tab-content"
236 169 id="tab-support"
170 + title="Contact support or report an issue"
237 171 hx-get="/dashboard/tabs/support"
238 172 hx-target="#tab-content"
239 173 hx-swap="innerHTML"
240 174 hx-indicator="#tab-spinner"
241 175 onclick="setActiveTab(this)">Support</button>
242 - {% endif %}{% endif %}
243 176 <span id="tab-spinner" class="htmx-indicator" style="margin-left: 1rem;" aria-live="polite"> Loading...</span>
244 177 </div>
245 178
246 - <script>
247 - document.addEventListener('click', function(e) {
248 - var menus = document.querySelectorAll('.tab-overflow-menu');
249 - menus.forEach(function(m) {
250 - if (!m.parentElement.contains(e.target)) m.style.display = 'none';
251 - });
252 - });
253 - </script>
254 -
255 179 <!-- Tab Content Container -->
256 180 <div id="tab-content" class="tab-content active"
257 181 role="tabpanel"
@@ -31,27 +31,6 @@
31 31 hx-swap="innerHTML"
32 32 hx-indicator="#tab-spinner"
33 33 onclick="setActiveTab(this)">Feed</button>
34 - <button class="tab"
35 - role="tab"
36 - aria-selected="false"
37 - aria-controls="tab-content"
38 - id="tab-subscriptions"
39 - hx-get="/library/tabs/subscriptions"
40 - hx-target="#tab-content"
41 - hx-swap="innerHTML"
42 - hx-indicator="#tab-spinner"
43 - onclick="setActiveTab(this)">Subscriptions</button>
44 - <button class="tab"
45 - role="tab"
46 - aria-selected="false"
47 - aria-controls="tab-content"
48 - id="tab-wishlists"
49 - title="Items you saved for later"
50 - hx-get="/library/tabs/wishlists"
51 - hx-target="#tab-content"
52 - hx-swap="innerHTML"
53 - hx-indicator="#tab-spinner"
54 - onclick="setActiveTab(this)">Wishlists</button>
55 34 <button class="tab"
56 35 role="tab"
57 36 aria-selected="false"
@@ -62,6 +41,7 @@
62 41 hx-swap="innerHTML"
63 42 hx-indicator="#tab-spinner"
64 43 onclick="setActiveTab(this)">Collections</button>
44 + {% if has_mt_memberships %}
65 45 <button class="tab"
66 46 role="tab"
67 47 aria-selected="false"
@@ -72,6 +52,8 @@
72 52 hx-swap="innerHTML"
73 53 hx-indicator="#tab-spinner"
74 54 onclick="setActiveTab(this)">Communities</button>
55 + {% endif %}
56 + {% if let Some(user) = session_user %}{% if user.can_create_projects %}
75 57 <button class="tab"
76 58 role="tab"
77 59 aria-selected="false"
@@ -82,6 +64,7 @@
82 64 hx-swap="innerHTML"
83 65 hx-indicator="#tab-spinner"
84 66 onclick="setActiveTab(this)">Contacts</button>
67 + {% endif %}{% endif %}
85 68 <span id="tab-spinner" class="htmx-indicator" style="margin-left: 1rem;" aria-live="polite"> Loading...</span>
86 69 </div>
87 70