Packages

phoenix_kit

1.7.51
1.7.208 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
phoenix_kit lib modules comments web comments_component.ex
Raw

lib/modules/comments/web/comments_component.ex

defmodule PhoenixKit.Modules.Comments.Web.CommentsComponent do
@moduledoc """
Reusable LiveComponent for displaying and managing comments on any resource.
## Usage
<.live_component
module={PhoenixKit.Modules.Comments.Web.CommentsComponent}
id={"comments-\#{@post.uuid}"}
resource_type="post"
resource_id={@post.uuid}
current_user={@current_user}
/>
## Required Attrs
- `resource_type` - String identifying the resource type (e.g., "post")
- `resource_id` - UUID of the resource
- `current_user` - Current authenticated user struct
- `id` - Unique component ID
## Optional Attrs
- `enabled` - Whether comments are enabled (default: true)
- `show_likes` - Show like/dislike buttons (default: false)
- `title` - Section title (default: "Comments")
## Parent Notifications
After create/delete, sends to the parent LiveView:
{:comments_updated, %{resource_type: "post", resource_id: uuid, action: :created | :deleted}}
"""
use PhoenixKitWeb, :live_component
import PhoenixKitWeb.Components.Core.Icon
alias PhoenixKit.Modules.Comments
alias PhoenixKit.Users.Roles
@impl true
def mount(socket) do
{:ok,
socket
|> assign(:comments, [])
|> assign(:reply_to, nil)
|> assign(:new_comment, "")}
end
@impl true
def update(assigns, socket) do
socket =
socket
|> assign(assigns)
|> assign_new(:enabled, fn -> true end)
|> assign_new(:show_likes, fn -> false end)
|> assign_new(:title, fn -> "Comments" end)
socket =
if changed?(socket, :resource_id) or socket.assigns.comments == [] do
load_comments(socket)
else
socket
end
{:ok, socket}
end
@impl true
def handle_event("add_comment", %{"comment" => comment_text}, socket) do
if comment_text != "" do
parent_id = socket.assigns.reply_to
attrs = %{
content: comment_text,
parent_id: parent_id
}
case Comments.create_comment(
socket.assigns.resource_type,
socket.assigns.resource_id,
socket.assigns.current_user.uuid,
attrs
) do
{:ok, _comment} ->
send(
self(),
{:comments_updated,
%{
resource_type: socket.assigns.resource_type,
resource_id: socket.assigns.resource_id,
action: :created
}}
)
{:noreply,
socket
|> assign(:new_comment, "")
|> assign(:reply_to, nil)
|> load_comments()
|> put_flash(:info, "Comment added")}
{:error, _changeset} ->
{:noreply, socket |> put_flash(:error, "Failed to add comment")}
end
else
{:noreply, socket}
end
end
@impl true
def handle_event("reply_to", %{"id" => comment_id}, socket) do
{:noreply, assign(socket, :reply_to, comment_id)}
end
@impl true
def handle_event("cancel_reply", _params, socket) do
{:noreply, assign(socket, :reply_to, nil)}
end
@impl true
def handle_event("delete_comment", %{"id" => comment_id}, socket) do
case Comments.get_comment(comment_id) do
nil ->
{:noreply, socket |> put_flash(:error, "Comment not found")}
comment ->
if can_delete_comment?(socket.assigns.current_user, comment) do
case Comments.delete_comment(comment) do
{:ok, _} ->
send(
self(),
{:comments_updated,
%{
resource_type: socket.assigns.resource_type,
resource_id: socket.assigns.resource_id,
action: :deleted
}}
)
{:noreply,
socket
|> load_comments()
|> put_flash(:info, "Comment deleted")}
{:error, _} ->
{:noreply, socket |> put_flash(:error, "Failed to delete comment")}
end
else
{:noreply,
socket |> put_flash(:error, "You don't have permission to delete this comment")}
end
end
end
defp load_comments(socket) do
comments =
Comments.get_comment_tree(socket.assigns.resource_type, socket.assigns.resource_id)
comment_count =
Comments.count_comments(
socket.assigns.resource_type,
socket.assigns.resource_id,
status: "published"
)
socket
|> assign(:comments, comments)
|> assign(:comment_count, comment_count)
end
attr :comment, :map, required: true
attr :current_user, :map, required: true
attr :myself, :any, required: true
def render_comment(assigns) do
~H"""
<div class={[
if(@comment.depth > 0, do: "ml-4 border-l-2 border-base-300", else: "")
]}>
<div class="bg-base-200 rounded-lg p-4">
<%!-- Comment Header --%>
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2 text-sm">
<.icon name="hero-user-circle" class="w-5 h-5 text-base-content/60" />
<span class="font-semibold">
<%= if @comment.user do %>
{@comment.user.email}
<% else %>
Unknown
<% end %>
</span>
<span class="text-base-content/60">&bull;</span>
<span class="text-base-content/60">
{Calendar.strftime(@comment.inserted_at, "%b %d, %Y %I:%M %p")}
</span>
</div>
<%!-- Comment Actions --%>
<div class="flex gap-2">
<button
phx-click="reply_to"
phx-value-id={@comment.uuid}
phx-target={@myself}
class="btn btn-ghost btn-xs"
>
<.icon name="hero-arrow-uturn-left" class="w-4 h-4" /> Reply
</button>
<%= if can_delete_comment?(@current_user, @comment) do %>
<button
phx-click="delete_comment"
phx-value-id={@comment.uuid}
phx-target={@myself}
class="btn btn-ghost btn-xs text-error"
data-confirm="Are you sure you want to delete this comment?"
>
<.icon name="hero-trash" class="w-4 h-4" />
</button>
<% end %>
</div>
</div>
<%!-- Comment Content --%>
<div class="text-base-content">
{@comment.content}
</div>
<%!-- Nested Comments (Replies) --%>
<%= if @comment.children && length(@comment.children) > 0 do %>
<div class="mt-4 space-y-3">
<%= for child <- @comment.children do %>
<.render_comment
comment={child}
current_user={@current_user}
myself={@myself}
/>
<% end %>
</div>
<% end %>
</div>
</div>
"""
end
defp can_delete_comment?(user, comment) do
user.uuid == comment.user_uuid or user_is_admin?(user)
end
defp user_is_admin?(user) do
Roles.user_has_role_owner?(user) or Roles.user_has_role_admin?(user)
end
end