diff --git a/ChessCubing.App/ChessCubing.App.csproj b/ChessCubing.App/ChessCubing.App.csproj index 883d2b4..b15de4d 100644 --- a/ChessCubing.App/ChessCubing.App.csproj +++ b/ChessCubing.App/ChessCubing.App.csproj @@ -13,6 +13,7 @@ + diff --git a/ChessCubing.App/Components/PlayInviteOverlay.razor b/ChessCubing.App/Components/PlayInviteOverlay.razor new file mode 100644 index 0000000..ff47cea --- /dev/null +++ b/ChessCubing.App/Components/PlayInviteOverlay.razor @@ -0,0 +1,61 @@ +@implements IDisposable +@inject SocialRealtimeService Realtime +@inject NavigationManager Navigation + +@if (Realtime.IncomingPlayInvite is not null) +{ +
+ + +
+} + +@code { + private string RecipientColorLabel + => string.Equals(Realtime.IncomingPlayInvite?.RecipientColor, "white", StringComparison.Ordinal) + ? "blanc" + : "noir"; + + protected override async Task OnInitializedAsync() + { + Realtime.Changed += HandleRealtimeChanged; + await Realtime.EnsureStartedAsync(); + } + + private void HandleRealtimeChanged() + => _ = InvokeAsync(StateHasChanged); + + private async Task AcceptAsync() + { + await Realtime.RespondToIncomingPlayInviteAsync(accept: true); + + var currentPath = new Uri(Navigation.Uri).AbsolutePath.Trim('/'); + if (!string.Equals(currentPath, "application", StringComparison.OrdinalIgnoreCase) && + !string.Equals(currentPath, "application.html", StringComparison.OrdinalIgnoreCase)) + { + Navigation.NavigateTo("/application.html"); + } + } + + private Task DeclineAsync() + => Realtime.RespondToIncomingPlayInviteAsync(accept: false); + + public void Dispose() + => Realtime.Changed -= HandleRealtimeChanged; +} diff --git a/ChessCubing.App/Layout/MainLayout.razor b/ChessCubing.App/Layout/MainLayout.razor index b0fff71..c01c421 100644 --- a/ChessCubing.App/Layout/MainLayout.razor +++ b/ChessCubing.App/Layout/MainLayout.razor @@ -13,6 +13,8 @@ else @Body } + + @code { private bool HideGlobalMenu { diff --git a/ChessCubing.App/Models/Social/SocialModels.cs b/ChessCubing.App/Models/Social/SocialModels.cs new file mode 100644 index 0000000..2d3a15c --- /dev/null +++ b/ChessCubing.App/Models/Social/SocialModels.cs @@ -0,0 +1,135 @@ +namespace ChessCubing.App.Models.Social; + +public sealed class SocialOverviewResponse +{ + public SocialFriendResponse[] Friends { get; init; } = []; + + public SocialInvitationResponse[] ReceivedInvitations { get; init; } = []; + + public SocialInvitationResponse[] SentInvitations { get; init; } = []; +} + +public sealed class SocialFriendResponse +{ + public string Subject { get; init; } = string.Empty; + + public string Username { get; init; } = string.Empty; + + public string DisplayName { get; init; } = string.Empty; + + public string? Email { get; init; } + + public string? Club { get; init; } + + public string? City { get; init; } + + public bool IsOnline { get; init; } +} + +public sealed class SocialInvitationResponse +{ + public long InvitationId { get; init; } + + public string Subject { get; init; } = string.Empty; + + public string Username { get; init; } = string.Empty; + + public string DisplayName { get; init; } = string.Empty; + + public string? Email { get; init; } + + public bool IsOnline { get; init; } + + public DateTime CreatedUtc { get; init; } +} + +public sealed class SocialSearchUserResponse +{ + public string Subject { get; init; } = string.Empty; + + public string Username { get; init; } = string.Empty; + + public string DisplayName { get; init; } = string.Empty; + + public string? Email { get; init; } + + public string? Club { get; init; } + + public string? City { get; init; } + + public bool IsOnline { get; init; } + + public bool IsFriend { get; init; } + + public bool HasSentInvitation { get; init; } + + public bool HasReceivedInvitation { get; init; } +} + +public sealed class SendFriendInvitationRequest +{ + public string TargetSubject { get; init; } = string.Empty; +} + +public sealed class PresenceSnapshotMessage +{ + public string[] OnlineSubjects { get; init; } = []; +} + +public sealed class PresenceChangedMessage +{ + public string Subject { get; init; } = string.Empty; + + public bool IsOnline { get; init; } +} + +public sealed class PlayInviteMessage +{ + public string InviteId { get; init; } = string.Empty; + + public string SenderSubject { get; init; } = string.Empty; + + public string SenderUsername { get; init; } = string.Empty; + + public string SenderDisplayName { get; init; } = string.Empty; + + public string RecipientSubject { get; init; } = string.Empty; + + public string RecipientUsername { get; init; } = string.Empty; + + public string RecipientDisplayName { get; init; } = string.Empty; + + public string RecipientColor { get; init; } = string.Empty; + + public DateTime CreatedUtc { get; init; } + + public DateTime ExpiresUtc { get; init; } +} + +public sealed class PlayInviteClosedMessage +{ + public string InviteId { get; init; } = string.Empty; + + public string Reason { get; init; } = string.Empty; + + public string Message { get; init; } = string.Empty; +} + +public sealed class PlaySessionResponse +{ + public string SessionId { get; init; } = string.Empty; + + public string WhiteSubject { get; init; } = string.Empty; + + public string WhiteName { get; init; } = string.Empty; + + public string BlackSubject { get; init; } = string.Empty; + + public string BlackName { get; init; } = string.Empty; + + public string InitiatorSubject { get; init; } = string.Empty; + + public string RecipientSubject { get; init; } = string.Empty; + + public DateTime ConfirmedUtc { get; init; } +} diff --git a/ChessCubing.App/Pages/ApplicationPage.razor b/ChessCubing.App/Pages/ApplicationPage.razor index 11eebd3..dc843c5 100644 --- a/ChessCubing.App/Pages/ApplicationPage.razor +++ b/ChessCubing.App/Pages/ApplicationPage.razor @@ -10,6 +10,7 @@ @inject NavigationManager Navigation @inject AuthenticationStateProvider AuthenticationStateProvider @inject HttpClient Http +@inject SocialRealtimeService Realtime ChessCubing Arena | Application @@ -180,6 +181,93 @@ + @if (IsAuthenticated) + { +
+ + + + + @if (!string.IsNullOrWhiteSpace(SocialLoadError)) + { +

@SocialLoadError

+ } + + @if (!string.IsNullOrWhiteSpace(InviteActionError)) + { +

@InviteActionError

+ } + + @if (!string.IsNullOrWhiteSpace(Realtime.LastInviteNotice)) + { + + } + + @if (Realtime.OutgoingPlayInvite is not null) + { +
+
+ En attente + @Realtime.OutgoingPlayInvite.RecipientDisplayName +

Invitation envoyee pour jouer cote @(BuildColorLabel(Realtime.OutgoingPlayInvite.RecipientColor)).

+
+ +
+ } + + @if (IsSocialLoading) + { + + } + else if (OnlineFriends.Length > 0) + { +
+ @foreach (var friend in OnlineFriends) + { +
+ + +
+ + +
+
+ } +
+ } + else + { + + } +
+ } + @if (Form.CompetitionMode) {