You've already forked algora-tv
							
							
				mirror of
				https://github.com/algora-io/tv.git
				synced 2025-10-30 23:07:56 +02:00 
			
		
		
		
	revamp homepage (#44)
This commit is contained in:
		| @@ -581,6 +581,20 @@ defmodule Algora.Library do | ||||
|     |> Enum.reverse() | ||||
|   end | ||||
|  | ||||
|   def list_videos_by_show_ids(ids) do | ||||
|     from(v in Video, | ||||
|       join: u in User, | ||||
|       on: v.user_id == u.id, | ||||
|       select_merge: %{ | ||||
|         channel_handle: u.handle, | ||||
|         channel_name: u.name, | ||||
|         channel_avatar_url: u.avatar_url | ||||
|       }, | ||||
|       where: v.show_id in ^ids | ||||
|     ) | ||||
|     |> Repo.all() | ||||
|   end | ||||
|  | ||||
|   def list_shorts(limit \\ 100) do | ||||
|     from(v in Video, | ||||
|       join: u in User, | ||||
|   | ||||
| @@ -3,9 +3,28 @@ defmodule Algora.Shows do | ||||
|   alias Algora.Repo | ||||
|  | ||||
|   alias Algora.Shows.Show | ||||
|   alias Algora.Accounts.User | ||||
|  | ||||
|   def list_shows do | ||||
|     Repo.all(Show) | ||||
|   def list_shows() do | ||||
|     from(s in Show) | ||||
|     |> Repo.all() | ||||
|   end | ||||
|  | ||||
|   def list_featured_shows(limit \\ 100) do | ||||
|     from(s in Show, | ||||
|       join: u in User, | ||||
|       on: s.user_id == u.id, | ||||
|       limit: ^limit, | ||||
|       where: not is_nil(s.ordering), | ||||
|       select_merge: %{ | ||||
|         channel_handle: u.handle, | ||||
|         channel_name: coalesce(u.name, u.handle), | ||||
|         channel_avatar_url: u.avatar_url, | ||||
|         channel_twitter_url: u.twitter_url | ||||
|       }, | ||||
|       order_by: [{:desc, s.ordering}, {:desc, s.id}] | ||||
|     ) | ||||
|     |> Repo.all() | ||||
|   end | ||||
|  | ||||
|   def get_show!(id), do: Repo.get!(Show, id) | ||||
| @@ -31,4 +50,8 @@ defmodule Algora.Shows do | ||||
|   def change_show(%Show{} = show, attrs \\ %{}) do | ||||
|     Show.changeset(show, attrs) | ||||
|   end | ||||
|  | ||||
|   def list_videos do | ||||
|     Repo.all(Show) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -12,6 +12,11 @@ defmodule Algora.Shows.Show do | ||||
|     field :image_url, :string | ||||
|     field :og_image_url, :string | ||||
|     field :url, :string | ||||
|     field :ordering, :integer | ||||
|     field :channel_handle, :string, virtual: true | ||||
|     field :channel_name, :string, virtual: true | ||||
|     field :channel_avatar_url, :string, virtual: true | ||||
|     field :channel_twitter_url, :string, virtual: true | ||||
|  | ||||
|     belongs_to :user, User | ||||
|  | ||||
|   | ||||
							
								
								
									
										170
									
								
								lib/algora_web/live/homepage_live.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								lib/algora_web/live/homepage_live.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| defmodule AlgoraWeb.HomepageLive do | ||||
|   use AlgoraWeb, :live_view | ||||
|  | ||||
|   alias Algora.{Library, Shows} | ||||
|  | ||||
|   def render(assigns) do | ||||
|     ~H""" | ||||
|     <div class="mx-auto pt-2 pb-6 px-4 sm:px-6 space-y-6"> | ||||
|       <.header class="pt-8"> | ||||
|         <h1 class="text-4xl font-semibold">Livestreaming for developers</h1> | ||||
|         <p class="text-xl font-medium text-gray-200 italic">You'll never ship alone!</p> | ||||
|       </.header> | ||||
|  | ||||
|       <div> | ||||
|         <ul role="list" class="grid grid-cols-1 gap-12 sm:grid-cols-2"> | ||||
|           <li :for={show <- @shows} class="col-span-1"> | ||||
|             <div class="h-full flex flex-col rounded-2xl overflow-hidden bg-[#15112b] ring-1 ring-white/20 text-center shadow-lg relative group"> | ||||
|               <img | ||||
|                 class="object-cover absolute inset-0 shrink-0 h-[12rem] w-full bg-gray-950" | ||||
|                 src={show.image_url} | ||||
|                 alt="" | ||||
|               /> | ||||
|               <div class="absolute h-[12rem] w-full inset-0 bg-gradient-to-b from-transparent to-[#15112b]" /> | ||||
|               <.link navigate={~p"/shows/#{show.slug}"} class="absolute h-[10rem] w-full inset-0 z-10"> | ||||
|               </.link> | ||||
|               <div class="relative text-left h-full"> | ||||
|                 <div class="flex flex-1 flex-col h-full"> | ||||
|                   <div class="px-4 mt-[8rem] flex-col sm:flex-row flex sm:items-center gap-4"> | ||||
|                     <.link :if={show.channel_handle != "algora"} navigate={~p"/shows/#{show.slug}"}> | ||||
|                       <img | ||||
|                         class="h-[8rem] w-[8rem] rounded-full ring-4 ring-white shrink-0" | ||||
|                         src={show.channel_avatar_url} | ||||
|                         alt="" | ||||
|                       /> | ||||
|                     </.link> | ||||
|                     <div :if={show.channel_handle == "algora"} class="h-[8rem] w-0 -ml-4"></div> | ||||
|                     <div> | ||||
|                       <.link navigate={~p"/shows/#{show.slug}"}> | ||||
|                         <h3 class="mt-auto text-3xl font-semibold text-white [text-shadow:#000_10px_5px_10px] line-clamp-2 hover:underline"> | ||||
|                           <%= show.title %> | ||||
|                         </h3> | ||||
|                       </.link> | ||||
|                       <div :if={show.channel_handle != "algora"} class="flex items-center gap-2"> | ||||
|                         <.link navigate={~p"/#{show.channel_handle}"}> | ||||
|                           <div class="text-base text-gray-300 font-semibold line-clamp-1 hover:underline"> | ||||
|                             <%= show.channel_name %> | ||||
|                           </div> | ||||
|                         </.link> | ||||
|                         <.link | ||||
|                           :if={show.channel_twitter_url} | ||||
|                           target="_blank" | ||||
|                           rel="noopener noreferrer" | ||||
|                           href={show.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={show.channel_handle == "algora"} class="h-[24px]"></div> | ||||
|                     </div> | ||||
|                     <.link | ||||
|                       :if={show.scheduled_for} | ||||
|                       navigate={~p"/shows/#{show.slug}"} | ||||
|                       class="shrink-0 sm:hidden xl:flex bg-gray-900 px-3 py-2 rounded-lg ring-1 ring-green-300 mr-auto sm:mr-0 sm:ml-auto flex items-center space-x-2" | ||||
|                     > | ||||
|                       <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> | ||||
|                       </div> | ||||
|                     </.link> | ||||
|                   </div> | ||||
|  | ||||
|                   <div | ||||
|                     :if={length(Enum.filter(@show_eps, fn v -> v.show_id == show.id end)) > 0} | ||||
|                     class="mt-auto pt-[2rem] -mb-2" | ||||
|                   > | ||||
|                     <div class="flex justify-between items-center gap-2 px-2"> | ||||
|                       <h3 class="text-sm uppercase text-gray-300 font-semibold">Past episodes</h3> | ||||
|                     </div> | ||||
|                     <div class="p-2 flex gap-4 overflow-x-scroll scrollbar-thin transition-all"> | ||||
|                       <div | ||||
|                         :for={video <- Enum.filter(@show_eps, fn v -> v.show_id == show.id end)} | ||||
|                         class="max-w-[12rem] sm:max-w-[16rem] shrink-0 w-full" | ||||
|                       > | ||||
|                         <.link class="truncate" href={~p"/#{video.channel_handle}/#{video.id}"}> | ||||
|                           <.video_thumbnail video={video} class="rounded-xl" /> | ||||
|                         </.link> | ||||
|                       </div> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </li> | ||||
|         </ul> | ||||
|       </div> | ||||
|  | ||||
|       <div class="pt-12"> | ||||
|         <h2 class="text-white text-3xl font-semibold"> | ||||
|           Most recent livestreams | ||||
|         </h2> | ||||
|         <div> | ||||
|           <div class="pt-4 gap-8 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"> | ||||
|             <.video_entry :for={video <- @videos} video={video} /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     """ | ||||
|   end | ||||
|  | ||||
|   def mount(_params, _session, socket) do | ||||
|     shows = Shows.list_featured_shows() | ||||
|     show_eps = shows |> Enum.map(fn s -> s.id end) |> Library.list_videos_by_show_ids() | ||||
|     videos = Library.list_videos(150) | ||||
|  | ||||
|     {:ok, | ||||
|      socket | ||||
|      |> assign(:show_eps, show_eps) | ||||
|      |> assign(:videos, videos) | ||||
|      |> assign(:shows, shows)} | ||||
|   end | ||||
|  | ||||
|   def handle_params(params, _url, socket) do | ||||
|     {:noreply, socket |> apply_action(socket.assigns.live_action, params)} | ||||
|   end | ||||
|  | ||||
|   defp apply_action(socket, :show, _params) do | ||||
|     socket |> assign(:page_title, nil) | ||||
|   end | ||||
| end | ||||
| @@ -154,7 +154,7 @@ defmodule AlgoraWeb.ShowLive.FormComponent do | ||||
|         {:noreply, | ||||
|          socket | ||||
|          |> put_flash(:info, "Show created successfully") | ||||
|          |> push_patch(to: ~p"/shows/#{show.slug}")} | ||||
|          |> push_navigate(to: ~p"/shows/#{show.slug}")} | ||||
|  | ||||
|       {:error, %Ecto.Changeset{} = changeset} -> | ||||
|         {:noreply, assign_form(socket, changeset)} | ||||
|   | ||||
| @@ -99,7 +99,9 @@ defmodule AlgoraWeb.ShowLive.Show do | ||||
|                 <span :if={@attendees_count > @max_attendee_names_count} class="font-medium"> | ||||
|                   and | ||||
|                   <span :if={@attendees_count == @max_attendee_names_count + 1}> | ||||
|                     <%= @attendees |> Enum.at(@max_attendee_names_count) %> | ||||
|                     <%= @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 | ||||
| @@ -274,7 +276,7 @@ defmodule AlgoraWeb.ShowLive.Show do | ||||
|  | ||||
|     channel = Accounts.get_user(show.user_id) |> Library.get_channel!() | ||||
|  | ||||
|     videos = Library.list_channel_videos(channel, 50) | ||||
|     videos = Library.list_videos_by_show_ids([show.id]) | ||||
|  | ||||
|     {:ok, | ||||
|      socket | ||||
|   | ||||
| @@ -105,7 +105,7 @@ defmodule AlgoraWeb.Router do | ||||
|     end | ||||
|  | ||||
|     live_session :default, on_mount: [{AlgoraWeb.UserAuth, :current_user}, AlgoraWeb.Nav] do | ||||
|       live "/", HomeLive, :show | ||||
|       live "/", HomepageLive, :show | ||||
|       live "/auth/login", SignInLive, :index | ||||
|       live "/cossgpt", COSSGPTLive, :index | ||||
|       live "/og/cossgpt", COSSGPTOGLive, :index | ||||
|   | ||||
| @@ -0,0 +1,9 @@ | ||||
| defmodule Algora.Repo.Local.Migrations.AddOrderingToShow do | ||||
|   use Ecto.Migration | ||||
|  | ||||
|   def change do | ||||
|     alter table("shows") do | ||||
|       add :ordering, :integer | ||||
|     end | ||||
|   end | ||||
| end | ||||
		Reference in New Issue
	
	Block a user