Initial commit on Forgejo
Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
This commit is contained in:
commit
af6077b9c3
455 changed files with 78366 additions and 0 deletions
79
lib/tarakan_web/live/moderation_queue_live/index.ex
Normal file
79
lib/tarakan_web/live/moderation_queue_live/index.ex
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
defmodule TarakanWeb.ModerationQueueLive.Index do
|
||||
use TarakanWeb, :live_view
|
||||
|
||||
alias Tarakan.Moderation
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
case Moderation.list_open(socket.assigns.current_scope) do
|
||||
{:ok, cases} ->
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:page_title, "Moderation queue")
|
||||
|> assign(:case_count, length(cases))
|
||||
|> stream(:cases, cases)}
|
||||
|
||||
{:error, :unauthorized} ->
|
||||
{:ok,
|
||||
socket
|
||||
|> put_flash(:error, "Moderator access is required.")
|
||||
|> redirect(to: ~p"/")}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("assign", %{"id" => id}, socket) do
|
||||
with {:ok, case_record} <- Moderation.get_case(socket.assigns.current_scope, id),
|
||||
{:ok, _assigned} <- Moderation.assign(socket.assigns.current_scope, case_record) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> refresh_queue()
|
||||
|> put_flash(:info, "Case assigned for independent review.")}
|
||||
else
|
||||
error -> {:noreply, put_flash(socket, :error, error_message(error))}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("assign", _params, socket) do
|
||||
{:noreply, put_flash(socket, :error, "That case is no longer available.")}
|
||||
end
|
||||
|
||||
defp refresh_queue(socket) do
|
||||
case Moderation.list_open(socket.assigns.current_scope) do
|
||||
{:ok, cases} ->
|
||||
socket
|
||||
|> assign(:case_count, length(cases))
|
||||
|> stream(:cases, cases, reset: true)
|
||||
|
||||
{:error, :unauthorized} ->
|
||||
socket
|
||||
|> put_flash(:error, "Moderator access is required.")
|
||||
|> push_navigate(to: ~p"/")
|
||||
end
|
||||
end
|
||||
|
||||
defp can_assign?(case_record, scope) do
|
||||
independent = scope.account_id not in [case_record.reporter_id, case_record.subject_owner_id]
|
||||
|
||||
independent and
|
||||
(case_record.status == "open" or
|
||||
(case_record.status == "in_review" and scope.platform_role == "admin"))
|
||||
end
|
||||
|
||||
defp status_label("in_review"), do: "In review"
|
||||
defp status_label(status), do: String.capitalize(status)
|
||||
|
||||
defp subject_label("review_task"), do: "Job"
|
||||
|
||||
defp subject_label(subject_type),
|
||||
do: subject_type |> String.replace("_", " ") |> String.capitalize()
|
||||
|
||||
defp reason_label(reason), do: reason |> String.replace("_", " ") |> String.capitalize()
|
||||
|
||||
defp error_message({:error, :conflict_of_interest}),
|
||||
do: "An independent moderator must handle that case."
|
||||
|
||||
defp error_message({:error, :unauthorized}), do: "Moderator access is required."
|
||||
defp error_message({:error, :not_found}), do: "That case is no longer available."
|
||||
defp error_message({:error, _reason}), do: "The case could not be assigned."
|
||||
end
|
||||
75
lib/tarakan_web/live/moderation_queue_live/index.html.heex
Normal file
75
lib/tarakan_web/live/moderation_queue_live/index.html.heex
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<Layouts.app flash={@flash} current_scope={@current_scope} current_path={assigns[:current_path]}>
|
||||
<Layouts.page>
|
||||
<.breadcrumbs>
|
||||
<:crumb navigate={~p"/"}>registry</:crumb>
|
||||
<:crumb>moderation</:crumb>
|
||||
</.breadcrumbs>
|
||||
|
||||
<main id="moderation-queue">
|
||||
<header class="flex flex-wrap items-end justify-between gap-4 border-b-2 border-strong pb-6">
|
||||
<div>
|
||||
<h1 class="font-display text-3xl uppercase tracking-[0.02em] text-ink sm:text-4xl">
|
||||
Moderation queue
|
||||
</h1>
|
||||
<p class="mt-3 max-w-2xl text-sm leading-6 text-ink-muted">
|
||||
Restricted reports awaiting independent review.
|
||||
</p>
|
||||
</div>
|
||||
<span id="moderation-case-count" class="font-mono text-xs text-ink-faint">
|
||||
{@case_count} open
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<div id="moderation-cases" phx-update="stream" class="mt-6 grid gap-4">
|
||||
<div
|
||||
id="moderation-cases-empty"
|
||||
class="hidden border border-rule px-5 py-10 text-center text-sm text-ink-muted only:block"
|
||||
>
|
||||
No cases are waiting for review.
|
||||
</div>
|
||||
|
||||
<article
|
||||
:for={{dom_id, case_record} <- @streams.cases}
|
||||
id={dom_id}
|
||||
class="border-2 border-strong bg-panel px-5 py-5 sm:px-6"
|
||||
>
|
||||
<div class="flex flex-col gap-5 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div class="min-w-0">
|
||||
<div class="flex flex-wrap items-center gap-3 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-faint">
|
||||
<span>Case #{case_record.id}</span>
|
||||
<span>{status_label(case_record.status)}</span>
|
||||
<span>{subject_label(case_record.subject_type)} #{case_record.subject_id}</span>
|
||||
</div>
|
||||
<h2 class="mt-3 font-display text-lg uppercase tracking-[0.02em] text-ink">
|
||||
{reason_label(case_record.reason)}
|
||||
</h2>
|
||||
<p
|
||||
class="mt-2 line-clamp-3 max-w-3xl whitespace-pre-line text-sm leading-6 text-ink-muted"
|
||||
phx-no-format
|
||||
>{case_record.description}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex shrink-0 flex-wrap items-center gap-4">
|
||||
<button
|
||||
:if={can_assign?(case_record, @current_scope)}
|
||||
id={"assign-moderation-case-#{case_record.id}"}
|
||||
phx-click="assign"
|
||||
phx-value-id={case_record.id}
|
||||
class="clip-notch bg-btn px-4 py-2 font-display text-xs uppercase tracking-[0.12em] text-btn-fg transition hover:opacity-90 phx-click-loading:opacity-60"
|
||||
>
|
||||
Assign
|
||||
</button>
|
||||
<.link
|
||||
id={"view-moderation-case-#{case_record.id}"}
|
||||
navigate={~p"/moderation/cases/#{case_record.id}"}
|
||||
class="font-mono text-xs text-signal transition hover:underline"
|
||||
>
|
||||
View case
|
||||
</.link>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</main>
|
||||
</Layouts.page>
|
||||
</Layouts.app>
|
||||
Loading…
Add table
Add a link
Reference in a new issue