Improve admin candidate modal UX and add clip menu visibility toggle
- Widen AdminCandidateEditorModal to size lg for better readability - Rename "Clip-Compilation" section to "Clip / Compilation", update copy to reflect single clips too, drop upload hint and Clip-Plattform field, rename label to "Link" - Fix NativeSelect dropdown clipping inside overflow-y-auto modals by teleporting the menu to body with fixed positioning, flip-up logic, and dynamic maxHeight capped to viewport - Add ClipAdminMenuVisible setting (backend domain, contracts, endpoint, migration) with matching frontend types, defaults, form wiring, and toggle in the Clip-Workflow modal — hides the Clips nav item from the admin sidebar when disabled Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ public static class AdminSiteSettingsEndpoints
|
||||
{
|
||||
private const string FallbackMaintenanceTitle = "Sternenpause";
|
||||
private const string FallbackMaintenanceMessage = "Die Award-Galaxie wird gerade liebevoll poliert. Schau gleich wieder vorbei.";
|
||||
private const string FallbackClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.";
|
||||
|
||||
public static RouteGroupBuilder MapAdminSiteSettingsEndpoints(this RouteGroupBuilder group)
|
||||
{
|
||||
@@ -32,6 +33,14 @@ public static class AdminSiteSettingsEndpoints
|
||||
.AddEndpointFilter((context, next) => AdminEndpointConventions.RequireWritePermission(context, next, AdminPermissionCatalog.Settings))
|
||||
.WithName("UpdateAdminOperationalSettings")
|
||||
.WithOpenApi();
|
||||
group.MapGet("/optional-feature-settings", GetOptionalFeatureSettings)
|
||||
.AddEndpointFilter((context, next) => AdminEndpointConventions.RequirePermission(context, next, AdminPermissionCatalog.Settings))
|
||||
.WithName("GetAdminOptionalFeatureSettings")
|
||||
.WithOpenApi();
|
||||
group.MapPut("/optional-feature-settings", UpdateOptionalFeatureSettings)
|
||||
.AddEndpointFilter((context, next) => AdminEndpointConventions.RequireWritePermission(context, next, AdminPermissionCatalog.Settings))
|
||||
.WithName("UpdateAdminOptionalFeatureSettings")
|
||||
.WithOpenApi();
|
||||
return group;
|
||||
}
|
||||
|
||||
@@ -57,6 +66,8 @@ public static class AdminSiteSettingsEndpoints
|
||||
settings.ContactContent,
|
||||
settings.SponsorsUrl,
|
||||
settings.SponsorsContent,
|
||||
settings.ShowactsUrl,
|
||||
settings.ShowactsContent,
|
||||
SeasonMappings.ReadSocialLinks(settings),
|
||||
SeasonMappings.ReadFaqItems(settings)));
|
||||
}
|
||||
@@ -100,6 +111,8 @@ public static class AdminSiteSettingsEndpoints
|
||||
settings.ContactContent = request.ContactContent.Trim();
|
||||
settings.SponsorsUrl = normalizedUrls.SponsorsUrl;
|
||||
settings.SponsorsContent = request.SponsorsContent.Trim();
|
||||
settings.ShowactsUrl = normalizedUrls.ShowactsUrl;
|
||||
settings.ShowactsContent = request.ShowactsContent.Trim();
|
||||
settings.SocialLinksJson = JsonSerializer.Serialize(socialLinks);
|
||||
settings.FaqJson = JsonSerializer.Serialize(request.Faq ?? []);
|
||||
|
||||
@@ -133,7 +146,8 @@ public static class AdminSiteSettingsEndpoints
|
||||
if (!TryNormalizePublicUrl(request.NewsletterUrl, "Newsletter-Link", out var newsletterUrl, out var errorMessage)
|
||||
|| !TryNormalizePublicUrl(request.ImprintUrl, "Impressum-Link", out var imprintUrl, out errorMessage)
|
||||
|| !TryNormalizePublicUrl(request.ContactUrl, "Kontakt-Link", out var contactUrl, out errorMessage)
|
||||
|| !TryNormalizePublicUrl(request.SponsorsUrl, "Sponsoren-Link", out var sponsorsUrl, out errorMessage))
|
||||
|| !TryNormalizePublicUrl(request.SponsorsUrl, "Sponsoren-Link", out var sponsorsUrl, out errorMessage)
|
||||
|| !TryNormalizePublicUrl(request.ShowactsUrl, "Showact-Link", out var showactsUrl, out errorMessage))
|
||||
{
|
||||
return Results.BadRequest(new { message = errorMessage });
|
||||
}
|
||||
@@ -144,6 +158,7 @@ public static class AdminSiteSettingsEndpoints
|
||||
ImprintUrl = imprintUrl,
|
||||
ContactUrl = contactUrl,
|
||||
SponsorsUrl = sponsorsUrl,
|
||||
ShowactsUrl = showactsUrl,
|
||||
};
|
||||
|
||||
var normalizedSocialLinks = new List<PublicSocialLinkDto>();
|
||||
@@ -187,6 +202,108 @@ public static class AdminSiteSettingsEndpoints
|
||||
public string ImprintUrl { get; set; } = string.Empty;
|
||||
public string ContactUrl { get; set; } = string.Empty;
|
||||
public string SponsorsUrl { get; set; } = string.Empty;
|
||||
public string ShowactsUrl { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetOptionalFeatureSettings(AwardsDbContext db)
|
||||
{
|
||||
var settings = await db.SiteSettings.AsNoTracking().FirstOrDefaultAsync(item => item.Id == 1);
|
||||
if (settings is null)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
return Results.Ok(ToOptionalFeatureSettingsResponse(settings));
|
||||
}
|
||||
|
||||
private static async Task<IResult> UpdateOptionalFeatureSettings(
|
||||
HttpContext context,
|
||||
UpdateOptionalFeatureSettingsRequest request,
|
||||
AwardsDbContext db,
|
||||
IAdminAuditService adminAuditService)
|
||||
{
|
||||
var session = AdminEndpointConventions.CurrentSession(context);
|
||||
var settings = await db.SiteSettings.FirstOrDefaultAsync(item => item.Id == 1);
|
||||
if (settings is null)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
var before = ToOptionalFeatureSettingsResponse(settings);
|
||||
var disabledMessage = NormalizeOptionalFeatureText(
|
||||
request.ClipSubmissionDisabledMessage,
|
||||
FallbackClipSubmissionDisabledMessage,
|
||||
240);
|
||||
var showactDisabledMessage = NormalizeOptionalFeatureText(
|
||||
request.ShowactApplicationDisabledMessage,
|
||||
"Showact-Bewerbungen sind aktuell geschlossen.",
|
||||
240);
|
||||
|
||||
settings.ClipSubmissionsEnabled = request.ClipSubmissionsEnabled;
|
||||
settings.ClipReviewEnabled = request.ClipReviewEnabled;
|
||||
settings.ClipAdminMenuVisible = request.ClipAdminMenuVisible;
|
||||
settings.ClipSubmissionDisabledMessage = disabledMessage;
|
||||
settings.ShowactApplicationsEnabled = request.ShowactApplicationsEnabled;
|
||||
settings.ShowactApplicationDisabledMessage = showactDisabledMessage;
|
||||
settings.SponsorsVisible = request.SponsorsVisible;
|
||||
|
||||
var after = ToOptionalFeatureSettingsResponse(settings);
|
||||
adminAuditService.AddEntry(
|
||||
session.TwitchUserId,
|
||||
"optional-features.update",
|
||||
"site-settings",
|
||||
settings.Id.ToString(),
|
||||
"Optionale Workflow-Features wurden aktualisiert.",
|
||||
new
|
||||
{
|
||||
before,
|
||||
after,
|
||||
changes = BuildOptionalFeatureChanges(before, after),
|
||||
},
|
||||
RequestMetadataReader.Read(context));
|
||||
|
||||
await db.SaveChangesAsync(context.RequestAborted);
|
||||
return Results.Ok(after);
|
||||
}
|
||||
|
||||
private static AdminOptionalFeatureSettingsResponse ToOptionalFeatureSettingsResponse(SiteSettings settings) =>
|
||||
new(
|
||||
settings.ClipSubmissionsEnabled,
|
||||
settings.ClipReviewEnabled,
|
||||
settings.ClipAdminMenuVisible,
|
||||
string.IsNullOrWhiteSpace(settings.ClipSubmissionDisabledMessage)
|
||||
? FallbackClipSubmissionDisabledMessage
|
||||
: settings.ClipSubmissionDisabledMessage,
|
||||
settings.ShowactApplicationsEnabled,
|
||||
string.IsNullOrWhiteSpace(settings.ShowactApplicationDisabledMessage)
|
||||
? "Showact-Bewerbungen sind aktuell geschlossen."
|
||||
: settings.ShowactApplicationDisabledMessage,
|
||||
settings.SponsorsVisible);
|
||||
|
||||
private static string NormalizeOptionalFeatureText(string? value, string fallback, int maxLength)
|
||||
{
|
||||
var trimmed = (value ?? string.Empty).Trim();
|
||||
if (string.IsNullOrWhiteSpace(trimmed))
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return trimmed.Length <= maxLength ? trimmed : trimmed[..maxLength];
|
||||
}
|
||||
|
||||
private static object[] BuildOptionalFeatureChanges(
|
||||
AdminOptionalFeatureSettingsResponse before,
|
||||
AdminOptionalFeatureSettingsResponse after)
|
||||
{
|
||||
var changes = new List<object>();
|
||||
AddOperationalChange(changes, "clipSubmissionsEnabled", "Clip-Einreichung", before.ClipSubmissionsEnabled, after.ClipSubmissionsEnabled);
|
||||
AddOperationalChange(changes, "clipReviewEnabled", "Clip-Review", before.ClipReviewEnabled, after.ClipReviewEnabled);
|
||||
AddOperationalChange(changes, "clipAdminMenuVisible", "Clips-Menüpunkt", before.ClipAdminMenuVisible, after.ClipAdminMenuVisible);
|
||||
AddOperationalChange(changes, "clipSubmissionDisabledMessage", "Deaktiviert-Hinweis", before.ClipSubmissionDisabledMessage, after.ClipSubmissionDisabledMessage);
|
||||
AddOperationalChange(changes, "showactApplicationsEnabled", "Showact-Bewerbungen", before.ShowactApplicationsEnabled, after.ShowactApplicationsEnabled);
|
||||
AddOperationalChange(changes, "showactApplicationDisabledMessage", "Showact-Hinweis", before.ShowactApplicationDisabledMessage, after.ShowactApplicationDisabledMessage);
|
||||
AddOperationalChange(changes, "sponsorsVisible", "Sponsoren sichtbar", before.SponsorsVisible, after.SponsorsVisible);
|
||||
return changes.ToArray();
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetOperationalSettings(AwardsDbContext db, IConfiguration configuration)
|
||||
|
||||
Reference in New Issue
Block a user