1
0
mirror of https://github.com/algora-io/tv.git synced 2025-03-17 20:17:45 +02:00
2024-05-31 12:29:56 +03:00

373 lines
14 KiB
Elixir

defmodule AlgoraWeb.ShowLive.Show do
use AlgoraWeb, :live_view
alias Algora.{Shows, Library, Events}
alias Algora.Accounts
alias AlgoraWeb.LayoutComponent
@impl true
def render(assigns) do
~H"""
<img
src={@show.image_url}
class="w-screen min-w-screen max-h-[200px] sm:max-h-none sm:h-[200px] md:h-[250px] lg:h-[350px] object-cover"
/>
<div class="text-white min-h-screen p-8">
<div class="flex flex-col md:grid md:grid-cols-3 gap-6">
<div class="md:col-span-1 bg-white/5 ring-1 ring-white/15 rounded-lg p-6 space-y-6">
<div class="space-y-2">
<div class="flex items-center space-x-2">
<span class="relative ring-4 ring-[#15122c] flex h-10 w-10 shrink-0 overflow-hidden rounded-full">
<img
class="aspect-square h-full w-full"
alt={@channel.name}
src={@channel.avatar_url}
/>
</span>
<span class="font-bold"><%= @channel.name %></span>
<.link
:if={@channel.twitter_url}
target="_blank"
rel="noopener noreferrer"
href={@channel.twitter_url}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-white"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M4 4l11.733 16h4.267l-11.733 -16z" /><path d="M4 20l6.768 -6.768m2.46 -2.46l6.772 -6.772" />
</svg>
</.link>
</div>
<div :if={!@owns_show?} class="pt-2">
<.button :if={@current_user} phx-click="toggle_subscription">
<%= if @subscribed? do %>
Unsubscribe
<% else %>
Subscribe
<% end %>
</.button>
<.button :if={!@current_user && @authorize_url}>
<.link navigate={@authorize_url}>
Subscribe
</.link>
</.button>
</div>
<div :if={@owns_show?} class="pt-2">
<.button>
<.link patch={~p"/shows/#{@show.slug}/edit"}>
Edit show
</.link>
</.button>
</div>
</div>
<div :if={@attendees_count > 0} class="border-t border-white/15 pt-6 space-y-4">
<div>
<span class="font-medium"><%= @attendees_count %> Attending</span>
</div>
<div>
<div class="flex -space-x-1">
<span
:for={attendee <- @attendees |> Enum.take(@max_attendee_avatars_count)}
class="relative ring-4 ring-[#15122c] flex h-10 w-10 shrink-0 overflow-hidden rounded-full"
>
<img
class="aspect-square h-full w-full"
alt={attendee.user_display_name}
src={attendee.user_avatar_url}
/>
</span>
</div>
<div class="mt-2">
<span
:for={
{attendee, i} <-
Enum.with_index(@attendees) |> Enum.take(@max_attendee_names_count)
}
class="font-medium"
>
<span :if={i != 0} class="-ml-1">, </span><span><%= attendee.user_display_name %></span>
</span>
<span :if={@attendees_count > @max_attendee_names_count} class="font-medium">
and
<span :if={@attendees_count == @max_attendee_names_count + 1}>
<%= @attendees
|> Enum.map(fn attendee -> attendee.user_display_name end)
|> Enum.at(@max_attendee_names_count) %>
</span>
<span :if={@attendees_count != @max_attendee_names_count + 1}>
<%= @attendees_count - @max_attendee_names_count %> others
</span>
</span>
</div>
</div>
</div>
</div>
<div class="md:col-span-2 space-y-6">
<div class="bg-white/5 ring-1 ring-white/15 rounded-lg p-6 space-y-6">
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-6">
<div>
<h1 class="text-4xl font-bold"><%= @show.title %></h1>
<div :if={@show.description} class="mt-4 space-y-4">
<div>
<h2 class="text-2xl font-bold">About</h2>
<p class="typography whitespace-pre-wrap"><%= @show.description %></p>
</div>
</div>
</div>
<div class="space-y-2 w-full max-w-xs md:max-w-sm">
<div :if={@show.scheduled_for} class="bg-gray-950/75 p-4 rounded-lg">
<div class="flex flex-col md:flex-wrap md:flex-row gap-2 md:items-center md:justify-between">
<div class="flex items-center space-x-4">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="h-6 w-6 text-green-300 shrink-0"
>
<path d="M8 2v4"></path>
<path d="M16 2v4"></path>
<rect width="18" height="18" x="3" y="4" rx="2"></rect>
<path d="M3 10h18"></path>
</svg>
<div class="shrink-0">
<div class="text-sm font-semibold">
<%= @show.scheduled_for
|> Timex.to_datetime("Etc/UTC")
|> Timex.Timezone.convert("America/New_York")
|> Timex.format!("{WDfull}, {Mshort} {D}") %>
</div>
<div class="text-sm">
<%= @show.scheduled_for
|> Timex.to_datetime("Etc/UTC")
|> Timex.Timezone.convert("America/New_York")
|> Timex.format!("{h12}:{m} {am}, Eastern Time") %>
</div>
<.link
phx-hook="TimezoneDetector"
class="text-sm underline"
href={~p"/shows/#{@show.slug}/event.ics?tz=#{@current_user_tz}"}
>
Add to calendar
</.link>
</div>
</div>
<.button :if={@current_user && !@rsvpd?} phx-click="toggle_rsvp">
Attend
</.button>
<.button
:if={@rsvpd?}
disabled
class="bg-green-400 hover:bg-green-400 disabled:opacity-100 text-green-950 flex items-center active:text-green-950"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="h-5 w-5 -ml-0.5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M5 12l5 5l10 -10" />
</svg>
<span class="ml-1">Attending</span>
</.button>
<.button :if={!@current_user && @authorize_url}>
<.link navigate={@authorize_url}>
Attend
</.link>
</.button>
</div>
</div>
<.link
href={@show.url || ~p"/#{@channel.handle}/latest"}
target="_blank"
rel="noopener"
class="block bg-gray-950/75 p-4 rounded-lg"
>
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4 shrink-0">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="h-6 w-6 text-red-400"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M18.364 19.364a9 9 0 1 0 -12.728 0" /><path d="M15.536 16.536a5 5 0 1 0 -7.072 0" /><path d="M12 13m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
</svg>
<div>
<div class="text-sm font-semibold">Watch live</div>
<div class="text-sm">
<%= @show.url || "tv.algora.io/#{@channel.handle}/latest" %>
</div>
</div>
</div>
</div>
</.link>
</div>
</div>
</div>
<div class="bg-white/5 ring-1 ring-white/15 rounded-lg p-6 pb-2 space-y-6">
<h2 class="pt-4 text-2xl font-bold">Past sessions</h2>
<div
id="past-sessions"
class="flex gap-8 overflow-x-scroll pb-4 scrollbar-thin"
phx-update="stream"
>
<div :for={{_id, video} <- @streams.videos} class="max-w-xs shrink-0 w-full">
<.link class="cursor-pointer truncate" href={~p"/#{video.channel_handle}/#{video.id}"}>
<.video_thumbnail video={video} class="rounded-2xl" />
<div class="pt-2 text-base font-semibold truncate"><%= video.title %></div>
<div class="text-gray-300 text-sm"><%= Timex.from_now(video.inserted_at) %></div>
</.link>
</div>
</div>
</div>
</div>
</div>
</div>
<.modal
:if={@live_action in [:new, :edit]}
id="show-modal"
show
on_cancel={JS.patch(~p"/shows/#{@show.slug}")}
>
<.live_component
module={AlgoraWeb.ShowLive.FormComponent}
id={@show.id || :new}
title={@page_title}
action={@live_action}
show={@show}
/>
</.modal>
"""
end
@impl true
def mount(%{"slug" => slug}, _session, socket) do
%{current_user: current_user} = socket.assigns
show = Shows.get_show_by_fields!(slug: slug)
channel = Accounts.get_user(show.user_id) |> Library.get_channel!()
videos = Library.list_videos_by_show_ids([show.id])
{:ok,
socket
|> assign_attendees(show)
|> assign(:current_user_tz, "Etc/UTC")
|> assign(:show, show)
|> assign(:owns_show?, current_user && show.user_id == current_user.id)
|> assign(:channel, channel)
|> assign(:max_attendee_avatars_count, 5)
|> assign(:max_attendee_names_count, 2)
|> assign(:subscribed?, Events.subscribed?(current_user, channel))
|> assign(:rsvpd?, Events.rsvpd?(current_user, channel))
|> stream(:videos, videos)}
end
@impl true
def handle_params(params, url, socket) do
%{path: path} = URI.parse(url)
LayoutComponent.hide_modal()
{:noreply,
socket
|> assign(:authorize_url, Algora.Github.authorize_url(path))
|> apply_action(socket.assigns.live_action, params)}
end
@impl true
def handle_event("toggle_subscription", _params, socket) do
Events.toggle_subscription_event(socket.assigns.current_user, socket.assigns.show)
{:noreply,
socket
|> assign(:subscribed?, !socket.assigns.subscribed?)}
end
def handle_event("toggle_rsvp", _params, socket) do
Events.toggle_rsvp_event(socket.assigns.current_user, socket.assigns.show)
{:noreply,
socket
|> assign_attendees(socket.assigns.show)
|> assign(:rsvpd?, !socket.assigns.rsvpd?)}
end
def handle_event("get_timezone", %{"tz" => tz}, socket) do
{:noreply, socket |> assign(:current_user_tz, tz)}
end
@impl true
def handle_info({AlgoraWeb.ShowLive.FormComponent, {:saved, show}}, socket) do
{:noreply, socket |> assign(:show, show)}
end
defp apply_action(socket, :show, %{"slug" => slug}) do
show = Shows.get_show_by_fields!(slug: slug)
socket
|> assign(:page_title, show.title)
|> assign(:page_description, show.description)
|> assign(:page_image, show.og_image_url)
end
defp apply_action(socket, :edit, %{"slug" => slug}) do
%{current_user: current_user} = socket.assigns
show = Shows.get_show_by_fields!(slug: slug)
cond do
current_user == nil ->
socket
|> redirect(to: ~p"/auth/login")
current_user.id != show.user_id && !Accounts.admin?(current_user) ->
socket
|> put_flash(:error, "You don't have permission to edit this show")
|> redirect(to: ~p"/shows/#{show.slug}")
true ->
socket
|> assign(:page_title, show.title)
|> assign(:page_description, show.description)
|> assign(:page_image, show.og_image_url)
end
end
defp assign_attendees(socket, show) do
attendees = Events.fetch_attendees(show)
socket
|> assign(:attendees, attendees)
|> assign(:attendees_count, length(attendees))
end
end