@@ -12,6 +12,7 @@ defmodule AlgoraWeb.Org.DashboardLive do
1212 alias Algora.Bounties
1313 alias Algora.Bounties.Bounty
1414 alias Algora.Bounties.Claim
15+ alias Algora.Chat
1516 alias Algora.Contracts
1617 alias Algora.Github
1718 alias Algora.Organizations
@@ -55,8 +56,11 @@ defmodule AlgoraWeb.Org.DashboardLive do
5556 Workspace . list_contributors ( current_org . provider_login )
5657 end
5758
59+ admins_last_active = Algora.Admin . admins_last_active ( )
60+
5861 { :ok ,
5962 socket
63+ |> assign ( :admins_last_active , admins_last_active )
6064 |> assign ( :has_fresh_token? , Accounts . has_fresh_token? ( socket . assigns . current_user ) )
6165 |> assign ( :installations , installations )
6266 |> assign ( :experts , experts )
@@ -74,7 +78,11 @@ defmodule AlgoraWeb.Org.DashboardLive do
7478 |> assign_login_form ( User . login_changeset ( % User { } , % { } ) )
7579 |> assign_payable_bounties ( )
7680 |> assign_contracts ( )
77- |> assign_achievements ( ) }
81+ |> assign_achievements ( )
82+ # Will be initialized when chat starts
83+ |> assign ( :thread , nil )
84+ |> assign ( :messages , [ ] )
85+ |> assign ( :show_chat , false ) }
7886 else
7987 { :ok , redirect ( socket , to: ~p" /org/#{ current_org . handle } /home" ) }
8088 end
@@ -521,6 +529,15 @@ defmodule AlgoraWeb.Org.DashboardLive do
521529 end
522530 end
523531
532+ @ impl true
533+ def handle_info ( % Chat.Message { } = message , socket ) do
534+ if message . id in Enum . map ( socket . assigns . messages , & & 1 . id ) do
535+ { :noreply , socket }
536+ else
537+ { :noreply , assign ( socket , :messages , socket . assigns . messages ++ [ message ] ) }
538+ end
539+ end
540+
524541 @ impl true
525542 def handle_event ( "install_app" = event , unsigned_params , socket ) do
526543 { :noreply ,
@@ -775,6 +792,46 @@ defmodule AlgoraWeb.Org.DashboardLive do
775792 end }
776793 end
777794
795+ @ impl true
796+ def handle_event ( "start_chat" , _params , socket ) do
797+ # Get or create thread between user and founders
798+ { :ok , thread } = Chat . get_or_create_admin_thread ( socket . assigns . current_user )
799+
800+ if connected? ( socket ) do
801+ Phoenix.PubSub . subscribe ( Algora.PubSub , "chat:thread:#{ thread . id } " )
802+ end
803+
804+ messages = thread . id |> Chat . list_messages ( ) |> Repo . preload ( :sender )
805+
806+ { :noreply ,
807+ socket
808+ |> assign ( :thread , thread )
809+ |> assign ( :messages , messages )
810+ |> assign ( :show_chat , true ) }
811+ end
812+
813+ @ impl true
814+ def handle_event ( "close_chat" , _params , socket ) do
815+ { :noreply , assign ( socket , :show_chat , false ) }
816+ end
817+
818+ @ impl true
819+ def handle_event ( "send_message" , % { "message" => content } , socket ) do
820+ { :ok , message } =
821+ Chat . send_message (
822+ socket . assigns . thread . id ,
823+ socket . assigns . current_user . id ,
824+ content
825+ )
826+
827+ message = Repo . preload ( message , :sender )
828+
829+ { :noreply ,
830+ socket
831+ |> Phoenix.Component . update ( :messages , & ( & 1 ++ [ message ] ) )
832+ |> push_event ( "clear-input" , % { selector: "#message-input" } ) }
833+ end
834+
778835 @ impl true
779836 def handle_event ( _event , _params , socket ) do
780837 { :noreply , socket }
@@ -1351,7 +1408,9 @@ defmodule AlgoraWeb.Org.DashboardLive do
13511408 class = "relative z-0 inline-block size-6 rounded-full ring-2 ring-background "
13521409 />
13531410 </ div >
1354- Chat with founders
1411+ < button phx-click = "start_chat " class = "hover:underline " >
1412+ Chat with founders
1413+ </ button >
13551414 </ div >
13561415 < div class = "flex items-center gap-2 " >
13571416 < . icon name = "tabler-brand-x " class = "size-6 text-muted-foreground " /> @algoraio
@@ -1363,6 +1422,113 @@ defmodule AlgoraWeb.Org.DashboardLive do
13631422 < . icon name = "tabler-mail " class = "size-6 text-muted-foreground " /> [email protected] 13641423 </ div >
13651424 </ div >
1425+
1426+ <%= if @ show_chat do %>
1427+ < div class = "fixed bottom-0 right-96 w-[400px] h-[500px] flex flex-col border border-border bg-background rounded-t-lg shadow-lg " >
1428+ < div class = "flex flex-none items-center justify-between border-b border-border bg-card/50 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60 " >
1429+ < div class = "flex items-center gap-3 " >
1430+ < div class = "flex -space-x-2 " >
1431+ < . avatar class = "relative z-10 h-8 w-8 ring-2 ring-background " >
1432+ < . avatar_image src = "https://github.com/ioannisflo.png " alt = "Ioannis Florokapis " />
1433+ </ . avatar >
1434+ < . avatar class = "relative z-0 h-8 w-8 ring-2 ring-background " >
1435+ < . avatar_image src = "https://github.com/zcesur.png " alt = "Zafer Cesur " />
1436+ </ . avatar >
1437+ </ div >
1438+ < div >
1439+ < h2 class = "text-lg font-semibold " > Algora Founders</ h2 >
1440+ < p class = "text-xs text-muted-foreground " >
1441+ Active { Algora.Util . time_ago ( @ admins_last_active ) }
1442+ </ p >
1443+ </ div >
1444+ </ div >
1445+ < . button variant = "ghost " size = "icon " phx-click = "close_chat " >
1446+ < . icon name = "tabler-x " class = "h-4 w-4 " />
1447+ </ . button >
1448+ </ div >
1449+
1450+ < . scroll_area
1451+ class = "flex h-full flex-1 flex-col-reverse gap-6 p-4 "
1452+ id = "messages-container "
1453+ phx-hook = "ScrollToBottom "
1454+ >
1455+ < div class = "space-y-6 " >
1456+ <%= for {date, messages} <- @messages
1457+ |> Enum.group_by(fn msg ->
1458+ case Date.diff(Date.utc_today(), DateTime.to_date(msg.inserted_at)) do
1459+ 0 - > "Today "
1460+ 1 - > "Yesterday "
1461+ n when n < = 7 - > Calendar.strftime(msg.inserted_at, "%A ")
1462+ _ - > Calendar.strftime(msg.inserted_at, "%b %d ")
1463+ end
1464+ end )
1465+ |> Enum . sort_by ( fn { _ , msgs } -> hd ( msgs ) . inserted_at end , Date ) do %>
1466+ < div class = "flex items-center justify-center " >
1467+ < div class = "rounded-full bg-background px-2 py-1 text-xs text-muted-foreground " >
1468+ { date }
1469+ </ div >
1470+ </ div >
1471+
1472+ < div class = "flex flex-col gap-6 " >
1473+ <%= for message <- Enum . sort_by ( messages , & & 1 . inserted_at , Date ) do %>
1474+ < div class = "group flex gap-3 " >
1475+ < . avatar class = "h-8 w-8 " >
1476+ < . avatar_image src = { message . sender . avatar_url } />
1477+ < . avatar_fallback >
1478+ { Algora.Util . initials ( message . sender . name ) }
1479+ </ . avatar_fallback >
1480+ </ . avatar >
1481+ < div class = "max-w-[80%] relative rounded-2xl rounded-tl-none bg-muted p-3 " >
1482+ { message . content }
1483+ < div class = "text-[10px] mt-1 text-muted-foreground " >
1484+ { message . inserted_at
1485+ |> DateTime . to_time ( )
1486+ |> Time . to_string ( )
1487+ |> String . slice ( 0 .. 4 ) }
1488+ </ div >
1489+ </ div >
1490+ </ div >
1491+ <% end %>
1492+ </ div >
1493+ <% end %>
1494+ </ div >
1495+ </ . scroll_area >
1496+
1497+ < div class = "mt-auto flex-none border-t border-border bg-card/50 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60 " >
1498+ < form phx-submit = "send_message " class = "flex items-center gap-2 " >
1499+ < div class = "relative flex-1 " >
1500+ < . input
1501+ id = "message-input "
1502+ type = "text "
1503+ name = "message "
1504+ value = ""
1505+ placeholder = "Type a message... "
1506+ autocomplete = "off "
1507+ class = "flex-1 pr-24 "
1508+ phx-hook = "ClearInput "
1509+ />
1510+ < div class = "absolute top-1/2 right-2 flex -translate-y-1/2 gap-1 " >
1511+ < . button
1512+ type = "button "
1513+ variant = "ghost "
1514+ size = "icon-sm "
1515+ phx-hook = "EmojiPicker "
1516+ id = "emoji-trigger "
1517+ >
1518+ < . icon name = "tabler-mood-smile " class = "h-4 w-4 " />
1519+ </ . button >
1520+ </ div >
1521+ </ div >
1522+ < . button type = "submit " size = "icon " >
1523+ < . icon name = "tabler-send " class = "h-4 w-4 " />
1524+ </ . button >
1525+ </ form >
1526+ < div id = "emoji-picker-container " class = "bottom-[80px] absolute right-4 hidden " >
1527+ < emoji-picker > </ emoji-picker >
1528+ </ div >
1529+ </ div >
1530+ </ div >
1531+ <% end %>
13661532 </ aside >
13671533 """
13681534 end
0 commit comments