Skip to main content

max / makenotwork

8.9 KB · 310 lines History Blame Raw
1 //! Askama template definitions for all HTML pages and fragments.
2 //!
3 //! Split by domain:
4 //! - `public`: landing, auth, content, blog, discover, health
5 //! - `dashboard`: creator dashboards, admin, export, account management
6 //! - `partials`: HTMX fragments, tab content, alerts, form status
7
8 mod dashboard;
9 mod embed;
10 mod partials;
11 mod public;
12
13 pub use dashboard::*;
14 pub use embed::*;
15 pub use partials::*;
16 pub use public::*;
17
18 use askama::Template;
19 use axum::{
20 http::StatusCode,
21 response::{Html, IntoResponse, Response},
22 };
23
24 /// Base context shared by all templates.
25 /// Note: csrf_token is Option to allow templates to work without CSRF
26 /// but all authenticated pages should include it.
27 pub type CsrfTokenOption = Option<String>;
28
29 /// One frame of the composable click-through carousel (`partials/carousel.html`).
30 ///
31 /// A carousel is just an ordered `&[CarouselFrame]`; the same macro renders it
32 /// on any surface (app product pages, landing), only the frame list differs.
33 /// Build a `Vec<CarouselFrame>` on a page template and pass it to the macro.
34 ///
35 /// Prefer [`CarouselFrame::new`] over a struct literal: it makes the alt text a
36 /// required, named argument and nudges (in debug builds) toward alt that
37 /// actually describes the screenshot. A carousel frame is a meaningful image,
38 /// so alt is not optional and should not be a filename or a bare label like
39 /// "screenshot". A screen-reader user should get the same information a
40 /// sighted viewer does.
41 #[derive(Clone)]
42 pub struct CarouselFrame {
43 /// Image URL (typically an optimized screenshot under `/static/images/shots/`).
44 pub image: String,
45 /// Alt text describing the screenshot. Required, every frame is an image.
46 pub alt: String,
47 /// Optional caption shown under the frame.
48 pub caption: Option<String>,
49 }
50
51 impl CarouselFrame {
52 /// Build a frame, nudging toward helpful alt text.
53 ///
54 /// In debug builds this asserts the alt text is non-empty and looks like a
55 /// description rather than a filename or a one-word placeholder. The checks
56 /// are debug-only so they guide authors during development without ever
57 /// affecting a release render.
58 pub fn new(image: impl Into<String>, alt: impl Into<String>) -> Self {
59 let image = image.into();
60 let alt = alt.into();
61 debug_assert!(
62 !alt.trim().is_empty(),
63 "carousel frame `{image}` has empty alt text, describe what the \
64 screenshot shows so screen-reader users get the same information \
65 sighted viewers do"
66 );
67 debug_assert!(
68 !alt.trim_start().starts_with('/') && !alt.contains(".webp") && !alt.contains(".png"),
69 "carousel frame alt text looks like a filename (`{alt}`), write a \
70 human description of what the screenshot shows instead"
71 );
72 Self {
73 image,
74 alt,
75 caption: None,
76 }
77 }
78
79 /// Attach an optional caption shown under the frame.
80 #[must_use]
81 pub fn with_caption(mut self, caption: impl Into<String>) -> Self {
82 self.caption = Some(caption.into());
83 self
84 }
85 }
86
87 /// Helper to convert any Askama template into an Axum response.
88 fn render_template<T: Template>(template: T) -> Response {
89 match template.render() {
90 Ok(html) => Html(html).into_response(),
91 Err(err) => {
92 tracing::error!(error = ?err, "template rendering error");
93 (StatusCode::INTERNAL_SERVER_ERROR, "Template error").into_response()
94 }
95 }
96 }
97
98 /// Implement `IntoResponse` for one or more Askama template structs.
99 macro_rules! impl_into_response {
100 ($($T:ty),+ $(,)?) => {
101 $(
102 impl IntoResponse for $T {
103 fn into_response(self) -> Response {
104 render_template(self)
105 }
106 }
107 )+
108 };
109 }
110
111 impl_into_response!(
112 // Public pages
113 SandboxTemplate,
114 PolicyTemplate,
115 IndexTemplate,
116 LibraryTemplate,
117 CartTemplate,
118 LoginTemplate,
119 TwoFactorTemplate,
120 OAuthAuthorizeTemplate,
121 ForgotPasswordTemplate,
122 ResetPasswordTemplate,
123 UserTemplate,
124 ProjectTemplate,
125 ProjectPaywallTemplate,
126 ItemTemplate,
127 LibraryAudioTemplate,
128 LibraryDownloadsTemplate,
129 LibraryLockedTemplate,
130 LibraryTextTemplate,
131 LibraryVideoTemplate,
132 TextReaderTemplate,
133 AudioPlayerTemplate,
134 VideoPlayerTemplate,
135 DiscoverTemplate,
136 DiscoverResultsTemplate,
137 PurchaseTemplate,
138 ReceiptTemplate,
139 BuyPageTemplate,
140 FeedTemplate,
141 StripeConnectDisclaimerTemplate,
142 // Blog pages
143 ProjectBlogTemplate,
144 BlogPostTemplate,
145 // Documentation pages
146 DocTemplate,
147 DocIndexTemplate,
148 // Pricing calculator
149 PricingTemplate,
150 PricingComparisonPartial,
151 // Platform economics + runway disclosure
152 EconomicsTemplate,
153 // Use cases
154 UseCasesTemplate,
155 // Team
156 TeamTemplate,
157 // Fan+
158 FanPlusTemplate,
159 // Creator invite system
160 CreatorsTemplate,
161 // Email & account
162 EmailResultTemplate,
163 ConfirmDeleteTemplate,
164 AccountDeletedTemplate,
165 // Health
166 HealthTemplate,
167 // Dashboard pages
168 DashboardUserTemplate,
169 DashboardProjectTemplate,
170 DashboardItemTemplate,
171 // Admin
172 AdminWaitlistTemplate,
173 AdminUsersTemplate,
174 AdminUploadsTemplate,
175 AdminScanAuditTemplate,
176 AdminAppealsTemplate,
177 AdminReportsTemplate,
178 AdminSignupsTemplate,
179 AdminMetricsTemplate,
180 AdminCompCodesTemplate,
181 // Export, import & account management
182 ExportPortalTemplate,
183 ImportPortalTemplate,
184 DeleteAccountTemplate,
185 BlogEditorTemplate,
186 // HTMX partials
187 AlertTemplate,
188 LibraryStatusTemplate,
189 ExportDownloadTemplate,
190 ExportContentReadyTemplate,
191 TransactionsTableTemplate,
192 UserProfileTabTemplate,
193 UserSettingsTabTemplate,
194 UserAccountTabTemplate,
195 UserSshKeysTabTemplate,
196 UserPaymentsTabTemplate,
197 UserProjectsTabTemplate,
198 UserCreatorTabTemplate,
199 ProjectOverviewTabTemplate,
200 ProjectContentTabTemplate,
201 ProjectAnalyticsTabTemplate,
202 UserAnalyticsTabTemplate,
203 BuyerContactsPartialTemplate,
204 PayoutSummaryPartialTemplate,
205 ProjectSettingsTabTemplate,
206 ProjectCodeTabTemplate,
207 ProjectBlogTabTemplate,
208 ProjectSubscriptionsTabTemplate,
209 ProjectMembersTabTemplate,
210 ProjectMonetizationTabTemplate,
211 ItemEditRowTemplate,
212 // Admin partials
213 AdminWaitlistEntriesTemplate,
214 AdminCompCodesEntriesTemplate,
215 AdminUserEntriesTemplate,
216 AdminUploadEntriesTemplate,
217 AdminQueueSummaryTemplate,
218 AdminAppealEntriesTemplate,
219 AdminReportEntriesTemplate,
220 SuspensionBannerTemplate,
221 // License keys
222 ItemLicenseKeysTemplate,
223 // Promo codes
224 PromoCodesListTemplate,
225 ProjectPromotionsTabTemplate,
226 // Sessions
227 UserSessionsPartialTemplate,
228 // SyncKit
229 UserSyncKitTabTemplate,
230 ProjectSyncKitTabTemplate,
231 // Forums (Multithreaded)
232 UserForumsTabTemplate,
233 // Media library
234 UserMediaTabTemplate,
235 // Support
236 UserSupportTabTemplate,
237 // Collections
238 CollectionTemplate,
239 // Library tabs
240 LibraryPurchasesTabTemplate,
241 LibraryFeedTabTemplate,
242 LibraryCollectionsTabTemplate,
243 LibraryContactsTabTemplate,
244 LibraryCommunitiesTabTemplate,
245 // Follow button
246 FollowButtonTemplate,
247 TagFollowToggleTemplate,
248 // Tag suggestions
249 TagSuggestionsTemplate,
250 // Item analytics
251 ItemAnalyticsPartialTemplate,
252 // Item dashboard tabs
253 ItemOverviewTabTemplate,
254 ItemDetailsTabTemplate,
255 ItemPricingTabTemplate,
256 ItemFilesTabTemplate,
257 ItemSalesTabTemplate,
258 ItemEmbedTabTemplate,
259 // Onboarding checklist
260 OnboardingChecklistPartialTemplate,
261 // Tag tree browser
262 TagTreeTemplate,
263 // TOTP 2FA
264 TotpSetupTemplate,
265 TotpStatusTemplate,
266 // Passkeys
267 PasskeyListTemplate,
268 // Git source browser
269 GitRepoTemplate,
270 GitTreeTemplate,
271 GitFileTemplate,
272 GitCommitsTemplate,
273 GitCommitDetailTemplate,
274 GitBlameTemplate,
275 GitUserReposTemplate,
276 GitExploreTemplate,
277 GitFileLogTemplate,
278 // Git issues
279 GitIssueListTemplate,
280 GitIssueDetailTemplate,
281 GitRepoSettingsTemplate,
282 // Join wizard
283 WizardJoinTemplate,
284 WizardJoinAccountTemplate,
285 WizardJoinProfileTemplate,
286 WizardJoinCompleteTemplate,
287 // Creation wizards, full pages
288 WizardProjectTemplate,
289 WizardItemTemplate,
290 // Creation wizards, project step partials
291 WizardProjectBasicsTemplate,
292 WizardProjectAppearanceTemplate,
293 WizardProjectMonetizationTemplate,
294 WizardProjectFirstContentTemplate,
295 WizardProjectPreviewTemplate,
296 // Creation wizards, item step partials
297 WizardItemTypeTemplate,
298 WizardItemBasicsTemplate,
299 WizardItemContentTemplate,
300 WizardItemSectionsTemplate,
301 WizardItemPricingTemplate,
302 WizardItemPreviewTemplate,
303 // Embed widgets
304 EmbedItemButtonTemplate,
305 EmbedItemCardTemplate,
306 EmbedItemPlayerTemplate,
307 EmbedTipButtonTemplate,
308 EmbedProjectCardTemplate,
309 );
310