Packages

phoenix_kit

1.7.57
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 posts web details.ex
Raw

lib/modules/posts/web/details.ex

defmodule PhoenixKitWeb.Live.Modules.Posts.Details do
@moduledoc """
LiveView for displaying a single post with all details and interactions.
Displays:
- Post content (title, subtitle, content, media)
- Author information
- Post statistics (views, likes, comments)
- Tags and groups
- Comments via standalone CommentsComponent
- Like/unlike functionality
- Admin actions (edit, delete, status changes)
## Route
This LiveView is mounted at `{prefix}/admin/posts/:id` and requires
appropriate permissions.
Note: `{prefix}` is your configured PhoenixKit URL prefix (default: `/phoenix_kit`).
"""
use PhoenixKitWeb, :live_view
require Logger
alias PhoenixKit.Modules.Comments
alias PhoenixKit.Modules.Posts
alias PhoenixKit.Modules.Publishing.Renderer
alias PhoenixKit.Settings
alias PhoenixKit.Users.Roles
alias PhoenixKit.Utils.Routes
@impl true
def mount(%{"id" => post_uuid}, _session, socket) do
# Get current user
current_user = socket.assigns[:phoenix_kit_current_user]
# Get project title
project_title = Settings.get_project_title()
# Load post with all associations
case Posts.get_post!(post_uuid, preload: [:user, [media: :file], :tags, :groups, :mentions]) do
nil ->
{:ok,
socket
|> put_flash(:error, "Post not found")
|> push_navigate(to: Routes.path("/admin/posts"))}
post ->
# Increment view count
Posts.increment_view_count(post)
# Check if current user liked this post
liked_by_user = Posts.post_liked_by?(post.uuid, current_user.uuid)
# Load settings
comments_enabled = Comments.enabled?()
likes_enabled = Settings.get_setting("posts_likes_enabled", "true") == "true"
show_view_count = Settings.get_setting("posts_show_view_count", "true") == "true"
socket =
socket
|> assign(:page_title, post.title)
|> assign(:project_title, project_title)
|> assign(:post, post)
|> assign(:rendered_content, render_markdown_content(post.content))
|> assign(:current_user, current_user)
|> assign(:liked_by_user, liked_by_user)
|> assign(:comments_enabled, comments_enabled)
|> assign(:likes_enabled, likes_enabled)
|> assign(:show_view_count, show_view_count)
{:ok, socket}
end
end
@impl true
def handle_event("like_post", _params, socket) do
post = socket.assigns.post
current_user = socket.assigns.current_user
if socket.assigns.liked_by_user do
# Unlike
Posts.unlike_post(post.uuid, current_user.uuid)
updated_post =
Posts.get_post!(post.uuid, preload: [:user, [media: :file], :tags, :groups, :mentions])
{:noreply,
socket
|> assign(:post, updated_post)
|> assign(:liked_by_user, false)}
else
# Like
Posts.like_post(post.uuid, current_user.uuid)
updated_post =
Posts.get_post!(post.uuid, preload: [:user, [media: :file], :tags, :groups, :mentions])
{:noreply,
socket
|> assign(:post, updated_post)
|> assign(:liked_by_user, true)}
end
end
@impl true
def handle_event("edit_post", _params, socket) do
{:noreply,
push_navigate(socket, to: Routes.path("/admin/posts/#{socket.assigns.post.uuid}/edit"))}
end
@impl true
def handle_event("delete_post", _params, socket) do
case Posts.delete_post(socket.assigns.post) do
{:ok, _} ->
{:noreply,
socket
|> put_flash(:info, "Post deleted successfully")
|> push_navigate(to: Routes.path("/admin/posts"))}
{:error, _} ->
{:noreply, socket |> put_flash(:error, "Failed to delete post")}
end
end
@impl true
def handle_event("change_status", %{"status" => status}, socket) do
case Posts.update_post(socket.assigns.post, %{status: status}) do
{:ok, updated_post} ->
{:noreply,
socket
|> assign(:post, updated_post)
|> put_flash(:info, "Post status updated to #{status}")}
{:error, _} ->
{:noreply, socket |> put_flash(:error, "Failed to update post status")}
end
end
# Handle comment count updates from CommentsComponent
@impl true
def handle_info({:comments_updated, _info}, socket) do
updated_post =
Posts.get_post!(socket.assigns.post.uuid,
preload: [:user, [media: :file], :tags, :groups, :mentions]
)
{:noreply, assign(socket, :post, updated_post)}
end
## --- Private Helper Functions ---
defp user_is_admin?(user) do
Roles.user_has_role_owner?(user) or Roles.user_has_role_admin?(user)
end
defp can_edit_post?(user, post) do
user.uuid == post.user_uuid or user_is_admin?(user)
end
defp format_post_type("post"), do: "Post"
defp format_post_type("snippet"), do: "Snippet"
defp format_post_type("repost"), do: "Repost"
defp format_post_type(_), do: "Unknown"
defp format_status("draft"), do: "Draft"
defp format_status("public"), do: "Public"
defp format_status("unlisted"), do: "Unlisted"
defp format_status("scheduled"), do: "Scheduled"
defp format_status(_), do: "Unknown"
defp format_type_badge_class("post"), do: "badge badge-primary"
defp format_type_badge_class("snippet"), do: "badge badge-secondary"
defp format_type_badge_class("repost"), do: "badge badge-accent"
defp format_type_badge_class(_), do: "badge badge-ghost"
defp format_status_badge_class("draft"), do: "badge badge-neutral"
defp format_status_badge_class("public"), do: "badge badge-success"
defp format_status_badge_class("unlisted"), do: "badge badge-warning"
defp format_status_badge_class("scheduled"), do: "badge badge-info"
defp format_status_badge_class(_), do: "badge badge-ghost"
defp render_markdown_content(content) when is_binary(content) and content != "" do
Renderer.render_markdown(content)
end
defp render_markdown_content(_), do: ""
end