max / makenotwork
- Co-Authored-By
- Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>© 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 ↓</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 |