@page "/club/{Slug}" @layout PublicLayout @inject ISessionStore SessionStore @inject IPortfolioStore PortfolioStore @inject NavigationManager Navigation @inject IHttpContextAccessor HttpContextAccessor @inject AuthorizedMembershipService MembershipService @using System.Security.Claims @using GmRelay.Web.Components.Portfolio @using GmRelay.Web.Services.Portfolio @PageTitleText @if (loaded && club is null) {
Недоступно

Публичная страница не найдена

Расписание клуба выключено или адрес больше не используется.

} else if (!loaded) {
} else if (club is not null) {
Публичное расписание

@club.Name

Открытые игры клуба без состава игроков, личных данных и приватных ссылок.

Ссылка клуба @PublicClubUrl
@if (!string.IsNullOrWhiteSpace(club.MasterProfileSlug)) {
Мастер @(club.MasterDisplayName ?? "Профиль мастера")
}
@if (club.Sessions.Count == 0) {

Опубликованных игр пока нет

Когда owner или co-GM откроет сессии для публичного расписания, они появятся здесь.

} else { var publicSessions = club.Sessions.Where(s => !s.IsMembersOnly).ToList(); var membersOnlySessions = club.Sessions.Where(s => s.IsMembersOnly).ToList(); @if (publicSessions.Count > 0) {
@foreach (var session in publicSessions) {
@TranslateStatus(session.Status)

@session.Title

@session.ScheduledAt.FormatMoscow() @FormatSeats(session)
Открыть
}
} @if (membersOnlySessions.Count > 0) {

Игры для участников клуба

@if (viewerIsActiveMember) {
@foreach (var session in membersOnlySessions) {
Только для участников

@session.Title

@session.ScheduledAt.FormatMoscow() @FormatSeats(session)
Открыть
}
} else {

Эти сессии доступны только одобренным участникам клуба.

@if (viewerPlayerId is null) { Войти как участник } else {
Подать заявку
@if (!string.IsNullOrEmpty(applicationError)) {

@applicationError

}
} }
} } @if (portfolioGames.Count > 0) {

Завершённые игры клуба

Публичные портфолио, опубликованные мастерами этого клуба.

} } @code { [Parameter] public string? Slug { get; set; } private WebPublicClub? club; private IReadOnlyList portfolioGames = []; private bool loaded; private Guid? viewerPlayerId; private bool viewerIsActiveMember; private string? applicationError; private string? applicationMessage; private bool isSubmittingApplication; private async Task TrySubmitApplicationAsync() { applicationError = null; if (club is null) return; try { isSubmittingApplication = true; await MembershipService.ApplyForCurrentUserAsync(club.GroupId, applicationMessage); applicationMessage = null; } catch (InvalidOperationException ex) { applicationError = ex.Message; } finally { isSubmittingApplication = false; } } private string PageTitleText => club is null ? "Публичный клуб — GM-Relay" : $"{club.Name} — GM-Relay"; private string PublicClubUrl => club is null ? Navigation.ToAbsoluteUri($"/club/{Slug}").ToString() : Navigation.ToAbsoluteUri($"/club/{club.Slug}").ToString(); protected override async Task OnParametersSetAsync() { loaded = false; var trimmedSlug = string.IsNullOrWhiteSpace(Slug) ? null : Slug.Trim(); applicationError = null; applicationMessage = null; // Resolve viewer identity (player id) for member-aware access. var user = HttpContextAccessor.HttpContext?.User; if (user?.Identity?.IsAuthenticated == true && user.TryGetPlatformIdentity(out _, out var externalUserId)) { // We don't have platform here, but AuthorizedSessionService resolves via claims; use SessionStore directly // by reading both claims. Simpler: only resolve when both Platform and externalUserId are present. var platform = user.FindFirst("Platform")?.Value; viewerPlayerId = !string.IsNullOrWhiteSpace(platform) ? await SessionStore.GetPlayerIdByPlatformIdentityAsync(platform, externalUserId) : null; } else { viewerPlayerId = null; } club = trimmedSlug is null ? null : await SessionStore.GetPublicClubBySlugAsync(trimmedSlug, viewerPlayerId); portfolioGames = trimmedSlug is null ? [] : await PortfolioStore.GetPublicPortfolioGamesForClubAsync(trimmedSlug); if (club is not null && viewerPlayerId is not null) { viewerIsActiveMember = await SessionStore.IsActiveClubMemberAsync(club.GroupId, viewerPlayerId.Value); } else { viewerIsActiveMember = false; } loaded = true; } private string PublicSessionPath(Guid sessionId) => $"/s/{sessionId}"; private static string MasterProfilePath(string slug) => $"/gm/{slug}"; private static string FormatSeats(WebPublicSession session) { var seats = session.MaxPlayers.HasValue ? $"{session.ActivePlayerCount}/{session.MaxPlayers.Value}" : $"{session.ActivePlayerCount} игроков"; return session.WaitlistedPlayerCount > 0 ? $"{seats}, ожидание {session.WaitlistedPlayerCount}" : seats; } 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 }; }