Skip to main content

max / makenotwork

31.1 KB · 982 lines History Blame Raw
1 //! Templates for HTMX partials: alerts, form status, tab content, tags.
2
3 use std::sync::Arc;
4
5 use askama::Template;
6
7 use crate::db::{DbUserSession, UserSessionId};
8 use crate::types::{
9 AdminAppealRow, AdminHeldUploadRow, AdminReportRow, AdminUserRow, AdminWaitlistRow,
10 BlogPostDashboardRow, BuyerContact, ChartBar, Collection, ContactRow, ContentItem,
11 DiscoverItem, DiscoverProject, InviteCodeDisplay, Item, ItemSection, LicenseKeyRow,
12 PayoutSummary, Project, ProjectCard, ProjectComparison, ProjectMemberRow, PromoCodeRow,
13 SaleRow, SidebarView, StatCard, SubscriptionTier, SyncAppRow, TipReceived, Transaction, User,
14 UserSubscription, Version, WaitlistEntry, WaveStats,
15 };
16
17 use super::CsrfTokenOption;
18
19 // HTMX Partials
20
21 /// HTMX partial for discover page results.
22 #[derive(Template)]
23 #[template(path = "partials/discover_results.html")]
24 pub struct DiscoverResultsTemplate {
25 pub items: Vec<DiscoverItem>,
26 pub projects: Vec<DiscoverProject>,
27 pub mode: String,
28 pub total_items: u32,
29 pub current_page: u32,
30 pub total_pages: u32,
31 pub pagination_range: Vec<u32>,
32 pub showing_start: u32,
33 pub showing_end: u32,
34 /// Currently selected category slug, for HTMX pagination to preserve.
35 pub current_category: String,
36 /// A search term was applied. Drives the empty-state copy.
37 ///
38 /// Not `!search_query.is_empty()`: that is the raw `?q=`, and a
39 /// whitespace-only term is browsing as far as the query is concerned.
40 pub is_search: bool,
41 /// The rendered count line ("247 results", "1 item"). Built once in
42 /// `results_count_label` because the page and the out-of-band partial both
43 /// render `#total-count`; a difference between them would show up as the
44 /// text changing on the first HTMX swap.
45 pub count_label: String,
46 /// Whether the current user is authenticated (for collection save buttons).
47 pub is_authenticated: bool,
48 /// The sidebar, re-rendered for an out-of-band swap. Its counts describe the
49 /// filter that produced these results, so leaving the previous sidebar in
50 /// place would show numbers that disagree with the list beside them.
51 pub sidebar: SidebarView,
52 /// Emit the sidebar as an out-of-band swap. False when the full page
53 /// includes this partial, which renders the sidebar itself; true only for
54 /// the standalone `/discover/results` response.
55 pub oob_sidebar: bool,
56 }
57
58 /// HTMX partial: dismissible alert/notification banner.
59 #[derive(Template)]
60 #[template(path = "partials/alert.html")]
61 pub struct AlertTemplate {
62 pub alert_type: String,
63 pub message: String,
64 pub link_url: Option<String>,
65 pub link_text: Option<String>,
66 }
67
68 impl AlertTemplate {
69 pub fn new(alert_type: &str, message: &str) -> Self {
70 Self {
71 alert_type: alert_type.to_string(),
72 message: message.to_string(),
73 link_url: None,
74 link_text: None,
75 }
76 }
77
78 #[must_use]
79 pub fn with_link(mut self, url: &str, text: &str) -> Self {
80 self.link_url = Some(url.to_string());
81 self.link_text = Some(text.to_string());
82 self
83 }
84 }
85
86 /// HTMX partial: success/error status message after form submission.
87 #[derive(Template)]
88 #[template(path = "partials/form_status.html")]
89 pub struct FormStatusTemplate {
90 pub success: bool,
91 pub message: String,
92 }
93
94 impl FormStatusTemplate {
95 pub fn render_string(&self) -> crate::error::Result<String> {
96 crate::helpers::render_fragment(self)
97 }
98 }
99
100 /// HTMX partial: library action status message.
101 #[derive(Template)]
102 #[template(path = "partials/library_status.html")]
103 pub struct LibraryStatusTemplate {
104 pub message: String,
105 }
106
107 /// HTMX partial: data-URI download link for an export file.
108 #[derive(Template)]
109 #[template(path = "partials/export_download.html")]
110 pub struct ExportDownloadTemplate {
111 pub data_uri: String,
112 pub filename: String,
113 }
114
115 /// HTMX partial: download button shown when a content export is ready.
116 #[derive(Template)]
117 #[template(path = "partials/export_content_ready.html")]
118 pub struct ExportContentReadyTemplate {
119 pub download_url: String,
120 }
121
122 /// HTMX partial: inline login error message.
123 #[derive(Template)]
124 #[template(path = "partials/login_error.html")]
125 pub struct LoginErrorTemplate {
126 pub message: String,
127 }
128
129 impl LoginErrorTemplate {
130 pub fn render_string(&self) -> crate::error::Result<String> {
131 crate::helpers::render_fragment(self)
132 }
133 }
134
135 /// HTMX partial: username availability check result.
136 #[derive(Template)]
137 #[template(path = "partials/username_status.html")]
138 pub struct UsernameStatusTemplate {
139 pub available: bool,
140 }
141
142 impl UsernameStatusTemplate {
143 pub fn render_string(&self) -> crate::error::Result<String> {
144 crate::helpers::render_fragment(self)
145 }
146 }
147
148 /// HTMX partial: project slug availability check result.
149 #[derive(Template)]
150 #[template(path = "partials/slug_status.html")]
151 pub struct SlugStatusTemplate {
152 pub available: bool,
153 pub suggestions: Vec<String>,
154 }
155
156 impl SlugStatusTemplate {
157 pub fn render_string(&self) -> crate::error::Result<String> {
158 crate::helpers::render_fragment(self)
159 }
160 }
161
162 /// HTMX partial: inline save confirmation or error indicator.
163 #[derive(Template)]
164 #[template(path = "partials/save_status.html")]
165 pub struct SaveStatusTemplate {
166 pub success: bool,
167 pub message: String,
168 }
169
170 impl SaveStatusTemplate {
171 pub fn render_string(&self) -> crate::error::Result<String> {
172 crate::helpers::render_fragment(self)
173 }
174 }
175
176 /// HTMX partial: paginated transaction history table.
177 #[derive(Template)]
178 #[template(path = "partials/transactions_table.html")]
179 pub struct TransactionsTableTemplate {
180 pub transactions: Vec<Transaction>,
181 }
182
183 // Dashboard Tab Partials
184
185 /// Dashboard tab: public identity, links, bio, domain, feed.
186 #[derive(Template)]
187 #[template(path = "partials/tabs/user_profile.html")]
188 pub struct UserProfileTabTemplate {
189 pub user: User,
190 pub custom_links: Vec<CustomLinkWithId>,
191 /// HMAC-signed personal RSS feed URL for this user.
192 pub feed_url: String,
193 /// Whether this user has creator access (controls custom domain visibility).
194 pub can_create_projects: bool,
195 /// The user's custom domain, if configured.
196 pub custom_domain: Option<CustomDomainInfo>,
197 /// Built-in theme choices for the profile theme picker (Tier 0).
198 pub theme_options: Vec<crate::theming::ThemeOption>,
199 }
200
201 /// Dashboard settings meta-tab with sub-navigation (profile, account, plan, etc.)
202 #[derive(Template)]
203 #[template(path = "partials/tabs/user_settings.html")]
204 pub struct UserSettingsTabTemplate {
205 pub user: User,
206 pub custom_links: Vec<CustomLinkWithId>,
207 pub feed_url: String,
208 pub can_create_projects: bool,
209 pub custom_domain: Option<CustomDomainInfo>,
210 pub has_media: bool,
211 pub git_enabled: bool,
212 pub has_mt_memberships: bool,
213 /// Built-in theme choices for the profile theme picker (Tier 0). Shared with
214 /// the included `user_profile.html` partial.
215 pub theme_options: Vec<crate::theming::ThemeOption>,
216 }
217
218 /// Dashboard tab: account mechanics, security, sessions, notifications, data.
219 #[derive(Template)]
220 #[template(path = "partials/tabs/user_account.html")]
221 pub struct UserAccountTabTemplate {
222 pub user: User,
223 pub sessions: Vec<DbUserSession>,
224 pub current_session_id: Option<UserSessionId>,
225 /// Whether this user has creator access (controls creator-specific prefs).
226 pub can_create_projects: bool,
227 /// Whether the user's email address has been verified.
228 pub email_verified: bool,
229 /// Active (unresolved) moderation actions for the "Account Status" section.
230 pub moderation_active: Vec<ModerationActionView>,
231 /// Resolved moderation history (collapsed by default).
232 pub moderation_history: Vec<ModerationActionView>,
233 /// Whether this creator has voluntarily paused their account.
234 pub creator_paused: bool,
235 /// Compact Fan+ pane state for the account tab. `None` = the user is not
236 /// a Fan+ subscriber; the tab renders a one-line "Support the platform"
237 /// link instead of the active pane.
238 pub fan_plus: Option<FanPlusPaneView>,
239 /// CSRF token for the Fan+ cancel/resume/billing-portal form posts. The
240 /// rest of the tab uses HTMX (which sends `X-CSRF-Token` automatically),
241 /// but these are vanilla form POSTs that redirect.
242 pub csrf_token: super::CsrfTokenOption,
243 }
244
245 /// Compact dashboard view of the user's Fan+ subscription. Lives under the
246 /// account tab; intentionally small, no upsell copy.
247 pub struct FanPlusPaneView {
248 /// Current period end as a formatted date (e.g., "Dec 14, 2026"). `None`
249 /// when Stripe hasn't reported a period yet (rare; just after checkout).
250 pub period_end: Option<String>,
251 /// Subscription is scheduled to cancel at `period_end`. Drives the Resume
252 /// affordance.
253 pub cancel_at_period_end: bool,
254 }
255
256 /// View model for a moderation action displayed on the settings page.
257 pub struct ModerationActionView {
258 /// Human-readable label (e.g., "Warning", "Content Removal", "Suspension")
259 pub action_label: String,
260 pub reason: String,
261 pub created_at: String,
262 pub resolved_at: Option<String>,
263 }
264
265 /// Custom domain info for dashboard display.
266 pub struct CustomDomainInfo {
267 pub id: String,
268 pub domain: String,
269 pub verified: bool,
270 pub verification_token: String,
271 pub instructions: String,
272 }
273
274 /// Custom link with ID for dashboard editing
275 #[derive(Clone)]
276 pub struct CustomLinkWithId {
277 pub id: String,
278 pub url: String,
279 pub title: String,
280 }
281
282 /// Dashboard tab: payment history, payouts, tips, and revenue splits.
283 #[derive(Template)]
284 #[template(path = "partials/tabs/user_payments.html")]
285 pub struct UserPaymentsTabTemplate {
286 pub user: User,
287 pub transactions: Vec<Transaction>,
288 pub tips_received: Vec<TipReceived>,
289 pub tips_total: String,
290 pub tips_count: i64,
291 /// Revenue owed to you from other creators' projects (as a collaborator).
292 pub splits_incoming_total: String,
293 pub splits_incoming_count: i64,
294 /// Revenue you owe to collaborators on your projects.
295 pub splits_outgoing_total: String,
296 /// Whether this user has creator access (controls seller section visibility).
297 pub can_create_projects: bool,
298 }
299
300 /// Dashboard tab: user's projects list with create button.
301 #[derive(Template)]
302 #[template(path = "partials/tabs/user_projects.html")]
303 pub struct UserProjectsTabTemplate {
304 pub projects: Vec<ProjectCard>,
305 pub can_create_projects: bool,
306 }
307
308 /// Creator tab in the user dashboard showing invite/waitlist status.
309 #[derive(Template)]
310 #[template(path = "partials/tabs/user_creator.html")]
311 #[allow(dead_code)] // Fields used by Askama template
312 pub struct UserCreatorTabTemplate {
313 pub csrf_token: CsrfTokenOption,
314 /// Whether this user has been granted creator privileges (can publish content).
315 pub can_create_projects: bool,
316 /// Whether the user's email is verified (required before joining waitlist).
317 pub email_verified: bool,
318 /// Number of unique followers who would receive a broadcast email.
319 pub follower_count: i64,
320 /// The user's waitlist entry, if they have applied for creator access.
321 pub waitlist_entry: Option<WaitlistEntry>,
322 /// Whether the invite system is enabled.
323 pub invites_enabled: bool,
324 /// Number of active (unredeemed) invite codes this creator has.
325 pub active_invite_count: i64,
326 /// Maximum number of unredeemed invite codes per creator.
327 pub invite_limit: i64,
328 /// Creator's invite codes for display.
329 pub invite_codes: Vec<InviteCodeDisplay>,
330 /// Total number of users with creator access.
331 pub total_creators: u32,
332 /// Number of waitlist entries still pending review.
333 pub waitlist_pending: u32,
334 /// Wave history for non-creator informational display.
335 pub waves: Vec<WaveStats>,
336 /// The creator's current tier label (e.g. "Basic", "Small Files"), if subscribed.
337 pub creator_tier_label: Option<String>,
338 /// The creator's subscription period end date (human-readable), if subscribed.
339 pub creator_period_end: Option<String>,
340 /// The creator's subscription status (e.g. Active, PastDue), if subscribed.
341 pub creator_sub_status: Option<crate::db::SubscriptionStatus>,
342 /// Whether creator tier Stripe checkout is configured.
343 pub creator_tiers_configured: bool,
344 /// Storage usage breakdown (audio, covers, downloads, insertions, video, media, gallery).
345 pub storage_audio: String,
346 pub storage_covers: String,
347 pub storage_downloads: String,
348 pub storage_insertions: String,
349 pub storage_video: String,
350 pub storage_media: String,
351 pub storage_gallery: String,
352 pub storage_total: String,
353 pub storage_max: String,
354 /// Storage usage percentage (0-100) for the progress bar.
355 pub storage_pct: u8,
356 /// Whether the founder pricing window is currently open. Used to badge
357 /// in-progress founders ("Founder pricing, locked in when the window
358 /// closes") before the close sweep stamps `founder_locked_at`.
359 pub founder_window_open: bool,
360 /// Whether this user holds an unsweep'd founder flag (subscribed during
361 /// the window, status not yet finalized by the close sweep).
362 pub is_founder: bool,
363 /// Whether this user's founder pricing is permanently locked in.
364 pub is_founder_locked: bool,
365 /// Tier cards rendered in the upgrade grid. Built from `state.tier_prices`
366 /// so a price change in `assumptions.toml` flows through automatically.
367 pub tier_cards: Vec<crate::tier_prices::TierCard>,
368 }
369
370 /// Dashboard tab: project overview with stat cards.
371 #[derive(Template)]
372 #[template(path = "partials/tabs/project_overview.html")]
373 pub struct ProjectOverviewTabTemplate {
374 pub stats: Vec<StatCard>,
375 pub project_slug: String,
376 pub stripe_connected: bool,
377 pub has_items: bool,
378 pub has_published_item: bool,
379 }
380
381 /// A soft-deleted item for the "Recently Deleted" section.
382 pub struct DeletedItemRow {
383 pub id: String,
384 pub title: String,
385 pub deleted_at: String,
386 }
387
388 /// Dashboard tab: project content items list.
389 #[derive(Template)]
390 #[template(path = "partials/tabs/project_content.html")]
391 pub struct ProjectContentTabTemplate {
392 pub items: Vec<ContentItem>,
393 pub deleted_items: Vec<DeletedItemRow>,
394 pub project_slug: String,
395 pub project_id: String,
396 pub posts: Vec<BlogPostDashboardRow>,
397 }
398
399 /// Dashboard tab: project analytics with stats, chart, and top items.
400 #[derive(Template)]
401 #[template(path = "partials/tabs/project_analytics.html")]
402 pub struct ProjectAnalyticsTabTemplate {
403 pub stats: Vec<StatCard>,
404 pub bars: Vec<ChartBar>,
405 pub items: Vec<ContentItem>,
406 pub project_slug: String,
407 pub active_range: String,
408 }
409
410 /// Dashboard tab: project settings, categories, labels, and features.
411 #[derive(Template)]
412 #[template(path = "partials/tabs/project_settings.html")]
413 pub struct ProjectSettingsTabTemplate {
414 pub project: Project,
415 /// Current category name for pre-populating the form, or empty.
416 pub category_name: String,
417 /// Project ID as string for HTMX targets.
418 pub project_id: String,
419 /// Active features on this project.
420 pub features: Vec<String>,
421 /// All available features as (value, label, description) tuples.
422 pub project_features: &'static [(&'static str, &'static str, &'static str)],
423 /// Tabbed markdown sections (privacy policy, terms, FAQ, etc).
424 pub sections: Vec<crate::db::DbProjectSection>,
425 /// Current pricing model as kebab string ("free", "buy_once", "pwyw", "subscription").
426 pub pricing_model: String,
427 /// Current buy-once price in dollars (formatted), empty if not set.
428 pub price_dollars: String,
429 /// Current PWYW minimum in dollars (formatted), empty if not set.
430 pub pwyw_min_dollars: String,
431 /// Built-in theme choices for the project theme picker (Tier 0).
432 pub theme_options: Vec<crate::theming::ThemeOption>,
433 }
434
435 /// Dashboard code tab partial (git repos management).
436 #[derive(Template)]
437 #[template(path = "partials/tabs/project_code.html")]
438 pub struct ProjectCodeTabTemplate {
439 pub project: Project,
440 pub git_enabled: bool,
441 pub linked_repos: Vec<LinkedRepoView>,
442 pub available_repos: Vec<crate::db::DbGitRepo>,
443 pub project_id: String,
444 }
445
446 /// Dashboard tab: SyncKit apps for a specific project.
447 #[derive(Template)]
448 #[template(path = "partials/tabs/project_synckit.html")]
449 pub struct ProjectSyncKitTabTemplate {
450 pub apps: Vec<SyncAppRow>,
451 pub project_id: String,
452 }
453
454 /// A linked repo with its collaborators, for the Code tab.
455 pub struct LinkedRepoView {
456 pub id: String,
457 pub name: String,
458 pub collaborators: Vec<RepoCollaboratorView>,
459 }
460
461 /// View model for a repo collaborator in the Code tab.
462 pub struct RepoCollaboratorView {
463 pub user_id: String,
464 pub username: String,
465 pub can_push: bool,
466 }
467
468 /// Dashboard blog tab partial.
469 #[derive(Template)]
470 #[template(path = "partials/tabs/project_blog.html")]
471 pub struct ProjectBlogTabTemplate {
472 pub project_id: String,
473 pub project_slug: String,
474 pub posts: Vec<BlogPostDashboardRow>,
475 }
476
477 /// Dashboard subscriptions tab partial for tier management.
478 #[derive(Template)]
479 #[template(path = "partials/tabs/project_subscriptions.html")]
480 #[allow(dead_code)] // Fields used by Askama template
481 pub struct ProjectSubscriptionsTabTemplate {
482 pub project_id: String,
483 pub project_slug: String,
484 pub tiers: Vec<SubscriptionTier>,
485 pub subscriber_count: i64,
486 pub stripe_connected: bool,
487 }
488
489 /// Dashboard members tab partial for managing project members and revenue splits.
490 #[derive(Template)]
491 #[template(path = "partials/tabs/project_members.html")]
492 #[allow(dead_code)]
493 pub struct ProjectMembersTabTemplate {
494 pub project_id: String,
495 pub project_slug: String,
496 pub members: Vec<ProjectMemberRow>,
497 pub owner_split: i64,
498 }
499
500 /// Combined monetization tab: tiers, promo codes, and team splits.
501 #[derive(Template)]
502 #[template(path = "partials/tabs/project_monetization.html")]
503 pub struct ProjectMonetizationTabTemplate {
504 pub project_id: String,
505 pub project_slug: String,
506 pub tiers: Vec<SubscriptionTier>,
507 pub subscriber_count: i64,
508 pub stripe_connected: bool,
509 pub promo_codes: Vec<crate::types::PromoCodeRow>,
510 pub items: Vec<ContentItem>,
511 pub members: Vec<ProjectMemberRow>,
512 pub owner_split: i64,
513 }
514
515 /// SyncKit tab in the user dashboard for managing sync apps.
516 #[derive(Template)]
517 #[template(path = "partials/tabs/user_synckit.html")]
518 pub struct UserSyncKitTabTemplate {
519 pub apps: Vec<SyncAppRow>,
520 pub projects: Vec<ProjectCard>,
521 }
522
523 /// Row in the Forums tab showing a community membership.
524 pub struct ForumMembership {
525 pub community_name: String,
526 pub profile_url: String,
527 pub role: String,
528 pub joined: String,
529 pub post_count: i64,
530 }
531
532 /// Forums tab in the user dashboard, lists MT community memberships.
533 #[derive(Template)]
534 #[template(path = "partials/tabs/user_forums.html")]
535 pub struct UserForumsTabTemplate {
536 pub memberships: Vec<ForumMembership>,
537 pub mt_base_url: String,
538 }
539
540 /// A media file row for the Media tab.
541 pub struct MediaFileRow {
542 pub id: String,
543 pub folder: String,
544 pub filename: String,
545 pub content_type: String,
546 pub file_size: String,
547 pub media_type: String,
548 pub cdn_url: String,
549 pub markdown_ref: String,
550 pub created_at: String,
551 }
552
553 /// Media tab in the user dashboard, media library with folders.
554 #[derive(Template)]
555 #[template(path = "partials/tabs/user_media.html")]
556 pub struct UserMediaTabTemplate {
557 pub files: Vec<MediaFileRow>,
558 pub folders: Vec<String>,
559 pub storage_display: String,
560 }
561
562 /// Support tab in the user dashboard, submit a support ticket.
563 #[derive(Template)]
564 #[template(path = "partials/tabs/user_support.html")]
565 pub struct UserSupportTabTemplate {
566 pub email: String,
567 }
568
569 // Library Tab Partials
570
571 /// Library purchases tab.
572 #[derive(Template)]
573 #[template(path = "partials/tabs/library_purchases.html")]
574 pub struct LibraryPurchasesTabTemplate {
575 pub purchases: Vec<crate::db::DbPurchaseRow>,
576 pub subscriptions: Vec<UserSubscription>,
577 }
578
579 /// Library collections tab.
580 #[derive(Template)]
581 #[template(path = "partials/tabs/library_collections.html")]
582 pub struct LibraryCollectionsTabTemplate {
583 pub collections: Vec<Collection>,
584 pub username: String,
585 pub wishlists: Vec<crate::db::wishlists::WishlistItem>,
586 }
587
588 /// Library contacts tab.
589 #[derive(Template)]
590 #[template(path = "partials/tabs/library_contacts.html")]
591 pub struct LibraryContactsTabTemplate {
592 pub shared_creators: Vec<crate::db::transactions::SharedCreatorRow>,
593 pub buyer_contacts: Vec<ContactRow>,
594 pub total_buyer_contacts: usize,
595 }
596
597 /// Library feed tab (items from followed users, projects, and tags).
598 #[derive(Template)]
599 #[template(path = "partials/tabs/library_feed.html")]
600 pub struct LibraryFeedTabTemplate {
601 pub items: Vec<DiscoverItem>,
602 pub total_items: u32,
603 pub current_page: u32,
604 pub total_pages: u32,
605 pub pagination_range: Vec<u32>,
606 pub showing_start: u32,
607 pub showing_end: u32,
608 }
609
610 /// Library communities tab (Multithreaded forum memberships).
611 #[derive(Template)]
612 #[template(path = "partials/tabs/library_communities.html")]
613 pub struct LibraryCommunitiesTabTemplate {
614 pub memberships: Vec<ForumMembership>,
615 pub mt_base_url: String,
616 }
617
618 /// Per-project revenue for the user analytics top projects list.
619 pub struct ProjectRevenue {
620 pub title: String,
621 pub revenue: String,
622 }
623
624 /// User-level analytics tab (aggregated across all projects).
625 #[derive(Template)]
626 #[template(path = "partials/tabs/user_analytics.html")]
627 pub struct UserAnalyticsTabTemplate {
628 pub stats: Vec<StatCard>,
629 pub bars: Vec<ChartBar>,
630 pub top_projects: Vec<ProjectRevenue>,
631 pub active_range: String,
632 pub project_comparisons: Vec<ProjectComparison>,
633 }
634
635 /// Buyer contacts section, HTMX-loaded into the Payments tab.
636 #[derive(Template)]
637 #[template(path = "partials/tabs/buyer_contacts.html")]
638 pub struct BuyerContactsPartialTemplate {
639 pub contacts: Vec<BuyerContact>,
640 }
641
642 /// Stripe payout-summary card, HTMX-loaded into the Payments tab so the tab
643 /// render never blocks on the Stripe balance round-trip (ultra-fuzz Run 11 Perf SER-2).
644 #[derive(Template)]
645 #[template(path = "partials/tabs/payout_summary.html")]
646 pub struct PayoutSummaryPartialTemplate {
647 pub payout_summary: Option<PayoutSummary>,
648 pub stripe_payouts_enabled: bool,
649 }
650
651 /// SSH keys list partial for HTMX updates.
652 #[derive(Template)]
653 #[template(path = "partials/ssh_keys_list.html")]
654 pub struct SshKeysListTemplate {
655 pub ssh_keys: Vec<crate::routes::api::ssh_keys::SshKeyView>,
656 }
657
658 /// Dashboard tab: SSH key management for git access.
659 #[derive(Template)]
660 #[template(path = "partials/tabs/user_ssh_keys_tab.html")]
661 pub struct UserSshKeysTabTemplate {
662 pub username: String,
663 }
664
665 /// Git access-token list partial for HTMX updates. `new_token` carries a
666 /// freshly-minted plaintext to show once (set only in the create response).
667 #[derive(Template)]
668 #[template(path = "partials/git_tokens_list.html")]
669 pub struct GitTokensListTemplate {
670 pub tokens: Vec<crate::routes::api::git_tokens::GitTokenView>,
671 pub new_token: Option<String>,
672 }
673
674 /// Sessions list partial, used by session revocation API responses (HTMX swap into `#sessions-list`).
675 #[derive(Template)]
676 #[template(path = "partials/tabs/user_sessions.html")]
677 pub struct UserSessionsPartialTemplate {
678 pub sessions: Vec<DbUserSession>,
679 pub current_session_id: Option<UserSessionId>,
680 }
681
682 /// HTMX partial: single removable tag pill on an item.
683 #[derive(Template)]
684 #[template(path = "partials/tag.html")]
685 pub struct TagTemplate {
686 pub item_id: String,
687 pub tag_id: String,
688 pub tag_name: String,
689 pub is_primary: bool,
690 }
691
692 impl TagTemplate {
693 pub fn render_string(&self) -> crate::error::Result<String> {
694 crate::helpers::render_fragment(self)
695 }
696 }
697
698 /// HTMX partial: editable content item row in the project dashboard.
699 #[derive(Template)]
700 #[template(path = "partials/item_edit_row.html")]
701 pub struct ItemEditRowTemplate {
702 pub item: ContentItem,
703 }
704
705 /// HTMX partial: editable custom link row in the user details tab.
706 #[derive(Template)]
707 #[template(path = "partials/link_row.html")]
708 pub struct LinkRowTemplate {
709 pub id: String,
710 pub title: String,
711 pub url: String,
712 }
713
714 impl LinkRowTemplate {
715 pub fn render_string(&self) -> crate::error::Result<String> {
716 crate::helpers::render_fragment(self)
717 }
718 }
719
720 // Project Labels Partial
721
722 // Admin Partials
723
724 /// Admin HTMX partial: creator waitlist entries table.
725 #[derive(Template)]
726 #[template(path = "partials/admin_waitlist_entries.html")]
727 pub struct AdminWaitlistEntriesTemplate {
728 pub entries: Vec<AdminWaitlistRow>,
729 }
730
731 /// Admin user table rows (HTMX partial).
732 #[derive(Template)]
733 #[template(path = "partials/admin_user_entries.html")]
734 pub struct AdminUserEntriesTemplate {
735 pub users: Vec<AdminUserRow>,
736 pub current_page: i64,
737 pub total_pages: i64,
738 pub current_filter: String,
739 }
740
741 /// Admin upload review entries (HTMX partial).
742 #[derive(Template)]
743 #[template(path = "partials/admin_upload_entries.html")]
744 pub struct AdminUploadEntriesTemplate {
745 pub held_uploads: Vec<AdminHeldUploadRow>,
746 }
747
748 /// Active-queue counts partial, refreshed by the admin dashboard every 10s.
749 #[derive(Template)]
750 #[template(path = "partials/admin_queue_summary.html")]
751 pub struct AdminQueueSummaryTemplate {
752 pub queue_pending: i64,
753 pub queue_running: i64,
754 }
755
756 /// Admin appeal rows (HTMX partial).
757 #[derive(Template)]
758 #[template(path = "partials/admin_appeal_entries.html")]
759 pub struct AdminAppealEntriesTemplate {
760 pub appeals: Vec<AdminAppealRow>,
761 }
762
763 /// Admin report entries (HTMX partial).
764 #[derive(Template)]
765 #[template(path = "partials/admin_report_entries.html")]
766 pub struct AdminReportEntriesTemplate {
767 pub reports: Vec<AdminReportRow>,
768 }
769
770 /// Suspension banner shown on dashboard for suspended users.
771 #[derive(Template)]
772 #[template(path = "partials/suspension_banner.html")]
773 pub struct SuspensionBannerTemplate {
774 pub reason: String,
775 pub has_pending_appeal: bool,
776 pub appeal_decision: Option<String>,
777 pub appeal_response: Option<String>,
778 }
779
780 // Promo Code Partials
781
782 /// Promo codes table partial (shared by project promotions tab, item dashboard, and API responses).
783 #[derive(Template)]
784 #[template(path = "partials/promo_codes_list.html")]
785 pub struct PromoCodesListTemplate {
786 pub promo_codes: Vec<crate::types::PromoCodeRow>,
787 }
788
789 /// Promotions tab content for the project dashboard.
790 #[derive(Template)]
791 #[template(path = "partials/tabs/project_promotions.html")]
792 pub struct ProjectPromotionsTabTemplate {
793 pub project_id: String,
794 pub project_slug: String,
795 pub promo_codes: Vec<crate::types::PromoCodeRow>,
796 pub items: Vec<ContentItem>,
797 }
798
799 // License Key Partials
800
801 /// License keys table for the item dashboard.
802 #[derive(Template)]
803 #[template(path = "partials/item_license_keys.html")]
804 pub struct ItemLicenseKeysTemplate {
805 pub license_keys: Vec<crate::types::LicenseKeyRow>,
806 }
807
808 // Follow Button Partial
809
810 /// HTMX follow/unfollow toggle button.
811 #[derive(Template)]
812 #[template(path = "partials/follow_button.html")]
813 pub struct FollowButtonTemplate {
814 pub target_type: String,
815 pub target_id: String,
816 pub is_following: bool,
817 pub follower_count: i64,
818 }
819
820 // Tag Follow Toggle (compact, for discover sidebar)
821
822 /// Compact follow/unfollow toggle for tags in the discover sidebar.
823 #[derive(Template)]
824 #[template(path = "partials/tag_follow_toggle.html")]
825 pub struct TagFollowToggleTemplate {
826 pub tag_id: String,
827 pub is_following: bool,
828 }
829
830 // TOTP 2FA Partials
831
832 /// TOTP setup partial: QR code, manual key, backup codes, and confirmation form.
833 #[derive(Template)]
834 #[template(path = "partials/totp_setup.html")]
835 pub struct TotpSetupTemplate {
836 pub qr_base64: String,
837 pub secret_base32: String,
838 pub backup_codes: Vec<String>,
839 }
840
841 /// TOTP status partial: enabled/disabled state with action buttons.
842 #[derive(Template)]
843 #[template(path = "partials/totp_status.html")]
844 pub struct TotpStatusTemplate {
845 pub enabled: bool,
846 }
847
848 // Passkey Partials
849
850 /// A registered passkey for dashboard display.
851 pub struct PasskeyDisplay {
852 pub id: String,
853 pub name: String,
854 pub created_at: String,
855 pub last_used_at: Option<String>,
856 }
857
858 /// Passkey list partial for the dashboard settings tab.
859 #[derive(Template)]
860 #[template(path = "partials/passkey_list.html")]
861 pub struct PasskeyListTemplate {
862 pub passkeys: Vec<PasskeyDisplay>,
863 }
864
865 // Tag Suggestions
866
867 /// A suggested tag for an item, based on metadata matching.
868 #[derive(Clone)]
869 pub struct TagSuggestion {
870 pub id: String,
871 pub name: String,
872 }
873
874 /// Auto-suggested tags for the item dashboard.
875 #[derive(Template)]
876 #[template(path = "partials/tag_suggestions.html")]
877 pub struct TagSuggestionsTemplate {
878 pub suggestions: Vec<TagSuggestion>,
879 }
880
881 // Content Insertion Partials
882
883 /// Item-level analytics partial (stats + revenue chart).
884 #[derive(Template)]
885 #[template(path = "partials/item_analytics.html")]
886 pub struct ItemAnalyticsPartialTemplate {
887 pub stats: Vec<StatCard>,
888 pub bars: Vec<ChartBar>,
889 pub item_id: String,
890 pub active_range: String,
891 }
892
893 /// An insertion clip for display in the dashboard.
894 #[derive(Clone)]
895 pub struct InsertionDisplay {
896 pub id: String,
897 pub title: String,
898 pub media_type: String,
899 pub duration_display: String,
900 pub created_at: String,
901 }
902
903 /// Creator's insertion clip library list.
904 #[derive(Template)]
905 #[template(path = "partials/insertion_list.html")]
906 pub struct InsertionListTemplate {
907 pub insertions: Vec<InsertionDisplay>,
908 }
909
910 /// A placement for display in the item dashboard.
911 #[derive(Clone)]
912 pub struct PlacementDisplay {
913 pub id: String,
914 pub insertion_title: String,
915 pub position: String,
916 pub offset_display: Option<String>,
917 pub sort_order: i32,
918 }
919
920 /// Per-item placement management list.
921 #[derive(Template)]
922 #[template(path = "partials/placement_list.html")]
923 pub struct PlacementListTemplate {
924 pub item_id: String,
925 pub placements: Vec<PlacementDisplay>,
926 pub available_insertions: Vec<InsertionDisplay>,
927 }
928
929 // Item Dashboard Tab Partials
930
931 /// Item overview tab: quick actions + analytics (lazy-loaded).
932 #[derive(Template)]
933 #[template(path = "partials/tabs/item_overview.html")]
934 pub struct ItemOverviewTabTemplate {
935 pub item: Item,
936 }
937
938 /// Item details tab: name, description, tags, content editor, bundle contents, sections.
939 #[derive(Template)]
940 #[template(path = "partials/tabs/item_details.html")]
941 pub struct ItemDetailsTabTemplate {
942 pub item: Item,
943 pub bundle_items: Vec<Item>,
944 pub bundleable_items: Vec<Item>,
945 pub sections: Vec<ItemSection>,
946 }
947
948 /// Item pricing tab: PWYW settings, license keys, promo codes.
949 #[derive(Template)]
950 #[template(path = "partials/tabs/item_pricing.html")]
951 pub struct ItemPricingTabTemplate {
952 pub item: Item,
953 pub license_keys: Vec<LicenseKeyRow>,
954 pub promo_codes: Vec<PromoCodeRow>,
955 pub license_preset_options: Vec<(&'static str, &'static str)>,
956 }
957
958 /// Item files tab: version upload + download table.
959 #[derive(Template)]
960 #[template(path = "partials/tabs/item_files.html")]
961 pub struct ItemFilesTabTemplate {
962 pub item: Item,
963 pub versions: Vec<Version>,
964 }
965
966 /// Item sales tab: transaction history with refund actions.
967 #[derive(Template)]
968 #[template(path = "partials/tabs/item_sales.html")]
969 pub struct ItemSalesTabTemplate {
970 pub item: Item,
971 pub sales: Vec<SaleRow>,
972 }
973
974 /// Item embed tab: copy-paste embed codes for this item.
975 #[derive(Template)]
976 #[template(path = "partials/tabs/item_embed.html")]
977 pub struct ItemEmbedTabTemplate {
978 pub item: Item,
979 pub host_url: Arc<str>,
980 pub is_audio: bool,
981 }
982