feat(web): update public session detail with showcase fields
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -16,4 +16,5 @@ public sealed record ShowcaseSessionDto(
|
|||||||
int? MaxPlayers,
|
int? MaxPlayers,
|
||||||
int ActivePlayerCount,
|
int ActivePlayerCount,
|
||||||
int WaitlistedPlayerCount,
|
int WaitlistedPlayerCount,
|
||||||
bool AllowDirectRegistration);
|
bool AllowDirectRegistration,
|
||||||
|
string? Description);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@layout PublicLayout
|
@layout PublicLayout
|
||||||
@inject ISessionStore SessionStore
|
@inject ISessionStore SessionStore
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
|
@using GmRelay.Shared.Features.Showcase
|
||||||
|
|
||||||
<PageTitle>@PageTitleText</PageTitle>
|
<PageTitle>@PageTitleText</PageTitle>
|
||||||
|
|
||||||
@@ -30,10 +31,29 @@ else if (session is not null)
|
|||||||
<meta name="description" content="@($"Публичная сессия {session.Title} клуба {session.GroupName} в GM-Relay.")" />
|
<meta name="description" content="@($"Публичная сессия {session.Title} клуба {session.GroupName} в GM-Relay.")" />
|
||||||
</HeadContent>
|
</HeadContent>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(session.CoverImageUrl))
|
||||||
|
{
|
||||||
|
<div class="session-cover-hero" style="background-image: url('@session.CoverImageUrl')"></div>
|
||||||
|
}
|
||||||
|
|
||||||
<section class="public-hero public-hero-compact">
|
<section class="public-hero public-hero-compact">
|
||||||
<span class="status-badge @GetStatusClass(session.Status)">@TranslateStatus(session.Status)</span>
|
<span class="status-badge @GetStatusClass(session.Status)">@TranslateStatus(session.Status)</span>
|
||||||
<h1>@session.Title</h1>
|
<h1>@session.Title</h1>
|
||||||
<p>@session.GroupName</p>
|
<p>@session.GroupName</p>
|
||||||
|
<div class="session-badges">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(session.System))
|
||||||
|
{
|
||||||
|
<span class="status-badge status-info">@GetSystemDisplayName(session.System)</span>
|
||||||
|
}
|
||||||
|
@if (session.IsOneShot)
|
||||||
|
{
|
||||||
|
<span class="status-badge status-warning">Ваншот</span>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(session.Format))
|
||||||
|
{
|
||||||
|
<span class="status-badge status-neutral">@TranslateFormat(session.Format)</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<article class="glass-card public-session-detail">
|
<article class="glass-card public-session-detail">
|
||||||
@@ -50,14 +70,33 @@ else if (session is not null)
|
|||||||
<span>Статус</span>
|
<span>Статус</span>
|
||||||
<strong>@TranslateStatus(session.Status)</strong>
|
<strong>@TranslateStatus(session.Status)</strong>
|
||||||
</div>
|
</div>
|
||||||
|
@if (session.DurationMinutes.HasValue)
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<span>Длительность</span>
|
||||||
|
<strong>@FormatDuration(session.DurationMinutes.Value)</strong>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(session.Description))
|
||||||
|
{
|
||||||
|
<div class="session-description">
|
||||||
|
<h3>Описание</h3>
|
||||||
|
<p>@session.Description</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="public-settings-actions">
|
<div class="public-settings-actions">
|
||||||
@if (!string.IsNullOrWhiteSpace(session.GroupSlug))
|
@if (!string.IsNullOrWhiteSpace(session.GroupSlug))
|
||||||
{
|
{
|
||||||
<a class="btn-gm btn-gm-primary" href="@($"/club/{session.GroupSlug}")">Расписание клуба</a>
|
<a class="btn-gm btn-gm-primary" href="@($"/club/{session.GroupSlug}")">Расписание клуба</a>
|
||||||
}
|
}
|
||||||
<a class="btn-gm btn-gm-outline" href="@PublicSessionUrl" target="_blank" rel="noopener noreferrer">Ссылка на сессию</a>
|
<a class="btn-gm btn-gm-outline" href="@PublicSessionUrl" target="_blank" rel="noopener noreferrer">Ссылка на сессию</a>
|
||||||
|
@if (session.AllowDirectRegistration)
|
||||||
|
{
|
||||||
|
<a class="btn-gm btn-gm-primary" href="@($"/s/{SessionId}?register=1")">Записаться</a>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
}
|
}
|
||||||
@@ -65,7 +104,7 @@ else if (session is not null)
|
|||||||
@code {
|
@code {
|
||||||
[Parameter] public Guid SessionId { get; set; }
|
[Parameter] public Guid SessionId { get; set; }
|
||||||
|
|
||||||
private WebPublicSession? session;
|
private ShowcaseSessionDto? session;
|
||||||
private bool loaded;
|
private bool loaded;
|
||||||
|
|
||||||
private string PageTitleText => session is null ? "Публичная сессия — GM-Relay" : $"{session.Title} — GM-Relay";
|
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()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
loaded = false;
|
loaded = false;
|
||||||
session = await SessionStore.GetPublicSessionAsync(SessionId);
|
session = await SessionStore.GetShowcaseSessionAsync(SessionId);
|
||||||
loaded = true;
|
loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatSeats(WebPublicSession session)
|
private static string FormatSeats(ShowcaseSessionDto session)
|
||||||
{
|
{
|
||||||
var seats = session.MaxPlayers.HasValue
|
var seats = session.MaxPlayers.HasValue
|
||||||
? $"{session.ActivePlayerCount}/{session.MaxPlayers.Value}"
|
? $"{session.ActivePlayerCount}/{session.MaxPlayers.Value}"
|
||||||
@@ -90,6 +129,35 @@ else if (session is not null)
|
|||||||
: seats;
|
: 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<GameSystem>(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
|
private static string GetStatusClass(string status) => status switch
|
||||||
{
|
{
|
||||||
SessionStatus.Confirmed => "status-success",
|
SessionStatus.Confirmed => "status-success",
|
||||||
|
|||||||
@@ -127,7 +127,8 @@ internal sealed record ShowcaseSessionRow(
|
|||||||
int? MaxPlayers,
|
int? MaxPlayers,
|
||||||
int ActivePlayerCount,
|
int ActivePlayerCount,
|
||||||
int WaitlistedPlayerCount,
|
int WaitlistedPlayerCount,
|
||||||
bool AllowDirectRegistration);
|
bool AllowDirectRegistration,
|
||||||
|
string? Description);
|
||||||
|
|
||||||
public sealed class SessionService(
|
public sealed class SessionService(
|
||||||
NpgsqlDataSource dataSource,
|
NpgsqlDataSource dataSource,
|
||||||
@@ -400,7 +401,8 @@ public sealed class SessionService(
|
|||||||
s.max_players AS MaxPlayers,
|
s.max_players AS MaxPlayers,
|
||||||
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
|
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
|
||||||
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
|
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
|
FROM sessions s
|
||||||
JOIN game_groups g ON g.id = s.group_id
|
JOIN game_groups g ON g.id = s.group_id
|
||||||
LEFT JOIN LATERAL (
|
LEFT JOIN LATERAL (
|
||||||
@@ -463,7 +465,8 @@ public sealed class SessionService(
|
|||||||
return rows.Select(r => new ShowcaseSessionDto(
|
return rows.Select(r => new ShowcaseSessionDto(
|
||||||
r.Id, r.GroupId, r.GroupName, r.GroupSlug, r.Title, r.ScheduledAt, r.Status,
|
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.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<ShowcaseSessionDto?> GetShowcaseSessionAsync(Guid sessionId)
|
public async Task<ShowcaseSessionDto?> GetShowcaseSessionAsync(Guid sessionId)
|
||||||
@@ -486,7 +489,8 @@ public sealed class SessionService(
|
|||||||
s.max_players AS MaxPlayers,
|
s.max_players AS MaxPlayers,
|
||||||
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
|
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
|
||||||
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
|
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
|
FROM sessions s
|
||||||
JOIN game_groups g ON g.id = s.group_id
|
JOIN game_groups g ON g.id = s.group_id
|
||||||
LEFT JOIN LATERAL (
|
LEFT JOIN LATERAL (
|
||||||
@@ -531,7 +535,8 @@ public sealed class SessionService(
|
|||||||
return new ShowcaseSessionDto(
|
return new ShowcaseSessionDto(
|
||||||
row.Id, row.GroupId, row.GroupName, row.GroupSlug, row.Title, row.ScheduledAt, row.Status,
|
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.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<bool> RegisterFromShowcaseAsync(Guid sessionId, string platform, string externalUserId, string displayName)
|
public async Task<bool> RegisterFromShowcaseAsync(Guid sessionId, string platform, string externalUserId, string displayName)
|
||||||
|
|||||||
Reference in New Issue
Block a user