diff --git a/src/GmRelay.Shared/Features/Showcase/ShowcaseSessionDto.cs b/src/GmRelay.Shared/Features/Showcase/ShowcaseSessionDto.cs
index 3c0e13e..4ff58c8 100644
--- a/src/GmRelay.Shared/Features/Showcase/ShowcaseSessionDto.cs
+++ b/src/GmRelay.Shared/Features/Showcase/ShowcaseSessionDto.cs
@@ -16,4 +16,5 @@ public sealed record ShowcaseSessionDto(
int? MaxPlayers,
int ActivePlayerCount,
int WaitlistedPlayerCount,
- bool AllowDirectRegistration);
+ bool AllowDirectRegistration,
+ string? Description);
diff --git a/src/GmRelay.Web/Components/Pages/PublicSession.razor b/src/GmRelay.Web/Components/Pages/PublicSession.razor
index 3abbe13..0fc2fae 100644
--- a/src/GmRelay.Web/Components/Pages/PublicSession.razor
+++ b/src/GmRelay.Web/Components/Pages/PublicSession.razor
@@ -2,6 +2,7 @@
@layout PublicLayout
@inject ISessionStore SessionStore
@inject NavigationManager Navigation
+@using GmRelay.Shared.Features.Showcase
@PageTitleText
@@ -30,10 +31,29 @@ else if (session is not null)
+ @if (!string.IsNullOrWhiteSpace(session.CoverImageUrl))
+ {
+
+ }
+
@TranslateStatus(session.Status)
@session.Title
@session.GroupName
+
+ @if (!string.IsNullOrWhiteSpace(session.System))
+ {
+ @GetSystemDisplayName(session.System)
+ }
+ @if (session.IsOneShot)
+ {
+ Ваншот
+ }
+ @if (!string.IsNullOrWhiteSpace(session.Format))
+ {
+ @TranslateFormat(session.Format)
+ }
+
@@ -50,14 +70,33 @@ else if (session is not null)
Статус
@TranslateStatus(session.Status)
+ @if (session.DurationMinutes.HasValue)
+ {
+
+ Длительность
+ @FormatDuration(session.DurationMinutes.Value)
+
+ }
+ @if (!string.IsNullOrWhiteSpace(session.Description))
+ {
+
+
Описание
+
@session.Description
+
+ }
+
}
@@ -65,7 +104,7 @@ else if (session is not null)
@code {
[Parameter] public Guid SessionId { get; set; }
- private WebPublicSession? session;
+ private ShowcaseSessionDto? session;
private bool loaded;
private string PageTitleText => session is null ? "Публичная сессия — GM-Relay" : $"{session.Title} — GM-Relay";
@@ -75,11 +114,11 @@ else if (session is not null)
protected override async Task OnParametersSetAsync()
{
loaded = false;
- session = await SessionStore.GetPublicSessionAsync(SessionId);
+ session = await SessionStore.GetShowcaseSessionAsync(SessionId);
loaded = true;
}
- private static string FormatSeats(WebPublicSession session)
+ private static string FormatSeats(ShowcaseSessionDto session)
{
var seats = session.MaxPlayers.HasValue
? $"{session.ActivePlayerCount}/{session.MaxPlayers.Value}"
@@ -90,6 +129,35 @@ else if (session is not null)
: seats;
}
+ private static string FormatDuration(int minutes)
+ {
+ if (minutes < 60)
+ return $"{minutes} мин";
+
+ var hours = minutes / 60;
+ var mins = minutes % 60;
+ return mins > 0 ? $"{hours} ч {mins} мин" : $"{hours} ч";
+ }
+
+ private static string GetSystemDisplayName(string? system)
+ {
+ if (string.IsNullOrWhiteSpace(system))
+ return system ?? string.Empty;
+
+ if (Enum.TryParse(system, out var gs))
+ return gs.ToDisplayName();
+
+ return system;
+ }
+
+ private static string TranslateFormat(string format) => format switch
+ {
+ "Online" => "Онлайн",
+ "Offline" => "Офлайн",
+ "Hybrid" => "Гибрид",
+ _ => format
+ };
+
private static string GetStatusClass(string status) => status switch
{
SessionStatus.Confirmed => "status-success",
diff --git a/src/GmRelay.Web/Services/SessionService.cs b/src/GmRelay.Web/Services/SessionService.cs
index 815512f..3e5f849 100644
--- a/src/GmRelay.Web/Services/SessionService.cs
+++ b/src/GmRelay.Web/Services/SessionService.cs
@@ -127,7 +127,8 @@ internal sealed record ShowcaseSessionRow(
int? MaxPlayers,
int ActivePlayerCount,
int WaitlistedPlayerCount,
- bool AllowDirectRegistration);
+ bool AllowDirectRegistration,
+ string? Description);
public sealed class SessionService(
NpgsqlDataSource dataSource,
@@ -400,7 +401,8 @@ public sealed class SessionService(
s.max_players AS MaxPlayers,
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
- s.allow_direct_registration AS AllowDirectRegistration
+ s.allow_direct_registration AS AllowDirectRegistration,
+ s.description AS Description
FROM sessions s
JOIN game_groups g ON g.id = s.group_id
LEFT JOIN LATERAL (
@@ -463,7 +465,8 @@ public sealed class SessionService(
return rows.Select(r => new ShowcaseSessionDto(
r.Id, r.GroupId, r.GroupName, r.GroupSlug, r.Title, r.ScheduledAt, r.Status,
r.System, r.IsOneShot, r.Format, r.DurationMinutes, r.CoverImageUrl,
- r.MaxPlayers, r.ActivePlayerCount, r.WaitlistedPlayerCount, r.AllowDirectRegistration)).ToList();
+ r.MaxPlayers, r.ActivePlayerCount, r.WaitlistedPlayerCount, r.AllowDirectRegistration,
+ r.Description)).ToList();
}
public async Task GetShowcaseSessionAsync(Guid sessionId)
@@ -486,7 +489,8 @@ public sealed class SessionService(
s.max_players AS MaxPlayers,
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
- s.allow_direct_registration AS AllowDirectRegistration
+ s.allow_direct_registration AS AllowDirectRegistration,
+ s.description AS Description
FROM sessions s
JOIN game_groups g ON g.id = s.group_id
LEFT JOIN LATERAL (
@@ -531,7 +535,8 @@ public sealed class SessionService(
return new ShowcaseSessionDto(
row.Id, row.GroupId, row.GroupName, row.GroupSlug, row.Title, row.ScheduledAt, row.Status,
row.System, row.IsOneShot, row.Format, row.DurationMinutes, row.CoverImageUrl,
- row.MaxPlayers, row.ActivePlayerCount, row.WaitlistedPlayerCount, row.AllowDirectRegistration);
+ row.MaxPlayers, row.ActivePlayerCount, row.WaitlistedPlayerCount, row.AllowDirectRegistration,
+ row.Description);
}
public async Task RegisterFromShowcaseAsync(Guid sessionId, string platform, string externalUserId, string displayName)