Packages
phoenix_kit
1.7.64
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/modules/comments/web/index.ex
defmodule PhoenixKit.Modules.Comments.Web.Index do
@moduledoc """
LiveView for comment moderation admin page.
Provides cross-resource comment management with filtering, search,
pagination, and bulk actions.
## Route
Mounted at `{prefix}/admin/comments`.
"""
use PhoenixKitWeb, :live_view
alias PhoenixKit.Modules.Comments
alias PhoenixKit.Settings
alias PhoenixKit.Utils.Routes
@impl true
def mount(_params, _session, socket) do
if Comments.enabled?() do
project_title = Settings.get_project_title()
socket =
socket
|> assign(:page_title, "Comments")
|> assign(:project_title, project_title)
|> assign(:comments, [])
|> assign(:total, 0)
|> assign(:total_pages, 1)
|> assign(:resource_context, %{})
|> assign(:stats, Comments.comment_stats())
|> assign(:selected_uuids, [])
|> assign_filter_defaults()
{:ok, socket}
else
{:ok,
socket
|> put_flash(:error, "Comments module is not enabled")
|> push_navigate(to: Routes.path("/admin"))}
end
end
@impl true
def handle_params(params, _url, socket) do
socket =
socket
|> apply_params(params)
|> load_comments()
{:noreply, socket}
end
@impl true
def handle_event("filter", params, socket) do
combined_params = %{"page" => "1"}
combined_params =
case Map.get(params, "search") do
%{"query" => query} -> Map.put(combined_params, "search", String.trim(query || ""))
_ -> combined_params
end
combined_params =
case Map.get(params, "filter") do
filter_params when is_map(filter_params) -> Map.merge(combined_params, filter_params)
_ -> combined_params
end
new_params = build_url_params(socket.assigns, combined_params)
{:noreply, push_patch(socket, to: Routes.path("/admin/comments?#{new_params}"))}
end
@impl true
def handle_event("clear_filters", _params, socket) do
{:noreply, push_patch(socket, to: Routes.path("/admin/comments"))}
end
@impl true
def handle_event("approve", %{"id" => id}, socket) do
case Comments.get_comment(id) do
nil ->
{:noreply, put_flash(socket, :error, "Comment not found")}
comment ->
Comments.approve_comment(comment)
{:noreply,
socket |> load_comments() |> reload_stats() |> put_flash(:info, "Comment approved")}
end
end
@impl true
def handle_event("hide", %{"id" => id}, socket) do
case Comments.get_comment(id) do
nil ->
{:noreply, put_flash(socket, :error, "Comment not found")}
comment ->
Comments.hide_comment(comment)
{:noreply,
socket |> load_comments() |> reload_stats() |> put_flash(:info, "Comment hidden")}
end
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
case Comments.get_comment(id) do
nil ->
{:noreply, put_flash(socket, :error, "Comment not found")}
comment ->
Comments.delete_comment(comment)
{:noreply,
socket |> load_comments() |> reload_stats() |> put_flash(:info, "Comment deleted")}
end
end
@impl true
def handle_event("toggle_select", %{"uuid" => uuid}, socket) do
selected = socket.assigns.selected_uuids
selected =
if uuid in selected,
do: List.delete(selected, uuid),
else: [uuid | selected]
{:noreply, assign(socket, :selected_uuids, selected)}
end
@impl true
def handle_event("bulk_action", %{"action" => action}, socket) do
uuids = socket.assigns.selected_uuids
if uuids == [] do
{:noreply, put_flash(socket, :error, "No comments selected")}
else
case action do
"approve" ->
Comments.bulk_update_status(uuids, "published")
{:noreply,
socket
|> assign(:selected_uuids, [])
|> load_comments()
|> reload_stats()
|> put_flash(:info, "Comments approved")}
"hide" ->
Comments.bulk_update_status(uuids, "hidden")
{:noreply,
socket
|> assign(:selected_uuids, [])
|> load_comments()
|> reload_stats()
|> put_flash(:info, "Comments hidden")}
"delete" ->
Comments.bulk_update_status(uuids, "deleted")
{:noreply,
socket
|> assign(:selected_uuids, [])
|> load_comments()
|> reload_stats()
|> put_flash(:info, "Comments deleted")}
_ ->
{:noreply, socket}
end
end
end
## --- Private ---
defp assign_filter_defaults(socket) do
socket
|> assign(:page, 1)
|> assign(:per_page, 20)
|> assign(:search, "")
|> assign(:filter_resource_type, nil)
|> assign(:filter_status, nil)
end
defp apply_params(socket, params) do
socket
|> assign(:page, parse_int(params["page"], 1))
|> assign(:search, params["search"] || "")
|> assign(:filter_resource_type, blank_to_nil(params["resource_type"]))
|> assign(:filter_status, blank_to_nil(params["status"]))
end
defp load_comments(socket) do
result =
Comments.list_all_comments(
page: socket.assigns.page,
per_page: socket.assigns.per_page,
search: socket.assigns.search,
resource_type: socket.assigns.filter_resource_type,
status: socket.assigns.filter_status
)
resource_context = Comments.resolve_resource_context(result.comments)
socket
|> assign(:comments, result.comments)
|> assign(:total, result.total)
|> assign(:total_pages, result.total_pages)
|> assign(:resource_context, resource_context)
end
defp reload_stats(socket) do
assign(socket, :stats, Comments.comment_stats())
end
defp build_url_params(assigns, overrides) do
params =
%{}
|> maybe_put("page", Map.get(overrides, "page", to_string(assigns.page)))
|> maybe_put("search", Map.get(overrides, "search", assigns.search))
|> maybe_put(
"resource_type",
Map.get(overrides, "resource_type", assigns.filter_resource_type)
)
|> maybe_put("status", Map.get(overrides, "status", assigns.filter_status))
URI.encode_query(params)
end
defp maybe_put(map, _key, nil), do: map
defp maybe_put(map, _key, ""), do: map
defp maybe_put(map, key, value), do: Map.put(map, key, value)
defp parse_int(nil, default), do: default
defp parse_int(str, default) when is_binary(str) do
case Integer.parse(str) do
{n, _} -> max(n, 1)
:error -> default
end
end
defp blank_to_nil(nil), do: nil
defp blank_to_nil(""), do: nil
defp blank_to_nil(val), do: val
defp resource_info(resource_context, comment) do
Map.get(resource_context, {comment.resource_type, comment.resource_uuid})
end
defp status_badge_class("published"), do: "badge badge-success badge-sm"
defp status_badge_class("pending"), do: "badge badge-warning badge-sm"
defp status_badge_class("hidden"), do: "badge badge-info badge-sm"
defp status_badge_class("deleted"), do: "badge badge-error badge-sm"
defp status_badge_class(_), do: "badge badge-ghost badge-sm"
end