From 8ea6ef84245bd63203c3397c6caf92626f484b8b Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 15 Apr 2026 23:08:48 +0200 Subject: [PATCH] Ajoute les amis et les invitations temps reel --- ChessCubing.App/ChessCubing.App.csproj | 1 + .../Components/PlayInviteOverlay.razor | 61 ++ ChessCubing.App/Layout/MainLayout.razor | 2 + ChessCubing.App/Models/Social/SocialModels.cs | 135 +++ ChessCubing.App/Pages/ApplicationPage.razor | 274 +++++- ChessCubing.App/Pages/UserPage.razor | 551 +++++++++++- ChessCubing.App/Program.cs | 1 + .../Services/SocialRealtimeService.cs | 329 ++++++++ ChessCubing.App/_Imports.razor | 1 + ChessCubing.Server/Program.cs | 206 +++++ .../Social/ConnectedUserTracker.cs | 60 ++ ChessCubing.Server/Social/MySqlSocialStore.cs | 787 ++++++++++++++++++ .../Social/PlayInviteCoordinator.cs | 211 +++++ ChessCubing.Server/Social/SocialContracts.cs | 137 +++ ChessCubing.Server/Social/SocialHub.cs | 168 ++++ .../Users/AuthenticatedSiteUser.cs | 23 + nginx.conf | 12 + styles.css | 202 +++++ 18 files changed, 3136 insertions(+), 25 deletions(-) create mode 100644 ChessCubing.App/Components/PlayInviteOverlay.razor create mode 100644 ChessCubing.App/Models/Social/SocialModels.cs create mode 100644 ChessCubing.App/Services/SocialRealtimeService.cs create mode 100644 ChessCubing.Server/Social/ConnectedUserTracker.cs create mode 100644 ChessCubing.Server/Social/MySqlSocialStore.cs create mode 100644 ChessCubing.Server/Social/PlayInviteCoordinator.cs create mode 100644 ChessCubing.Server/Social/SocialContracts.cs create mode 100644 ChessCubing.Server/Social/SocialHub.cs 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) {