diff --git a/Modix.Web/Components/EditPromotionCommentDialog.razor b/Modix.Web/Components/EditPromotionCommentDialog.razor new file mode 100644 index 000000000..2b5c1638d --- /dev/null +++ b/Modix.Web/Components/EditPromotionCommentDialog.razor @@ -0,0 +1,43 @@ +@using Modix.Data.Models.Promotions; +@using MudBlazor + + + + Edit Comment + + +
+ + + + + + + + + +
+
+ +
+ Update +
+ Cancel +
+
+ + + +@code { + [CascadingParameter] + MudDialogInstance MudDialog { get; set; } + + [Parameter] + public PromotionSentiment PromotionSentiment { get; set; } + + [Parameter] + public required string Content { get; set; } + + void Submit() => MudDialog.Close(DialogResult.Ok((PromotionSentiment, Content))); + void Cancel() => MudDialog.Cancel(); +} diff --git a/Modix.Web/Pages/CreatePromotion.razor b/Modix.Web/Pages/CreatePromotion.razor new file mode 100644 index 000000000..fdc98ab10 --- /dev/null +++ b/Modix.Web/Pages/CreatePromotion.razor @@ -0,0 +1,131 @@ +@page "/promotions/create" +@using Modix.Data.Models.Core; +@using Modix.Services.Promotions; +@using Modix.Web.Components +@using Modix.Web.Models; +@using Modix.Web.Services; +@using MudBlazor + +@attribute [Authorize(Policy = nameof(AuthorizationClaim.PromotionsCreateCampaign))] + + + + + + Start a Campaign + + + +

Feel like someone deserves recognition? Start a promotion campaign for them - even if that person is yourself!

+
+ +

Once a campaign is started, users can anonymously comment, voicing their opinions for or against the individual up for promotion

+
+ +

Staff will periodically review campaigns. If approved, the user will be immediately promoted! If not, they may be permanently denied, or further looked into as the campaign runs its course.

+
+
+ + + + + @if(_selectedUser is not null && _nextRank is not null) + { +
+ @_selectedUser.Name can be promoted to this rank + @_nextRank.Name +
+ +
+ Finally, say a few words on their behalf + + + +
+ + + Submit + + } + +
+ +
+
+ +
+
+
+ + + + +@code { + public record NextRank(string? Name, string Color); + + [Inject] + public IPromotionsService PromotionsService { get; set; } = null!; + + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Inject] + public ISnackbar Snackbar { get; set; } = null!; + + [Inject] + public NavigationManager NavigationManager { get; set; } = null!; + + private ModixUser? _selectedUser; + private string? _promotionComment; + + private NextRank? _nextRank; + + private async Task SelectedUserChanged(ModixUser user) + { + if(user != _selectedUser) + { + _nextRank = null; + _promotionComment = null; + } + + _selectedUser = user; + if (user is null) + return; + + var nextRank = await PromotionsService.GetNextRankRoleForUserAsync(user.UserId); + var currentGuild = DiscordUserService.GetUserGuild(); + + _nextRank = new NextRank(nextRank?.Name, currentGuild.Roles.First(x => x.Id == nextRank?.Id).Color.ToString()); + } + + private async Task CreateCampaign() + { + try + { + await PromotionsService.CreateCampaignAsync(_selectedUser!.UserId, _promotionComment); + } + catch (InvalidOperationException ex) + { + Snackbar.Configuration.PositionClass = Defaults.Classes.Position.BottomCenter; + Snackbar.Add(ex.Message, Severity.Error); + return; + } + + NavigationManager.NavigateTo("/promotions"); + } +} diff --git a/Modix.Web/Pages/Promotions.razor b/Modix.Web/Pages/Promotions.razor index b436a0407..7761badd4 100644 --- a/Modix.Web/Pages/Promotions.razor +++ b/Modix.Web/Pages/Promotions.razor @@ -1,23 +1,215 @@ @page "/promotions" +@using Modix.Data.Models.Core; +@using Modix.Data.Models.Promotions; +@using Modix.Services.Promotions; +@using Modix.Web.Components; +@using Modix.Web.Services; +@using MudBlazor + +@attribute [Authorize(Policy = nameof(AuthorizationClaim.PromotionsRead))] - - - Promotions -

Promotion Campaigns

-

Current count: @currentCount

- + Promotions + + + Promotion Campaigns +
+ + Start One +
+ + @foreach(var campaign in Campaigns.Where(x => _showInactive ? true : (x.Outcome is null)).OrderByDescending(x => x.Outcome is null).ThenByDescending(x => x.CreateAction.Created)) + { + var isCurrentUserCampaign = CurrentUserId == campaign.Subject.Id; + + var icon = campaign.Outcome switch + { + PromotionCampaignOutcome.Accepted => Icons.Material.Filled.Check, + PromotionCampaignOutcome.Rejected => Icons.Material.Filled.NotInterested, + PromotionCampaignOutcome.Failed => Icons.Material.Filled.Error, + _ => Icons.Material.Filled.HowToVote + }; + + var sentimentRatio = isCurrentUserCampaign ? 0d : (double)campaign.ApproveCount / (campaign.ApproveCount + campaign.OpposeCount); + var sentimentColor = sentimentRatio switch + { + _ when isCurrentUserCampaign => Color.Transparent, + _ when sentimentRatio > 0.67 => Color.Success, + _ when sentimentRatio > 0.33 => Color.Warning, + _ => Color.Error + }; + + + +
+
+ + + + + @($"{campaign.Subject.Username + (campaign.Subject.Discriminator == "0000" ? "" : "#" + campaign.Subject.Discriminator)}") + + ➥ @campaign.TargetRole.Name +
+
+
+
+ + @(isCurrentUserCampaign ? "?" : campaign.ApproveCount.ToString()) +
+
+ + @(isCurrentUserCampaign ? "?" : campaign.OpposeCount.ToString()) +
+
+ +
+
+
+ + Campaign started @campaign.CreateAction.Created.ToString("dd/MM/yy, h:MM:ss tt") + + @if(campaign.Subject.Id == CurrentUserId) + { + Sorry, you aren't allowed to see comments on your own campaign. + } + else if(!campaignCommentData.ContainsKey(campaign.Id)) + { + + } + else + { + foreach(var comment in campaignCommentData[campaign.Id]) + { + var sentimentIcon = comment.PromotionSentiment == PromotionSentiment.Approve ? Icons.Material.Filled.ThumbUp : Icons.Material.Filled.ThumbDown; +
+ + @comment.Content + @if (comment.IsFromCurrentUser) + { + Edit + } + @comment.CreatedAt.ToString("dd/MM/yy, h:MM:ss tt") +
+ + } + } +
+
+ } +
+
-
-
+ + @code { - private int currentCount = 0; - private void IncrementCount() + public record CampaignCommentData(long Id, PromotionSentiment PromotionSentiment, string Content, DateTimeOffset CreatedAt, bool IsFromCurrentUser); + + + [Inject] + public DiscordUserService DiscordUserService { get; set; } = null!; + + [Inject] + public IPromotionsService PromotionsService { get; set; } = null!; + + [Inject] + public IDialogService DialogService { get; set; } = null!; + + private ulong CurrentUserId { get; set; } + + private IReadOnlyCollection Campaigns = Array.Empty(); + private Dictionary RoleColors = new Dictionary(); + private Dictionary> campaignCommentData = new Dictionary>(); + + private bool _showInactive; + + protected override async Task OnInitializedAsync() + { + var currentGuild = DiscordUserService.GetUserGuild(); + RoleColors = currentGuild.Roles.ToDictionary(x => x.Id, x => x.Color.ToString()); + + Campaigns = await PromotionsService.SearchCampaignsAsync(new PromotionCampaignSearchCriteria + { + GuildId = currentGuild.Id + }); + + var currentUser = await DiscordUserService.GetCurrentUserAsync(); + CurrentUserId = currentUser!.Id; + } + + protected override void OnAfterRender(bool firstRender) { - currentCount++; + base.OnAfterRender(firstRender); + } + + private async Task CampaignExpanded(bool wasExpanded, long campaignId, ulong userId) + { + if (!wasExpanded) + return; + + if (CurrentUserId == userId) + return; + + if (campaignCommentData.ContainsKey(campaignId)) + return; + + var result = await PromotionsService.GetCampaignDetailsAsync(campaignId); + + if (result is null) // TODO: What to do here? + return; + + //TODO: Fix IsFromCurrentUser + campaignCommentData[campaignId] = result.Comments + .Where(x => x.ModifyAction is null) + .Select(c => new CampaignCommentData(c.Id, c.Sentiment, c.Content, c.CreateAction.Created, c.CreateAction.CreatedBy.Id == CurrentUserId)) + .ToList(); + + StateHasChanged(); + + + // { + // c.Id, + // c.Sentiment, + // c.Content, + // CreateAction = new { c.CreateAction.Id, c.CreateAction.Created }, + // IsModified = !(c.ModifyAction is null), + // IsFromCurrentUser = c.CreateAction.CreatedBy.Id == ModixAuth.CurrentUserId, + // })); + } + + private async Task ToggleEditDialog(long commentId, PromotionSentiment oldPromotionSentiment, string oldContent) + { + var dialogParams = new DialogParameters + { + { nameof(EditPromotionCommentDialog.PromotionSentiment), oldPromotionSentiment }, + { nameof(EditPromotionCommentDialog.Content), oldContent} + }; + + var dialog = DialogService.Show("", dialogParams); + var result = await dialog.Result; + + if (result.Canceled) + return; + + var (newPromotionSentiment, newContent) = ((PromotionSentiment, string))result.Data; + + await PromotionsService.UpdateCommentAsync(commentId, newPromotionSentiment, newContent); } }