Skip to main content

max / makenotwork

16.7 KB · 520 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, Item, OnboardingChecklist, Project,
9 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 email signups page.
178 #[derive(Template)]
179 #[template(path = "dashboards/admin-signups.html")]
180 pub struct AdminSignupsTemplate {
181 pub csrf_token: CsrfTokenOption,
182 pub session_user: Option<SessionUser>,
183 pub signups: Vec<AdminSignupRow>,
184 pub total: i64,
185 pub admin_active_page: &'static str,
186 }
187
188 /// Admin metrics dashboard page.
189 #[derive(Template)]
190 #[template(path = "dashboards/admin-metrics.html")]
191 pub struct AdminMetricsTemplate {
192 pub csrf_token: CsrfTokenOption,
193 pub session_user: Option<SessionUser>,
194 pub admin_active_page: &'static str,
195 pub uptime: String,
196 pub total_requests: u64,
197 pub error_rate: f64,
198 pub total_errors: u64,
199 pub pool_max: u32,
200 pub pool_active: u32,
201 pub pool_idle: u32,
202 pub top_routes: Vec<RouteMetric>,
203 pub error_breakdown: Vec<ErrorMetric>,
204 }
205
206 /// Admin comp-codes dashboard page (mint form + status list).
207 #[derive(Template)]
208 #[template(path = "dashboards/admin-comp-codes.html")]
209 pub struct AdminCompCodesTemplate {
210 pub csrf_token: CsrfTokenOption,
211 pub session_user: Option<SessionUser>,
212 pub admin_active_page: &'static str,
213 pub comp_codes: Vec<AdminCompCodeRow>,
214 }
215
216 /// The comp-codes table body, re-rendered after a mint to refresh the list.
217 #[derive(Template)]
218 #[template(path = "partials/admin_comp_codes_entries.html")]
219 pub struct AdminCompCodesEntriesTemplate {
220 pub comp_codes: Vec<AdminCompCodeRow>,
221 }
222
223 /// A row in the top routes table.
224 pub struct RouteMetric {
225 pub method: String,
226 pub path: String,
227 pub status: String,
228 pub count: u64,
229 }
230
231 /// A row in the error breakdown table.
232 pub struct ErrorMetric {
233 pub kind: String,
234 pub count: u64,
235 }
236
237 // Export & Account Management
238
239 /// Data export portal for the no-lock-in guarantee.
240 #[derive(Template)]
241 #[template(path = "dashboards/dashboard-export.html")]
242 pub struct ExportPortalTemplate {
243 pub csrf_token: CsrfTokenOption,
244 pub session_user: Option<SessionUser>,
245 /// Whether the user has any exportable content (projects, items, files).
246 pub has_content: bool,
247 /// Human-readable total size of exportable data (e.g. "12.3 MB").
248 pub content_size: String,
249 }
250
251 /// Data import portal for migrating from other platforms.
252 #[derive(Template)]
253 #[template(path = "dashboards/dashboard-import.html")]
254 pub struct ImportPortalTemplate {
255 pub csrf_token: CsrfTokenOption,
256 pub session_user: Option<SessionUser>,
257 pub projects: Vec<ImportProjectOption>,
258 pub jobs: Vec<ImportJobRow>,
259 }
260
261 /// Minimal project info for the import page project selector.
262 pub struct ImportProjectOption {
263 pub id: String,
264 pub title: String,
265 }
266
267 /// A row in the import history table.
268 pub struct ImportJobRow {
269 pub source: String,
270 pub status: String,
271 /// Badge class for `status`, from `ImportJobStatus::badge_status`.
272 pub status_tone: &'static str,
273 pub total_rows: i32,
274 pub created_rows: i32,
275 pub created_at: chrono::DateTime<chrono::Utc>,
276 }
277
278 /// Account deletion confirmation page with username verification.
279 #[derive(Template)]
280 #[template(path = "dashboards/dashboard-delete-account.html")]
281 pub struct DeleteAccountTemplate {
282 pub csrf_token: CsrfTokenOption,
283 pub session_user: Option<SessionUser>,
284 pub username: String,
285 }
286
287 /// Blog post editor page (create/edit).
288 #[derive(Template)]
289 #[template(path = "dashboards/dashboard-blog-editor.html")]
290 pub struct BlogEditorTemplate {
291 pub csrf_token: CsrfTokenOption,
292 pub session_user: Option<SessionUser>,
293 pub project_id: String,
294 pub project_slug: String,
295 pub editing: bool,
296 pub post_id: String,
297 pub post_title: String,
298 pub post_slug: String,
299 pub post_body: String,
300 pub post_is_published: bool,
301 /// Whether this editor is for the platform changelog project. Gates the
302 /// owner-only "Show on landing" control so it never appears on a regular
303 /// creator's blog, where the flag would be inert.
304 pub is_changelog_project: bool,
305 /// Current value of the post's landing flag (false for new posts).
306 pub post_show_on_landing: bool,
307 }
308
309 /// HTMX partial: onboarding checklist (returned by the restore handler).
310 #[derive(Template)]
311 #[template(path = "partials/onboarding_checklist.html")]
312 pub struct OnboardingChecklistPartialTemplate {
313 pub checklist: OnboardingChecklist,
314 }
315
316 // License key and promo code view types live in crate::types (LicenseKeyRow, PromoCodeRow).
317
318 // Creation Wizards
319
320 /// Step navigation item for the wizard sidebar.
321 pub struct StepNavItem {
322 pub name: &'static str,
323 pub label: &'static str,
324 pub state: &'static str, // "completed", "active", "pending"
325 }
326
327 /// A subscription tier row for the wizard monetization/preview steps.
328 pub struct WizardTierRow {
329 pub id: String,
330 pub name: String,
331 pub price_display: String,
332 pub price_dollars: String,
333 pub description: String,
334 }
335
336 /// Full page: project creation wizard.
337 #[derive(Template)]
338 #[template(path = "wizards/wizard_project.html")]
339 pub struct WizardProjectTemplate {
340 pub csrf_token: CsrfTokenOption,
341 pub session_user: Option<SessionUser>,
342 pub nav: Vec<StepNavItem>,
343 pub project_features: &'static [(&'static str, &'static str, &'static str)],
344 }
345
346 /// Full page: item creation wizard.
347 #[derive(Template)]
348 #[template(path = "wizards/wizard_item.html")]
349 pub struct WizardItemTemplate {
350 pub csrf_token: CsrfTokenOption,
351 pub session_user: Option<SessionUser>,
352 pub nav: Vec<StepNavItem>,
353 pub project_slug: String,
354 pub item_type_cards: Vec<(&'static str, &'static str, &'static str)>,
355 }
356
357 // --- Project step partials ---
358
359 /// Wizard step partial: project basics (title, slug, features, category).
360 #[derive(Template)]
361 #[template(path = "wizards/steps/project/basics.html")]
362 pub struct WizardProjectBasicsTemplate {
363 pub nav: Vec<StepNavItem>,
364 pub slug: String,
365 pub project_features: &'static [(&'static str, &'static str, &'static str)],
366 pub title: String,
367 pub features: Vec<String>,
368 pub description: String,
369 pub category_name: String,
370 pub ai_tier: String,
371 pub ai_disclosure: String,
372 }
373
374 /// Wizard step partial: project appearance (cover image upload).
375 #[derive(Template)]
376 #[template(path = "wizards/steps/project/appearance.html")]
377 pub struct WizardProjectAppearanceTemplate {
378 pub nav: Vec<StepNavItem>,
379 pub slug: String,
380 pub project_id: String,
381 pub cover_image_url: Option<String>,
382 pub project_title: String,
383 }
384
385 /// Wizard step partial: project monetization (pricing model, tiers, Stripe).
386 #[derive(Template)]
387 #[template(path = "wizards/steps/project/monetization.html")]
388 pub struct WizardProjectMonetizationTemplate {
389 pub nav: Vec<StepNavItem>,
390 pub slug: String,
391 pub tiers: Vec<WizardTierRow>,
392 pub stripe_connected: bool,
393 /// Current pricing model string value (free, buy_once, pwyw, subscription).
394 pub pricing_model: String,
395 /// Current fixed price in dollars (for buy_once).
396 pub price_dollars: String,
397 /// Current PWYW minimum in dollars.
398 pub pwyw_min_dollars: String,
399 }
400
401 /// Wizard step partial: project first content prompt (item count gate).
402 #[derive(Template)]
403 #[template(path = "wizards/steps/project/first_content.html")]
404 pub struct WizardProjectFirstContentTemplate {
405 pub nav: Vec<StepNavItem>,
406 pub slug: String,
407 pub item_count: u32,
408 }
409
410 /// Wizard step partial: project preview and publish confirmation.
411 #[derive(Template)]
412 #[template(path = "wizards/steps/project/preview.html")]
413 #[allow(dead_code)]
414 pub struct WizardProjectPreviewTemplate {
415 pub csrf_token: CsrfTokenOption,
416 pub nav: Vec<StepNavItem>,
417 pub slug: String,
418 pub title: String,
419 pub features: Vec<String>,
420 pub description: String,
421 pub cover_image_url: Option<String>,
422 pub category_name: Option<String>,
423 pub tier_count: u32,
424 pub item_count: u32,
425 pub tiers: Vec<WizardTierRow>,
426 pub is_public: bool,
427 /// Human-readable pricing display (e.g. "Free", "$9.99", "PWYW").
428 pub pricing_display: String,
429 }
430
431 // --- Item step partials ---
432
433 /// Wizard step partial: item type selection (text, audio, video, software, bundle).
434 #[derive(Template)]
435 #[template(path = "wizards/steps/item/type.html")]
436 pub struct WizardItemTypeTemplate {
437 pub nav: Vec<StepNavItem>,
438 pub project_slug: String,
439 pub item_id: String,
440 pub item_type_cards: Vec<(&'static str, &'static str, &'static str)>,
441 pub selected_type: String,
442 }
443
444 /// Wizard step partial: item basics (title, description, cover image).
445 #[derive(Template)]
446 #[template(path = "wizards/steps/item/basics.html")]
447 pub struct WizardItemBasicsTemplate {
448 pub nav: Vec<StepNavItem>,
449 pub project_slug: String,
450 pub item_id: String,
451 pub title: String,
452 pub description: String,
453 pub cover_image_url: Option<String>,
454 }
455
456 /// Wizard step partial: item content (body editor, bundle picker).
457 #[derive(Template)]
458 #[template(path = "wizards/steps/item/content.html")]
459 pub struct WizardItemContentTemplate {
460 pub nav: Vec<StepNavItem>,
461 pub project_slug: String,
462 pub project_id: String,
463 pub item_id: String,
464 pub item_type: String,
465 pub body: String,
466 /// Non-bundle items in the project available for inclusion.
467 pub bundleable_items: Vec<BundleableItem>,
468 /// IDs of items already selected for this bundle.
469 pub selected_bundle_ids: Vec<String>,
470 /// IDs of items currently marked unlisted.
471 pub unlisted_ids: Vec<String>,
472 }
473
474 /// Lightweight item info for the bundle item picker.
475 pub struct BundleableItem {
476 pub id: String,
477 pub title: String,
478 pub item_type: String,
479 }
480
481 /// Wizard step partial: item sections (reorderable content sections).
482 #[derive(Template)]
483 #[template(path = "wizards/steps/item/sections.html")]
484 pub struct WizardItemSectionsTemplate {
485 pub nav: Vec<StepNavItem>,
486 pub project_slug: String,
487 pub item_id: String,
488 pub sections: Vec<crate::types::ItemSection>,
489 }
490
491 /// Wizard step partial: item pricing (model selection, price entry).
492 #[derive(Template)]
493 #[template(path = "wizards/steps/item/pricing.html")]
494 pub struct WizardItemPricingTemplate {
495 pub nav: Vec<StepNavItem>,
496 pub project_slug: String,
497 pub item_id: String,
498 pub pricing_model: String,
499 pub price_dollars: String,
500 pub pwyw_suggested_dollars: String,
501 pub pwyw_min_dollars: String,
502 }
503
504 /// Wizard step partial: item preview and publish confirmation.
505 #[derive(Template)]
506 #[template(path = "wizards/steps/item/preview.html")]
507 pub struct WizardItemPreviewTemplate {
508 pub csrf_token: CsrfTokenOption,
509 pub nav: Vec<StepNavItem>,
510 pub project_slug: String,
511 pub item_id: String,
512 pub title: String,
513 pub item_type: String,
514 pub description: String,
515 pub price_display: String,
516 pub tag_names: Vec<String>,
517 pub has_content: bool,
518 pub is_public: bool,
519 }
520