Skip to main content

max / makenotwork

16.9 KB · 527 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 import portal for migrating from other platforms.
259 #[derive(Template)]
260 #[template(path = "dashboards/dashboard-import.html")]
261 pub struct ImportPortalTemplate {
262 pub csrf_token: CsrfTokenOption,
263 pub session_user: Option<SessionUser>,
264 pub projects: Vec<ImportProjectOption>,
265 pub jobs: Vec<ImportJobRow>,
266 }
267
268 /// Minimal project info for the import page project selector.
269 pub struct ImportProjectOption {
270 pub id: String,
271 pub title: String,
272 }
273
274 /// A row in the import history table.
275 pub struct ImportJobRow {
276 pub source: String,
277 pub status: String,
278 /// Badge class for `status`, from `ImportJobStatus::badge_status`.
279 pub status_tone: &'static str,
280 pub total_rows: i32,
281 pub created_rows: i32,
282 pub created_at: chrono::DateTime<chrono::Utc>,
283 }
284
285 /// Account deletion confirmation page with username verification.
286 #[derive(Template)]
287 #[template(path = "dashboards/dashboard-delete-account.html")]
288 pub struct DeleteAccountTemplate {
289 pub csrf_token: CsrfTokenOption,
290 pub session_user: Option<SessionUser>,
291 pub username: String,
292 }
293
294 /// Blog post editor page (create/edit).
295 #[derive(Template)]
296 #[template(path = "dashboards/dashboard-blog-editor.html")]
297 pub struct BlogEditorTemplate {
298 pub csrf_token: CsrfTokenOption,
299 pub session_user: Option<SessionUser>,
300 pub project_id: String,
301 pub project_slug: String,
302 pub editing: bool,
303 pub post_id: String,
304 pub post_title: String,
305 pub post_slug: String,
306 pub post_body: String,
307 pub post_is_published: bool,
308 /// Whether this editor is for the platform changelog project. Gates the
309 /// owner-only "Show on landing" control so it never appears on a regular
310 /// creator's blog, where the flag would be inert.
311 pub is_changelog_project: bool,
312 /// Current value of the post's landing flag (false for new posts).
313 pub post_show_on_landing: bool,
314 }
315
316 /// HTMX partial: onboarding checklist (returned by the restore handler).
317 #[derive(Template)]
318 #[template(path = "partials/onboarding_checklist.html")]
319 pub struct OnboardingChecklistPartialTemplate {
320 pub checklist: OnboardingChecklist,
321 }
322
323 // License key and promo code view types live in crate::types (LicenseKeyRow, PromoCodeRow).
324
325 // Creation Wizards
326
327 /// Step navigation item for the wizard sidebar.
328 pub struct StepNavItem {
329 pub name: &'static str,
330 pub label: &'static str,
331 pub state: &'static str, // "completed", "active", "pending"
332 }
333
334 /// A subscription tier row for the wizard monetization/preview steps.
335 pub struct WizardTierRow {
336 pub id: String,
337 pub name: String,
338 pub price_display: String,
339 pub price_dollars: String,
340 pub description: String,
341 }
342
343 /// Full page: project creation wizard.
344 #[derive(Template)]
345 #[template(path = "wizards/wizard_project.html")]
346 pub struct WizardProjectTemplate {
347 pub csrf_token: CsrfTokenOption,
348 pub session_user: Option<SessionUser>,
349 pub nav: Vec<StepNavItem>,
350 pub project_features: &'static [(&'static str, &'static str, &'static str)],
351 }
352
353 /// Full page: item creation wizard.
354 #[derive(Template)]
355 #[template(path = "wizards/wizard_item.html")]
356 pub struct WizardItemTemplate {
357 pub csrf_token: CsrfTokenOption,
358 pub session_user: Option<SessionUser>,
359 pub nav: Vec<StepNavItem>,
360 pub project_slug: String,
361 pub item_type_cards: Vec<(&'static str, &'static str, &'static str)>,
362 }
363
364 // --- Project step partials ---
365
366 /// Wizard step partial: project basics (title, slug, features, category).
367 #[derive(Template)]
368 #[template(path = "wizards/steps/project/basics.html")]
369 pub struct WizardProjectBasicsTemplate {
370 pub nav: Vec<StepNavItem>,
371 pub slug: String,
372 pub project_features: &'static [(&'static str, &'static str, &'static str)],
373 pub title: String,
374 pub features: Vec<String>,
375 pub description: String,
376 pub category_name: String,
377 pub ai_tier: String,
378 pub ai_disclosure: String,
379 }
380
381 /// Wizard step partial: project appearance (cover image upload).
382 #[derive(Template)]
383 #[template(path = "wizards/steps/project/appearance.html")]
384 pub struct WizardProjectAppearanceTemplate {
385 pub nav: Vec<StepNavItem>,
386 pub slug: String,
387 pub project_id: String,
388 pub cover_image_url: Option<String>,
389 pub project_title: String,
390 }
391
392 /// Wizard step partial: project monetization (pricing model, tiers, Stripe).
393 #[derive(Template)]
394 #[template(path = "wizards/steps/project/monetization.html")]
395 pub struct WizardProjectMonetizationTemplate {
396 pub nav: Vec<StepNavItem>,
397 pub slug: String,
398 pub tiers: Vec<WizardTierRow>,
399 pub stripe_connected: bool,
400 /// Current pricing model string value (free, buy_once, pwyw, subscription).
401 pub pricing_model: String,
402 /// Current fixed price in dollars (for buy_once).
403 pub price_dollars: String,
404 /// Current PWYW minimum in dollars.
405 pub pwyw_min_dollars: String,
406 }
407
408 /// Wizard step partial: project first content prompt (item count gate).
409 #[derive(Template)]
410 #[template(path = "wizards/steps/project/first_content.html")]
411 pub struct WizardProjectFirstContentTemplate {
412 pub nav: Vec<StepNavItem>,
413 pub slug: String,
414 pub item_count: u32,
415 }
416
417 /// Wizard step partial: project preview and publish confirmation.
418 #[derive(Template)]
419 #[template(path = "wizards/steps/project/preview.html")]
420 #[allow(dead_code)]
421 pub struct WizardProjectPreviewTemplate {
422 pub csrf_token: CsrfTokenOption,
423 pub nav: Vec<StepNavItem>,
424 pub slug: String,
425 pub title: String,
426 pub features: Vec<String>,
427 pub description: String,
428 pub cover_image_url: Option<String>,
429 pub category_name: Option<String>,
430 pub tier_count: u32,
431 pub item_count: u32,
432 pub tiers: Vec<WizardTierRow>,
433 pub is_public: bool,
434 /// Human-readable pricing display (e.g. "Free", "$9.99", "PWYW").
435 pub pricing_display: String,
436 }
437
438 // --- Item step partials ---
439
440 /// Wizard step partial: item type selection (text, audio, video, software, bundle).
441 #[derive(Template)]
442 #[template(path = "wizards/steps/item/type.html")]
443 pub struct WizardItemTypeTemplate {
444 pub nav: Vec<StepNavItem>,
445 pub project_slug: String,
446 pub item_id: String,
447 pub item_type_cards: Vec<(&'static str, &'static str, &'static str)>,
448 pub selected_type: String,
449 }
450
451 /// Wizard step partial: item basics (title, description, cover image).
452 #[derive(Template)]
453 #[template(path = "wizards/steps/item/basics.html")]
454 pub struct WizardItemBasicsTemplate {
455 pub nav: Vec<StepNavItem>,
456 pub project_slug: String,
457 pub item_id: String,
458 pub title: String,
459 pub description: String,
460 pub cover_image_url: Option<String>,
461 }
462
463 /// Wizard step partial: item content (body editor, bundle picker).
464 #[derive(Template)]
465 #[template(path = "wizards/steps/item/content.html")]
466 pub struct WizardItemContentTemplate {
467 pub nav: Vec<StepNavItem>,
468 pub project_slug: String,
469 pub project_id: String,
470 pub item_id: String,
471 pub item_type: String,
472 pub body: String,
473 /// Non-bundle items in the project available for inclusion.
474 pub bundleable_items: Vec<BundleableItem>,
475 /// IDs of items already selected for this bundle.
476 pub selected_bundle_ids: Vec<String>,
477 /// IDs of items currently marked unlisted.
478 pub unlisted_ids: Vec<String>,
479 }
480
481 /// Lightweight item info for the bundle item picker.
482 pub struct BundleableItem {
483 pub id: String,
484 pub title: String,
485 pub item_type: String,
486 }
487
488 /// Wizard step partial: item sections (reorderable content sections).
489 #[derive(Template)]
490 #[template(path = "wizards/steps/item/sections.html")]
491 pub struct WizardItemSectionsTemplate {
492 pub nav: Vec<StepNavItem>,
493 pub project_slug: String,
494 pub item_id: String,
495 pub sections: Vec<crate::types::ItemSection>,
496 }
497
498 /// Wizard step partial: item pricing (model selection, price entry).
499 #[derive(Template)]
500 #[template(path = "wizards/steps/item/pricing.html")]
501 pub struct WizardItemPricingTemplate {
502 pub nav: Vec<StepNavItem>,
503 pub project_slug: String,
504 pub item_id: String,
505 pub pricing_model: String,
506 pub price_dollars: String,
507 pub pwyw_suggested_dollars: String,
508 pub pwyw_min_dollars: String,
509 }
510
511 /// Wizard step partial: item preview and publish confirmation.
512 #[derive(Template)]
513 #[template(path = "wizards/steps/item/preview.html")]
514 pub struct WizardItemPreviewTemplate {
515 pub csrf_token: CsrfTokenOption,
516 pub nav: Vec<StepNavItem>,
517 pub project_slug: String,
518 pub item_id: String,
519 pub title: String,
520 pub item_type: String,
521 pub description: String,
522 pub price_display: String,
523 pub tag_names: Vec<String>,
524 pub has_content: bool,
525 pub is_public: bool,
526 }
527