Skip to main content

max / makenotwork

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