Add LiveView editor dashboard with drafts and scheduled tabs

This commit is contained in:
Willem van den Ende
2026-04-01 21:42:45 +00:00
parent 5395b2de80
commit f1560ff0e7
6 changed files with 309 additions and 1 deletions
@@ -0,0 +1,113 @@
defmodule FirehoseWeb.EditorDashboardLive do
use FirehoseWeb, :live_view
alias Blogex.Post
@impl true
def mount(_params, _session, socket) do
all_posts = Blogex.Registry.all_posts_unfiltered()
drafts =
all_posts
|> Enum.filter(&(Post.visibility(&1) == :draft))
|> Enum.sort_by(& &1.date, {:desc, Date})
scheduled =
all_posts
|> Enum.filter(&(Post.visibility(&1) == :scheduled))
|> Enum.sort_by(& &1.date, {:asc, Date})
{:ok,
socket
|> assign(:page_title, "Editor Dashboard")
|> assign(:drafts, drafts)
|> assign(:scheduled, scheduled)
|> assign(:active_tab, :drafts)}
end
@impl true
def render(assigns) do
~H"""
<div class="max-w-4xl mx-auto">
<h1 class="text-2xl font-bold mb-6">Dashboard</h1>
<div class="flex gap-4 mb-6 border-b border-zinc-200">
<button
phx-click="switch_tab"
phx-value-tab="drafts"
class={[
"pb-2 px-1 text-sm font-medium transition-colors",
if(@active_tab == :drafts,
do: "border-b-2 border-zinc-900 text-zinc-900",
else: "text-zinc-500 hover:text-zinc-700"
)
]}
>
Drafts ({length(@drafts)})
</button>
<button
phx-click="switch_tab"
phx-value-tab="scheduled"
class={[
"pb-2 px-1 text-sm font-medium transition-colors",
if(@active_tab == :scheduled,
do: "border-b-2 border-zinc-900 text-zinc-900",
else: "text-zinc-500 hover:text-zinc-700"
)
]}
>
Scheduled ({length(@scheduled)})
</button>
</div>
<div id="drafts-tab" class={if(@active_tab != :drafts, do: "hidden")}>
<div :if={@drafts == []} class="text-zinc-500 text-sm">No drafts</div>
<div :for={post <- @drafts} class="py-4 border-b border-zinc-100 last:border-0">
<div class="flex items-center justify-between">
<div>
<.link
navigate={post_path(post)}
class="text-base font-medium text-zinc-900 hover:underline"
>
{post.title}
</.link>
<div class="text-sm text-zinc-500 mt-1">
{post.author} · {Calendar.strftime(post.date, "%b %d, %Y")} · <span class="text-amber-600 font-medium">Draft</span>
</div>
</div>
</div>
</div>
</div>
<div id="scheduled-tab" class={if(@active_tab != :scheduled, do: "hidden")}>
<div :if={@scheduled == []} class="text-zinc-500 text-sm">No scheduled posts</div>
<div :for={post <- @scheduled} class="py-4 border-b border-zinc-100 last:border-0">
<div class="flex items-center justify-between">
<div>
<.link
navigate={post_path(post)}
class="text-base font-medium text-zinc-900 hover:underline"
>
{post.title}
</.link>
<div class="text-sm text-zinc-500 mt-1">
{post.author} · {Calendar.strftime(post.date, "%b %d, %Y")} · <span class="text-blue-600 font-medium">{Post.days_until_live(post)} days until live</span>
</div>
</div>
</div>
</div>
</div>
</div>
"""
end
@impl true
def handle_event("switch_tab", %{"tab" => tab}, socket) do
{:noreply, assign(socket, :active_tab, String.to_existing_atom(tab))}
end
defp post_path(post) do
blog = Blogex.Registry.get_blog!(post.blog)
"#{blog.base_path()}/#{post.id}"
end
end
+5
View File
@@ -67,6 +67,11 @@ defmodule FirehoseWeb.Router do
scope "/", FirehoseWeb do
pipe_through [:browser, :require_authenticated_user]
live_session :authenticated_user,
on_mount: [{FirehoseWeb.UserAuth, :ensure_authenticated}] do
live "/editor/dashboard", EditorDashboardLive
end
get "/users/settings", UserSettingsController, :edit
put "/users/settings", UserSettingsController, :update
get "/users/settings/confirm-email/:token", UserSettingsController, :confirm_email
+37
View File
@@ -216,4 +216,41 @@ defmodule FirehoseWeb.UserAuth do
end
defp maybe_store_return_to(conn), do: conn
@doc """
LiveView on_mount callback that ensures the user is authenticated.
Used in `live_session` blocks in the router:
live_session :authenticated, on_mount: [{FirehoseWeb.UserAuth, :ensure_authenticated}] do
live "/editor/dashboard", EditorDashboardLive
end
"""
def on_mount(:ensure_authenticated, _params, session, socket) do
socket = mount_current_scope(socket, session)
if socket.assigns.current_scope && socket.assigns.current_scope.user do
{:cont, socket}
else
socket =
socket
|> Phoenix.LiveView.put_flash(:error, "You must log in to access this page.")
|> Phoenix.LiveView.redirect(to: ~p"/users/log-in")
{:halt, socket}
end
end
defp mount_current_scope(socket, session) do
Phoenix.Component.assign_new(socket, :current_scope, fn ->
if token = session["user_token"] do
case Accounts.get_user_by_session_token(token) do
{user, _token_inserted_at} -> Scope.for_user(user)
nil -> Scope.for_user(nil)
end
else
Scope.for_user(nil)
end
end)
end
end