Packages
phoenix_kit
1.7.58
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
Current section
Files
lib/modules/posts/web/posts.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title="Posts"
current_path={@url_path}
project_title={@project_title}
>
<div class="container flex-col mx-auto px-4 py-6">
<%!-- Header Section --%>
<.admin_page_header
back={Routes.path("/admin")}
title="Posts"
subtitle="Manage and organize your social posts"
>
<:actions>
<.link
navigate={Routes.path("/admin/settings/posts")}
class="btn btn-ghost btn-sm"
title="Posts Settings"
>
<.icon name="hero-cog-6-tooth" class="w-5 h-5" />
</.link>
<.link
href={Routes.path("/admin/posts/new")}
class="btn btn-primary btn-sm"
>
+ New Post
</.link>
</:actions>
</.admin_page_header>
<%!-- Statistics Summary --%>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div class="stat bg-base-200 rounded-xl">
<div class="stat-title">Total Posts</div>
<div class="stat-value text-primary">{@stats.total}</div>
<div class="stat-desc">All time</div>
</div>
<div class="stat bg-base-200 rounded-xl">
<div class="stat-title">Drafts</div>
<div class="stat-value text-neutral">{@stats.drafts}</div>
<div class="stat-desc">Unpublished</div>
</div>
<div class="stat bg-base-200 rounded-xl">
<div class="stat-title">Public</div>
<div class="stat-value text-success">{@stats.public}</div>
<div class="stat-desc">Live posts</div>
</div>
<div class="stat bg-base-200 rounded-xl">
<div class="stat-title">Scheduled</div>
<div class="stat-value text-info">{@stats.scheduled}</div>
<div class="stat-desc">Auto-publish</div>
</div>
</div>
<%!-- Filters & Search --%>
<div class="bg-base-200 rounded-lg p-6 mb-6">
<.form for={%{}} phx-change="filter" class="space-y-4">
<%!-- Search Bar --%>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Search</span>
</label>
<input
type="text"
name="search[query]"
value={@search_query}
placeholder="Search titles and content..."
class="input input-bordered w-full"
/>
</div>
<%!-- Filter Row --%>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<%!-- Type Filter --%>
<div class="form-control">
<label class="label">
<span class="label-text">Type</span>
</label>
<select name="filter[type]" class="select select-bordered w-full">
<option value="all" selected={@filter_type == "all"}>All Types</option>
<option value="post" selected={@filter_type == "post"}>Post</option>
<option value="snippet" selected={@filter_type == "snippet"}>Snippet</option>
<option value="repost" selected={@filter_type == "repost"}>Repost</option>
</select>
</div>
<%!-- Status Filter --%>
<div class="form-control">
<label class="label">
<span class="label-text">Status</span>
</label>
<select name="filter[status]" class="select select-bordered w-full">
<option value="all" selected={@filter_status == "all"}>All Statuses</option>
<option value="draft" selected={@filter_status == "draft"}>Draft</option>
<option value="public" selected={@filter_status == "public"}>Public</option>
<option value="unlisted" selected={@filter_status == "unlisted"}>Unlisted</option>
<option value="scheduled" selected={@filter_status == "scheduled"}>
Scheduled
</option>
</select>
</div>
<%!-- Group Filter --%>
<div class="form-control">
<label class="label">
<span class="label-text">Group</span>
</label>
<select name="filter[group]" class="select select-bordered w-full">
<option value="all" selected={@filter_group == "all"}>All Groups</option>
<%!-- TODO: Add dynamic groups --%>
</select>
</div>
<%!-- Tag Filter --%>
<div class="form-control">
<label class="label">
<span class="label-text">Tag</span>
</label>
<select name="filter[tag]" class="select select-bordered w-full">
<option value="all" selected={@filter_tag == "all"}>All Tags</option>
<%!-- TODO: Add dynamic tags --%>
</select>
</div>
</div>
<%!-- Action Buttons --%>
<div class="flex gap-2">
<button type="button" phx-click="clear_filters" class="btn btn-ghost btn-sm">
Clear Filters
</button>
<button type="button" phx-click="refresh" class="btn btn-ghost btn-sm">
Refresh
</button>
</div>
</.form>
</div>
<%!-- Bulk Actions --%>
<%= if length(@selected_posts) > 0 do %>
<div class="alert alert-info mb-4">
<div class="flex-1">
<span>{length(@selected_posts)} post(s) selected</span>
</div>
<div class="flex gap-2">
<button phx-click="bulk_publish" class="btn btn-success btn-sm">
Publish Selected
</button>
<button
phx-click="bulk_delete"
class="btn btn-error btn-sm"
data-confirm="Are you sure you want to delete the selected posts?"
>
Delete Selected
</button>
</div>
</div>
<% end %>
<%!-- Posts Table --%>
<div class="bg-base-100 rounded-lg shadow">
<.table_default variant="zebra" class="w-full">
<.table_default_header>
<.table_default_row>
<.table_default_header_cell>
<input
type="checkbox"
class="checkbox checkbox-sm"
phx-click="select_all"
phx-value-value={
if length(@selected_posts) == length(@posts), do: "off", else: "on"
}
/>
</.table_default_header_cell>
<.table_default_header_cell>Title</.table_default_header_cell>
<.table_default_header_cell>Type</.table_default_header_cell>
<.table_default_header_cell>Status</.table_default_header_cell>
<.table_default_header_cell>Author</.table_default_header_cell>
<.table_default_header_cell>Stats</.table_default_header_cell>
<.table_default_header_cell>Created</.table_default_header_cell>
<.table_default_header_cell>Actions</.table_default_header_cell>
</.table_default_row>
</.table_default_header>
<.table_default_body>
<%= if @loading do %>
<.table_default_row>
<.table_default_cell colspan={8} class="text-center py-8">
<span class="loading loading-spinner loading-lg"></span>
<p class="mt-2">Loading posts...</p>
</.table_default_cell>
</.table_default_row>
<% else %>
<%= if length(@posts) == 0 do %>
<.table_default_row>
<.table_default_cell colspan={8} class="text-center py-8">
<div class="text-base-content/60">
<p class="text-lg mb-2">No posts found</p>
<p class="text-sm">
<%= if @search_query != "" or @filter_status != "all" or @filter_type != "all" do %>
Try adjusting your filters or
<button phx-click="clear_filters" class="link link-primary">
clear filters
</button>
<% else %>
Create your first post to get started
<% end %>
</p>
</div>
</.table_default_cell>
</.table_default_row>
<% else %>
<%= for post <- @posts do %>
<.table_default_row hover>
<.table_default_cell>
<input
type="checkbox"
class="checkbox checkbox-sm"
phx-click="select_post"
phx-value-id={post.uuid}
phx-value-value={if post.uuid in @selected_posts, do: "off", else: "on"}
checked={post.uuid in @selected_posts}
/>
</.table_default_cell>
<.table_default_cell>
<div class="flex flex-col">
<.link
href={Routes.path("/admin/posts/#{post.uuid}/edit")}
class="link link-primary font-semibold"
>
{post.title}
</.link>
<%= if post.sub_title do %>
<span class="text-sm text-base-content/60">{post.sub_title}</span>
<% end %>
</div>
</.table_default_cell>
<.table_default_cell>
<span class={"badge #{type_badge_class(post.type)}"}>
{format_post_type(post.type)}
</span>
</.table_default_cell>
<.table_default_cell>
<span class={"badge #{status_badge_class(post.status)}"}>
{format_status(post.status)}
</span>
</.table_default_cell>
<.table_default_cell>
<%= if post.user do %>
{post.user.email}
<% else %>
Unknown
<% end %>
</.table_default_cell>
<.table_default_cell>
<div class="flex gap-2 text-sm">
<span title="Views">👁️ {post.view_count}</span>
<span title="Likes">❤️ {post.like_count}</span>
<span title="Comments">💬 {post.comment_count}</span>
</div>
</.table_default_cell>
<.table_default_cell class="text-sm">
{Calendar.strftime(post.inserted_at, "%b %d, %Y")}
</.table_default_cell>
<.table_default_cell>
<div class="flex items-center gap-1">
<.link
navigate={Routes.path("/admin/posts/#{post.uuid}")}
class="btn btn-xs btn-outline btn-info tooltip tooltip-bottom"
data-tip={gettext("View post")}
>
<.icon name="hero-eye" class="w-4 h-4 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">{gettext("View post")}</span>
</.link>
<button
phx-click="edit_post"
phx-value-id={post.uuid}
class="btn btn-xs btn-outline btn-info tooltip tooltip-bottom"
data-tip={gettext("Edit post")}
>
<.icon name="hero-pencil" class="w-4 h-4 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">{gettext("Edit post")}</span>
</button>
<%= if post.status == "draft" do %>
<button
phx-click="publish_post"
phx-value-id={post.uuid}
class="btn btn-xs btn-outline btn-info tooltip tooltip-bottom"
data-tip={gettext("Publish post")}
>
<.icon name="hero-play" class="w-4 h-4 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">
{gettext("Publish post")}
</span>
</button>
<% else %>
<button
phx-click="draft_post"
phx-value-id={post.uuid}
class="btn btn-xs btn-outline btn-info tooltip tooltip-bottom"
data-tip={gettext("Move to draft")}
>
<.icon name="hero-pause" class="w-4 h-4 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">
{gettext("Move to draft")}
</span>
</button>
<% end %>
<button
phx-click="delete_post"
phx-value-id={post.uuid}
data-confirm="Are you sure you want to delete this post?"
class="btn btn-xs btn-outline btn-error tooltip tooltip-bottom"
data-tip={gettext("Delete post")}
>
<.icon name="hero-trash" class="w-4 h-4 hidden sm:inline" />
<span class="sm:hidden whitespace-nowrap">{gettext("Delete post")}</span>
</button>
</div>
</.table_default_cell>
</.table_default_row>
<% end %>
<% end %>
<% end %>
</.table_default_body>
</.table_default>
</div>
<%!-- Pagination --%>
<%= if @total_count > @per_page do %>
<div class="flex justify-center mt-6">
<div class="btn-group">
<%= if @page > 1 do %>
<.link
patch={Routes.path("/admin/posts?page=#{@page - 1}")}
class="btn btn-outline"
>
« Previous
</.link>
<% end %>
<button class="btn btn-outline btn-active">Page {@page}</button>
<%= if @total_count > @page * @per_page do %>
<.link
patch={Routes.path("/admin/posts?page=#{@page + 1}")}
class="btn btn-outline"
>
Next »
</.link>
<% end %>
</div>
</div>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>