Skip to main content

max / makenotwork

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