Files
tribes-plugin-aether/lib/aether_web/live/chat_recipient_picker_component.ex
self b4b8c83ddb feat: namespace plugin identity
Adopt canonical plugin id/slug manifest fields, vendor-prefixed OTP app naming, and fully-qualified capability ids for Aether.
2026-05-27 19:05:51 +02:00

140 lines
4.7 KiB
Elixir

defmodule TribeOne.TribesPlugin.AetherWeb.ChatRecipientPickerComponent do
@moduledoc """
Recipient picker exposed as part of Aether's `org.tribe-one.caps.chat@1` UI contract.
"""
use Phoenix.LiveComponent
alias Tribes.Plugin.Services.Alliance
alias Tribes.Plugin.User
alias Tribes.Scope
@impl true
def update(assigns, socket) do
recipients = Map.get(assigns, :recipients) || load_recipients(Map.get(assigns, :current_user))
{:ok,
socket
|> assign(assigns)
|> assign_new(:query, fn -> "" end)
|> assign(:recipients, recipients)}
end
@impl true
def handle_event("search", %{"recipient" => %{"query" => query}}, socket) do
{:noreply, assign(socket, :query, String.trim(query || ""))}
end
def handle_event("select", %{"pubkey" => pubkey}, socket) do
case Enum.find(socket.assigns.recipients, &(&1.pubkey_hex == pubkey)) do
%User{} = user -> send(self(), {:aether_chat, :recipient_selected, user})
_other -> :ok
end
{:noreply, socket}
end
@impl true
def render(assigns) do
~H"""
<details id="chat-recipient-picker" class="dropdown dropdown-end">
<summary id="chat-recipient-picker-button" class="btn btn-primary btn-sm">
New message
</summary>
<div class="dropdown-content z-30 mt-2 w-80 rounded-box border border-base-300 bg-base-100 p-3 shadow-xl">
<form id="chat-recipient-search-form" phx-change="search" phx-target={@myself}>
<label class="input input-sm input-bordered flex items-center gap-2">
<span class="text-xs text-base-content/50">To</span>
<input
id="chat-recipient-search"
name="recipient[query]"
value={@query}
type="search"
class="grow"
placeholder="Search tribe users"
/>
</label>
</form>
<div id="chat-recipient-options" class="mt-3 max-h-72 space-y-1 overflow-y-auto">
<button
:for={recipient <- filtered_recipients(@recipients, @query, @current_user)}
id={"chat-recipient-#{recipient.id}"}
type="button"
class="flex w-full items-center gap-3 rounded-box px-3 py-2 text-left hover:bg-base-200"
phx-click="select"
phx-target={@myself}
phx-value-pubkey={recipient.pubkey_hex}
>
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-primary/10 text-sm font-semibold text-primary">
{recipient_initial(recipient)}
</span>
<span class="min-w-0">
<span class="block truncate text-sm font-medium text-base-content">{recipient_label(recipient)}</span>
<span class="block truncate text-xs text-base-content/50">{recipient_pubkey(recipient)}</span>
</span>
</button>
<p
:if={filtered_recipients(@recipients, @query, @current_user) == []}
id="chat-recipient-empty"
class="rounded-box border border-dashed border-base-300 p-4 text-center text-sm text-base-content/60"
>
No matching users.
</p>
</div>
</div>
</details>
"""
end
defp load_recipients(_current_user) do
scope = Scope.new()
with {:ok, tribe} when is_map(tribe) <- Alliance.local_tribe(scope),
{:ok, users} <- Alliance.list_tribe_users(scope, tribe.id) do
Enum.filter(users, &is_binary(&1.pubkey_hex))
else
_other -> []
end
end
defp filtered_recipients(recipients, query, current_user) do
current_pubkey = current_user && current_user.pubkey_hex
query = String.downcase(query || "")
recipients
|> Enum.reject(&(&1.pubkey_hex == current_pubkey))
|> Enum.filter(&matches_query?(&1, query))
end
defp matches_query?(_recipient, ""), do: true
defp matches_query?(recipient, query) do
[recipient.username, recipient.display_name, recipient.npub, recipient.pubkey_hex]
|> Enum.filter(&is_binary/1)
|> Enum.any?(&String.contains?(String.downcase(&1), query))
end
defp recipient_label(%User{display_name: display_name})
when is_binary(display_name) and display_name != "" do
display_name
end
defp recipient_label(%User{username: username}) when is_binary(username) and username != "",
do: username
defp recipient_label(%User{pubkey_hex: pubkey}), do: String.slice(pubkey, 0, 8) <> ""
defp recipient_pubkey(%User{npub: npub}) when is_binary(npub) and npub != "", do: npub
defp recipient_pubkey(%User{pubkey_hex: pubkey}), do: pubkey
defp recipient_initial(recipient) do
recipient
|> recipient_label()
|> String.first()
|> Kernel.||("?")
|> String.upcase()
end
end