Packages

phoenix_kit

1.7.21
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 posts.html.heex
Raw

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 --%>
<header class="w-full relative mb-6">
<%!-- Back Button (Left aligned) --%>
<.link
navigate={Routes.path("/admin")}
class="btn btn-outline btn-primary btn-sm absolute left-0 top-0 -mb-12"
>
← Back to Dashboard
</.link>
<%!-- Title Section --%>
<div class="text-center">
<h1 class="text-4xl font-bold text-base-content mb-3">Posts</h1>
<p class="text-lg text-base-content">Manage and organize your social posts</p>
</div>
<%!-- Right Actions --%>
<div class="absolute right-0 top-0 -mb-12 flex gap-2">
<.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>
</div>
</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.id}
phx-value-value={if post.id in @selected_posts, do: "off", else: "on"}
checked={post.id in @selected_posts}
/>
</.table_default_cell>
<.table_default_cell>
<div class="flex flex-col">
<.link
href={Routes.path("/admin/posts/#{post.id}/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.id}")}
class="btn btn-ghost btn-xs"
title="Preview post"
>
<.icon name="hero-eye" class="w-4 h-4" />
</.link>
<div class="dropdown dropdown-end dropdown-left">
<label tabindex="0" class="btn btn-ghost btn-xs"></label>
<ul
tabindex="0"
class="dropdown-content menu p-2 shadow bg-base-200 rounded-box w-52 z-50"
>
<li>
<button phx-click="view_post" phx-value-id={post.id}>View</button>
</li>
<li>
<button phx-click="edit_post" phx-value-id={post.id}>Edit</button>
</li>
<%= if post.status == "draft" do %>
<li>
<button phx-click="publish_post" phx-value-id={post.id}>
Publish
</button>
</li>
<% else %>
<li>
<button phx-click="draft_post" phx-value-id={post.id}>
Move to Draft
</button>
</li>
<% end %>
<li>
<button
phx-click="delete_post"
phx-value-id={post.id}
data-confirm="Are you sure you want to delete this post?"
class="text-error"
>
Delete
</button>
</li>
</ul>
</div>
</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>