Current section
Files
Jump to
Current section
Files
lib/phoenix_kit_comments/web/index.html.heex
<div class="container flex flex-col mx-auto px-4 py-6">
<.admin_page_header
back={Routes.path("/admin")}
title="Comments"
subtitle="Moderate comments across all content"
>
<:actions>
<.link
navigate={Routes.path("/admin/settings/comments")}
class="btn btn-ghost btn-sm"
>
<.icon name="hero-cog-6-tooth" class="w-4 h-4" /> Settings
</.link>
</:actions>
</.admin_page_header>
<%!-- Statistics --%>
<div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-6">
<div class="stat bg-base-200 rounded-xl">
<div class="stat-title">Total</div>
<div class="stat-value text-primary text-2xl">{@stats.total}</div>
</div>
<div class="stat bg-base-200 rounded-xl">
<div class="stat-title">Published</div>
<div class="stat-value text-success text-2xl">{@stats.published}</div>
</div>
<div class="stat bg-base-200 rounded-xl">
<div class="stat-title">Pending</div>
<div class="stat-value text-warning text-2xl">{@stats.pending}</div>
</div>
<div class="stat bg-base-200 rounded-xl">
<div class="stat-title">Hidden</div>
<div class="stat-value text-info text-2xl">{@stats.hidden}</div>
</div>
<div class="stat bg-base-200 rounded-xl">
<div class="stat-title">Deleted</div>
<div class="stat-value text-error text-2xl">{@stats.deleted}</div>
</div>
</div>
<%!-- Filters --%>
<div class="bg-base-200 rounded-lg p-4 mb-6">
<.form for={%{}} phx-change="filter" class="flex flex-wrap gap-4 items-end">
<div class="form-control flex-1 min-w-48">
<label class="label"><span class="label-text">Search</span></label>
<input
type="text"
name="search[query]"
value={@search}
placeholder="Search comment content..."
class="input input-bordered input-sm w-full"
phx-debounce="300"
/>
</div>
<div class="form-control">
<label class="label"><span class="label-text">Resource Type</span></label>
<select name="filter[resource_type]" class="select select-bordered select-sm">
<option value="">All Types</option>
<%= for rt <- @resource_types do %>
<option value={rt} selected={@filter_resource_type == rt}>
{String.capitalize(rt)}
</option>
<% end %>
</select>
</div>
<div class="form-control">
<label class="label"><span class="label-text">Status</span></label>
<select name="filter[status]" class="select select-bordered select-sm">
<option value="">All Statuses</option>
<option value="published" selected={@filter_status == "published"}>Published</option>
<option value="pending" selected={@filter_status == "pending"}>Pending</option>
<option value="hidden" selected={@filter_status == "hidden"}>Hidden</option>
<option value="deleted" selected={@filter_status == "deleted"}>Deleted</option>
</select>
</div>
<button type="button" phx-click="clear_filters" class="btn btn-ghost btn-sm">
Clear
</button>
</.form>
</div>
<%!-- Bulk Actions --%>
<%= if length(@selected_uuids) > 0 do %>
<div class="flex gap-2 mb-4 items-center">
<span class="text-sm text-base-content/70">{length(@selected_uuids)} selected</span>
<button phx-click="bulk_action" phx-value-action="approve" class="btn btn-success btn-xs">
Approve
</button>
<button phx-click="bulk_action" phx-value-action="hide" class="btn btn-warning btn-xs">
Hide
</button>
<button
phx-click="bulk_action"
phx-value-action="delete"
class="btn btn-error btn-xs"
data-confirm="Delete selected comments?"
>
Delete
</button>
</div>
<% end %>
<%!-- Comments Table --%>
<%= if length(@comments) > 0 do %>
<.table_default
id="comments-table"
variant="zebra"
size="sm"
class="w-full"
toggleable={true}
items={@comments}
card_title={fn comment -> String.slice(comment.content, 0..99) end}
card_fields={
fn comment ->
fields = [
%{
label: gettext("Author"),
value: if(comment.user, do: comment.user.email, else: "Deleted user")
},
%{label: gettext("Resource"), value: comment.resource_type}
]
fields =
case resource_info(@resource_context, comment) do
%{title: title} ->
fields ++ [%{label: gettext("Display"), value: title}]
_ ->
fields
end
fields ++
[
%{label: gettext("Status"), value: comment.status},
%{
label: gettext("Date"),
value: Calendar.strftime(comment.inserted_at, "%b %d, %Y")
}
]
end
}
>
<:card_actions :let={comment}>
<%= if comment.status != "published" do %>
<button
phx-click="approve"
phx-value-uuid={comment.uuid}
class="btn btn-xs btn-outline btn-success"
>
{gettext("Approve")}
</button>
<% end %>
<%= if comment.status != "hidden" do %>
<button
phx-click="hide"
phx-value-uuid={comment.uuid}
class="btn btn-xs btn-outline btn-warning"
>
{gettext("Hide")}
</button>
<% end %>
<button
phx-click="delete"
phx-value-uuid={comment.uuid}
class="btn btn-xs btn-outline btn-error"
data-confirm="Delete this comment?"
>
{gettext("Delete")}
</button>
</:card_actions>
<.table_default_header>
<.table_default_row hover={false}>
<.table_default_header_cell class="w-8"></.table_default_header_cell>
<.table_default_header_cell>Content</.table_default_header_cell>
<.table_default_header_cell>Author</.table_default_header_cell>
<.table_default_header_cell>Resource</.table_default_header_cell>
<.table_default_header_cell>Status</.table_default_header_cell>
<.table_default_header_cell>Date</.table_default_header_cell>
<.table_default_header_cell>Actions</.table_default_header_cell>
</.table_default_row>
</.table_default_header>
<.table_default_body>
<%= for comment <- @comments do %>
<.table_default_row>
<.table_default_cell>
<input
type="checkbox"
class="checkbox checkbox-xs"
phx-click="toggle_select"
phx-value-uuid={comment.uuid}
checked={comment.uuid in @selected_uuids}
/>
</.table_default_cell>
<.table_default_cell class="max-w-xs">
<%= if comment.depth > 0 do %>
<div class="flex items-center gap-1 text-base-content/50 text-xs mb-0.5">
<.icon name="hero-arrow-uturn-right-mini" class="size-3" />
<span>Reply</span>
<%= if comment.parent do %>
<span class="truncate max-w-[200px]">
— Re: {String.slice(comment.parent.content, 0..39)}
</span>
<% end %>
</div>
<% end %>
<div class="truncate text-sm">{String.slice(comment.content, 0..99)}</div>
</.table_default_cell>
<.table_default_cell class="text-sm">
<%= if comment.user do %>
{comment.user.email}
<% else %>
<span class="text-base-content/50">Deleted user</span>
<% end %>
</.table_default_cell>
<.table_default_cell>
<span class="badge badge-ghost badge-sm h-auto">
{comment.resource_type}
</span>
<%= case resource_info(@resource_context, comment) do %>
<% %{title: title, path: path, prefixed: true} -> %>
<.link
navigate={Routes.path(path)}
class="link link-hover text-sm ml-1 truncate max-w-[200px] inline-block align-bottom"
title={title}
>
{String.slice(title, 0..49)}
</.link>
<% %{title: title, path: path} -> %>
<.link
navigate={path}
class="link link-hover text-sm ml-1 truncate max-w-[200px] inline-block align-bottom"
title={title}
>
{String.slice(title, 0..49)}
</.link>
<% _ -> %>
<span
class="text-base-content/40 text-xs ml-1"
title={to_string(comment.resource_uuid)}
>
{String.slice(to_string(comment.resource_uuid), 0..7)}
</span>
<% end %>
</.table_default_cell>
<.table_default_cell>
<span class={status_badge_class(comment.status)}>
{comment.status}
</span>
</.table_default_cell>
<.table_default_cell class="text-sm text-base-content/70">
{Calendar.strftime(comment.inserted_at, "%b %d, %Y")}
</.table_default_cell>
<.table_default_cell>
<div class="flex gap-1">
<%= if comment.status != "published" do %>
<button
phx-click="approve"
phx-value-uuid={comment.uuid}
class="btn btn-xs btn-outline btn-success tooltip tooltip-bottom"
data-tip={gettext("Approve")}
>
<.icon name="hero-check" class="w-4 h-4 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">{gettext("Approve")}</span>
</button>
<% end %>
<%= if comment.status != "hidden" do %>
<button
phx-click="hide"
phx-value-uuid={comment.uuid}
class="btn btn-xs btn-outline btn-warning tooltip tooltip-bottom"
data-tip={gettext("Hide")}
>
<.icon name="hero-eye-slash" class="w-4 h-4 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">{gettext("Hide")}</span>
</button>
<% end %>
<button
phx-click="delete"
phx-value-uuid={comment.uuid}
class="btn btn-xs btn-outline btn-error tooltip tooltip-bottom"
data-tip={gettext("Delete")}
data-confirm="Delete this comment?"
>
<.icon name="hero-trash" class="w-4 h-4 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">{gettext("Delete")}</span>
</button>
</div>
</.table_default_cell>
</.table_default_row>
<% end %>
</.table_default_body>
</.table_default>
<% else %>
<div class="text-center py-12 text-base-content/60">
<.icon name="hero-chat-bubble-left" class="w-12 h-12 mx-auto mb-2 opacity-50" />
<p>No comments found</p>
</div>
<% end %>
<%!-- Pagination --%>
<%= if @total_pages > 1 do %>
<div class="flex justify-center mt-6">
<div class="join">
<%= for page <- max(1, @page - 2)..min(@total_pages, @page + 2) do %>
<.link
patch={
Routes.path(
"/admin/comments?#{URI.encode_query(%{"page" => page, "search" => @search || "", "resource_type" => @filter_resource_type || "", "status" => @filter_status || ""})}"
)
}
class={["join-item btn btn-sm", if(page == @page, do: "btn-active", else: "")]}
>
{page}
</.link>
<% end %>
</div>
</div>
<% end %>
</div>