Files
decePubClient/Components/ReportDialog.razor
T
thepraandClaude Opus 5.5 4c88980374
Build / Build (push) Successful in 1m5s
R5 the server's own settings in decePub: instance, rules, announcements, relays, media limits, the staff's log, About
Administration's Pleroma placeholders give way to real sections: Instance (title, descriptions, contact, thumbnail,
privacy policy, terms, the weekly activity), Rules, Announcements, Relays, Media limits and the Moderation log, next to
Reports, blocked servers (now published or obfuscated), statistics and backups; moderators see Reports and the log. Every
save sends the whole settings form built from the latest read, since PrivaPub replaces what it carries. /about shows the
server's documents to anyone. The report dialog picks the rules a violation breaks, and reports show them. Announcements
show above the feed tabs, dismissed and reacted to, live from the user stream. Login names the server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
2026-10-17 04:32:57 +02:00

122 lines
4.6 KiB
Plaintext

@inherits LocalizableComponentBase
@* Reports an account, and the post it was opened from, to the persona's server's moderators (/api/v1/reports).
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. *@
<DModal Placement="DModalPlacement.ResponsiveBottom" IsOpen="true" IsOpenChanged="Closed" CloseText="@Localizer["Close"]" data-report-dialog>
<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>
@if (choices.Count > 0)
{
<option value="@ReportCategory.Violation">@Localizer[nameof(ReportCategory.Violation)]</option>
}
<option value="@ReportCategory.Other">@Localizer[nameof(ReportCategory.Other)]</option>
</DSelect>
@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>
}
<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>
}
<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>
</EditForm>
</ChildContent>
</DModal>
@code {
[Inject] AccountService AccountService { get; set; }
[Inject] AboutService AboutService { get; set; }
[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;
// the server's rules, each ticked or not, for a violation
List<RuleChoice> choices = [];
// the account's own server, for a remote account
string Host => Account.Acct?.Split('@') is [_, { Length: > 0 } host] ? host : null;
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 })];
async Task Send()
{
error = null;
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);
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();
sealed class RuleChoice
{
public InstanceRule Rule { get; init; }
public bool Picked { get; set; }
}
}