accb3b2405
PR Checks / test-and-build (pull_request) Successful in 12m50s
Заменен Navigation.Uri.Contains() на QueryHelpers.ParseQuery для корректного определения параметра register без ложных срабатываний на подстроки (например, register=10). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
236 lines
8.8 KiB
Plaintext
236 lines
8.8 KiB
Plaintext
@page "/s/{SessionId:guid}"
|
|
@layout PublicLayout
|
|
@inject ISessionStore SessionStore
|
|
@inject NavigationManager Navigation
|
|
@inject AuthenticationStateProvider AuthStateProvider
|
|
@using GmRelay.Shared.Features.Showcase
|
|
@using GmRelay.Web.Services
|
|
|
|
<PageTitle>@PageTitleText</PageTitle>
|
|
|
|
@if (loaded && session is null)
|
|
{
|
|
<HeadContent>
|
|
<meta name="robots" content="noindex, nofollow" />
|
|
</HeadContent>
|
|
|
|
<section class="public-hero public-hero-compact">
|
|
<span class="status-badge status-neutral">Недоступно</span>
|
|
<h1>Сессия не опубликована</h1>
|
|
<p>Эта игра скрыта, отменена, уже прошла или клуб выключил публичное расписание.</p>
|
|
</section>
|
|
}
|
|
else if (!loaded)
|
|
{
|
|
<section class="public-hero public-hero-compact">
|
|
<div class="skeleton skeleton-text" style="width: 55%; height: 2rem;"></div>
|
|
<div class="skeleton skeleton-text" style="width: 75%;"></div>
|
|
</section>
|
|
}
|
|
else if (session is not null)
|
|
{
|
|
<HeadContent>
|
|
<meta name="description" content="@($"Публичная сессия {session.Title} клуба {session.GroupName} в GM-Relay.")" />
|
|
</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">
|
|
<span class="status-badge @GetStatusClass(session.Status)">@TranslateStatus(session.Status)</span>
|
|
<h1>@session.Title</h1>
|
|
<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>
|
|
|
|
<article class="glass-card public-session-detail">
|
|
<div class="public-detail-grid">
|
|
<div>
|
|
<span>Время</span>
|
|
<strong>@session.ScheduledAt.FormatMoscow()</strong>
|
|
</div>
|
|
<div>
|
|
<span>Места</span>
|
|
<strong>@FormatSeats(session)</strong>
|
|
</div>
|
|
<div>
|
|
<span>Статус</span>
|
|
<strong>@TranslateStatus(session.Status)</strong>
|
|
</div>
|
|
@if (session.DurationMinutes.HasValue)
|
|
{
|
|
<div>
|
|
<span>Длительность</span>
|
|
<strong>@FormatDuration(session.DurationMinutes.Value)</strong>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@if (!string.IsNullOrWhiteSpace(session.Description))
|
|
{
|
|
<div class="session-description">
|
|
<h3>Описание</h3>
|
|
<p>@session.Description</p>
|
|
</div>
|
|
}
|
|
|
|
@if (registrationResult is not null)
|
|
{
|
|
<div class="glass-card @GetRegistrationResultClass()">
|
|
<p>@registrationResult</p>
|
|
</div>
|
|
}
|
|
|
|
<div class="public-settings-actions">
|
|
@if (!string.IsNullOrWhiteSpace(session.GroupSlug))
|
|
{
|
|
<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>
|
|
@if (session.AllowDirectRegistration)
|
|
{
|
|
@if (isAuthenticated)
|
|
{
|
|
<button class="btn-gm btn-gm-primary" @onclick="RegisterAsync">Записаться</button>
|
|
}
|
|
else
|
|
{
|
|
<a class="btn-gm btn-gm-primary" href="@GetLoginUrl()">Войти, чтобы записаться</a>
|
|
}
|
|
}
|
|
</div>
|
|
</article>
|
|
}
|
|
|
|
@code {
|
|
[Parameter] public Guid SessionId { get; set; }
|
|
|
|
private ShowcaseSessionDto? session;
|
|
private bool loaded;
|
|
private bool isAuthenticated;
|
|
private string? registrationResult;
|
|
|
|
private string PageTitleText => session is null ? "Публичная сессия — GM-Relay" : $"{session.Title} — GM-Relay";
|
|
|
|
private string PublicSessionUrl => Navigation.ToAbsoluteUri($"/s/{SessionId}").ToString();
|
|
|
|
protected override async Task OnParametersSetAsync()
|
|
{
|
|
loaded = false;
|
|
registrationResult = null;
|
|
session = await SessionStore.GetShowcaseSessionAsync(SessionId);
|
|
|
|
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
|
isAuthenticated = authState.User.Identity?.IsAuthenticated ?? false;
|
|
|
|
var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
|
|
var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query);
|
|
var shouldRegister = query.TryGetValue("register", out var val) && val == "1";
|
|
|
|
if (session is not null && shouldRegister && session.AllowDirectRegistration)
|
|
{
|
|
if (isAuthenticated && authState.User.TryGetPlatformIdentity(out var platform, out var externalUserId))
|
|
{
|
|
var success = await SessionStore.RegisterFromShowcaseAsync(SessionId, platform, externalUserId, authState.User.Identity?.Name ?? "Игрок");
|
|
registrationResult = success
|
|
? "Вы успешно записались на игру!"
|
|
: "Не удалось записаться. Возможно, места закончились или вы уже зарегистрированы.";
|
|
}
|
|
else if (!isAuthenticated)
|
|
{
|
|
Navigation.NavigateTo($"/login?returnUrl={Uri.EscapeDataString($"/s/{SessionId}")}");
|
|
return;
|
|
}
|
|
}
|
|
|
|
loaded = true;
|
|
}
|
|
|
|
private async Task RegisterAsync()
|
|
{
|
|
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
|
if (authState.User.TryGetPlatformIdentity(out var platform, out var externalUserId))
|
|
{
|
|
var success = await SessionStore.RegisterFromShowcaseAsync(SessionId, platform, externalUserId, authState.User.Identity?.Name ?? "Игрок");
|
|
registrationResult = success
|
|
? "Вы успешно записались на игру!"
|
|
: "Не удалось записаться. Возможно, места закончились или вы уже зарегистрированы.";
|
|
}
|
|
}
|
|
|
|
private string GetLoginUrl() => $"/login?returnUrl={Uri.EscapeDataString($"/s/{SessionId}?register=1")}";
|
|
|
|
private string GetRegistrationResultClass() => registrationResult?.StartsWith("Вы успешно") == true ? "status-success-bg" : "status-warning-bg";
|
|
|
|
private static string FormatSeats(ShowcaseSessionDto session)
|
|
{
|
|
var seats = session.MaxPlayers.HasValue
|
|
? $"{session.ActivePlayerCount}/{session.MaxPlayers.Value}"
|
|
: $"{session.ActivePlayerCount} игроков";
|
|
|
|
return session.WaitlistedPlayerCount > 0
|
|
? $"{seats}, ожидание {session.WaitlistedPlayerCount}"
|
|
: 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
|
|
{
|
|
SessionStatus.Confirmed => "status-success",
|
|
SessionStatus.ConfirmationSent => "status-warning",
|
|
SessionStatus.Planned => "status-info",
|
|
_ => "status-neutral"
|
|
};
|
|
|
|
private static string TranslateStatus(string status) => status switch
|
|
{
|
|
SessionStatus.Planned => "Запланировано",
|
|
SessionStatus.ConfirmationSent => "Ждем подтверждения",
|
|
SessionStatus.Confirmed => "Подтверждено",
|
|
_ => status
|
|
};
|
|
}
|