feat(cms): compose Session hero image into the upload form (8.F)

Session upload now carries a deferred hero-image input; the submit handler
creates the release then POSTs the held hero to the existing resource-addressed
endpoint. Hero is optional with a non-blocking warn-if-missing gate. The
per-row hero upload in CmsSessionBrowser remains the replace/correct path.
This commit is contained in:
daniel-c-harvey
2026-06-13 20:46:46 -04:00
parent 18f4b596f2
commit 4701804594
4 changed files with 110 additions and 8 deletions
@@ -1,10 +1,51 @@
@using Microsoft.AspNetCore.Components.Forms
@* Session-medium fields. The hero image is resource-addressed (POST api/release/{id}/session/hero-image)
and therefore set after the release exists — managed per-row in the Sessions browser, not at create
time when no release id is yet assigned. This section states that contract so the admin knows where
the hero image is managed; it carries no input of its own. *@
and therefore cannot be uploaded until the release exists. The file is selected here, held by the
parent form, and uploaded after the create call returns a release id — the same deferred-upload
pattern AlbumHeaderFields uses for cover art ("Will upload on submit"). The hero is optional; the
parent warns (does not block) when a Session is submitted without one. *@
<MudItem xs="12">
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined">
Sessions are single-track live releases. After upload, set the hero image from the
<strong>Release Archive → Sessions</strong> browser.
</MudAlert>
<MudField Label="Hero Image" Variant="Variant.Outlined" InnerPadding="false">
<MudStack Spacing="3">
<MudText Typo="Typo.body2" Color="Color.Default">
Sessions are single-track live releases. The hero image is the session's primary visual identity.
</MudText>
@if (HeroImageFile is { } selectedHero)
{
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudText Typo="Typo.body2" Color="Color.Default">Selected: @selectedHero.Name</MudText>
<MudIconButton Icon="@Icons.Material.Filled.Clear"
Color="Color.Error"
Size="Size.Small"
Disabled="Disabled"
OnClick="ClearHeroFile"
aria-label="Cancel hero image selection" />
</MudStack>
}
else
{
<MudText Typo="Typo.body2" Color="Color.Default">No hero image — optional, but recommended.</MudText>
}
<InputFile OnChange="HandleHeroFileSelected" accept="image/*" disabled="@Disabled" />
@if (HeroImageFile is not null)
{
<MudText Typo="Typo.caption">Will upload on submit.</MudText>
}
</MudStack>
</MudField>
</MudItem>
@code {
[Parameter] public IBrowserFile? HeroImageFile { get; set; }
[Parameter] public EventCallback<IBrowserFile?> HeroImageFileChanged { get; set; }
[Parameter] public bool Disabled { get; set; }
private Task HandleHeroFileSelected(InputFileChangeEventArgs e) =>
HeroImageFileChanged.InvokeAsync(e.File);
private Task ClearHeroFile() =>
HeroImageFileChanged.InvokeAsync(null);
}