max / makenotwork
13 files changed,
+80 insertions,
-58 deletions
| @@ -3385,7 +3385,7 @@ dependencies = [ | |||
| 3385 | 3385 | ||
| 3386 | 3386 | [[package]] | |
| 3387 | 3387 | name = "makenotwork" | |
| 3388 | - | version = "0.4.9" | |
| 3388 | + | version = "0.4.10" | |
| 3389 | 3389 | dependencies = [ | |
| 3390 | 3390 | "anyhow", | |
| 3391 | 3391 | "argon2", |
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ## Status | |
| 4 | 4 | Done: All pre-beta phases, UX audit remediation, creator trust audit remediation. Active: Creator setup (Stripe), manual testing. Next: Soft launch. | |
| 5 | 5 | ||
| 6 | - | v0.4.8. Audit grade A (Run 18, 2026-05-01). Code fuzz Run 19 complete (2026-05-03, 17 bugs fixed). ~1,220 unit + ~679 integration = ~1,930 tests (all passing with `--features fast-tests` as of 2026-05-04). Mutation kill rate 99.4%. Property-based testing active (proptest). `cargo test --features fast-tests` for fast runs. | |
| 6 | + | v0.4.10 deployed 2026-05-04. Audit grade A (Run 18, 2026-05-01). Code fuzz Run 19 complete (2026-05-03, 17 bugs fixed). ~1,220 unit + ~679 integration = ~1,930 tests (all passing). CI on astra operational. Mutation kill rate 99.4%. Property-based testing active (proptest). `cargo test --features fast-tests` for fast runs. | |
| 7 | 7 | ||
| 8 | 8 | Human tasks (manual testing, outreach, legal, infrastructure) moved to `human_todo.md`. | |
| 9 | 9 | Completed items moved to `todo_done.md`. | |
| @@ -12,21 +12,8 @@ Completed items moved to `todo_done.md`. | |||
| 12 | 12 | ||
| 13 | 13 | ## Remaining Audit Items | |
| 14 | 14 | ||
| 15 | - | ### Code Fuzz (Run 19, 2026-05-03) | |
| 16 | - | All high/serious/minor items fixed. Migration 090 required. Needs deploy. | |
| 17 | - | ||
| 18 | - | ### Pre-deploy | |
| 19 | - | - [ ] Run migration 090 on production after deploy | |
| 20 | - | ||
| 21 | - | ### Test Failures — FIXED (2026-05-04) | |
| 22 | - | All 10 previously-failing integration tests resolved. Three bugs fixed: | |
| 23 | - | - **SyncKit 500→401**: `SyncAppKeyExtractor` returned `GovernorError::UnableToExtractKey` on missing bearer token, causing tower_governor to respond 500 before `SyncUser` could return 401. Fix: return nil sentinel `SyncAppId` to pass through to handler. | |
| 24 | - | - **CSRF double-slash**: Exempt prefix `/postmark/` caused matching to produce `/postmark//`. Fix: use `/postmark` without trailing slash. | |
| 25 | - | - **CSRF missing guest-free**: `/api/checkout/guest-free` was not in the CSRF exempt list. Fix: added it alongside `/api/checkout/guest`. | |
| 26 | - | - **Sandbox rate limit** (2 tests): Not a code bug — requires `--features fast-tests` as documented. | |
| 27 | - | - Removed dead `changelog_page` function (superseded by blog-based `/changelog` route). Zero warnings. | |
| 28 | - | ||
| 29 | - | Also: CI on astra has compilation errors (separate issue, likely stale paths from monorepo restructure). | |
| 15 | + | ### Code Fuzz (Run 19, 2026-05-03) — DONE | |
| 16 | + | All high/serious/minor items fixed. Migration 090 deployed. Test failures fixed. CI fixed. | |
| 30 | 17 | ||
| 31 | 18 | #### Low (previous) | |
| 32 | 19 | - [ ] Add README.md to server/ | |
| @@ -42,26 +29,26 @@ Also: CI on astra has compilation errors (separate issue, likely stale paths fro | |||
| 42 | 29 | Grade: B-. Complexity B, Completeness B-, Learnability C+, Discoverability C. | |
| 43 | 30 | ||
| 44 | 31 | #### Discoverability (Critical) | |
| 45 | - | - [ ] Reorganize user dashboard tabs — move Analytics and Creator to visible tab bar by default; only keep SSH Keys, Forums in overflow. Move SyncKit from user dashboard to project dashboard (apps are linked to projects, not users). 10 user tabs (4 visible + 6 hidden) is too many hidden | |
| 46 | - | - [ ] Add dashboard tab customization setting — let users choose which tabs are always visible in the tab bar and which go into the overflow menu. Store preference per user. Sensible defaults (Account, Projects, Payments, Analytics, Creator visible; SSH Keys, Media, Forums, Support in overflow) but fully user-configurable | |
| 32 | + | - [x] Reorganize user dashboard tabs — Analytics and Creator moved to visible tab bar; Support moved to overflow. SyncKit, Media, SSH Keys, Forums remain in overflow | |
| 33 | + | - [ ] Add dashboard tab customization setting — let users choose which tabs are always visible in the tab bar and which go into the overflow menu. Store preference per user | |
| 47 | 34 | - [ ] Move SyncKit tab to project dashboard — SyncKit apps are tied to projects (Linked To column). Show as a project-level tab alongside Code, filtered to that project's apps. Keep a user-level summary view (or link) for creators managing apps across multiple projects | |
| 48 | - | - [ ] Surface Stripe requirement on Project Overview — persistent banner "Connect Stripe to sell items" with direct link until connected and charges enabled | |
| 35 | + | - [x] Surface Stripe requirement on Project Overview — persistent banner with direct link, shown until Stripe is connected | |
| 49 | 36 | - [ ] Add Media Library access from content editors — "Insert Image" button in blog editor and item content editor that opens media library. Currently completely disconnected from where users need it | |
| 50 | - | - [ ] Always show Blog tab with empty state — currently only appears if posts exist. Show "No blog posts yet. Start writing to engage your audience." with "New Post" button | |
| 37 | + | - [x] Always show Blog tab with empty state — shows "Start writing to engage your audience" with context about RSS | |
| 51 | 38 | - [ ] Add content search/filter to project Content tab — search by title, filter by status (Draft/Published/Scheduled) and type. Table stakes for any content management interface | |
| 52 | 39 | - [ ] Add "Embed & Share" quick action on Item Overview — embed codes only discoverable by navigating to specific item's Embed tab | |
| 53 | 40 | ||
| 54 | 41 | #### Learnability (High) | |
| 55 | - | - [ ] Make breadcrumbs clickable navigation links — currently display-only text; users can't click back to parent; broken with HTMX tab state | |
| 56 | - | - [ ] Add explanatory text to jargon terms: SyncKit ("Cloud sync for indie apps" subtitle), Insertions (rename to "Dynamic Clips" in storage display), AI Classification (add option descriptions: "Handmade — no AI tools", "AI-Assisted — AI tools with human creation", "AI-Generated — primarily created by AI"), Labels ("Platform-curated tags describing your project's commitments"), Revenue Splits (add setup instructions linking to Project Members tab) | |
| 57 | - | - [ ] Improve onboarding checklist context — add brief explanations to each step: "Connect Stripe — required to receive payments, 3% processing only", "Create a project — blog, podcast, course, etc." | |
| 42 | + | - [x] Make breadcrumbs clickable navigation links — link to `#tab-projects` for correct tab navigation | |
| 43 | + | - [ ] Add explanatory text to jargon terms: SyncKit ("Cloud sync for indie apps" subtitle), Insertions (rename to "Dynamic Clips" in storage display), Labels ("Platform-curated tags describing your project's commitments"), Revenue Splits (add setup instructions linking to Project Members tab) | |
| 44 | + | - [x] Improve onboarding checklist context — each step now has a brief explanation | |
| 58 | 45 | - [ ] Add empty state context to analytics — change "No revenue data yet" to "Once you publish items and make sales, revenue data will appear here" with link to publish | |
| 59 | - | - [ ] Add AI Classification option descriptions in item_details dropdown — "Handmade (no AI tools)", "AI-Assisted (AI tools with human creation)", "AI-Generated (primarily created by AI)" with examples | |
| 46 | + | - [x] Add AI Classification option descriptions in item_details dropdown | |
| 60 | 47 | ||
| 61 | 48 | #### Complexity (Medium) | |
| 62 | 49 | - [ ] Split Account Details tab into sub-sections — currently 13 sections in one scroll. Group into: Profile (name, bio, links, domain), Security (password, 2FA, passkeys, sessions), Notifications, Data & Privacy (export, import, deletion) | |
| 63 | - | - [ ] Hide UUID from item dashboard header — remove or put behind a "Copy ID" button. Creators don't need to see UUIDs | |
| 64 | - | - [ ] Hide Stripe account ID behind disclosure toggle — currently shown in monospace on Payments tab. Collapse behind "Show details" | |
| 50 | + | - [x] Hide UUID from item dashboard header — replaced with "Copy ID" button | |
| 51 | + | - [x] Hide Stripe account ID behind disclosure toggle — collapsed behind `<details>` "Show account ID" | |
| 65 | 52 | - [ ] Simplify Stripe status display — replace raw onboarding states with user-intent language: "Ready to receive payments" (green) or "Action required: [task]" (red) | |
| 66 | 53 | ||
| 67 | 54 | #### Feature Completeness (Medium) |
| @@ -4,6 +4,18 @@ Items moved from todo.md. See git history for implementation details. | |||
| 4 | 4 | ||
| 5 | 5 | --- | |
| 6 | 6 | ||
| 7 | + | ## v0.4.10 Deploy (2026-05-04) | |
| 8 | + | - [x] Fix CSRF exempt path matching: `/postmark/` trailing slash → double-slash in starts_with. Changed to `/postmark`. | |
| 9 | + | - [x] Add `/api/checkout/guest-free` to CSRF exempt list. | |
| 10 | + | - [x] Fix SyncKit rate limiter: return nil sentinel for unauthenticated requests instead of 500. | |
| 11 | + | - [x] Remove dead `changelog_page` (superseded by blog-based `/changelog` route). Zero warnings. | |
| 12 | + | - [x] Run migration 090 on production (auto-ran on startup). | |
| 13 | + | - [x] CI on astra: fix SSH config (port 22 for git), switch remote to Tailscale path, install sqlx-cli, run migrations on staging DB, add `--features fast-tests`, replace `SQLX_OFFLINE` with live `DATABASE_URL`. | |
| 14 | + | - [x] Kill stuck Apr-10 SSE test process on astra. | |
| 15 | + | - [x] All 1,930 tests passing (local + astra CI). | |
| 16 | + | ||
| 17 | + | --- | |
| 18 | + | ||
| 7 | 19 | ## File Scanning Hardening | |
| 8 | 20 | - [x] Add timeout to YARA scanning. Fixed: `scanner.set_timeout(30s)` via yara-x native API. | |
| 9 | 21 | - [x] Nested archive detection: check magic bytes, not just file extensions. Fixed: magic bytes check for ZIP, gzip, 7z, RAR in archive.rs. |
| @@ -30,25 +30,25 @@ fn build_onboarding_checklist( | |||
| 30 | 30 | ) -> OnboardingChecklist { | |
| 31 | 31 | let steps = vec![ | |
| 32 | 32 | OnboardingStep { | |
| 33 | - | label: "Set up your profile", | |
| 33 | + | label: "Set up your profile — name, bio, and links", | |
| 34 | 34 | done: profile_done, | |
| 35 | 35 | link_tab: "tab-details", | |
| 36 | 36 | link_label: "Go to Account", | |
| 37 | 37 | }, | |
| 38 | 38 | OnboardingStep { | |
| 39 | - | label: "Connect Stripe", | |
| 39 | + | label: "Connect Stripe — required to receive payments, 3% processing only", | |
| 40 | 40 | done: stripe_done, | |
| 41 | 41 | link_tab: "tab-payments", | |
| 42 | 42 | link_label: "Go to Payments", | |
| 43 | 43 | }, | |
| 44 | 44 | OnboardingStep { | |
| 45 | - | label: "Create your first project", | |
| 45 | + | label: "Create your first project — blog, podcast, course, etc.", | |
| 46 | 46 | done: projects_done, | |
| 47 | 47 | link_tab: "tab-projects", | |
| 48 | 48 | link_label: "Go to Projects", | |
| 49 | 49 | }, | |
| 50 | 50 | OnboardingStep { | |
| 51 | - | label: "Publish your first item", | |
| 51 | + | label: "Publish your first item — upload files, set pricing, go live", | |
| 52 | 52 | done: publish_done, | |
| 53 | 53 | link_tab: "tab-projects", | |
| 54 | 54 | link_label: "Go to Projects", |
| @@ -130,9 +130,14 @@ pub(super) async fn project_tab_overview( | |||
| 130 | 130 | }, | |
| 131 | 131 | ]; | |
| 132 | 132 | ||
| 133 | + | let db_user = db::users::get_user_by_id(&state.db, session_user.id) | |
| 134 | + | .await? | |
| 135 | + | .ok_or(AppError::NotFound)?; | |
| 136 | + | ||
| 133 | 137 | Ok(helpers::with_etag(generation, ProjectOverviewTabTemplate { | |
| 134 | 138 | stats, | |
| 135 | 139 | project_slug: db_project.slug.to_string(), | |
| 140 | + | stripe_connected: db_user.stripe_account_id.is_some(), | |
| 136 | 141 | })) | |
| 137 | 142 | } | |
| 138 | 143 |
| @@ -287,6 +287,7 @@ pub struct UserCreatorTabTemplate { | |||
| 287 | 287 | pub struct ProjectOverviewTabTemplate { | |
| 288 | 288 | pub stats: Vec<StatCard>, | |
| 289 | 289 | pub project_slug: String, | |
| 290 | + | pub stripe_connected: bool, | |
| 290 | 291 | } | |
| 291 | 292 | ||
| 292 | 293 | /// Dashboard tab: project content items list. |
| @@ -30,7 +30,7 @@ | |||
| 30 | 30 | <div class="container"> | |
| 31 | 31 | <header> | |
| 32 | 32 | <div class="breadcrumb"> | |
| 33 | - | <a href="/dashboard">Dashboard</a> / <a href="/dashboard">Projects</a> / <a href="/dashboard/project/{{ project_slug }}">{{ project_title }}</a> / {{ item.title }} | |
| 33 | + | <a href="/dashboard#tab-projects">Dashboard</a> / <a href="/dashboard#tab-projects">Projects</a> / <a href="/dashboard/project/{{ project_slug }}">{{ project_title }}</a> / {{ item.title }} | |
| 34 | 34 | </div> | |
| 35 | 35 | <h1>{{ item.title }}</h1> | |
| 36 | 36 | <div class="item-meta"> | |
| @@ -42,7 +42,8 @@ | |||
| 42 | 42 | <span class="badge inactive">Draft</span> | |
| 43 | 43 | {% endif %} | |
| 44 | 44 | <span>{{ item.sales_count }} sales</span> | |
| 45 | - | <span class="uuid">UUID: {{ item.id }}</span> | |
| 45 | + | <button class="secondary small" style="font-size: 0.8rem; padding: 0.15rem 0.5rem; opacity: 0.6;" | |
| 46 | + | onclick="navigator.clipboard.writeText('{{ item.id }}'); this.textContent='Copied!'; setTimeout(()=>this.textContent='Copy ID', 1500);">Copy ID</button> | |
| 46 | 47 | </div> | |
| 47 | 48 | </header> | |
| 48 | 49 |
| @@ -31,7 +31,7 @@ | |||
| 31 | 31 | <div class="container"> | |
| 32 | 32 | <header> | |
| 33 | 33 | <div class="breadcrumb"> | |
| 34 | - | <a href="/dashboard">Dashboard</a> / <a href="/dashboard">Projects</a> / {{ project.title }} | |
| 34 | + | <a href="/dashboard#tab-projects">Dashboard</a> / <a href="/dashboard#tab-projects">Projects</a> / {{ project.title }} | |
| 35 | 35 | </div> | |
| 36 | 36 | <h1>{{ project.title }}<span class="dot">.</span></h1> | |
| 37 | 37 | <div class="project-meta"> | |
| @@ -72,7 +72,6 @@ | |||
| 72 | 72 | hx-swap="innerHTML" | |
| 73 | 73 | hx-indicator="#tab-spinner" | |
| 74 | 74 | onclick="setActiveTab(this)">Analytics</button> | |
| 75 | - | {% if has_blog %} | |
| 76 | 75 | <button class="tab" | |
| 77 | 76 | role="tab" | |
| 78 | 77 | aria-selected="false" | |
| @@ -83,7 +82,6 @@ | |||
| 83 | 82 | hx-swap="innerHTML" | |
| 84 | 83 | hx-indicator="#tab-spinner" | |
| 85 | 84 | onclick="setActiveTab(this)">Blog</button> | |
| 86 | - | {% endif %} | |
| 87 | 85 | <button class="tab" | |
| 88 | 86 | role="tab" | |
| 89 | 87 | aria-selected="false" |
| @@ -142,34 +142,32 @@ | |||
| 142 | 142 | hx-swap="innerHTML" | |
| 143 | 143 | hx-indicator="#tab-spinner" | |
| 144 | 144 | onclick="setActiveTab(this)">Payments</button> | |
| 145 | + | {% if let Some(su) = session_user %}{% if su.can_create_projects %} | |
| 145 | 146 | <button class="tab" | |
| 146 | 147 | role="tab" | |
| 147 | 148 | aria-selected="false" | |
| 148 | 149 | aria-controls="tab-content" | |
| 149 | - | id="tab-support" | |
| 150 | - | hx-get="/dashboard/tabs/support" | |
| 150 | + | id="tab-analytics" | |
| 151 | + | hx-get="/dashboard/tabs/analytics" | |
| 151 | 152 | hx-target="#tab-content" | |
| 152 | 153 | hx-swap="innerHTML" | |
| 153 | 154 | hx-indicator="#tab-spinner" | |
| 154 | - | onclick="setActiveTab(this)">Support</button> | |
| 155 | + | onclick="setActiveTab(this)">Analytics</button> | |
| 156 | + | {% endif %}{% endif %} | |
| 157 | + | <button class="tab" | |
| 158 | + | role="tab" | |
| 159 | + | aria-selected="false" | |
| 160 | + | aria-controls="tab-content" | |
| 161 | + | id="tab-creator" | |
| 162 | + | hx-get="/dashboard/tabs/creator" | |
| 163 | + | hx-target="#tab-content" | |
| 164 | + | hx-swap="innerHTML" | |
| 165 | + | hx-indicator="#tab-spinner" | |
| 166 | + | onclick="setActiveTab(this)">Creator</button> | |
| 155 | 167 | ||
| 156 | 168 | <div class="tab-overflow" style="position: relative; display: inline-block;"> | |
| 157 | 169 | <button class="tab" onclick="var m=this.nextElementSibling; m.style.display=m.style.display==='block'?'none':'block';" type="button">More</button> | |
| 158 | 170 | <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);"> | |
| 159 | - | {% if let Some(su) = session_user %}{% if su.can_create_projects %} | |
| 160 | - | <button class="tab" style="display: block; width: 100%; text-align: left; padding: 0.5rem 1rem;" | |
| 161 | - | hx-get="/dashboard/tabs/analytics" | |
| 162 | - | hx-target="#tab-content" | |
| 163 | - | hx-swap="innerHTML" | |
| 164 | - | hx-indicator="#tab-spinner" | |
| 165 | - | onclick="setActiveTab(this); this.closest('.tab-overflow-menu').style.display='none';">Analytics</button> | |
| 166 | - | {% endif %}{% endif %} | |
| 167 | - | <button class="tab" style="display: block; width: 100%; text-align: left; padding: 0.5rem 1rem;" | |
| 168 | - | hx-get="/dashboard/tabs/creator" | |
| 169 | - | hx-target="#tab-content" | |
| 170 | - | hx-swap="innerHTML" | |
| 171 | - | hx-indicator="#tab-spinner" | |
| 172 | - | onclick="setActiveTab(this); this.closest('.tab-overflow-menu').style.display='none';">Creator</button> | |
| 173 | 171 | {% if let Some(su) = session_user %}{% if su.can_create_projects && !projects.is_empty() %} | |
| 174 | 172 | <button class="tab" style="display: block; width: 100%; text-align: left; padding: 0.5rem 1rem;" | |
| 175 | 173 | hx-get="/dashboard/tabs/synckit" | |
| @@ -200,6 +198,12 @@ | |||
| 200 | 198 | hx-indicator="#tab-spinner" | |
| 201 | 199 | onclick="setActiveTab(this); this.closest('.tab-overflow-menu').style.display='none';">Forums</button> | |
| 202 | 200 | {% endif %} | |
| 201 | + | <button class="tab" style="display: block; width: 100%; text-align: left; padding: 0.5rem 1rem;" | |
| 202 | + | hx-get="/dashboard/tabs/support" | |
| 203 | + | hx-target="#tab-content" | |
| 204 | + | hx-swap="innerHTML" | |
| 205 | + | hx-indicator="#tab-spinner" | |
| 206 | + | onclick="setActiveTab(this); this.closest('.tab-overflow-menu').style.display='none';">Support</button> | |
| 203 | 207 | </div> | |
| 204 | 208 | </div> | |
| 205 | 209 | <span id="tab-spinner" class="htmx-indicator" style="margin-left: 1rem;" aria-live="polite"> Loading...</span> |
| @@ -38,9 +38,9 @@ | |||
| 38 | 38 | <div class="form-group"> | |
| 39 | 39 | <label for="ai_tier">AI Classification</label> | |
| 40 | 40 | <select id="ai_tier" name="ai_tier"> | |
| 41 | - | <option value="handmade" {% if item.ai_tier == "handmade" %}selected{% endif %}>Handmade</option> | |
| 42 | - | <option value="assisted" {% if item.ai_tier == "assisted" %}selected{% endif %}>Assisted</option> | |
| 43 | - | <option value="generated" {% if item.ai_tier == "generated" %}selected{% endif %}>Generated</option> | |
| 41 | + | <option value="handmade" {% if item.ai_tier == "handmade" %}selected{% endif %}>Handmade — no AI tools used</option> | |
| 42 | + | <option value="assisted" {% if item.ai_tier == "assisted" %}selected{% endif %}>AI-Assisted — AI tools with human creation</option> | |
| 43 | + | <option value="generated" {% if item.ai_tier == "generated" %}selected{% endif %}>AI-Generated — primarily created by AI</option> | |
| 44 | 44 | </select> | |
| 45 | 45 | </div> | |
| 46 | 46 | </div> |
| @@ -7,7 +7,8 @@ | |||
| 7 | 7 | ||
| 8 | 8 | {% if posts.is_empty() %} | |
| 9 | 9 | <div class="empty-state" style="padding: 2rem 0;"> | |
| 10 | - | <p>No blog posts yet. Click "New Post" to create your first post.</p> | |
| 10 | + | <p>No blog posts yet. Start writing to engage your audience.</p> | |
| 11 | + | <p style="opacity: 0.7; font-size: 0.9rem; margin-top: 0.5rem;">Blog posts appear on your project page and in your RSS feed.</p> | |
| 11 | 12 | </div> | |
| 12 | 13 | {% else %} | |
| 13 | 14 | <table class="data-table"> |
| @@ -1,3 +1,13 @@ | |||
| 1 | + | {% if !stripe_connected %} | |
| 2 | + | <div style="margin-bottom: 1.5rem; padding: 1rem 1.25rem; background: var(--surface-muted); border: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.75rem;"> | |
| 3 | + | <div> | |
| 4 | + | <strong>Connect Stripe to sell items</strong> | |
| 5 | + | <span style="opacity: 0.7; margin-left: 0.5rem;">Only 3% processing — no platform fee.</span> | |
| 6 | + | </div> | |
| 7 | + | <a href="/dashboard#tab-payments"><button class="primary small">Connect Stripe</button></a> | |
| 8 | + | </div> | |
| 9 | + | {% endif %} | |
| 10 | + | ||
| 1 | 11 | <div class="tab-docs"><a href="/docs/projects">Docs: Projects →</a></div> | |
| 2 | 12 | ||
| 3 | 13 | <div class="stats-grid"> |
| @@ -10,7 +10,10 @@ | |||
| 10 | 10 | <span class="badge {{ user.stripe_status_class() }}">{{ user.stripe_status_text() }}</span> | |
| 11 | 11 | </div> | |
| 12 | 12 | {% if let Some(account_id) = user.stripe_account_id %} | |
| 13 | - | <div class="text-xs" style="opacity: 0.5; font-family: monospace;">{{ account_id }}</div> | |
| 13 | + | <details style="margin-top: 0.25rem;"> | |
| 14 | + | <summary style="font-size: 0.8rem; opacity: 0.5; cursor: pointer;">Show account ID</summary> | |
| 15 | + | <div class="text-xs" style="opacity: 0.5; font-family: monospace; margin-top: 0.25rem;">{{ account_id }}</div> | |
| 16 | + | </details> | |
| 14 | 17 | {% endif %} | |
| 15 | 18 | </div> | |
| 16 | 19 | {% if !user.stripe_onboarding_complete || !user.stripe_charges_enabled || !user.stripe_payouts_enabled %} |