Skip to main content

max / makenotwork

16.4 KB · 515 lines History Blame Raw
1 //! Templates for creator dashboards, admin panels, and account management.
2
3 use askama::Template;
4
5 use crate::auth::SessionUser;
6 use crate::types::{
7 AdminAppealRow, AdminAuditLogRow, AdminCompCodeRow, AdminHeldUploadRow, AdminReportRow,
8 AdminSignupRow, AdminUserRow, AdminWaitlistRow, ContentItem, Item, OnboardingChecklist,
9 Project, ProjectCard, ReportStats, ScanHistoryDisplay, StatCard, Transaction, User,
10 WaitlistStats,
11 };
12
13 use super::CsrfTokenOption;
14
15 // Dashboard Pages
16
17 /// Main user dashboard page with tabbed navigation.
18 #[derive(Template)]
19 #[template(path = "dashboards/dashboard-user.html")]
20 #[allow(dead_code)] // Fields used by Askama template
21 pub struct DashboardUserTemplate {
22 pub csrf_token: CsrfTokenOption,
23 pub session_user: Option<SessionUser>,
24 pub user: User,
25 pub transactions: Vec<Transaction>,
26 /// Used to conditionally show the first-run onboarding banner.
27 pub projects: Vec<ProjectCard>,
28 /// Onboarding checklist for new creators (None once all steps complete).
29 pub onboarding: Option<OnboardingChecklist>,
30 /// Show "Show setup checklist" link when checklist was dismissed but steps remain.
31 pub show_checklist_recovery: bool,
32 /// Whether user has MT forum memberships (controls Forums tab visibility).
33 pub has_mt_memberships: bool,
34 // Suspension context (for banner)
35 pub suspended: bool,
36 pub suspension_reason: Option<String>,
37 pub has_pending_appeal: bool,
38 pub appeal_decision: Option<String>,
39 pub appeal_response: Option<String>,
40 /// One-time warning when a breached password is detected (cleared after display).
41 pub password_warning: Option<String>,
42 /// Whether the user has self-deactivated their account (limbo state).
43 pub deactivated: bool,
44 /// Whether this creator has voluntarily paused their account.
45 pub creator_paused: bool,
46 /// Whether git hosting is configured on this server (controls SSH Keys tab visibility).
47 pub git_enabled: bool,
48 }
49
50 /// Project dashboard page with stats, content list, and management tabs.
51 #[derive(Template)]
52 #[template(path = "dashboards/dashboard-project.html")]
53 #[allow(dead_code)] // Fields used by Askama template
54 pub struct DashboardProjectTemplate {
55 pub csrf_token: CsrfTokenOption,
56 pub session_user: Option<SessionUser>,
57 pub project: Project,
58 pub creator_username: String,
59 pub stats: Vec<StatCard>,
60 pub items: Vec<ContentItem>,
61 /// Whether this creator has connected their Stripe account (controls Subscriptions tab visibility).
62 pub stripe_connected: bool,
63 /// Whether this project has the "blog" feature enabled (controls Blog tab visibility).
64 pub has_blog: bool,
65 /// Whether git hosting is configured on this server (controls Code tab visibility).
66 pub git_enabled: bool,
67 /// Whether this project has the "cloud_sync" feature enabled (controls Cloud Sync tab visibility).
68 pub synckit_enabled: bool,
69 }
70
71 /// Item management dashboard shell with tabbed navigation (content loaded via HTMX).
72 #[derive(Template)]
73 #[template(path = "dashboards/dashboard-item.html")]
74 pub struct DashboardItemTemplate {
75 pub csrf_token: CsrfTokenOption,
76 pub session_user: Option<SessionUser>,
77 pub item: Item,
78 pub project_title: String,
79 pub project_slug: String,
80 }
81
82 // Admin
83
84 /// Admin waitlist management page with filtering and invite controls.
85 #[derive(Template)]
86 #[template(path = "dashboards/admin-waitlist.html")]
87 pub struct AdminWaitlistTemplate {
88 pub csrf_token: CsrfTokenOption,
89 pub session_user: Option<SessionUser>,
90 pub stats: WaitlistStats,
91 pub entries: Vec<AdminWaitlistRow>,
92 pub current_filter: String,
93 pub admin_active_page: &'static str,
94 }
95
96 /// Admin user management page.
97 #[derive(Template)]
98 #[template(path = "dashboards/admin-users.html")]
99 pub struct AdminUsersTemplate {
100 pub csrf_token: CsrfTokenOption,
101 pub session_user: Option<SessionUser>,
102 pub users: Vec<AdminUserRow>,
103 pub total_users: usize,
104 pub total_suspended: usize,
105 pub current_filter: String,
106 pub current_page: i64,
107 pub total_pages: i64,
108 pub admin_active_page: &'static str,
109 }
110
111 /// Per-layer health stat displayed in the dashboard's top panel.
112 #[derive(Clone)]
113 pub struct LayerHealthCard {
114 pub layer: String,
115 pub total: i64,
116 pub success_rate_pct: i32,
117 pub error_rate_pct: i32,
118 pub fail_count: i64,
119 pub status_badge: &'static str,
120 pub last_seen: String,
121 }
122
123 /// Admin upload review queue page.
124 #[derive(Template)]
125 #[template(path = "dashboards/admin-uploads.html")]
126 pub struct AdminUploadsTemplate {
127 pub csrf_token: CsrfTokenOption,
128 pub session_user: Option<SessionUser>,
129 pub held_uploads: Vec<AdminHeldUploadRow>,
130 pub total_held: usize,
131 pub admin_active_page: &'static str,
132 pub layer_health: Vec<LayerHealthCard>,
133 pub queue_pending: i64,
134 pub queue_running: i64,
135 pub recent_history: Vec<ScanHistoryDisplay>,
136 pub history_total: usize,
137 }
138
139 /// Full audit log for the scan pipeline (Phase 2b).
140 #[derive(Template)]
141 #[template(path = "dashboards/admin-scan-audit.html")]
142 pub struct AdminScanAuditTemplate {
143 pub csrf_token: CsrfTokenOption,
144 pub session_user: Option<SessionUser>,
145 pub admin_active_page: &'static str,
146 pub entries: Vec<AdminAuditLogRow>,
147 pub filter_action: String,
148 pub filter_admin: String,
149 pub filter_since_days: String,
150 }
151
152 /// Admin appeals queue page.
153 #[derive(Template)]
154 #[template(path = "dashboards/admin-appeals.html")]
155 pub struct AdminAppealsTemplate {
156 pub csrf_token: CsrfTokenOption,
157 pub session_user: Option<SessionUser>,
158 pub appeals: Vec<AdminAppealRow>,
159 pub admin_active_page: &'static str,
160 }
161
162 /// Admin reports queue page.
163 #[derive(Template)]
164 #[template(path = "dashboards/admin-reports.html")]
165 pub struct AdminReportsTemplate {
166 pub csrf_token: CsrfTokenOption,
167 pub session_user: Option<SessionUser>,
168 pub reports: Vec<AdminReportRow>,
169 pub stats: ReportStats,
170 pub current_filter: String,
171 pub admin_active_page: &'static str,
172 }
173
174 /// Admin email signups page.
175 #[derive(Template)]
176 #[template(path = "dashboards/admin-signups.html")]
177 pub struct AdminSignupsTemplate {
178 pub csrf_token: CsrfTokenOption,
179 pub session_user: Option<SessionUser>,
180 pub signups: Vec<AdminSignupRow>,
181 pub total: i64,
182 pub admin_active_page: &'static str,
183 }
184
185 /// Admin metrics dashboard page.
186 #[derive(Template)]
187 #[template(path = "dashboards/admin-metrics.html")]
188 pub struct AdminMetricsTemplate {
189 pub csrf_token: CsrfTokenOption,
190 pub session_user: Option<SessionUser>,
191 pub admin_active_page: &'static str,
192 pub uptime: String,
193 pub total_requests: u64,
194 pub error_rate: f64,
195 pub total_errors: u64,
196 pub pool_max: u32,
197 pub pool_active: u32,
198 pub pool_idle: u32,
199 pub top_routes: Vec<RouteMetric>,
200 pub error_breakdown: Vec<ErrorMetric>,
201 }
202
203 /// Admin comp-codes dashboard page (mint form + status list).
204 #[derive(Template)]
205 #[template(path = "dashboards/admin-comp-codes.html")]
206 pub struct AdminCompCodesTemplate {
207 pub csrf_token: CsrfTokenOption,
208 pub session_user: Option<SessionUser>,
209 pub admin_active_page: &'static str,
210 pub comp_codes: Vec<AdminCompCodeRow>,
211 }
212
213 /// The comp-codes table body, re-rendered after a mint to refresh the list.
214 #[derive(Template)]
215 #[template(path = "partials/admin_comp_codes_entries.html")]
216 pub struct AdminCompCodesEntriesTemplate {
217 pub comp_codes: Vec<AdminCompCodeRow>,
218 }
219
220 /// A row in the top routes table.
221 pub struct RouteMetric {
222 pub method: String,
223 pub path: String,
224 pub status: String,
225 pub count: u64,
226 }
227
228 /// A row in the error breakdown table.
229 pub struct ErrorMetric {
230 pub kind: String,
231 pub count: u64,
232 }
233
234 // Export & Account Management
235
236 /// Data export portal for the no-lock-in guarantee.
237 #[derive(Template)]
238 #[template(path = "dashboards/dashboard-export.html")]
239 pub struct ExportPortalTemplate {
240 pub csrf_token: CsrfTokenOption,
241 pub session_user: Option<SessionUser>,
242 /// Whether the user has any exportable content (projects, items, files).
243 pub has_content: bool,
244 /// Human-readable total size of exportable data (e.g. "12.3 MB").
245 pub content_size: String,
246 }
247
248 /// Data import portal for migrating from other platforms.
249 #[derive(Template)]
250 #[template(path = "dashboards/dashboard-import.html")]
251 pub struct ImportPortalTemplate {
252 pub csrf_token: CsrfTokenOption,
253 pub session_user: Option<SessionUser>,
254 pub projects: Vec<ImportProjectOption>,
255 pub jobs: Vec<ImportJobRow>,
256 }
257
258 /// Minimal project info for the import page project selector.
259 pub struct ImportProjectOption {
260 pub id: String,
261 pub title: String,
262 }
263
264 /// A row in the import history table.
265 pub struct ImportJobRow {
266 pub source: String,
267 pub status: String,
268 pub total_rows: i32,
269 pub created_rows: i32,
270 pub created_at: chrono::DateTime<chrono::Utc>,
271 }
272
273 /// Account deletion confirmation page with username verification.
274 #[derive(Template)]
275 #[template(path = "dashboards/dashboard-delete-account.html")]
276 pub struct DeleteAccountTemplate {
277 pub csrf_token: CsrfTokenOption,
278 pub session_user: Option<SessionUser>,
279 pub username: String,
280 }
281
282 /// Blog post editor page (create/edit).
283 #[derive(Template)]
284 #[template(path = "dashboards/dashboard-blog-editor.html")]
285 pub struct BlogEditorTemplate {
286 pub csrf_token: CsrfTokenOption,
287 pub session_user: Option<SessionUser>,
288 pub project_id: String,
289 pub project_slug: String,
290 pub editing: bool,
291 pub post_id: String,
292 pub post_title: String,
293 pub post_slug: String,
294 pub post_body: String,
295 pub post_is_published: bool,
296 /// Whether this editor is for the platform changelog project. Gates the
297 /// owner-only "Show on landing" control so it never appears on a regular
298 /// creator's blog, where the flag would be inert.
299 pub is_changelog_project: bool,
300 /// Current value of the post's landing flag (false for new posts).
301 pub post_show_on_landing: bool,
302 }
303
304 /// HTMX partial: onboarding checklist (returned by the restore handler).
305 #[derive(Template)]
306 #[template(path = "partials/onboarding_checklist.html")]
307 pub struct OnboardingChecklistPartialTemplate {
308 pub checklist: OnboardingChecklist,
309 }
310
311 // License key and promo code view types live in crate::types (LicenseKeyRow, PromoCodeRow).
312
313 // Creation Wizards
314
315 /// Step navigation item for the wizard sidebar.
316 pub struct StepNavItem {
317 pub name: &'static str,
318 pub label: &'static str,
319 pub state: &'static str, // "completed", "active", "pending"
320 }
321
322 /// A subscription tier row for the wizard monetization/preview steps.
323 pub struct WizardTierRow {
324 pub id: String,
325 pub name: String,
326 pub price_display: String,
327 pub price_dollars: String,
328 pub description: String,
329 }
330
331 /// Full page: project creation wizard.
332 #[derive(Template)]
333 #[template(path = "wizards/wizard_project.html")]
334 pub struct WizardProjectTemplate {
335 pub csrf_token: CsrfTokenOption,
336 pub session_user: Option<SessionUser>,
337 pub nav: Vec<StepNavItem>,
338 pub project_features: &'static [(&'static str, &'static str, &'static str)],
339 }
340
341 /// Full page: item creation wizard.
342 #[derive(Template)]
343 #[template(path = "wizards/wizard_item.html")]
344 pub struct WizardItemTemplate {
345 pub csrf_token: CsrfTokenOption,
346 pub session_user: Option<SessionUser>,
347 pub nav: Vec<StepNavItem>,
348 pub project_slug: String,
349 pub item_type_cards: Vec<(&'static str, &'static str, &'static str)>,
350 }
351
352 // --- Project step partials ---
353
354 /// Wizard step partial: project basics (title, slug, features, category).
355 #[derive(Template)]
356 #[template(path = "wizards/steps/project/basics.html")]
357 pub struct WizardProjectBasicsTemplate {
358 pub nav: Vec<StepNavItem>,
359 pub slug: String,
360 pub project_features: &'static [(&'static str, &'static str, &'static str)],
361 pub title: String,
362 pub features: Vec<String>,
363 pub description: String,
364 pub category_name: String,
365 pub ai_tier: String,
366 pub ai_disclosure: String,
367 }
368
369 /// Wizard step partial: project appearance (cover image upload).
370 #[derive(Template)]
371 #[template(path = "wizards/steps/project/appearance.html")]
372 pub struct WizardProjectAppearanceTemplate {
373 pub nav: Vec<StepNavItem>,
374 pub slug: String,
375 pub project_id: String,
376 pub cover_image_url: Option<String>,
377 pub project_title: String,
378 }
379
380 /// Wizard step partial: project monetization (pricing model, tiers, Stripe).
381 #[derive(Template)]
382 #[template(path = "wizards/steps/project/monetization.html")]
383 pub struct WizardProjectMonetizationTemplate {
384 pub nav: Vec<StepNavItem>,
385 pub slug: String,
386 pub tiers: Vec<WizardTierRow>,
387 pub stripe_connected: bool,
388 /// Current pricing model string value (free, buy_once, pwyw, subscription).
389 pub pricing_model: String,
390 /// Current fixed price in dollars (for buy_once).
391 pub price_dollars: String,
392 /// Current PWYW minimum in dollars.
393 pub pwyw_min_dollars: String,
394 }
395
396 /// Wizard step partial: project first content prompt (item count gate).
397 #[derive(Template)]
398 #[template(path = "wizards/steps/project/first_content.html")]
399 pub struct WizardProjectFirstContentTemplate {
400 pub nav: Vec<StepNavItem>,
401 pub slug: String,
402 pub item_count: u32,
403 }
404
405 /// Wizard step partial: project preview and publish confirmation.
406 #[derive(Template)]
407 #[template(path = "wizards/steps/project/preview.html")]
408 #[allow(dead_code)]
409 pub struct WizardProjectPreviewTemplate {
410 pub csrf_token: CsrfTokenOption,
411 pub nav: Vec<StepNavItem>,
412 pub slug: String,
413 pub title: String,
414 pub features: Vec<String>,
415 pub description: String,
416 pub cover_image_url: Option<String>,
417 pub category_name: Option<String>,
418 pub tier_count: u32,
419 pub item_count: u32,
420 pub tiers: Vec<WizardTierRow>,
421 pub is_public: bool,
422 /// Human-readable pricing display (e.g. "Free", "$9.99", "PWYW").
423 pub pricing_display: String,
424 }
425
426 // --- Item step partials ---
427
428 /// Wizard step partial: item type selection (text, audio, video, software, bundle).
429 #[derive(Template)]
430 #[template(path = "wizards/steps/item/type.html")]
431 pub struct WizardItemTypeTemplate {
432 pub nav: Vec<StepNavItem>,
433 pub project_slug: String,
434 pub item_id: String,
435 pub item_type_cards: Vec<(&'static str, &'static str, &'static str)>,
436 pub selected_type: String,
437 }
438
439 /// Wizard step partial: item basics (title, description, cover image).
440 #[derive(Template)]
441 #[template(path = "wizards/steps/item/basics.html")]
442 pub struct WizardItemBasicsTemplate {
443 pub nav: Vec<StepNavItem>,
444 pub project_slug: String,
445 pub item_id: String,
446 pub title: String,
447 pub description: String,
448 pub cover_image_url: Option<String>,
449 }
450
451 /// Wizard step partial: item content (body editor, bundle picker).
452 #[derive(Template)]
453 #[template(path = "wizards/steps/item/content.html")]
454 pub struct WizardItemContentTemplate {
455 pub nav: Vec<StepNavItem>,
456 pub project_slug: String,
457 pub project_id: String,
458 pub item_id: String,
459 pub item_type: String,
460 pub body: String,
461 /// Non-bundle items in the project available for inclusion.
462 pub bundleable_items: Vec<BundleableItem>,
463 /// IDs of items already selected for this bundle.
464 pub selected_bundle_ids: Vec<String>,
465 /// IDs of items currently marked unlisted.
466 pub unlisted_ids: Vec<String>,
467 }
468
469 /// Lightweight item info for the bundle item picker.
470 pub struct BundleableItem {
471 pub id: String,
472 pub title: String,
473 pub item_type: String,
474 }
475
476 /// Wizard step partial: item sections (reorderable content sections).
477 #[derive(Template)]
478 #[template(path = "wizards/steps/item/sections.html")]
479 pub struct WizardItemSectionsTemplate {
480 pub nav: Vec<StepNavItem>,
481 pub project_slug: String,
482 pub item_id: String,
483 pub sections: Vec<crate::types::ItemSection>,
484 }
485
486 /// Wizard step partial: item pricing (model selection, price entry).
487 #[derive(Template)]
488 #[template(path = "wizards/steps/item/pricing.html")]
489 pub struct WizardItemPricingTemplate {
490 pub nav: Vec<StepNavItem>,
491 pub project_slug: String,
492 pub item_id: String,
493 pub pricing_model: String,
494 pub price_dollars: String,
495 pub pwyw_suggested_dollars: String,
496 pub pwyw_min_dollars: String,
497 }
498
499 /// Wizard step partial: item preview and publish confirmation.
500 #[derive(Template)]
501 #[template(path = "wizards/steps/item/preview.html")]
502 pub struct WizardItemPreviewTemplate {
503 pub csrf_token: CsrfTokenOption,
504 pub nav: Vec<StepNavItem>,
505 pub project_slug: String,
506 pub item_id: String,
507 pub title: String,
508 pub item_type: String,
509 pub description: String,
510 pub price_display: String,
511 pub tag_names: Vec<String>,
512 pub has_content: bool,
513 pub is_public: bool,
514 }
515