lib/openagents_web/live/forum_home_live.ex

436d37facc96 · 2 KB

defmodule OpenAgentsWeb.ForumHomeLive do
  @moduledoc """
  The forum entry point: every listed board with its counts.

  The counts are current. A topic started anywhere moves the board it was
  started on, and because the list is ordered by topic count a board can
  overtake another while you are looking at it.

  The list is `Forum.list_public_forums/0` at mount and again on every refresh,
  which is the same predicate `list_readable_forums/1` composes: a board marked
  unlisted answers to its own slug and never arrives here, whoever is looking
  and however busy it gets. The badge is the board's stored counter rather than
  a count of its topics, so a page that lists every board does not load every
  board's collection to measure it.
  """
  use OpenAgentsWeb, :live_view

  alias OpenAgents.Forum
  alias OpenAgentsWeb.LiveRefresh

  def mount(_params, _session, socket) do
    if connected?(socket), do: Forum.subscribe_posts()

    {:ok,
     socket
     |> LiveRefresh.init()
     |> assign(:current_scope, socket.assigns[:current_scope])
     |> refresh_panel(:boards)}
  end

  def handle_info({:forum_posts_changed, _topic_id}, socket),
    do: {:noreply, LiveRefresh.mark_stale(socket, :boards, &refresh_panel/2)}

  def handle_info(:live_refresh, socket),
    do: {:noreply, LiveRefresh.run(socket, &refresh_panel/2)}

  defp refresh_panel(socket, :boards),
    do: assign(socket, :forums, Forum.list_public_forums())

  def render(assigns) do
    ~H"""
    <Layouts.app
      flash={@flash}
      sidebar_sections={assigns[:sidebar_sections]}
      current_scope={@current_scope}
    >
      <div class="mx-auto w-full max-w-3xl">
        <div class="flex items-center justify-between mb-6">
          <h1 class="text-2xl font-semibold tracking-tight">Forum</h1>
        </div>

        <%= if @forums == [] do %>
          <div class="alert" data-variant="info" role="status">
            <.icon name="info-circle" class="size-5" />
            <section>No boards yet.</section>
          </div>
        <% else %>
          <div class="flex flex-col gap-4">
            <%= for forum <- @forums do %>
              <.link
                navigate={~p"/forum/f/#{forum.slug}"}
                class="card !m-0 block hover:border-foreground/20 transition-colors"
              >
                <div class="flex items-baseline justify-between gap-3">
                  <h2 class="text-base font-semibold">{forum.title}</h2>
                  <span
                    id={"board-topics-#{forum.slug}"}
                    class="text-sm text-muted-foreground whitespace-nowrap"
                  >
                    {forum.topic_count} topics
                  </span>
                </div>
                <%= if forum.description do %>
                  <p class="text-sm text-muted-foreground mt-1.5 max-w-[60ch]">
                    {forum.description}
                  </p>
                <% end %>
              </.link>
            <% end %>
          </div>
        <% end %>
      </div>
    </Layouts.app>
    """
  end
end