The code and data behind xeiaso.net
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(sponsor-panel): redesign dashboard as tabbed admin panel

Replace the multi-card dashboard with a sidebar-tab layout so every
benefit lives on its own panel instead of competing for space in a
2-column grid. Tabs group by purpose (Account / Benefits / Developer)
and the active tab is remembered via URL hash and localStorage, with
arrow-key navigation across the tablist.

Each panel now carries a consistent header (icon tile + serif title +
subtitle), the sponsorship tab surfaces monthly amount and tier as
stat cards, logo submission uses a proper drag-and-drop zone, and a
bottom-center toast confirms HTMX form successes. All existing form
posts (/invite, /logo, /thoth-token) are preserved; no backend or
route changes.

Non-sponsors see only the Sponsorship tab with the upgrade CTA.
$50+ sponsors get the Team Invitations tab added to the Benefits
group.

Based on a Claude Design handoff bundle.

Assisted-by: Claude Opus 4.7 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

Xe Iaso 47d1ffef 146ad4c3

+993 -213
+119
cmd/sponsor-panel/static/js/admin-panel.js
··· 1 + (() => { 2 + const STORAGE_KEY = "sponsor-panel-tab"; 3 + const tabs = Array.from(document.querySelectorAll("nav.tabs [role=tab]")); 4 + const panels = Array.from(document.querySelectorAll(".tabpanel")); 5 + const validTabs = tabs.map((t) => t.dataset.tab); 6 + 7 + function activate(name, persist = true) { 8 + if (!validTabs.includes(name)) return; 9 + tabs.forEach((t) => t.setAttribute("aria-selected", t.dataset.tab === name ? "true" : "false")); 10 + panels.forEach((p) => p.classList.toggle("active", p.id === `panel-${name}`)); 11 + if (persist) { 12 + try { 13 + localStorage.setItem(STORAGE_KEY, name); 14 + } catch (e) {} 15 + const url = new URL(window.location); 16 + url.hash = name; 17 + history.replaceState(null, "", url); 18 + } 19 + } 20 + 21 + tabs.forEach((tab) => { 22 + tab.addEventListener("click", () => activate(tab.dataset.tab)); 23 + }); 24 + 25 + const tablist = document.querySelector("nav.tabs"); 26 + if (tablist) { 27 + tablist.addEventListener("keydown", (e) => { 28 + const idx = tabs.findIndex((t) => t === document.activeElement); 29 + if (idx < 0) return; 30 + if (e.key === "ArrowDown" || e.key === "ArrowRight") { 31 + e.preventDefault(); 32 + const next = tabs[(idx + 1) % tabs.length]; 33 + next.focus(); 34 + activate(next.dataset.tab); 35 + } else if (e.key === "ArrowUp" || e.key === "ArrowLeft") { 36 + e.preventDefault(); 37 + const prev = tabs[(idx - 1 + tabs.length) % tabs.length]; 38 + prev.focus(); 39 + activate(prev.dataset.tab); 40 + } 41 + }); 42 + } 43 + 44 + const hashTab = window.location.hash.replace("#", ""); 45 + let initial = validTabs[0] || "sponsorship"; 46 + if (validTabs.includes(hashTab)) { 47 + initial = hashTab; 48 + } else { 49 + try { 50 + const saved = localStorage.getItem(STORAGE_KEY); 51 + if (validTabs.includes(saved)) initial = saved; 52 + } catch (e) {} 53 + } 54 + activate(initial, false); 55 + 56 + // ---------- Toast ---------- 57 + const toast = document.getElementById("admin-toast"); 58 + const toastMsg = document.getElementById("admin-toast-msg"); 59 + let toastTimer; 60 + window.adminToast = function (msg) { 61 + if (!toast) return; 62 + toastMsg.textContent = msg; 63 + toast.classList.add("show"); 64 + clearTimeout(toastTimer); 65 + toastTimer = setTimeout(() => toast.classList.remove("show"), 2200); 66 + }; 67 + 68 + // ---------- Logo drop zone ---------- 69 + const drop = document.getElementById("logo-drop"); 70 + const fileInput = document.getElementById("logo-file"); 71 + const dropName = document.getElementById("logo-drop-name"); 72 + const logoReset = document.getElementById("logo-reset"); 73 + 74 + if (drop && fileInput && dropName) { 75 + const defaultText = dropName.textContent; 76 + 77 + ["dragenter", "dragover"].forEach((ev) => 78 + drop.addEventListener(ev, (e) => { 79 + e.preventDefault(); 80 + drop.classList.add("drag"); 81 + }), 82 + ); 83 + ["dragleave", "drop"].forEach((ev) => 84 + drop.addEventListener(ev, (e) => { 85 + e.preventDefault(); 86 + drop.classList.remove("drag"); 87 + }), 88 + ); 89 + drop.addEventListener("drop", (e) => { 90 + if (e.dataTransfer && e.dataTransfer.files.length) { 91 + fileInput.files = e.dataTransfer.files; 92 + fileInput.dispatchEvent(new Event("change")); 93 + } 94 + }); 95 + fileInput.addEventListener("change", () => { 96 + if (fileInput.files && fileInput.files[0]) { 97 + dropName.textContent = fileInput.files[0].name; 98 + } 99 + }); 100 + if (logoReset) { 101 + logoReset.addEventListener("click", () => { 102 + dropName.textContent = defaultText; 103 + }); 104 + } 105 + } 106 + 107 + // Surface HTMX success responses as toasts 108 + document.body.addEventListener("htmx:afterSwap", (e) => { 109 + const target = e.detail && e.detail.target; 110 + if (!target) return; 111 + if (target.id === "invite-result" && target.querySelector(".alert-success")) { 112 + window.adminToast("Invitation sent"); 113 + } else if (target.id === "logo-result" && target.querySelector(".alert-success")) { 114 + window.adminToast("Logo submitted"); 115 + } else if (target.id === "thoth-result" && target.querySelector(".alert-success")) { 116 + window.adminToast("Token generated"); 117 + } 118 + }); 119 + })();
+465
cmd/sponsor-panel/styles.css
··· 372 372 @apply hover:file:bg-orange-light/20 dark:hover:file:bg-orangeDark-dark/30; 373 373 @apply cursor-pointer; 374 374 } 375 + 376 + /* -------- Admin tabbed panel -------- */ 377 + 378 + .welcome-band { 379 + @apply max-w-5xl mx-auto w-full px-4 md:px-8 pt-8 md:pt-10 pb-4; 380 + } 381 + 382 + .welcome-sub { 383 + @apply text-fg-3 dark:text-fgDark-3 text-sm md:text-base; 384 + } 385 + 386 + .admin-shell { 387 + @apply max-w-5xl mx-auto w-full px-4 md:px-8 pb-20; 388 + display: grid; 389 + grid-template-columns: 260px 1fr; 390 + gap: 28px; 391 + align-items: start; 392 + } 393 + 394 + @media (max-width: 860px) { 395 + .admin-shell { 396 + grid-template-columns: 1fr; 397 + gap: 16px; 398 + } 399 + } 400 + 401 + .tabs { 402 + position: sticky; 403 + top: 88px; 404 + display: flex; 405 + flex-direction: column; 406 + gap: 2px; 407 + padding: 6px; 408 + @apply bg-surface dark:bg-surface-dark rounded-xl shadow-sm; 409 + @apply border border-bg-2 dark:border-bgDark-2; 410 + } 411 + 412 + @media (max-width: 860px) { 413 + .tabs { 414 + position: static; 415 + flex-direction: row; 416 + overflow-x: auto; 417 + padding: 4px; 418 + scroll-snap-type: x mandatory; 419 + } 420 + } 421 + 422 + .tab { 423 + all: unset; 424 + cursor: pointer; 425 + box-sizing: border-box; 426 + display: flex; 427 + align-items: center; 428 + gap: 12px; 429 + padding: 11px 14px; 430 + border-radius: 8px; 431 + font-family: var(--font-sans); 432 + font-size: 14.5px; 433 + font-weight: 500; 434 + position: relative; 435 + transition: 436 + background 0.12s ease, 437 + color 0.12s ease; 438 + @apply text-fg-2 dark:text-fgDark-2; 439 + } 440 + 441 + @media (max-width: 860px) { 442 + .tab { 443 + white-space: nowrap; 444 + padding: 9px 12px; 445 + scroll-snap-align: start; 446 + font-size: 13.5px; 447 + } 448 + } 449 + 450 + .tab:hover { 451 + @apply bg-bg-1 dark:bg-bgDark-1 text-fg-0 dark:text-fgDark-1; 452 + } 453 + 454 + .tab .tab-icon { 455 + width: 20px; 456 + height: 20px; 457 + flex-shrink: 0; 458 + @apply text-fg-3 dark:text-fgDark-3; 459 + } 460 + 461 + .tab:hover .tab-icon { 462 + @apply text-fg-1 dark:text-fgDark-1; 463 + } 464 + 465 + .tab[aria-selected="true"] { 466 + @apply bg-bg-1 dark:bg-bgDark-1 text-fg-0 dark:text-fgDark-0; 467 + } 468 + 469 + .tab[aria-selected="true"] .tab-icon { 470 + @apply text-orange-light dark:text-orangeDark-light; 471 + } 472 + 473 + .tab[aria-selected="true"]::before { 474 + content: ""; 475 + position: absolute; 476 + left: 0; 477 + top: 8px; 478 + bottom: 8px; 479 + width: 3px; 480 + border-radius: 3px; 481 + background: linear-gradient( 482 + to bottom, 483 + var(--color-orange-light), 484 + var(--color-purple-light) 485 + ); 486 + } 487 + 488 + .tab[aria-selected="true"]::before { 489 + @variant dark { 490 + background: linear-gradient( 491 + to bottom, 492 + var(--color-orangeDark-light), 493 + var(--color-purpleDark-light) 494 + ); 495 + } 496 + } 497 + 498 + @media (max-width: 860px) { 499 + .tab[aria-selected="true"]::before { 500 + display: none; 501 + } 502 + } 503 + 504 + .tab-sep { 505 + height: 1px; 506 + @apply bg-bg-2 dark:bg-bgDark-2; 507 + margin: 6px 4px; 508 + } 509 + 510 + .tab-section-label { 511 + font-size: 10.5px; 512 + text-transform: uppercase; 513 + letter-spacing: 0.08em; 514 + padding: 10px 14px 4px; 515 + font-weight: 600; 516 + @apply text-fg-4 dark:text-fgDark-4; 517 + } 518 + 519 + @media (max-width: 860px) { 520 + .tab-sep, 521 + .tab-section-label { 522 + display: none; 523 + } 524 + } 525 + 526 + /* Override the default .card padding to let panel-body control it */ 527 + .admin-panel.card { 528 + padding: 0; 529 + min-height: 480px; 530 + } 531 + 532 + .panel-body { 533 + padding: 28px 32px 32px; 534 + } 535 + 536 + @media (max-width: 860px) { 537 + .panel-body { 538 + padding: 22px 20px 24px; 539 + } 540 + } 541 + 542 + .panel-head { 543 + display: flex; 544 + align-items: flex-start; 545 + gap: 14px; 546 + margin-bottom: 24px; 547 + } 548 + 549 + .panel-head-icon { 550 + width: 36px; 551 + height: 36px; 552 + flex-shrink: 0; 553 + display: grid; 554 + place-items: center; 555 + border-radius: 10px; 556 + @apply bg-bg-1 dark:bg-bgDark-1 text-orange-light dark:text-orangeDark-light; 557 + } 558 + 559 + .panel-head-icon svg { 560 + width: 22px; 561 + height: 22px; 562 + } 563 + 564 + .panel-head-icon--discord { 565 + color: #5865f2; 566 + } 567 + 568 + .panel-head-icon--purple { 569 + @apply text-purple-light dark:text-purpleDark-light; 570 + } 571 + 572 + .panel-head-icon--yellow { 573 + @apply text-yellow-light dark:text-yellowDark-dark; 574 + } 575 + 576 + .panel-title { 577 + @apply font-serif text-xl md:text-2xl font-bold; 578 + @apply text-fg-0 dark:text-fgDark-0; 579 + line-height: 1.2; 580 + margin: 0 0 4px; 581 + } 582 + 583 + .panel-sub { 584 + @apply text-fg-3 dark:text-fgDark-3 text-sm md:text-[14.5px]; 585 + margin: 0; 586 + } 587 + 588 + /* Stat cards */ 589 + .stat-grid { 590 + display: grid; 591 + grid-template-columns: 1fr 1fr; 592 + gap: 14px; 593 + margin-bottom: 20px; 594 + } 595 + 596 + @media (max-width: 860px) { 597 + .stat-grid { 598 + grid-template-columns: 1fr; 599 + } 600 + } 601 + 602 + .stat-card { 603 + @apply bg-bg-1 dark:bg-bgDark-1 rounded-xl p-5; 604 + @apply border border-bg-2 dark:border-bgDark-2; 605 + } 606 + 607 + .stat-k { 608 + font-size: 12px; 609 + text-transform: uppercase; 610 + letter-spacing: 0.08em; 611 + font-weight: 600; 612 + margin-bottom: 6px; 613 + @apply text-fg-3 dark:text-fgDark-3; 614 + } 615 + 616 + .stat-v { 617 + @apply font-serif font-bold; 618 + font-size: 24px; 619 + display: flex; 620 + align-items: baseline; 621 + gap: 6px; 622 + @apply text-orange-light dark:text-orangeDark-light; 623 + } 624 + 625 + .stat-v-muted { 626 + @apply text-fg-0 dark:text-fgDark-0; 627 + font-size: 20px; 628 + } 629 + 630 + .stat-v small { 631 + font-size: 14px; 632 + font-weight: 500; 633 + @apply font-sans text-fg-3 dark:text-fgDark-3; 634 + } 635 + 636 + .stat-sub { 637 + font-size: 14px; 638 + margin-top: 4px; 639 + @apply text-fg-2 dark:text-fgDark-2; 640 + } 641 + 642 + .thanks-card { 643 + background: linear-gradient( 644 + 135deg, 645 + rgba(214, 93, 14, 0.1), 646 + rgba(177, 98, 134, 0.1) 647 + ); 648 + border: 1px solid rgba(214, 93, 14, 0.25); 649 + border-radius: 12px; 650 + padding: 14px 18px; 651 + display: flex; 652 + align-items: center; 653 + gap: 12px; 654 + font-weight: 500; 655 + @apply text-orange-light dark:text-orangeDark-light; 656 + } 657 + 658 + .thanks-card svg { 659 + flex-shrink: 0; 660 + } 661 + 662 + /* Meta list */ 663 + .meta-list { 664 + display: grid; 665 + grid-template-columns: 1fr; 666 + gap: 0; 667 + margin: 22px 0 0; 668 + padding: 0; 669 + } 670 + 671 + .meta-list > div { 672 + display: flex; 673 + align-items: center; 674 + justify-content: space-between; 675 + padding: 12px 0; 676 + border-top: 1px solid; 677 + @apply border-bg-2 dark:border-bgDark-2; 678 + font-size: 14.5px; 679 + } 680 + 681 + .meta-list dt { 682 + margin: 0; 683 + @apply text-fg-3 dark:text-fgDark-3; 684 + } 685 + 686 + .meta-list dd { 687 + margin: 0; 688 + font-weight: 500; 689 + @apply text-fg-1 dark:text-fgDark-1; 690 + } 691 + 692 + .meta-list dd code { 693 + @apply bg-bg-1 dark:bg-bgDark-1 rounded px-2 py-0.5 font-mono text-xs; 694 + @apply text-fg-0 dark:text-fgDark-0; 695 + } 696 + 697 + /* Field labels + action rows */ 698 + .field-label { 699 + display: block; 700 + font-size: 13px; 701 + font-weight: 600; 702 + letter-spacing: 0.01em; 703 + margin-bottom: 6px; 704 + @apply text-fg-2 dark:text-fgDark-2; 705 + } 706 + 707 + .btn-row { 708 + display: flex; 709 + gap: 10px; 710 + flex-wrap: wrap; 711 + margin-top: 18px; 712 + } 713 + 714 + /* Grid-2 for logo form */ 715 + .grid-2 { 716 + display: grid; 717 + gap: 16px; 718 + grid-template-columns: 1fr 1fr; 719 + } 720 + 721 + @media (max-width: 860px) { 722 + .grid-2 { 723 + grid-template-columns: 1fr; 724 + } 725 + } 726 + 727 + /* Drop zone */ 728 + .drop-zone { 729 + border: 2px dashed; 730 + @apply border-bg-3 dark:border-bgDark-3; 731 + border-radius: 12px; 732 + padding: 24px; 733 + text-align: center; 734 + display: flex; 735 + flex-direction: column; 736 + align-items: center; 737 + gap: 8px; 738 + cursor: pointer; 739 + transition: 740 + border-color 0.15s, 741 + background 0.15s; 742 + @apply bg-bg-1 dark:bg-bgDark-1 text-fg-3 dark:text-fgDark-3; 743 + } 744 + 745 + .drop-zone:hover, 746 + .drop-zone.drag { 747 + @apply border-orange-light dark:border-orangeDark-light; 748 + @apply text-fg-1 dark:text-fgDark-1; 749 + background: rgba(214, 93, 14, 0.06); 750 + } 751 + 752 + .drop-zone svg { 753 + @apply text-fg-4 dark:text-fgDark-4; 754 + } 755 + 756 + .drop-zone:hover svg, 757 + .drop-zone.drag svg { 758 + @apply text-orange-light dark:text-orangeDark-light; 759 + } 760 + 761 + .drop-zone strong { 762 + font-weight: 600; 763 + font-size: 15px; 764 + @apply text-fg-1 dark:text-fgDark-1; 765 + } 766 + 767 + .drop-hint { 768 + font-size: 13px; 769 + @apply text-fg-4 dark:text-fgDark-4; 770 + } 771 + 772 + /* Accent button (purple) */ 773 + .btn-accent { 774 + @apply bg-purple-light dark:bg-purpleDark-light text-white; 775 + @apply hover:bg-purple-dark dark:hover:bg-purpleDark-dark; 776 + @apply focus:ring-purple-light dark:focus:ring-purpleDark-light; 777 + @apply dark:text-white; 778 + } 779 + 780 + /* Discord-branded button */ 781 + .btn-discord { 782 + background: #5865f2; 783 + color: #fff; 784 + } 785 + 786 + .btn-discord:hover { 787 + background: #4752c4; 788 + color: #fff; 789 + } 790 + 791 + /* Tab content transitions */ 792 + .tabpanel { 793 + display: none; 794 + animation: tab-fade 0.22s ease; 795 + } 796 + 797 + .tabpanel.active { 798 + display: block; 799 + } 800 + 801 + @keyframes tab-fade { 802 + from { 803 + opacity: 0; 804 + transform: translateY(4px); 805 + } 806 + to { 807 + opacity: 1; 808 + transform: translateY(0); 809 + } 810 + } 811 + 812 + /* Toast */ 813 + .toast { 814 + position: fixed; 815 + bottom: 24px; 816 + left: 50%; 817 + transform: translateX(-50%) translateY(30px); 818 + @apply bg-bg-0 dark:bg-bgDark-1; 819 + @apply border border-green-light dark:border-greenDark-light; 820 + @apply text-fg-0 dark:text-fgDark-0; 821 + padding: 10px 18px; 822 + border-radius: 10px; 823 + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); 824 + font-size: 14px; 825 + opacity: 0; 826 + pointer-events: none; 827 + transition: 828 + opacity 0.2s, 829 + transform 0.25s; 830 + z-index: 100; 831 + display: flex; 832 + align-items: center; 833 + gap: 8px; 834 + } 835 + 836 + .toast.show { 837 + opacity: 1; 838 + transform: translateX(-50%) translateY(0); 839 + } 375 840 } 376 841 377 842 /* Custom scrollbar */
+251 -143
cmd/sponsor-panel/templates/dashboard.templ
··· 19 19 20 20 templ Dashboard(props DashboardProps) { 21 21 @Navbar(props.User.Login, props.User.AvatarURL) 22 - <main class="max-w-4xl mx-auto px-4 md:px-10 py-4 md:py-10"> 23 - <div class=""> 24 - <h1 class="hero-title mb-3">Welcome back, { props.User.Login }</h1> 25 - <p class="text-fg-3 dark:text-fgDark-3">Manage your sponsorship benefits</p> 26 - </div> 27 - <div class="grid md:grid-cols-2 gap-8 mt-4"> 28 - if props.IsSponsor { 29 - @DiscordCard(props.DiscordInvite) 30 - } 31 - @SponsorshipCard(props.IsSponsor, props.SponsorAmount, props.SponsorTier, props.User.Provider) 32 - </div> 33 - <div class="grid md:grid-cols-2 gap-8 mt-8"> 34 - if props.IsFiftyPlus { 35 - @TeamInviteCard() 36 - } 37 - if props.IsSponsor { 38 - @LogoSubmitCard() 39 - } 40 - </div> 41 - <div class="grid md:grid-cols-2 gap-8 mt-8"> 42 - if props.IsSponsor { 43 - @ThothTokenCard() 44 - } 45 - </div> 46 - </main> 22 + <section class="welcome-band"> 23 + <h1 class="hero-title">Welcome back, { props.User.Login }</h1> 24 + <p class="welcome-sub">Manage your sponsorship benefits</p> 25 + </section> 26 + <div class="admin-shell"> 27 + @Sidebar(props) 28 + <main class="admin-panel card card-warm" aria-label="Sponsorship panel"> 29 + <div class="panel-body"> 30 + <section class="tabpanel active" id="panel-sponsorship" role="tabpanel" aria-labelledby="tab-sponsorship"> 31 + @SponsorshipTab(props) 32 + </section> 33 + if props.IsSponsor { 34 + <section class="tabpanel" id="panel-discord" role="tabpanel" aria-labelledby="tab-discord"> 35 + @DiscordTab(props.DiscordInvite) 36 + </section> 37 + } 38 + if props.IsFiftyPlus { 39 + <section class="tabpanel" id="panel-team" role="tabpanel" aria-labelledby="tab-team"> 40 + @TeamTab() 41 + </section> 42 + } 43 + if props.IsSponsor { 44 + <section class="tabpanel" id="panel-logo" role="tabpanel" aria-labelledby="tab-logo"> 45 + @LogoTab() 46 + </section> 47 + <section class="tabpanel" id="panel-token" role="tabpanel" aria-labelledby="tab-token"> 48 + @TokenTab() 49 + </section> 50 + } 51 + </div> 52 + </main> 53 + </div> 54 + <div class="toast" id="admin-toast" role="status" aria-live="polite"> 55 + <svg class="w-4 h-4 text-green-light dark:text-greenDark-light" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"> 56 + <path d="m5 12 5 5L20 7"></path> 57 + </svg> 58 + <span id="admin-toast-msg">Saved</span> 59 + </div> 60 + <script src="/static/js/admin-panel.js" defer></script> 47 61 } 48 62 49 63 templ Navbar(login, avatarURL string) { 50 - <nav class="navbar sticky top-0 z-50 px-8 py-5 md:px-12"> 51 - <div class="max-w-4xl mx-auto flex items-center justify-between"> 64 + <nav class="navbar sticky top-0 z-50 px-4 md:px-8 py-4"> 65 + <div class="max-w-5xl mx-auto flex items-center justify-between"> 52 66 <div class="flex items-center gap-3"> 53 67 <img src={ templ.SafeURL(avatarURL) } class="w-9 h-9 rounded-full ring-2 ring-bg-3 dark:ring-bgDark-3" alt=""/> 54 68 <span class="font-medium text-fg-1 dark:text-fgDark-1">{ login }</span> 55 69 </div> 56 - <a href="/logout" class="btn btn-ghost text-sm p-2"> 57 - Logout 58 - </a> 70 + <a href="/logout" class="btn btn-ghost text-sm px-4 py-2">Logout</a> 59 71 </div> 60 72 </nav> 61 73 } 62 74 63 - templ DiscordCard(inviteURL string) { 64 - <div class="card card-cool p-4"> 65 - <h2 class="card-title flex items-center gap-3 !mb-4"> 66 - <svg class="w-6 h-6 text-blue-light dark:text-blueDark-light" viewBox="0 0 24 24" fill="currentColor"> 67 - <path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"></path> 75 + templ Sidebar(props DashboardProps) { 76 + <nav class="tabs" role="tablist" aria-label="Sponsor benefits"> 77 + <div class="tab-section-label">Account</div> 78 + <button class="tab" role="tab" id="tab-sponsorship" data-tab="sponsorship" aria-selected="true" aria-controls="panel-sponsorship" type="button"> 79 + <svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 80 + <path d="M19.5 13.572 12 21l-7.5-7.428A5 5 0 1 1 12 6.006a5 5 0 1 1 7.5 7.566Z"></path> 68 81 </svg> 69 - Discord Community 70 - </h2> 71 - <p class="card-description !mb-6"> 72 - Connect with other sponsors and get early access to updates. 73 - </p> 74 - <a href={ templ.SafeURL(inviteURL) } target="_blank" class="btn btn-secondary p-2"> 75 - Join Discord 76 - </a> 77 - </div> 82 + Your Sponsorship 83 + </button> 84 + if props.IsSponsor { 85 + <div class="tab-sep"></div> 86 + <div class="tab-section-label">Benefits</div> 87 + <button class="tab" role="tab" id="tab-discord" data-tab="discord" aria-selected="false" aria-controls="panel-discord" type="button"> 88 + <svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 89 + <path d="M8 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0M14 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0M15.5 17c0 1 1.5 3 2 3 1.5 0 2.833-1.667 3.5-3 .667-1.778.5-5.5-1.5-11.5-1.457-1.015-3-1.34-4.5-1.5l-1 2.5M8.5 17c0 1-1.356 3-1.832 3-1.429 0-2.698-1.667-3.333-3-.635-1.778-.476-5.5 1.428-11.5C6.151 4.485 7.545 4.16 9 4l1 2.5M7 16.5c3.5 1 6.5 1 10 0"></path> 90 + </svg> 91 + Discord Community 92 + </button> 93 + if props.IsFiftyPlus { 94 + <button class="tab" role="tab" id="tab-team" data-tab="team" aria-selected="false" aria-controls="panel-team" type="button"> 95 + <svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 96 + <path d="M9 7a4 4 0 1 0 8 0 4 4 0 0 0-8 0M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2M16 3.13a4 4 0 0 1 0 7.75M21 21v-2a4 4 0 0 0-3-3.85"></path> 97 + </svg> 98 + Team Invitations 99 + </button> 100 + } 101 + <button class="tab" role="tab" id="tab-logo" data-tab="logo" aria-selected="false" aria-controls="panel-logo" type="button"> 102 + <svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 103 + <path d="M15 8h.01M12.5 21H6a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v6.5M3 16l5-5c.928-.893 2.072-.893 3 0l5 5M14 14l1-1c.679-.653 1.473-.829 2.214-.526"></path> 104 + </svg> 105 + Logo Submission 106 + </button> 107 + <div class="tab-sep"></div> 108 + <div class="tab-section-label">Developer</div> 109 + <button class="tab" role="tab" id="tab-token" data-tab="token" aria-selected="false" aria-controls="panel-token" type="button"> 110 + <svg class="tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 111 + <path d="M8 15a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM10.85 12.15 21 22l-2 2-1.5-1.5L17 22l-1.5-1.5L14 22l-4-4 1-1-1-1"></path> 112 + </svg> 113 + Thoth API Token 114 + </button> 115 + } 116 + </nav> 78 117 } 79 118 80 - templ SponsorshipCard(isSponsor bool, amount int, tier string, provider string) { 81 - <div class="card card-warm p-4"> 82 - <h2 class="card-title flex items-center gap-2"> 83 - <svg class="w-5 h-5 text-orange-light dark:text-orangeDark-light" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 84 - <path stroke-linecap="round" stroke-linejoin="round" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path> 119 + templ SponsorshipTab(props DashboardProps) { 120 + <div class="panel-head"> 121 + <div class="panel-head-icon"> 122 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 123 + <path d="M19.5 13.572 12 21l-7.5-7.428A5 5 0 1 1 12 6.006a5 5 0 1 1 7.5 7.566Z"></path> 85 124 </svg> 86 - Your Sponsorship 87 - </h2> 88 - if isSponsor { 89 - <div class="flex items-center gap-2 mb-2"> 90 - <span class="accent-dot"></span> 91 - <span class="text-lg font-semibold text-green-light dark:text-greenDark-light"> 92 - ${ formatDollars(amount) }/month 93 - </span> 125 + </div> 126 + <div> 127 + <h2 class="panel-title">Your Sponsorship</h2> 128 + <p class="panel-sub">A snapshot of your support and benefits.</p> 129 + </div> 130 + </div> 131 + if props.IsSponsor { 132 + <div class="stat-grid"> 133 + <div class="stat-card"> 134 + <div class="stat-k">Monthly support</div> 135 + <div class="stat-v"> 136 + ${ formatDollars(props.SponsorAmount) } 137 + <small>/ month</small> 138 + </div> 94 139 </div> 95 - <p class="text-sm text-fg-3 dark:text-fgDark-3 mb-3">{ tier }</p> 96 - <p class="text-sm font-medium text-orange-light dark:text-orangeDark-light">Thank you for your support!</p> 140 + <div class="stat-card"> 141 + <div class="stat-k">Tier</div> 142 + <div class="stat-v stat-v-muted">{ props.SponsorTier }</div> 143 + <div class="stat-sub">via { props.User.Provider }</div> 144 + </div> 145 + </div> 146 + <div class="thanks-card"> 147 + <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 148 + <path d="M19.5 13.572 12 21l-7.5-7.428A5 5 0 1 1 12 6.006a5 5 0 1 1 7.5 7.566Z"></path> 149 + </svg> 150 + Thank you for your support! Seriously. It keeps the lights on. 151 + </div> 152 + } else { 153 + <p class="panel-sub mb-6">You're not currently an active sponsor.</p> 154 + if props.User.Provider == "patreon" { 155 + <a href="https://www.patreon.com/cadey" target="_blank" class="btn btn-pink">Become a Patron</a> 97 156 } else { 98 - <p class="card-description">You're not currently an active sponsor.</p> 99 - if provider == "patreon" { 100 - <a href="https://www.patreon.com/cadey" target="_blank" class="btn btn-pink"> 101 - Become a Patron 102 - </a> 103 - } else { 104 - <a href="https://github.com/sponsors/Xe" target="_blank" class="btn btn-pink"> 105 - Become a Sponsor 106 - </a> 107 - } 108 - <p class="text-xs text-fg-4 dark:text-fgDark-4 mt-4 leading-relaxed"> 109 - If you're part of an organization that sponsors Anubis and see this message, please contact me@xeiaso.net for help. 110 - </p> 157 + <a href="https://github.com/sponsors/Xe" target="_blank" class="btn btn-pink">Become a Sponsor</a> 111 158 } 159 + <p class="text-xs text-fg-4 dark:text-fgDark-4 mt-4 leading-relaxed"> 160 + If you're part of an organization that sponsors Anubis and see this message, please contact me@xeiaso.net for help. 161 + </p> 162 + } 163 + } 164 + 165 + templ DiscordTab(inviteURL string) { 166 + <div class="panel-head"> 167 + <div class="panel-head-icon panel-head-icon--discord"> 168 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 169 + <path d="M8 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0M14 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0M15.5 17c0 1 1.5 3 2 3 1.5 0 2.833-1.667 3.5-3 .667-1.778.5-5.5-1.5-11.5-1.457-1.015-3-1.34-4.5-1.5l-1 2.5M8.5 17c0 1-1.356 3-1.832 3-1.429 0-2.698-1.667-3.333-3-.635-1.778-.476-5.5 1.428-11.5C6.151 4.485 7.545 4.16 9 4l1 2.5M7 16.5c3.5 1 6.5 1 10 0"></path> 170 + </svg> 171 + </div> 172 + <div> 173 + <h2 class="panel-title">Discord Community</h2> 174 + <p class="panel-sub">Connect with other sponsors and get early access to updates and drafts.</p> 175 + </div> 176 + </div> 177 + <dl class="meta-list"> 178 + <div> 179 + <dt>Access level</dt> 180 + <dd>Sponsor channels + early drafts</dd> 181 + </div> 182 + <div> 183 + <dt>Expires</dt> 184 + <dd>While sponsorship is active</dd> 185 + </div> 186 + </dl> 187 + <div class="btn-row"> 188 + <a href={ templ.SafeURL(inviteURL) } target="_blank" class="btn btn-discord"> 189 + <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> 190 + <path d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25a.08.08 0 0 1 .08-.01c3.44 1.57 7.15 1.57 10.55 0a.08.08 0 0 1 .08.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"></path> 191 + </svg> 192 + Join Discord 193 + </a> 112 194 </div> 113 195 } 114 196 115 - templ TeamInviteCard() { 116 - <div class="card card-green p-4"> 117 - <h2 class="card-title flex items-center gap-2"> 118 - <svg class="w-5 h-5 text-green-light dark:text-greenDark-light" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 119 - <path stroke-linecap="round" stroke-linejoin="round" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"></path> 197 + templ TeamTab() { 198 + <div class="panel-head"> 199 + <div class="panel-head-icon"> 200 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 201 + <path d="M9 7a4 4 0 1 0 8 0 4 4 0 0 0-8 0M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2M16 3.13a4 4 0 0 1 0 7.75M21 21v-2a4 4 0 0 0-3-3.85"></path> 120 202 </svg> 121 - Team Invitation 122 - </h2> 123 - <p class="card-description"> 124 - Invite team members to TecharoHQ as part of your sponsorship. 125 - </p> 126 - <form hx-post="/invite" hx-target="#invite-result" class="space-y-3"> 127 - <input 128 - type="text" 129 - name="username" 130 - placeholder="GitHub username" 131 - required 132 - class="input" 133 - /> 134 - <button type="submit" class="btn btn-primary w-full"> 203 + </div> 204 + <div> 205 + <h2 class="panel-title">Team Invitations</h2> 206 + <p class="panel-sub">Invite team members to TecharoHQ as part of your sponsorship.</p> 207 + </div> 208 + </div> 209 + <form hx-post="/invite" hx-target="#invite-result" class="space-y-4"> 210 + <label class="field-label" for="invite-username">GitHub username</label> 211 + <input 212 + id="invite-username" 213 + type="text" 214 + name="username" 215 + placeholder="octocat" 216 + autocomplete="off" 217 + required 218 + class="input" 219 + /> 220 + <div class="btn-row"> 221 + <button type="submit" class="btn btn-primary"> 222 + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> 223 + <path d="M5 12h14M12 5v14"></path> 224 + </svg> 135 225 Send Invitation 136 226 </button> 137 - </form> 138 - <div id="invite-result"></div> 139 - </div> 227 + </div> 228 + </form> 229 + <div id="invite-result"></div> 140 230 } 141 231 142 - templ LogoSubmitCard() { 143 - <div class="card p-4"> 144 - <h2 class="card-title flex items-center gap-2"> 145 - <svg class="w-5 h-5 text-purple-light dark:text-purpleDark-light" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 146 - <path stroke-linecap="round" stroke-linejoin="round" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path> 232 + templ LogoTab() { 233 + <div class="panel-head"> 234 + <div class="panel-head-icon panel-head-icon--purple"> 235 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 236 + <path d="M15 8h.01M12.5 21H6a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v6.5M3 16l5-5c.928-.893 2.072-.893 3 0l5 5M14 14l1-1c.679-.653 1.473-.829 2.214-.526"></path> 147 237 </svg> 148 - Logo Submission 149 - </h2> 150 - <p class="card-description"> 151 - Submit your company logo for the Anubis README. 152 - </p> 153 - <form hx-post="/logo" hx-encoding="multipart/form-data" hx-target="#logo-result" class="space-y-3"> 154 - <input 155 - type="text" 156 - name="company" 157 - placeholder="Company Name" 158 - required 159 - class="input" 160 - /> 161 - <input 162 - type="url" 163 - name="website" 164 - placeholder="Website URL" 165 - required 166 - class="input" 167 - /> 168 - <input 169 - type="file" 170 - name="logo" 171 - accept="image/png,image/jpeg,image/svg+xml" 172 - required 173 - class="file-input" 174 - /> 175 - <button type="submit" class="btn btn-dark w-full"> 176 - Submit Logo 177 - </button> 178 - </form> 179 - <div id="logo-result"></div> 238 + </div> 239 + <div> 240 + <h2 class="panel-title">Logo Submission</h2> 241 + <p class="panel-sub">Submit your company logo for the Anubis README. SVG preferred; PNG at 512×512 accepted.</p> 242 + </div> 180 243 </div> 244 + <form hx-post="/logo" hx-encoding="multipart/form-data" hx-target="#logo-result" class="space-y-4"> 245 + <div class="grid-2"> 246 + <div> 247 + <label class="field-label" for="logo-company">Company name</label> 248 + <input id="logo-company" type="text" name="company" placeholder="Techaro Inc." required class="input"/> 249 + </div> 250 + <div> 251 + <label class="field-label" for="logo-website">Website URL</label> 252 + <input id="logo-website" type="url" name="website" placeholder="https://techaro.lol" required class="input"/> 253 + </div> 254 + </div> 255 + <div> 256 + <label class="field-label" for="logo-file">Logo file</label> 257 + <label class="drop-zone" for="logo-file" id="logo-drop"> 258 + <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> 259 + <path d="M7 18a4.6 4.4 0 0 1 0-9 5 4.5 0 0 1 11 2h1a3.5 3.5 0 0 1 0 7h-1M9 15l3-3l3 3M12 12v9"></path> 260 + </svg> 261 + <strong id="logo-drop-name">Drop your logo here, or click to browse</strong> 262 + <span class="drop-hint">PNG, JPEG, or SVG · up to 5 MB</span> 263 + </label> 264 + <input id="logo-file" type="file" name="logo" accept="image/png,image/jpeg,image/svg+xml" required class="sr-only"/> 265 + </div> 266 + <div class="btn-row"> 267 + <button type="submit" class="btn btn-dark">Submit Logo</button> 268 + <button type="reset" class="btn btn-ghost" id="logo-reset">Clear</button> 269 + </div> 270 + </form> 271 + <div id="logo-result"></div> 181 272 } 182 273 183 - templ ThothTokenCard() { 184 - <div class="card p-4"> 185 - <h2 class="card-title flex items-center gap-2"> 186 - <svg class="w-5 h-5 text-yellow-light dark:text-yellowDark-light" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 187 - <path stroke-linecap="round" stroke-linejoin="round" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path> 274 + templ TokenTab() { 275 + <div class="panel-head"> 276 + <div class="panel-head-icon panel-head-icon--yellow"> 277 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 278 + <path d="M8 15a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM10.85 12.15 21 22l-2 2-1.5-1.5L17 22l-1.5-1.5L14 22l-4-4 1-1-1-1"></path> 188 279 </svg> 189 - Thoth API Token 190 - </h2> 191 - <p class="card-description"> 192 - Generate an API token for <a href="https://anubis.techaro.lol/docs/admin/thoth" class="underline">Thoth</a>. Use it to enable IP ASN and country code checks. 193 - </p> 280 + </div> 281 + <div> 282 + <h2 class="panel-title">Thoth API Token</h2> 283 + <p class="panel-sub"> 284 + Generate an API token for <a href="https://anubis.techaro.lol/docs/admin/thoth" class="underline">Thoth</a>. Use it to enable IP, ASN and country code checks in your Anubis deployment. 285 + </p> 286 + </div> 287 + </div> 288 + <dl class="meta-list"> 289 + <div> 290 + <dt>Scope</dt> 291 + <dd>read:asn, read:geoip, read:ip</dd> 292 + </div> 293 + <div> 294 + <dt>Endpoint</dt> 295 + <dd><code>thoth.techaro.lol:443</code></dd> 296 + </div> 297 + </dl> 298 + <div class="btn-row"> 194 299 <form hx-post="/thoth-token" hx-target="#thoth-result"> 195 - <button type="submit" class="btn btn-dark w-full" hx-disabled-elt="this"> 300 + <button type="submit" class="btn btn-accent" hx-disabled-elt="this"> 301 + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> 302 + <path d="M20 11A8.1 8.1 0 0 0 4.5 9M4 5v4h4M4 13a8.1 8.1 0 0 0 15.5 2M20 19v-4h-4"></path> 303 + </svg> 196 304 Generate Token 197 305 </button> 198 306 </form> 199 - <div id="thoth-result"></div> 200 307 </div> 308 + <div id="thoth-result"></div> 201 309 }
+158 -70
cmd/sponsor-panel/templates/dashboard_templ.go
··· 50 50 if templ_7745c5c3_Err != nil { 51 51 return templ_7745c5c3_Err 52 52 } 53 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<main class=\"max-w-4xl mx-auto px-4 md:px-10 py-4 md:py-10\"><div class=\"\"><h1 class=\"hero-title mb-3\">Welcome back, ") 53 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"welcome-band\"><h1 class=\"hero-title\">Welcome back, ") 54 54 if templ_7745c5c3_Err != nil { 55 55 return templ_7745c5c3_Err 56 56 } 57 57 var templ_7745c5c3_Var2 string 58 58 templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(props.User.Login) 59 59 if templ_7745c5c3_Err != nil { 60 - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 24, Col: 63} 60 + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 23, Col: 57} 61 61 } 62 62 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) 63 63 if templ_7745c5c3_Err != nil { 64 64 return templ_7745c5c3_Err 65 65 } 66 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</h1><p class=\"text-fg-3 dark:text-fgDark-3\">Manage your sponsorship benefits</p></div><div class=\"grid md:grid-cols-2 gap-8 mt-4\">") 66 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</h1><p class=\"welcome-sub\">Manage your sponsorship benefits</p></section><div class=\"admin-shell\">") 67 + if templ_7745c5c3_Err != nil { 68 + return templ_7745c5c3_Err 69 + } 70 + templ_7745c5c3_Err = Sidebar(props).Render(ctx, templ_7745c5c3_Buffer) 67 71 if templ_7745c5c3_Err != nil { 68 72 return templ_7745c5c3_Err 69 73 } 70 - if props.IsSponsor { 71 - templ_7745c5c3_Err = DiscordCard(props.DiscordInvite).Render(ctx, templ_7745c5c3_Buffer) 72 - if templ_7745c5c3_Err != nil { 73 - return templ_7745c5c3_Err 74 - } 74 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<main class=\"admin-panel card card-warm\" aria-label=\"Sponsorship panel\"><div class=\"panel-body\"><section class=\"tabpanel active\" id=\"panel-sponsorship\" role=\"tabpanel\" aria-labelledby=\"tab-sponsorship\">") 75 + if templ_7745c5c3_Err != nil { 76 + return templ_7745c5c3_Err 75 77 } 76 - templ_7745c5c3_Err = SponsorshipCard(props.IsSponsor, props.SponsorAmount, props.SponsorTier, props.User.Provider).Render(ctx, templ_7745c5c3_Buffer) 78 + templ_7745c5c3_Err = SponsorshipTab(props).Render(ctx, templ_7745c5c3_Buffer) 77 79 if templ_7745c5c3_Err != nil { 78 80 return templ_7745c5c3_Err 79 81 } 80 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div><div class=\"grid md:grid-cols-2 gap-8 mt-8\">") 82 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</section>") 81 83 if templ_7745c5c3_Err != nil { 82 84 return templ_7745c5c3_Err 83 85 } 84 - if props.IsFiftyPlus { 85 - templ_7745c5c3_Err = TeamInviteCard().Render(ctx, templ_7745c5c3_Buffer) 86 + if props.IsSponsor { 87 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<section class=\"tabpanel\" id=\"panel-discord\" role=\"tabpanel\" aria-labelledby=\"tab-discord\">") 88 + if templ_7745c5c3_Err != nil { 89 + return templ_7745c5c3_Err 90 + } 91 + templ_7745c5c3_Err = DiscordTab(props.DiscordInvite).Render(ctx, templ_7745c5c3_Buffer) 92 + if templ_7745c5c3_Err != nil { 93 + return templ_7745c5c3_Err 94 + } 95 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</section>") 86 96 if templ_7745c5c3_Err != nil { 87 97 return templ_7745c5c3_Err 88 98 } 89 99 } 90 - if props.IsSponsor { 91 - templ_7745c5c3_Err = LogoSubmitCard().Render(ctx, templ_7745c5c3_Buffer) 100 + if props.IsFiftyPlus { 101 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<section class=\"tabpanel\" id=\"panel-team\" role=\"tabpanel\" aria-labelledby=\"tab-team\">") 92 102 if templ_7745c5c3_Err != nil { 93 103 return templ_7745c5c3_Err 94 104 } 95 - } 96 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div><div class=\"grid md:grid-cols-2 gap-8 mt-8\">") 97 - if templ_7745c5c3_Err != nil { 98 - return templ_7745c5c3_Err 105 + templ_7745c5c3_Err = TeamTab().Render(ctx, templ_7745c5c3_Buffer) 106 + if templ_7745c5c3_Err != nil { 107 + return templ_7745c5c3_Err 108 + } 109 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</section>") 110 + if templ_7745c5c3_Err != nil { 111 + return templ_7745c5c3_Err 112 + } 99 113 } 100 114 if props.IsSponsor { 101 - templ_7745c5c3_Err = ThothTokenCard().Render(ctx, templ_7745c5c3_Buffer) 115 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<section class=\"tabpanel\" id=\"panel-logo\" role=\"tabpanel\" aria-labelledby=\"tab-logo\">") 116 + if templ_7745c5c3_Err != nil { 117 + return templ_7745c5c3_Err 118 + } 119 + templ_7745c5c3_Err = LogoTab().Render(ctx, templ_7745c5c3_Buffer) 120 + if templ_7745c5c3_Err != nil { 121 + return templ_7745c5c3_Err 122 + } 123 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</section><section class=\"tabpanel\" id=\"panel-token\" role=\"tabpanel\" aria-labelledby=\"tab-token\">") 124 + if templ_7745c5c3_Err != nil { 125 + return templ_7745c5c3_Err 126 + } 127 + templ_7745c5c3_Err = TokenTab().Render(ctx, templ_7745c5c3_Buffer) 128 + if templ_7745c5c3_Err != nil { 129 + return templ_7745c5c3_Err 130 + } 131 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</section>") 102 132 if templ_7745c5c3_Err != nil { 103 133 return templ_7745c5c3_Err 104 134 } 105 135 } 106 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div></main>") 136 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div></main></div><div class=\"toast\" id=\"admin-toast\" role=\"status\" aria-live=\"polite\"><svg class=\"w-4 h-4 text-green-light dark:text-greenDark-light\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m5 12 5 5L20 7\"></path></svg> <span id=\"admin-toast-msg\">Saved</span></div><script src=\"/static/js/admin-panel.js\" defer></script>") 107 137 if templ_7745c5c3_Err != nil { 108 138 return templ_7745c5c3_Err 109 139 } ··· 132 162 templ_7745c5c3_Var3 = templ.NopComponent 133 163 } 134 164 ctx = templ.ClearChildren(ctx) 135 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<nav class=\"navbar sticky top-0 z-50 px-8 py-5 md:px-12\"><div class=\"max-w-4xl mx-auto flex items-center justify-between\"><div class=\"flex items-center gap-3\"><img src=\"") 165 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<nav class=\"navbar sticky top-0 z-50 px-4 md:px-8 py-4\"><div class=\"max-w-5xl mx-auto flex items-center justify-between\"><div class=\"flex items-center gap-3\"><img src=\"") 136 166 if templ_7745c5c3_Err != nil { 137 167 return templ_7745c5c3_Err 138 168 } 139 169 var templ_7745c5c3_Var4 string 140 170 templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.SafeURL(avatarURL)) 141 171 if templ_7745c5c3_Err != nil { 142 - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 53, Col: 39} 172 + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 67, Col: 39} 143 173 } 144 174 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) 145 175 if templ_7745c5c3_Err != nil { 146 176 return templ_7745c5c3_Err 147 177 } 148 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" class=\"w-9 h-9 rounded-full ring-2 ring-bg-3 dark:ring-bgDark-3\" alt=\"\"> <span class=\"font-medium text-fg-1 dark:text-fgDark-1\">") 178 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" class=\"w-9 h-9 rounded-full ring-2 ring-bg-3 dark:ring-bgDark-3\" alt=\"\"> <span class=\"font-medium text-fg-1 dark:text-fgDark-1\">") 149 179 if templ_7745c5c3_Err != nil { 150 180 return templ_7745c5c3_Err 151 181 } 152 182 var templ_7745c5c3_Var5 string 153 183 templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(login) 154 184 if templ_7745c5c3_Err != nil { 155 - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 54, Col: 66} 185 + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 68, Col: 66} 156 186 } 157 187 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) 158 188 if templ_7745c5c3_Err != nil { 159 189 return templ_7745c5c3_Err 160 190 } 161 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</span></div><a href=\"/logout\" class=\"btn btn-ghost text-sm p-2\">Logout</a></div></nav>") 191 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</span></div><a href=\"/logout\" class=\"btn btn-ghost text-sm px-4 py-2\">Logout</a></div></nav>") 162 192 if templ_7745c5c3_Err != nil { 163 193 return templ_7745c5c3_Err 164 194 } ··· 166 196 }) 167 197 } 168 198 169 - func DiscordCard(inviteURL string) templ.Component { 199 + func Sidebar(props DashboardProps) templ.Component { 170 200 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 171 201 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 172 202 if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { ··· 187 217 templ_7745c5c3_Var6 = templ.NopComponent 188 218 } 189 219 ctx = templ.ClearChildren(ctx) 190 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"card card-cool p-4\"><h2 class=\"card-title flex items-center gap-3 !mb-4\"><svg class=\"w-6 h-6 text-blue-light dark:text-blueDark-light\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z\"></path></svg> Discord Community</h2><p class=\"card-description !mb-6\">Connect with other sponsors and get early access to updates.</p><a href=\"") 220 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<nav class=\"tabs\" role=\"tablist\" aria-label=\"Sponsor benefits\"><div class=\"tab-section-label\">Account</div><button class=\"tab\" role=\"tab\" id=\"tab-sponsorship\" data-tab=\"sponsorship\" aria-selected=\"true\" aria-controls=\"panel-sponsorship\" type=\"button\"><svg class=\"tab-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M19.5 13.572 12 21l-7.5-7.428A5 5 0 1 1 12 6.006a5 5 0 1 1 7.5 7.566Z\"></path></svg> Your Sponsorship</button> ") 191 221 if templ_7745c5c3_Err != nil { 192 222 return templ_7745c5c3_Err 193 223 } 194 - var templ_7745c5c3_Var7 templ.SafeURL 195 - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(inviteURL)) 196 - if templ_7745c5c3_Err != nil { 197 - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 74, Col: 36} 198 - } 199 - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) 200 - if templ_7745c5c3_Err != nil { 201 - return templ_7745c5c3_Err 224 + if props.IsSponsor { 225 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"tab-sep\"></div><div class=\"tab-section-label\">Benefits</div><button class=\"tab\" role=\"tab\" id=\"tab-discord\" data-tab=\"discord\" aria-selected=\"false\" aria-controls=\"panel-discord\" type=\"button\"><svg class=\"tab-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0M14 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0M15.5 17c0 1 1.5 3 2 3 1.5 0 2.833-1.667 3.5-3 .667-1.778.5-5.5-1.5-11.5-1.457-1.015-3-1.34-4.5-1.5l-1 2.5M8.5 17c0 1-1.356 3-1.832 3-1.429 0-2.698-1.667-3.333-3-.635-1.778-.476-5.5 1.428-11.5C6.151 4.485 7.545 4.16 9 4l1 2.5M7 16.5c3.5 1 6.5 1 10 0\"></path></svg> Discord Community</button> ") 226 + if templ_7745c5c3_Err != nil { 227 + return templ_7745c5c3_Err 228 + } 229 + if props.IsFiftyPlus { 230 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<button class=\"tab\" role=\"tab\" id=\"tab-team\" data-tab=\"team\" aria-selected=\"false\" aria-controls=\"panel-team\" type=\"button\"><svg class=\"tab-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 7a4 4 0 1 0 8 0 4 4 0 0 0-8 0M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2M16 3.13a4 4 0 0 1 0 7.75M21 21v-2a4 4 0 0 0-3-3.85\"></path></svg> Team Invitations</button>") 231 + if templ_7745c5c3_Err != nil { 232 + return templ_7745c5c3_Err 233 + } 234 + } 235 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, " <button class=\"tab\" role=\"tab\" id=\"tab-logo\" data-tab=\"logo\" aria-selected=\"false\" aria-controls=\"panel-logo\" type=\"button\"><svg class=\"tab-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 8h.01M12.5 21H6a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v6.5M3 16l5-5c.928-.893 2.072-.893 3 0l5 5M14 14l1-1c.679-.653 1.473-.829 2.214-.526\"></path></svg> Logo Submission</button><div class=\"tab-sep\"></div><div class=\"tab-section-label\">Developer</div><button class=\"tab\" role=\"tab\" id=\"tab-token\" data-tab=\"token\" aria-selected=\"false\" aria-controls=\"panel-token\" type=\"button\"><svg class=\"tab-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 15a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM10.85 12.15 21 22l-2 2-1.5-1.5L17 22l-1.5-1.5L14 22l-4-4 1-1-1-1\"></path></svg> Thoth API Token</button>") 236 + if templ_7745c5c3_Err != nil { 237 + return templ_7745c5c3_Err 238 + } 202 239 } 203 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" target=\"_blank\" class=\"btn btn-secondary p-2\">Join Discord</a></div>") 240 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</nav>") 204 241 if templ_7745c5c3_Err != nil { 205 242 return templ_7745c5c3_Err 206 243 } ··· 208 245 }) 209 246 } 210 247 211 - func SponsorshipCard(isSponsor bool, amount int, tier string, provider string) templ.Component { 248 + func SponsorshipTab(props DashboardProps) templ.Component { 212 249 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 213 250 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 214 251 if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { ··· 224 261 }() 225 262 } 226 263 ctx = templ.InitializeContext(ctx) 227 - templ_7745c5c3_Var8 := templ.GetChildren(ctx) 228 - if templ_7745c5c3_Var8 == nil { 229 - templ_7745c5c3_Var8 = templ.NopComponent 264 + templ_7745c5c3_Var7 := templ.GetChildren(ctx) 265 + if templ_7745c5c3_Var7 == nil { 266 + templ_7745c5c3_Var7 = templ.NopComponent 230 267 } 231 268 ctx = templ.ClearChildren(ctx) 232 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div class=\"card card-warm p-4\"><h2 class=\"card-title flex items-center gap-2\"><svg class=\"w-5 h-5 text-orange-light dark:text-orangeDark-light\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z\"></path></svg> Your Sponsorship</h2>") 269 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<div class=\"panel-head\"><div class=\"panel-head-icon\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M19.5 13.572 12 21l-7.5-7.428A5 5 0 1 1 12 6.006a5 5 0 1 1 7.5 7.566Z\"></path></svg></div><div><h2 class=\"panel-title\">Your Sponsorship</h2><p class=\"panel-sub\">A snapshot of your support and benefits.</p></div></div>") 233 270 if templ_7745c5c3_Err != nil { 234 271 return templ_7745c5c3_Err 235 272 } 236 - if isSponsor { 237 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"flex items-center gap-2 mb-2\"><span class=\"accent-dot\"></span> <span class=\"text-lg font-semibold text-green-light dark:text-greenDark-light\">$") 273 + if props.IsSponsor { 274 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<div class=\"stat-grid\"><div class=\"stat-card\"><div class=\"stat-k\">Monthly support</div><div class=\"stat-v\">$") 275 + if templ_7745c5c3_Err != nil { 276 + return templ_7745c5c3_Err 277 + } 278 + var templ_7745c5c3_Var8 string 279 + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(formatDollars(props.SponsorAmount)) 280 + if templ_7745c5c3_Err != nil { 281 + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 136, Col: 42} 282 + } 283 + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) 284 + if templ_7745c5c3_Err != nil { 285 + return templ_7745c5c3_Err 286 + } 287 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, " <small>/ month</small></div></div><div class=\"stat-card\"><div class=\"stat-k\">Tier</div><div class=\"stat-v stat-v-muted\">") 238 288 if templ_7745c5c3_Err != nil { 239 289 return templ_7745c5c3_Err 240 290 } 241 291 var templ_7745c5c3_Var9 string 242 - templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(formatDollars(amount)) 292 + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(props.SponsorTier) 243 293 if templ_7745c5c3_Err != nil { 244 - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 92, Col: 29} 294 + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 142, Col: 56} 245 295 } 246 296 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) 247 297 if templ_7745c5c3_Err != nil { 248 298 return templ_7745c5c3_Err 249 299 } 250 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "/month</span></div><p class=\"text-sm text-fg-3 dark:text-fgDark-3 mb-3\">") 300 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</div><div class=\"stat-sub\">via ") 251 301 if templ_7745c5c3_Err != nil { 252 302 return templ_7745c5c3_Err 253 303 } 254 304 var templ_7745c5c3_Var10 string 255 - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(tier) 305 + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(props.User.Provider) 256 306 if templ_7745c5c3_Err != nil { 257 - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 95, Col: 62} 307 + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 143, Col: 51} 258 308 } 259 309 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) 260 310 if templ_7745c5c3_Err != nil { 261 311 return templ_7745c5c3_Err 262 312 } 263 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</p><p class=\"text-sm font-medium text-orange-light dark:text-orangeDark-light\">Thank you for your support!</p>") 313 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</div></div></div><div class=\"thanks-card\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M19.5 13.572 12 21l-7.5-7.428A5 5 0 1 1 12 6.006a5 5 0 1 1 7.5 7.566Z\"></path></svg> Thank you for your support! Seriously. It keeps the lights on.</div>") 264 314 if templ_7745c5c3_Err != nil { 265 315 return templ_7745c5c3_Err 266 316 } 267 317 } else { 268 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<p class=\"card-description\">You're not currently an active sponsor.</p>") 318 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<p class=\"panel-sub mb-6\">You're not currently an active sponsor.</p>") 269 319 if templ_7745c5c3_Err != nil { 270 320 return templ_7745c5c3_Err 271 321 } 272 - if provider == "patreon" { 273 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<a href=\"https://www.patreon.com/cadey\" target=\"_blank\" class=\"btn btn-pink\">Become a Patron</a>") 322 + if props.User.Provider == "patreon" { 323 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<a href=\"https://www.patreon.com/cadey\" target=\"_blank\" class=\"btn btn-pink\">Become a Patron</a>") 274 324 if templ_7745c5c3_Err != nil { 275 325 return templ_7745c5c3_Err 276 326 } 277 327 } else { 278 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<a href=\"https://github.com/sponsors/Xe\" target=\"_blank\" class=\"btn btn-pink\">Become a Sponsor</a>") 328 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<a href=\"https://github.com/sponsors/Xe\" target=\"_blank\" class=\"btn btn-pink\">Become a Sponsor</a>") 279 329 if templ_7745c5c3_Err != nil { 280 330 return templ_7745c5c3_Err 281 331 } 282 332 } 283 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, " <p class=\"text-xs text-fg-4 dark:text-fgDark-4 mt-4 leading-relaxed\">If you're part of an organization that sponsors Anubis and see this message, please contact me@xeiaso.net for help.</p>") 333 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, " <p class=\"text-xs text-fg-4 dark:text-fgDark-4 mt-4 leading-relaxed\">If you're part of an organization that sponsors Anubis and see this message, please contact me@xeiaso.net for help.</p>") 284 334 if templ_7745c5c3_Err != nil { 285 335 return templ_7745c5c3_Err 286 336 } 287 337 } 288 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</div>") 289 - if templ_7745c5c3_Err != nil { 290 - return templ_7745c5c3_Err 291 - } 292 338 return nil 293 339 }) 294 340 } 295 341 296 - func TeamInviteCard() templ.Component { 342 + func DiscordTab(inviteURL string) templ.Component { 297 343 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 298 344 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 299 345 if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { ··· 314 360 templ_7745c5c3_Var11 = templ.NopComponent 315 361 } 316 362 ctx = templ.ClearChildren(ctx) 317 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<div class=\"card card-green p-4\"><h2 class=\"card-title flex items-center gap-2\"><svg class=\"w-5 h-5 text-green-light dark:text-greenDark-light\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z\"></path></svg> Team Invitation</h2><p class=\"card-description\">Invite team members to TecharoHQ as part of your sponsorship.</p><form hx-post=\"/invite\" hx-target=\"#invite-result\" class=\"space-y-3\"><input type=\"text\" name=\"username\" placeholder=\"GitHub username\" required class=\"input\"> <button type=\"submit\" class=\"btn btn-primary w-full\">Send Invitation</button></form><div id=\"invite-result\"></div></div>") 363 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<div class=\"panel-head\"><div class=\"panel-head-icon panel-head-icon--discord\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0M14 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0M15.5 17c0 1 1.5 3 2 3 1.5 0 2.833-1.667 3.5-3 .667-1.778.5-5.5-1.5-11.5-1.457-1.015-3-1.34-4.5-1.5l-1 2.5M8.5 17c0 1-1.356 3-1.832 3-1.429 0-2.698-1.667-3.333-3-.635-1.778-.476-5.5 1.428-11.5C6.151 4.485 7.545 4.16 9 4l1 2.5M7 16.5c3.5 1 6.5 1 10 0\"></path></svg></div><div><h2 class=\"panel-title\">Discord Community</h2><p class=\"panel-sub\">Connect with other sponsors and get early access to updates and drafts.</p></div></div><dl class=\"meta-list\"><div><dt>Access level</dt><dd>Sponsor channels + early drafts</dd></div><div><dt>Expires</dt><dd>While sponsorship is active</dd></div></dl><div class=\"btn-row\"><a href=\"") 364 + if templ_7745c5c3_Err != nil { 365 + return templ_7745c5c3_Err 366 + } 367 + var templ_7745c5c3_Var12 templ.SafeURL 368 + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(inviteURL)) 369 + if templ_7745c5c3_Err != nil { 370 + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/dashboard.templ`, Line: 188, Col: 36} 371 + } 372 + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) 373 + if templ_7745c5c3_Err != nil { 374 + return templ_7745c5c3_Err 375 + } 376 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "\" target=\"_blank\" class=\"btn btn-discord\"><svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25a.08.08 0 0 1 .08-.01c3.44 1.57 7.15 1.57 10.55 0a.08.08 0 0 1 .08.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z\"></path></svg> Join Discord</a></div>") 318 377 if templ_7745c5c3_Err != nil { 319 378 return templ_7745c5c3_Err 320 379 } ··· 322 381 }) 323 382 } 324 383 325 - func LogoSubmitCard() templ.Component { 384 + func TeamTab() templ.Component { 385 + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 386 + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 387 + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 388 + return templ_7745c5c3_CtxErr 389 + } 390 + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) 391 + if !templ_7745c5c3_IsBuffer { 392 + defer func() { 393 + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) 394 + if templ_7745c5c3_Err == nil { 395 + templ_7745c5c3_Err = templ_7745c5c3_BufErr 396 + } 397 + }() 398 + } 399 + ctx = templ.InitializeContext(ctx) 400 + templ_7745c5c3_Var13 := templ.GetChildren(ctx) 401 + if templ_7745c5c3_Var13 == nil { 402 + templ_7745c5c3_Var13 = templ.NopComponent 403 + } 404 + ctx = templ.ClearChildren(ctx) 405 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<div class=\"panel-head\"><div class=\"panel-head-icon\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 7a4 4 0 1 0 8 0 4 4 0 0 0-8 0M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2M16 3.13a4 4 0 0 1 0 7.75M21 21v-2a4 4 0 0 0-3-3.85\"></path></svg></div><div><h2 class=\"panel-title\">Team Invitations</h2><p class=\"panel-sub\">Invite team members to TecharoHQ as part of your sponsorship.</p></div></div><form hx-post=\"/invite\" hx-target=\"#invite-result\" class=\"space-y-4\"><label class=\"field-label\" for=\"invite-username\">GitHub username</label> <input id=\"invite-username\" type=\"text\" name=\"username\" placeholder=\"octocat\" autocomplete=\"off\" required class=\"input\"><div class=\"btn-row\"><button type=\"submit\" class=\"btn btn-primary\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5v14\"></path></svg> Send Invitation</button></div></form><div id=\"invite-result\"></div>") 406 + if templ_7745c5c3_Err != nil { 407 + return templ_7745c5c3_Err 408 + } 409 + return nil 410 + }) 411 + } 412 + 413 + func LogoTab() templ.Component { 326 414 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 327 415 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 328 416 if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { ··· 338 426 }() 339 427 } 340 428 ctx = templ.InitializeContext(ctx) 341 - templ_7745c5c3_Var12 := templ.GetChildren(ctx) 342 - if templ_7745c5c3_Var12 == nil { 343 - templ_7745c5c3_Var12 = templ.NopComponent 429 + templ_7745c5c3_Var14 := templ.GetChildren(ctx) 430 + if templ_7745c5c3_Var14 == nil { 431 + templ_7745c5c3_Var14 = templ.NopComponent 344 432 } 345 433 ctx = templ.ClearChildren(ctx) 346 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<div class=\"card p-4\"><h2 class=\"card-title flex items-center gap-2\"><svg class=\"w-5 h-5 text-purple-light dark:text-purpleDark-light\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\"></path></svg> Logo Submission</h2><p class=\"card-description\">Submit your company logo for the Anubis README.</p><form hx-post=\"/logo\" hx-encoding=\"multipart/form-data\" hx-target=\"#logo-result\" class=\"space-y-3\"><input type=\"text\" name=\"company\" placeholder=\"Company Name\" required class=\"input\"> <input type=\"url\" name=\"website\" placeholder=\"Website URL\" required class=\"input\"> <input type=\"file\" name=\"logo\" accept=\"image/png,image/jpeg,image/svg+xml\" required class=\"file-input\"> <button type=\"submit\" class=\"btn btn-dark w-full\">Submit Logo</button></form><div id=\"logo-result\"></div></div>") 434 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<div class=\"panel-head\"><div class=\"panel-head-icon panel-head-icon--purple\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 8h.01M12.5 21H6a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v6.5M3 16l5-5c.928-.893 2.072-.893 3 0l5 5M14 14l1-1c.679-.653 1.473-.829 2.214-.526\"></path></svg></div><div><h2 class=\"panel-title\">Logo Submission</h2><p class=\"panel-sub\">Submit your company logo for the Anubis README. SVG preferred; PNG at 512×512 accepted.</p></div></div><form hx-post=\"/logo\" hx-encoding=\"multipart/form-data\" hx-target=\"#logo-result\" class=\"space-y-4\"><div class=\"grid-2\"><div><label class=\"field-label\" for=\"logo-company\">Company name</label> <input id=\"logo-company\" type=\"text\" name=\"company\" placeholder=\"Techaro Inc.\" required class=\"input\"></div><div><label class=\"field-label\" for=\"logo-website\">Website URL</label> <input id=\"logo-website\" type=\"url\" name=\"website\" placeholder=\"https://techaro.lol\" required class=\"input\"></div></div><div><label class=\"field-label\" for=\"logo-file\">Logo file</label> <label class=\"drop-zone\" for=\"logo-file\" id=\"logo-drop\"><svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M7 18a4.6 4.4 0 0 1 0-9 5 4.5 0 0 1 11 2h1a3.5 3.5 0 0 1 0 7h-1M9 15l3-3l3 3M12 12v9\"></path></svg> <strong id=\"logo-drop-name\">Drop your logo here, or click to browse</strong> <span class=\"drop-hint\">PNG, JPEG, or SVG · up to 5 MB</span></label> <input id=\"logo-file\" type=\"file\" name=\"logo\" accept=\"image/png,image/jpeg,image/svg+xml\" required class=\"sr-only\"></div><div class=\"btn-row\"><button type=\"submit\" class=\"btn btn-dark\">Submit Logo</button> <button type=\"reset\" class=\"btn btn-ghost\" id=\"logo-reset\">Clear</button></div></form><div id=\"logo-result\"></div>") 347 435 if templ_7745c5c3_Err != nil { 348 436 return templ_7745c5c3_Err 349 437 } ··· 351 439 }) 352 440 } 353 441 354 - func ThothTokenCard() templ.Component { 442 + func TokenTab() templ.Component { 355 443 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { 356 444 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context 357 445 if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { ··· 367 455 }() 368 456 } 369 457 ctx = templ.InitializeContext(ctx) 370 - templ_7745c5c3_Var13 := templ.GetChildren(ctx) 371 - if templ_7745c5c3_Var13 == nil { 372 - templ_7745c5c3_Var13 = templ.NopComponent 458 + templ_7745c5c3_Var15 := templ.GetChildren(ctx) 459 + if templ_7745c5c3_Var15 == nil { 460 + templ_7745c5c3_Var15 = templ.NopComponent 373 461 } 374 462 ctx = templ.ClearChildren(ctx) 375 - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<div class=\"card p-4\"><h2 class=\"card-title flex items-center gap-2\"><svg class=\"w-5 h-5 text-yellow-light dark:text-yellowDark-light\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z\"></path></svg> Thoth API Token</h2><p class=\"card-description\">Generate an API token for <a href=\"https://anubis.techaro.lol/docs/admin/thoth\" class=\"underline\">Thoth</a>. Use it to enable IP ASN and country code checks.</p><form hx-post=\"/thoth-token\" hx-target=\"#thoth-result\"><button type=\"submit\" class=\"btn btn-dark w-full\" hx-disabled-elt=\"this\">Generate Token</button></form><div id=\"thoth-result\"></div></div>") 463 + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div class=\"panel-head\"><div class=\"panel-head-icon panel-head-icon--yellow\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 15a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM10.85 12.15 21 22l-2 2-1.5-1.5L17 22l-1.5-1.5L14 22l-4-4 1-1-1-1\"></path></svg></div><div><h2 class=\"panel-title\">Thoth API Token</h2><p class=\"panel-sub\">Generate an API token for <a href=\"https://anubis.techaro.lol/docs/admin/thoth\" class=\"underline\">Thoth</a>. Use it to enable IP, ASN and country code checks in your Anubis deployment.</p></div></div><dl class=\"meta-list\"><div><dt>Scope</dt><dd>read:asn, read:geoip, read:ip</dd></div><div><dt>Endpoint</dt><dd><code>thoth.techaro.lol:443</code></dd></div></dl><div class=\"btn-row\"><form hx-post=\"/thoth-token\" hx-target=\"#thoth-result\"><button type=\"submit\" class=\"btn btn-accent\" hx-disabled-elt=\"this\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 11A8.1 8.1 0 0 0 4.5 9M4 5v4h4M4 13a8.1 8.1 0 0 0 15.5 2M20 19v-4h-4\"></path></svg> Generate Token</button></form></div><div id=\"thoth-result\"></div>") 376 464 if templ_7745c5c3_Err != nil { 377 465 return templ_7745c5c3_Err 378 466 }