2026-10-06 16:47:19 +02:00
|
|
|
@inherits LocalizableComponentBase
|
|
|
|
|
|
|
|
|
|
@* Reports an account, and the post it was opened from, to the persona's server's moderators (/api/v1/reports).
|
2026-10-17 04:32:57 +02:00
|
|
|
Forwarded, a remote account's server gets the report too, from PrivaPub's own actor: it never names the persona. A
|
|
|
|
|
violation names the server's rules it breaks (/api/v1/instance/rules, sent as rule_ids); it is offered only while the
|
|
|
|
|
server has rules. *@
|
2026-10-06 22:43:35 +02:00
|
|
|
<DModal Placement="DModalPlacement.ResponsiveBottom" IsOpen="true" IsOpenChanged="Closed" CloseText="@Localizer["Close"]" data-report-dialog>
|
2026-10-06 16:47:19 +02:00
|
|
|
<Heading>@Localizer["Report {0}", Account.Name]</Heading>
|
|
|
|
|
<ChildContent>
|
|
|
|
|
<EditForm Model="form" OnValidSubmit="Send" class="flex flex-col gap-3 mt-3">
|
|
|
|
|
<DataAnnotationsValidator />
|
|
|
|
|
<ErrorViewer AdditionalError="@error" />
|
|
|
|
|
<DSelect TValue="ReportCategory" @bind-Value="form.Category" data-report-category>
|
|
|
|
|
<option value="@ReportCategory.Spam">@Localizer[nameof(ReportCategory.Spam)]</option>
|
|
|
|
|
<option value="@ReportCategory.Legal">@Localizer[nameof(ReportCategory.Legal)]</option>
|
2026-10-17 04:32:57 +02:00
|
|
|
@if (choices.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
<option value="@ReportCategory.Violation">@Localizer[nameof(ReportCategory.Violation)]</option>
|
|
|
|
|
}
|
2026-10-06 16:47:19 +02:00
|
|
|
<option value="@ReportCategory.Other">@Localizer[nameof(ReportCategory.Other)]</option>
|
|
|
|
|
</DSelect>
|
2026-10-17 04:32:57 +02:00
|
|
|
@if (form.Category is ReportCategory.Violation)
|
|
|
|
|
{
|
|
|
|
|
<fieldset class="flex flex-col gap-1 pointer-coarse:gap-2" data-report-rules>
|
|
|
|
|
<legend class="text-sm mb-1">@Localizer["Which of the server's rules does it break?"]</legend>
|
|
|
|
|
@foreach (var choice in choices)
|
|
|
|
|
{
|
|
|
|
|
<label class="flex items-start gap-2 cursor-pointer pointer-coarse:py-2">
|
|
|
|
|
<DCheckbox @bind-Value="choice.Picked" class="mt-0.5 flex-none" data-report-rule="@choice.Rule.Id" />
|
|
|
|
|
<span class="flex flex-col min-w-0">
|
|
|
|
|
<span class="text-sm break-words">@choice.Rule.Text</span>
|
|
|
|
|
@if (choice.Rule.Hint is { Length: > 0 } hint)
|
|
|
|
|
{
|
|
|
|
|
<span class="text-xs opacity-70 break-words">@hint</span>
|
|
|
|
|
}
|
|
|
|
|
</span>
|
|
|
|
|
</label>
|
|
|
|
|
}
|
|
|
|
|
</fieldset>
|
|
|
|
|
}
|
2026-10-06 16:47:19 +02:00
|
|
|
<DTextarea TValue="string" @bind-Value="form.Comment" Immediate="true" rows="3" autocomplete="off" maxlength="@ReportForm.CommentLength"
|
|
|
|
|
placeholder="@Localizer["What is wrong with it?"]" data-report-comment />
|
|
|
|
|
@if (Host is { } host)
|
|
|
|
|
{
|
|
|
|
|
<DCheckbox @bind-Value="form.Forward" LabelText="@Localizer["Send a copy to {0}", host]" data-report-forward />
|
|
|
|
|
<p class="text-xs opacity-70">@Localizer["The copy comes from the server, never from you."]</p>
|
|
|
|
|
}
|
2026-10-06 22:43:35 +02:00
|
|
|
<div class="flex justify-end gap-2">
|
|
|
|
|
<DButton OnClick="() => Closed(false)" data-report-cancel>@Localizer["Cancel"]</DButton>
|
|
|
|
|
<DButton Type="submit" Color="DColor.Error" Disabled="@(IsDefaultDisabled || isSending)" data-report-send>@Localizer["Report"]</DButton>
|
|
|
|
|
</div>
|
2026-10-06 16:47:19 +02:00
|
|
|
</EditForm>
|
|
|
|
|
</ChildContent>
|
|
|
|
|
</DModal>
|
|
|
|
|
|
|
|
|
|
@code {
|
|
|
|
|
[Inject] AccountService AccountService { get; set; }
|
2026-10-17 04:32:57 +02:00
|
|
|
[Inject] AboutService AboutService { get; set; }
|
2026-10-06 16:47:19 +02:00
|
|
|
|
|
|
|
|
[Parameter, EditorRequired] public Account Account { get; set; }
|
|
|
|
|
/// <summary>The post it was opened from, sent with the report.</summary>
|
|
|
|
|
[Parameter] public Status Post { get; set; }
|
|
|
|
|
[Parameter] public EventCallback OnClosed { get; set; }
|
|
|
|
|
|
|
|
|
|
ReportForm form = new();
|
|
|
|
|
bool isSending;
|
|
|
|
|
string error;
|
2026-10-17 04:32:57 +02:00
|
|
|
// the server's rules, each ticked or not, for a violation
|
|
|
|
|
List<RuleChoice> choices = [];
|
2026-10-06 16:47:19 +02:00
|
|
|
|
|
|
|
|
// the account's own server, for a remote account
|
|
|
|
|
string Host => Account.Acct?.Split('@') is [_, { Length: > 0 } host] ? host : null;
|
|
|
|
|
|
2026-10-17 04:32:57 +02:00
|
|
|
protected override async Task OnInitializedAsync()
|
|
|
|
|
{
|
|
|
|
|
if (CascadingState.IsOffline)
|
|
|
|
|
{
|
|
|
|
|
WhenOnline(LoadData);
|
|
|
|
|
StartOnlinePolling();
|
|
|
|
|
IsLoading = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await LoadData();
|
|
|
|
|
IsLoading = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async Task LoadData() => choices = [.. (await AboutService.Rules()).Select(rule => new RuleChoice { Rule = rule })];
|
2026-10-06 16:47:19 +02:00
|
|
|
|
|
|
|
|
async Task Send()
|
|
|
|
|
{
|
|
|
|
|
error = null;
|
2026-10-17 04:32:57 +02:00
|
|
|
var ruleIds = form.Category is ReportCategory.Violation ? choices.Where(c => c.Picked).Select(c => c.Rule.Id).ToList() : [];
|
|
|
|
|
if (form.Category is ReportCategory.Violation && ruleIds.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
error = Localizer["Tick the rules it breaks."];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isSending = true;
|
|
|
|
|
var result = await AccountService.Report(Account.Id, Post is null ? [] : [Post.Id], form.Comment, form.Category, ruleIds,
|
|
|
|
|
Host is not null && form.Forward);
|
2026-10-06 16:47:19 +02:00
|
|
|
isSending = false;
|
|
|
|
|
if (result.IsInvalid)
|
|
|
|
|
{
|
|
|
|
|
Toast.AddResponseError(result);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Toast.AddResponseSuccess(Localizer["Report sent to the moderators."]);
|
|
|
|
|
await OnClosed.InvokeAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Task Closed(bool open) => open ? Task.CompletedTask : OnClosed.InvokeAsync();
|
2026-10-17 04:32:57 +02:00
|
|
|
|
|
|
|
|
sealed class RuleChoice
|
|
|
|
|
{
|
|
|
|
|
public InstanceRule Rule { get; init; }
|
|
|
|
|
public bool Picked { get; set; }
|
|
|
|
|
}
|
2026-10-06 16:47:19 +02:00
|
|
|
}
|