//! Askama template definitions for all HTML pages and fragments.
//!
//! Split by domain:
//! - `public`: landing, auth, content, blog, discover, health
//! - `dashboard`: creator dashboards, admin, export, account management
//! - `partials`: HTMX fragments, tab content, alerts, form status
mod dashboard;
mod embed;
mod partials;
mod public;
pub use dashboard::*;
pub use embed::*;
pub use partials::*;
pub use public::*;
use askama::Template;
use axum::{
http::StatusCode,
response::{Html, IntoResponse, Response},
};
/// Base context shared by all templates.
/// Note: csrf_token is Option to allow templates to work without CSRF
/// but all authenticated pages should include it.
pub type CsrfTokenOption = Option;
include!(concat!(env!("OUT_DIR"), "/shot_dimensions.rs"));
/// The picture's own size, for a static asset this build read off disk.
///
/// `None` for anything not in the generated table -- a creator's gallery upload,
/// whose dimensions the database never recorded. That is honest: the renderer
/// reserves nothing rather than reserving the wrong thing.
#[must_use]
pub fn shot_size(path: &str) -> Option<(u32, u32)> {
SHOT_DIMENSIONS
.iter()
.find(|(p, _, _)| *p == path)
.map(|&(_, w, h)| (w, h))
}
/// One frame of the composable click-through carousel (`partials/carousel.html`).
///
/// A carousel is just an ordered `&[CarouselFrame]`; the same macro renders it
/// on any surface (app product pages, landing), only the frame list differs.
/// Build a `Vec` on a page template and pass it to the macro.
///
/// Prefer [`CarouselFrame::new`] over a struct literal: it makes the alt text a
/// required, named argument and nudges (in debug builds) toward alt that
/// actually describes the screenshot. A carousel frame is a meaningful image,
/// so alt is not optional and should not be a filename or a bare label like
/// "screenshot". A screen-reader user should get the same information a
/// sighted viewer does.
#[derive(Clone)]
pub struct CarouselFrame {
/// Image URL (typically an optimized screenshot under `/static/images/shots/`).
pub image: String,
/// Alt text describing the screenshot. Required, every frame is an image.
pub alt: String,
/// Optional caption shown under the frame.
pub caption: Option,
/// The image's own pixel dimensions, where this build could learn them.
///
/// Static shots get theirs read off disk by build.rs. A creator's gallery
/// upload gets `None`, because `gallery_images` records a byte count and
/// never recorded a size.
///
/// What it buys: the renderer writes `width`/`height`, the browser holds
/// the frame's place from first paint, and nothing below the carousel moves
/// when the picture lands.
pub intrinsic: Option<(u32, u32)>,
}
impl CarouselFrame {
/// Build a frame, nudging toward helpful alt text.
///
/// In debug builds this asserts the alt text is non-empty and looks like a
/// description rather than a filename or a one-word placeholder. The checks
/// are debug-only so they guide authors during development without ever
/// affecting a release render.
pub fn new(image: impl Into, alt: impl Into) -> Self {
let image = image.into();
let alt = alt.into();
debug_assert!(
!alt.trim().is_empty(),
"carousel frame `{image}` has empty alt text, describe what the \
screenshot shows so screen-reader users get the same information \
sighted viewers do"
);
debug_assert!(
!alt.trim_start().starts_with('/') && !alt.contains(".webp") && !alt.contains(".png"),
"carousel frame alt text looks like a filename (`{alt}`), write a \
human description of what the screenshot shows instead"
);
Self {
// Looked up rather than passed, so every caller that names a static
// shot reserves its space without having to know it did. A path the
// table does not carry -- a creator upload -- stays None, which is
// the honest answer and not a fallback.
intrinsic: shot_size(&image),
image,
alt,
caption: None,
}
}
/// Attach an optional caption shown under the frame.
#[must_use]
pub fn with_caption(mut self, caption: impl Into) -> Self {
self.caption = Some(caption.into());
self
}
}
/// Helper to convert any Askama template into an Axum response.
fn render_template(template: T) -> Response {
match template.render() {
Ok(html) => Html(html).into_response(),
Err(err) => {
tracing::error!(error = ?err, "template rendering error");
(StatusCode::INTERNAL_SERVER_ERROR, "Template error").into_response()
}
}
}
/// Implement `IntoResponse` for one or more Askama template structs.
macro_rules! impl_into_response {
($($T:ty),+ $(,)?) => {
$(
impl IntoResponse for $T {
fn into_response(self) -> Response {
render_template(self)
}
}
)+
};
}
impl_into_response!(
// Public pages
SandboxTemplate,
PolicyTemplate,
IndexTemplate,
LibraryTemplate,
CartTemplate,
LoginTemplate,
TwoFactorTemplate,
OAuthAuthorizeTemplate,
ForgotPasswordTemplate,
ResetPasswordTemplate,
UserTemplate,
ProjectTemplate,
ProjectPaywallTemplate,
ItemTemplate,
LibraryAudioTemplate,
LibraryDownloadsTemplate,
LibraryLockedTemplate,
LibraryTextTemplate,
LibraryVideoTemplate,
TextReaderTemplate,
AudioPlayerTemplate,
VideoPlayerTemplate,
DiscoverTemplate,
DiscoverResultsTemplate,
PurchaseTemplate,
ReceiptTemplate,
BuyPageTemplate,
StripeConnectDisclaimerTemplate,
// Blog pages
ProjectBlogTemplate,
BlogPostTemplate,
// Documentation pages
DocTemplate,
DocIndexTemplate,
// Platform economics + runway disclosure
EconomicsTemplate,
// Use cases
UseCasesTemplate,
// Team
TeamTemplate,
// Fan+
FanPlusTemplate,
// Creator invite system
CreatorsTemplate,
// Email & account
EmailResultTemplate,
EmailPreferencesTemplate,
ConfirmDeleteTemplate,
AccountDeletedTemplate,
AcknowledgeTemplate,
// Health
HealthTemplate,
// Dashboard pages
DashboardUserTemplate,
DashboardProjectTemplate,
DashboardItemTemplate,
// Admin
AdminWaitlistTemplate,
AdminUsersTemplate,
AdminUploadsTemplate,
AdminScanAuditTemplate,
AdminAppealsTemplate,
AdminReportsTemplate,
AdminSignupsTemplate,
AdminMailCapsTemplate,
AdminMailCapEntriesTemplate,
AdminMetricsTemplate,
AdminCompCodesTemplate,
// Export, import & account management
ExportPortalTemplate,
ImportPortalTemplate,
DeleteAccountTemplate,
BlogEditorTemplate,
// HTMX partials
AlertTemplate,
LibraryStatusTemplate,
ExportDownloadTemplate,
ExportContentReadyTemplate,
TransactionsTableTemplate,
UserProfileTabTemplate,
UserSettingsTabTemplate,
UserAccountTabTemplate,
UserPaymentsTabTemplate,
UserCreatorTabTemplate,
ProjectSettingsTabTemplate,
ProjectCodeTabTemplate,
ProjectSubscriptionsTabTemplate,
ProjectMonetizationTabTemplate,
ItemEditRowTemplate,
// Admin partials
AdminWaitlistEntriesTemplate,
AdminCompCodesEntriesTemplate,
AdminUserEntriesTemplate,
AdminUploadEntriesTemplate,
AdminQueueSummaryTemplate,
AdminAppealEntriesTemplate,
AdminReportEntriesTemplate,
SuspensionBannerTemplate,
// License keys
ItemLicenseKeysTemplate,
// Promo codes
PromoCodesListTemplate,
// Sessions
UserSessionsPartialTemplate,
// SyncKit
UserSyncKitTabTemplate,
ProjectSyncKitTabTemplate,
// Forums (Multithreaded)
// Media library
UserMediaTabTemplate,
// Support
// Collections
CollectionTemplate,
// Library tabs
LibraryPurchasesTabTemplate,
LibraryCollectionsTabTemplate,
// Follow button
FollowButtonTemplate,
TagFollowToggleTemplate,
// Tag suggestions
TagSuggestionsTemplate,
// Item analytics
ItemAnalyticsPartialTemplate,
// Item dashboard tabs
ItemOverviewTabTemplate,
ItemDetailsTabTemplate,
ItemPricingTabTemplate,
ItemVersionUploadTemplate,
ItemEmbedTabTemplate,
// Onboarding checklist
OnboardingChecklistPartialTemplate,
// Tag tree browser
TagTreeTemplate,
// TOTP 2FA
TotpSetupTemplate,
TotpStatusTemplate,
// Passkeys
PasskeyListTemplate,
// Git source browser
GitRepoTemplate,
GitTreeTemplate,
GitFileTemplate,
GitCommitsTemplate,
GitCommitDetailTemplate,
GitNotesTemplate,
GitAnnotationsTemplate,
GitTagsTemplate,
GitReplaceTemplate,
GitBlameTemplate,
GitUserReposTemplate,
GitExploreTemplate,
GitFileLogTemplate,
// Git issues
GitIssueListTemplate,
GitIssueDetailTemplate,
GitRepoSettingsTemplate,
// Join wizard
WizardJoinTemplate,
WizardJoinAccountTemplate,
WizardJoinProfileTemplate,
WizardJoinCompleteTemplate,
// Creation wizards, full pages
WizardProjectTemplate,
WizardItemTemplate,
// Creation wizards, project step partials
WizardProjectBasicsTemplate,
WizardProjectAppearanceTemplate,
WizardProjectMonetizationTemplate,
WizardProjectFirstContentTemplate,
WizardProjectPreviewTemplate,
// Creation wizards, item step partials
WizardItemTypeTemplate,
WizardItemBasicsTemplate,
WizardItemContentTemplate,
WizardItemSectionsTemplate,
WizardItemPricingTemplate,
WizardItemPreviewTemplate,
);