admin improvements pt. 2

This commit is contained in:
sloane 2025-04-12 18:05:05 -04:00
parent 1675efb514
commit 0bcb8f0768
Signed by: sloanelybutsurely
SSH key fingerprint: SHA256:8SBnwhl+RY3oEyQxy1a9wByPzxWM0x+/Ejc+sIlY5qQ
13 changed files with 124 additions and 55 deletions

View file

@ -1,4 +1,4 @@
defmodule Core do
@moduledoc false
use Boundary, deps: [Schema], exports: [Accounts, Posts, Author]
use Boundary, deps: [Schema], exports: [Accounts, Posts, Author, DateTime]
end

9
lib/core/date_time.ex Normal file
View file

@ -0,0 +1,9 @@
defmodule Core.DateTime do
@local_time_zone "America/New_York"
def to_local_time(nil), do: nil
def to_local_time(date_time) do
DateTime.shift_zone!(date_time, @local_time_zone)
end
end

View file

@ -50,11 +50,15 @@ defmodule Core.Posts do
|> Flop.validate_and_run(params, for: Schema.Post)
end
def publish_date(%Schema.Post{published_at: nil}), do: nil
def publish_date_time(%Schema.Post{published_at: nil}), do: nil
def publish_date(%Schema.Post{published_at: published_at}) do
published_at
|> DateTime.shift_zone!("America/New_York")
def publish_date_time(%Schema.Post{published_at: published_at}) do
Core.DateTime.to_local_time(published_at)
end
def publish_date(%Schema.Post{} = post) do
post
|> publish_date_time()
|> DateTime.to_date()
end

View file

@ -6,7 +6,7 @@ defmodule Core.Posts.Post do
def content_changeset(%Schema.Post{} = post, attrs) do
changeset =
post
|> cast(attrs, [:tid, :kind, :slug, :title, :body])
|> cast(attrs, [:tid, :kind, :slug, :title, :body, :deleted_at, :published_at])
|> validate_required([:kind], message: "must have a kind")
|> validate_required([:body], message: "must have a body")
@ -40,7 +40,9 @@ defmodule Core.Posts.Post do
changeset = change(post)
if is_nil(get_field(changeset, :published_at)) do
put_change(changeset, :published_at, published_at)
changeset
|> put_change(:deleted_at, nil)
|> put_change(:published_at, published_at)
else
changeset
end
@ -50,7 +52,9 @@ defmodule Core.Posts.Post do
changeset = change(post)
if is_nil(get_field(changeset, :deleted_at)) do
put_change(changeset, :deleted_at, deleted_at)
changeset
|> put_change(:published_at, nil)
|> put_change(:deleted_at, deleted_at)
else
changeset
end
@ -81,7 +85,12 @@ defmodule Core.Posts.Post do
end
def default_order(query \\ base()) do
order_by(query, [posts: p], desc: :inserted_at, desc: :updated_at)
order_by(query, [posts: p],
desc: :published_at,
desc: :inserted_at,
desc: :updated_at,
desc: :id
)
end
def where_kind(query \\ base(), kind) do

View file

@ -5,11 +5,11 @@ defmodule Schema.Post do
@derive {
Flop.Schema,
filterable: [],
sortable: [:published_at, :id],
sortable: [:published_at, :inserted_at, :updated_at, :id],
pagination_types: [:first, :last],
default_order: %{
order_by: [:published_at, :id],
order_directions: [:desc, :desc]
order_by: [:published_at, :inserted_at, :updated_at, :id],
order_directions: [:desc, :desc, :desc, :desc]
},
default_pagination_type: :first,
default_limit: 20,

View file

@ -10,7 +10,8 @@ defmodule Web.CoreComponents do
attr :name, :any
attr :label, :string, default: nil
attr :value, :any
attr :type, :string, default: "text", values: ~w[text password textarea]
attr :class, :string, default: nil
attr :type, :string, default: "text", values: ~w[text password textarea datetime-local]
attr :field, FormField
attr :errors, :list, default: []
attr :rest, :global, include: ~w[disabled form pattern placeholder readonly required]
@ -36,9 +37,9 @@ defmodule Web.CoreComponents do
def input(%{type: "textarea"} = assigns) do
~H"""
<div>
<div class={["flex flex-col", @class]}>
<.label for={@id}>{@label}</.label>
<textarea id={@id} name={@name}>{Phoenix.HTML.Form.normalize_value(@type, @value)}</textarea>
<textarea id={@id} name={@name} class="h-80">{Phoenix.HTML.Form.normalize_value(@type, @value)}</textarea>
<.error :for={error <- @errors}>{error}</.error>
</div>
"""
@ -46,7 +47,7 @@ defmodule Web.CoreComponents do
def input(assigns) do
~H"""
<div>
<div class="flex flex-col">
<.label for={@id}>{@label}</.label>
<input
id={@id}

View file

@ -16,7 +16,7 @@
>
<h3 class="p-name u-url">{blog.title}</h3>
<div class="text-gray-500 dt-published">
<.timex value={blog.published_at} format="{Mfull} {D}" />
<.timex value={Core.Posts.publish_date_time(blog)} format="{Mfull} {D}" />
</div>
</.link>
</article>

View file

@ -3,10 +3,18 @@
<header class="mb-4">
<h1 class="p-name text-2xl font-bold mb-2">{@blog.title}</h1>
<%= if @blog.published_at do %>
<div class="text-sm text-gray-500 flex items-center">
<div class="text-sm text-gray-500 flex items-center gap-x-2">
<span class="p-author h-card">{Core.Author.get(:name)}</span>
<span class="mx-2">·</span>
<.timex value={@blog.published_at} format="{Mfull} {D}, {YYYY}" class="dt-published" />
<span>·</span>
<.timex
value={Core.Posts.publish_date_time(@blog)}
format="{Mfull} {D}, {YYYY}"
class="dt-published"
/>
<%= if @current_user do %>
<span>·</span>
<.link navigate={~p"/admin/posts/#{@blog}"}>edit</.link>
<% end %>
</div>
<% end %>
</header>

View file

@ -18,7 +18,7 @@
>
<h3 class="p-name u-url">{blog.title}</h3>
<div class="text-gray-500 dt-published">
<.timex value={blog.published_at} format="{Mfull} {D}" />
<.timex value={Core.Posts.publish_date_time(blog)} format="{Mfull} {D}" />
</div>
</.link>
</article>

View file

@ -49,7 +49,7 @@ defmodule Web.StatusHTML do
<span class="mx-2 text-sm text-gray-500">·</span>
<div class="text-sm text-gray-500">
<.timex
value={@status.published_at}
value={Core.Posts.publish_date_time(@status)}
format="{relative}"
formatter={:relative}
class="dt-published"

View file

@ -10,7 +10,7 @@
</div>
<article class="h-entry p-4 border border-gray-200">
<header class="flex items-center mb-3">
<header class="flex mb-3 justify-between">
<div class="h-card flex items-center gap-3 p-author">
<%= if Core.Author.get(:avatar_url) do %>
<img
@ -34,6 +34,10 @@
</p>
</div>
</div>
<%= if @current_user do %>
<.link class="text-sm text-gray-500" navigate={~p"/admin/posts/#{@status}"}>edit</.link>
<% end %>
</header>
<div class="e-content my-4">
@ -43,7 +47,7 @@
<footer class="mt-4 border-t border-gray-200 pt-2 text-sm text-gray-500">
<%= if @status.published_at do %>
<.timex
value={@status.published_at}
value={Core.Posts.publish_date_time(@status)}
format="{Mfull} {D}, {YYYY} at {h12}:{m} {AM}"
class="dt-published"
/>

View file

@ -53,7 +53,13 @@ defmodule Web.AdminPostLive do
socket =
case Core.Posts.create_or_update_post(post, attrs) do
{:ok, post} ->
form =
post
|> Core.Posts.change_post(%{})
|> to_form()
socket
|> assign(post: post, form: form)
|> put_flash(:info, "post saved")
|> push_patch(to: ~p"/admin/posts/#{post}", replace: true)
@ -68,7 +74,12 @@ defmodule Web.AdminPostLive do
socket =
case Core.Posts.publish_post(post) do
{:ok, post} ->
assign(socket, post: post)
form =
post
|> Core.Posts.change_post(%{})
|> to_form()
assign(socket, post: post, form: form)
_ ->
socket
@ -81,7 +92,12 @@ defmodule Web.AdminPostLive do
socket =
case Core.Posts.unpublish_post(post) do
{:ok, post} ->
assign(socket, post: post)
form =
post
|> Core.Posts.change_post(%{})
|> to_form()
assign(socket, post: post, form: form)
_ ->
socket
@ -94,7 +110,12 @@ defmodule Web.AdminPostLive do
socket =
case Core.Posts.delete_post(post) do
{:ok, post} ->
assign(socket, post: post)
form =
post
|> Core.Posts.change_post(%{})
|> to_form()
assign(socket, post: post, form: form)
_ ->
socket
@ -107,7 +128,12 @@ defmodule Web.AdminPostLive do
socket =
case Core.Posts.undelete_post(post) do
{:ok, post} ->
assign(socket, post: post)
form =
post
|> Core.Posts.change_post(%{})
|> to_form()
assign(socket, post: post, form: form)
_ ->
socket

View file

@ -1,33 +1,41 @@
<main>
<header>
<h1>{page_title(@post, @live_action)}</h1>
</header>
<.form for={@form} phx-change="validate" phx-submit="save">
<.input :if={@post.kind == :blog} type="text" field={@form[:title]} />
<.form
for={@form}
phx-change="validate"
phx-submit="save"
class="flex flex-col md:flex-row p-4 gap-4"
>
<div class="flex-grow flex flex-col gap-y-4">
<.input :if={@post.kind == :blog} type="text" label="title" field={@form[:title]} />
<.input type="textarea" field={@form[:body]} label="body" class="md:flex-grow" />
</div>
<div class="md:w-80 flex flex-col gap-y-2">
<.button type="submit" class="self-end">save</.button>
<.link
:if={@post.published_at}
class="self-end"
navigate={Web.Paths.public_post_path(@post)}
target="_blank"
>
view
</.link>
<.input type="datetime-local" label="published (utc)" field={@form[:published_at]} />
<%= if @post.published_at do %>
<.button phx-click="unpublish" class="self-end">unpublish</.button>
<% else %>
<.button phx-click="publish" class="self-end">publish</.button>
<% end %>
<.input type="datetime-local" label="deleted (utc)" field={@form[:deleted_at]} />
<%= if @post.deleted_at do %>
<.button phx-click="undelete" class="self-end">undelete</.button>
<% else %>
<.button phx-click="delete" class="self-end">delete</.button>
<% end %>
<.input
:if={@post.kind == :blog}
label="slug"
type="text"
field={@form[:slug]}
disabled={not update_slug?(@post)}
/>
<.input type="textarea" field={@form[:body]} />
<.button type="submit">save</.button>
</.form>
<%= if @live_action == :edit do %>
<div>
<%= if @post.published_at do %>
<.button phx-click="unpublish">unpublish</.button>
<% else %>
<.button phx-click="publish">publish</.button>
<% end %>
<%= if @post.deleted_at do %>
<.button phx-click="undelete">undelete</.button>
<% else %>
<.button phx-click="delete">delete</.button>
<% end %>
</div>
<% end %>
</main>
</div>
</.form>