Skip to main content

max / makenotwork

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