Skip to content

Commit

Permalink
List users for chat
Browse files Browse the repository at this point in the history
* List users with Phoenix presence
* Move messages list out of form component
* See #1299
  • Loading branch information
axelclark committed Apr 7, 2024
1 parent bcd87ee commit 9c7958b
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 81 deletions.
1 change: 1 addition & 0 deletions lib/ex338/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ defmodule Ex338.Application do
Ex338.Repo,
{Phoenix.PubSub, pubsub_options},
Ex338Web.Telemetry,
Ex338Web.Presence,
# Start the endpoint when the application starts
Ex338Web.Endpoint,
{Oban, Application.fetch_env!(:ex338, Oban)}
Expand Down
4 changes: 2 additions & 2 deletions lib/ex338/chats.ex
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ defmodule Ex338.Chats do
Phoenix.PubSub.broadcast(Ex338.PubSub, topic(message), {__MODULE__, event})
end

defp topic(%Message{} = message), do: "chat:#{message.chat_id}"
defp topic(%Chat{} = chat), do: "chat:#{chat.id}"
def topic(%Message{} = message), do: "chat:#{message.chat_id}"
def topic(%Chat{} = chat), do: "chat:#{chat.id}"
end
103 changes: 29 additions & 74 deletions lib/ex338_web/live/championship_live/chat_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,83 +52,38 @@ defmodule Ex338Web.ChampionshipLive.ChatComponent do
@impl true
def render(assigns) do
~H"""
<div class="overflow-hidden bg-white shadow sm:rounded-lg">
<div class="py-5 border-b border-gray-200">
<ul
id="messages"
phx-update="stream"
role="list"
phx-hook="ChatScrollToBottom"
class="flex flex-col h-[800px] overflow-y-auto overflow-x-hidden pb-6"
>
<.comment :for={{id, message} <- @messages} id={id} message={message} />
</ul>
<div class="flex gap-x-3 px-4 sm:px-6">
<.user_icon name={@current_user.name} class="!mt-0" />
<.form
id="create-message"
for={@form}
phx-target={@myself}
phx-change="validate"
phx-submit="save"
class="relative flex-auto"
>
<div class="overflow-hidden rounded-lg pb-12 shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-indigo-600">
<label for="comment" class="sr-only">Add your comment</label>
<.input
field={@form[:content]}
phx-debounce="blur"
type="commenttextarea"
rows="2"
class="block w-full resize-none border-0 bg-transparent py-1.5 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
placeholder="Add your comment..."
/>
</div>
<div class="absolute inset-x-0 bottom-0 flex justify-end py-2 pl-3 pr-2">
<button
type="submit"
class="rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
>
Comment
</button>
</div>
</.form>
<div class="flex gap-x-3 px-4 sm:px-6">
<.user_icon name={@current_user.name} class="!mt-0" />
<.form
id="create-message"
for={@form}
phx-target={@myself}
phx-change="validate"
phx-submit="save"
class="relative flex-auto"
>
<div class="overflow-hidden rounded-lg pb-12 shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-indigo-600">
<label for="comment" class="sr-only">Add your comment</label>
<.input
field={@form[:content]}
phx-debounce="blur"
type="commenttextarea"
rows="2"
class="block w-full resize-none border-0 bg-transparent py-1.5 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
placeholder="Add your comment..."
/>
</div>
</div>
</div>
"""
end
defp comment(%{message: %{user: nil}} = assigns) do
~H"""
<li id={@id} class="flex gap-x-4 hover:bg-gray-50 px-4 sm:px-6 py-2">
<div class="flex h-6 w-6 flex-none items-center justify-center">
<.icon name="hero-check-circle" class="h-6 w-6 text-indigo-600" />
</div>
<p class="flex-auto py-0.5 text-xs leading-5 text-gray-500">
<%= @message.content %>
</p>
</li>
"""
end

defp comment(assigns) do
~H"""
<li id={@id} class="flex gap-x-4 hover:bg-gray-50 px-4 sm:px-6 py-2">
<.user_icon name={@message.user.name} />
<div class="flex-auto">
<div class="flex justify-between items-start gap-x-4">
<div class="text-xs leading-5 font-medium text-gray-900">
<%= @message.user.name %>
</div>
<div class="absolute inset-x-0 bottom-0 flex justify-end py-2 pl-3 pr-2">
<button
type="submit"
class="rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
>
Comment
</button>
</div>
<p class="text-sm leading-6 text-gray-500">
<%= @message.content %>
</p>
</div>
</li>
</.form>
</div>
"""
end

Expand Down
141 changes: 136 additions & 5 deletions lib/ex338_web/live/championship_live/show.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule Ex338Web.ChampionshipLive.Show do
alias Ex338.FantasyLeagues
alias Ex338.InSeasonDraftPicks
alias Ex338Web.ChampionshipLive.ChatComponent
alias Ex338Web.Presence

@impl true
def mount(_params, _session, socket) do
Expand Down Expand Up @@ -50,18 +51,38 @@ defmodule Ex338Web.ChampionshipLive.Show do
Chats.subscribe(chat, socket.assigns.current_user)
end

current_user = socket.assigns.current_user

if chat && current_user do
Presence.track(
self(),
Chats.topic(chat),
current_user.id,
default_user_presence_payload(current_user)
)
end

socket
|> assign(:chat, chat)
|> assign(:message, %Message{})
|> stream(:messages, chat.messages)
|> assign(:users, Presence.list_presences(Chats.topic(chat)))
else
_ ->
socket
|> assign(:chat, nil)
|> assign(:users, [])
|> stream(:messages, [])
end
end

defp default_user_presence_payload(user) do
%{
name: user.name,
user_id: user.id
}
end

@impl true
def handle_info(:refresh, socket) do
championship = Championships.update_next_in_season_pick(socket.assigns.championship)
Expand Down Expand Up @@ -112,6 +133,16 @@ defmodule Ex338Web.ChampionshipLive.Show do
{:noreply, stream_insert(socket, :messages, message)}
end

def handle_info(
%{event: "presence_diff", payload: _payload},
%{assigns: %{chat: chat}} = socket
) do
users =
Presence.list_presences(Chats.topic(chat))

{:noreply, assign(socket, users: users)}
end

# Implementations

defp schedule_refresh do
Expand Down Expand Up @@ -312,14 +343,14 @@ defmodule Ex338Web.ChampionshipLive.Show do
<.section_header>
Draft Chat
</.section_header>
<.live_component
module={ChatComponent}
id="chat"
<.chat_list
championship={@championship}
chat={@chat}
current_user={@current_user}
fantasy_league={@fantasy_league}
messages={@streams.messages}
message={@message}
current_user={@current_user}
patch={~p"/fantasy_leagues/#{@fantasy_league.id}/championships/#{@championship.id}"}
users={@users}
/>
</div>
<% end %>
Expand Down Expand Up @@ -552,6 +583,106 @@ defmodule Ex338Web.ChampionshipLive.Show do
"""
end

attr :chat, :map, required: true
attr :championship, :map, required: true
attr :current_user, :map, required: true
attr :fantasy_league, :map, required: true
attr :message, :map, required: true
attr :messages, :list, required: true
attr :users, :list, required: true

def chat_list(assigns) do
~H"""
<div class="overflow-hidden bg-white shadow sm:rounded-lg">
<div class="py-5 border-b border-gray-200">
<ul
id="messages"
phx-update="stream"
role="list"
phx-hook="ChatScrollToBottom"
class="flex flex-col h-[800px] overflow-y-auto overflow-x-hidden pb-6"
>
<.comment :for={{id, message} <- @messages} id={id} message={message} />
</ul>
<.live_component
module={ChatComponent}
id="chat"
chat={@chat}
message={@message}
current_user={@current_user}
patch={~p"/fantasy_leagues/#{@fantasy_league.id}/championships/#{@championship.id}"}
/>
<div class="mt-4 px-4 sm:px-6 ">
<h3>Online Users</h3>
<%= for user <- @users do %>
<p id={"online-user-#{user.user_id}"}>
<span class="inline-block h-2 w-2 flex-shrink-0 rounded-full bg-green-400">
<span class="sr-only">Online</span>
</span>
<span class="ml-1 text-xs leading-5 font-medium text-gray-900">
<%= user.name %>
</span>
</p>
<% end %>
</div>
</div>
</div>
"""
end

defp comment(%{message: %{user: nil}} = assigns) do
~H"""
<li id={@id} class="flex gap-x-4 hover:bg-gray-50 px-4 sm:px-6 py-2">
<div class="flex h-6 w-6 flex-none items-center justify-center">
<.icon name="hero-check-circle" class="h-6 w-6 text-indigo-600" />
</div>
<p class="flex-auto py-0.5 text-xs leading-5 text-gray-500">
<%= @message.content %>
</p>
</li>
"""
end

defp comment(assigns) do
~H"""
<li id={@id} class="flex gap-x-4 hover:bg-gray-50 px-4 sm:px-6 py-2">
<.user_icon name={@message.user.name} />
<div class="flex-auto">
<div class="flex justify-between items-start gap-x-4">
<div class="text-xs leading-5 font-medium text-gray-900">
<%= @message.user.name %>
</div>
</div>
<p class="text-sm leading-6 text-gray-500">
<%= @message.content %>
</p>
</div>
</li>
"""
end

attr :name, :string, required: true
attr :class, :string, default: nil

defp user_icon(assigns) do
~H"""
<div class={[
"h-6 w-6 flex flex-shrink-0 items-center justify-center bg-gray-600 rounded-full text-xs font-medium text-white",
@class
]}>
<%= get_initials(@name) %>
</div>
"""
end

defp get_initials(name) do
name
|> String.split(" ")
|> Enum.take(2)
|> Enum.map_join("", &String.at(&1, 0))
end

defp get_team_name(%{fantasy_player: %{roster_positions: [position]}}) do
position.fantasy_team.team_name
end
Expand Down
16 changes: 16 additions & 0 deletions lib/ex338_web/presence.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
defmodule Ex338Web.Presence do
@moduledoc false
use Phoenix.Presence,
otp_app: :ex338,
pubsub_server: Ex338.PubSub

alias Ex338Web.Presence

def list_presences(topic) do
topic
|> Presence.list()
|> Enum.map(fn {_user_id, data} ->
List.first(data[:metas])
end)
end
end
1 change: 1 addition & 0 deletions test/ex338_web/live/championship_live/show_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ defmodule Ex338Web.ChampionshipLive.ShowTest do

assert has_element?(view, "h3", "Draft")
assert has_element?(view, "p", "hello world!")
assert has_element?(view, "p#online-user-#{user.id}", user.name)

long_comment = """
In a quiet town nestled between rolling hills and dense forests, a small but spirited
Expand Down

0 comments on commit 9c7958b

Please sign in to comment.