You've already forked algora-tv
							
							
				mirror of
				https://github.com/algora-io/tv.git
				synced 2025-10-30 23:07:56 +02:00 
			
		
		
		
	add subscriptions page (#43)
This commit is contained in:
		| @@ -6,6 +6,17 @@ defmodule Algora.Events do | ||||
|   alias Algora.Events.Event | ||||
|   alias Algora.Accounts.{User, Identity} | ||||
|  | ||||
|   def unsubscribe(user, channel_id) do | ||||
|     %Event{ | ||||
|       actor_id: "user_#{user.id}", | ||||
|       user_id: user.id, | ||||
|       channel_id: channel_id, | ||||
|       name: :unsubscribed | ||||
|     } | ||||
|     |> Event.changeset(%{}) | ||||
|     |> Repo.insert() | ||||
|   end | ||||
|  | ||||
|   def toggle_subscription_event(user, show) do | ||||
|     name = if subscribed?(user, show), do: :unsubscribed, else: :subscribed | ||||
|  | ||||
| @@ -161,4 +172,31 @@ defmodule Algora.Events do | ||||
|     ) | ||||
|     |> Repo.all() | ||||
|   end | ||||
|  | ||||
|   def fetch_subscriptions(user) do | ||||
|     # Get the latest relevant events (:subscribed and :unsubscribed) for each user | ||||
|     latest_events_query = | ||||
|       from(e in Event, | ||||
|         where: e.user_id == ^user.id and e.name in [:subscribed, :unsubscribed], | ||||
|         order_by: [desc: e.inserted_at], | ||||
|         distinct: e.channel_id | ||||
|       ) | ||||
|  | ||||
|     # Join user data and filter for :subscribed events | ||||
|     from(e in subquery(latest_events_query), | ||||
|       join: u in User, | ||||
|       on: e.channel_id == u.id, | ||||
|       join: i in Identity, | ||||
|       on: i.user_id == u.id and i.provider == "github", | ||||
|       select_merge: %{ | ||||
|         user_handle: u.handle, | ||||
|         user_display_name: coalesce(u.name, u.handle), | ||||
|         user_avatar_url: u.avatar_url, | ||||
|         user_meta: i.provider_meta | ||||
|       }, | ||||
|       where: e.name == :subscribed, | ||||
|       order_by: [desc: e.inserted_at, desc: e.id] | ||||
|     ) | ||||
|     |> Repo.all() | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -13,6 +13,7 @@ defmodule Algora.Events.Event do | ||||
|     field :user_email, :string, virtual: true | ||||
|     field :user_avatar_url, :string, virtual: true | ||||
|     field :user_github_handle, :string, virtual: true | ||||
|     field :user_meta, :string, virtual: true | ||||
|     field :first_video_id, :integer, virtual: true | ||||
|     field :first_video_title, :string, virtual: true | ||||
|     field :name, Ecto.Enum, values: [:subscribed, :unsubscribed, :watched, :rsvpd, :unrsvpd] | ||||
|   | ||||
| @@ -114,6 +114,29 @@ defmodule AlgoraWeb.Layouts do | ||||
|         </svg> | ||||
|         Studio | ||||
|       </.link> | ||||
|       <.link | ||||
|         navigate="/subscriptions" | ||||
|         class={ | ||||
|             "text-gray-200 hover:text-gray-50 group flex items-center px-2 py-2 text-sm font-medium rounded-md #{if @active_tab == :studio, do: "bg-gray-800", else: "hover:bg-gray-900"}" | ||||
|           } | ||||
|         aria-current={if @active_tab == :studio, do: "true", else: "false"} | ||||
|       > | ||||
|         <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-gray-400 group-hover:text-gray-300 mr-3 flex-shrink-0 h-6 w-6" | ||||
|         > | ||||
|           <path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M19.5 12.572l-7.5 7.428l-7.5 -7.428a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" /> | ||||
|         </svg> | ||||
|         Subscriptions | ||||
|       </.link> | ||||
|       <.link | ||||
|         navigate={~p"/channel/settings"} | ||||
|         class={ | ||||
| @@ -151,6 +174,8 @@ defmodule AlgoraWeb.Layouts do | ||||
|       <:title><%= @current_user.name %></:title> | ||||
|       <:subtitle>@<%= @current_user.handle %></:subtitle> | ||||
|       <:link navigate={channel_path(@current_user)}>Channel</:link> | ||||
|       <:link navigate={~p"/channel/studio"}>Studio</:link> | ||||
|       <:link navigate={~p"/subscriptions"}>Subscriptions</:link> | ||||
|       <:link navigate={~p"/channel/settings"}>Settings</:link> | ||||
|       <:link href={~p"/auth/logout"} method={:delete}>Sign out</:link> | ||||
|     </.dropdown> | ||||
|   | ||||
| @@ -191,6 +191,8 @@ | ||||
|               <.simple_dropdown id="navbar-account-dropdown"> | ||||
|                 <:img src={@current_user.avatar_url} alt={@current_user.handle} /> | ||||
|                 <:link navigate={channel_path(@current_user)}>Channel</:link> | ||||
|                 <:link navigate={~p"/channel/studio"}>Studio</:link> | ||||
|                 <:link navigate={~p"/subscriptions"}>Subscriptions</:link> | ||||
|                 <:link navigate={~p"/channel/settings"}>Settings</:link> | ||||
|                 <:link href={~p"/auth/logout"} method={:delete}>Sign out</:link> | ||||
|               </.simple_dropdown> | ||||
|   | ||||
							
								
								
									
										77
									
								
								lib/algora_web/live/subscriptions_live.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								lib/algora_web/live/subscriptions_live.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| defmodule AlgoraWeb.SubscriptionsLive do | ||||
|   use AlgoraWeb, :live_view | ||||
|  | ||||
|   alias Algora.Events | ||||
|  | ||||
|   def render(assigns) do | ||||
|     ~H""" | ||||
|     <div class="max-w-5xl mx-auto pt-2 pb-6 px-4 sm:px-6 space-y-6"> | ||||
|       <.header> | ||||
|         <h1 class="text-3xl font-semibold">Subscriptions</h1> | ||||
|         <p class="text-base font-medium text-gray-200">View & manage your subscriptions</p> | ||||
|       </.header> | ||||
|  | ||||
|       <ul role="list" class="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3"> | ||||
|         <li | ||||
|           :for={subscription <- @subscriptions} | ||||
|           class="col-span-1 flex flex-col rounded-2xl overflow-hidden bg-white/5 ring-1 ring-white/20 text-center shadow-lg relative group" | ||||
|         > | ||||
|           <img | ||||
|             class="mx-auto absolute inset-0 flex-shrink-0 object-cover h-full w-full" | ||||
|             src={subscription.user_avatar_url} | ||||
|             alt="" | ||||
|           /> | ||||
|           <div class="absolute h-full w-full inset-0 bg-gradient-to-b from-transparent to-80% to-gray-950/80" /> | ||||
|           <div class="absolute inset-0 bg-purple-700/10" /> | ||||
|           <div class="relative"> | ||||
|             <div class="flex flex-1 flex-col min-h-[16rem]"> | ||||
|               <h3 class="mt-auto text-2xl font-semibold text-white [text-shadow:#000_10px_5px_10px]"> | ||||
|                 <%= subscription.user_display_name %> | ||||
|               </h3> | ||||
|               <dl class="mt-1 flex flex-col justify-between px-2"> | ||||
|                 <dt class="sr-only">Bio</dt> | ||||
|                 <dd class="text-sm text-gray-300 font-medium line-clamp-1"> | ||||
|                   <%= subscription.user_meta["user"]["bio"] || | ||||
|                     subscription.user_meta["user"]["company"] || "@#{subscription.user_handle}" %> | ||||
|                 </dd> | ||||
|               </dl> | ||||
|               <div class="-mt-2 group-hover:mt-2 grid grid-cols-2 divide-white/20 divide-x border-t border-transparent group-hover:border-white/20 transition-all"> | ||||
|                 <.button class="opacity-0 rounded-none h-0 group-hover:opacity-100 group-hover:h-10 transition-all text-sm bg-gray-950/50 hover:bg-gray-950/75 text-white"> | ||||
|                   <.link navigate={~p"/#{subscription.user_handle}"} class="relative"> | ||||
|                     Watch | ||||
|                   </.link> | ||||
|                 </.button> | ||||
|                 <.button | ||||
|                   phx-click="unsubscribe" | ||||
|                   phx-value-id={subscription.channel_id} | ||||
|                   class="opacity-0 rounded-none h-0 group-hover:opacity-100 group-hover:h-10 transition-all text-sm bg-gray-950/50 hover:bg-gray-950/75 text-white" | ||||
|                 > | ||||
|                   Unsubscribe | ||||
|                 </.button> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </li> | ||||
|       </ul> | ||||
|     </div> | ||||
|     """ | ||||
|   end | ||||
|  | ||||
|   def mount(_params, _session, socket) do | ||||
|     user = socket.assigns.current_user | ||||
|     subscriptions = Events.fetch_subscriptions(user) | ||||
|  | ||||
|     {:ok, | ||||
|      socket | ||||
|      |> assign(:subscriptions, subscriptions)} | ||||
|   end | ||||
|  | ||||
|   def handle_event("unsubscribe", %{"id" => id}, socket) do | ||||
|     Events.unsubscribe(socket.assigns.current_user, String.to_integer(id)) | ||||
|     subscriptions = Events.fetch_subscriptions(socket.assigns.current_user) | ||||
|  | ||||
|     {:noreply, | ||||
|      socket | ||||
|      |> assign(:subscriptions, subscriptions)} | ||||
|   end | ||||
| end | ||||
| @@ -89,6 +89,8 @@ defmodule AlgoraWeb.Router do | ||||
|  | ||||
|     live_session :authenticated, | ||||
|       on_mount: [{AlgoraWeb.UserAuth, :ensure_authenticated}, AlgoraWeb.Nav] do | ||||
|       live "/subscriptions", SubscriptionsLive, :show | ||||
|  | ||||
|       live "/channel/settings", SettingsLive, :edit | ||||
|       live "/channel/studio", StudioLive, :show | ||||
|       live "/channel/studio/upload", StudioLive, :upload | ||||
|   | ||||
		Reference in New Issue
	
	Block a user