Skip to main content

max / makenotwork

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