Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: improve main page #531

Merged
merged 21 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions lib/atomic_web/components/activity.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,29 @@ defmodule AtomicWeb.Components.Activity do
</div>
<h2 class="mt-3 text-base font-semibold text-gray-900"><%= @activity.title %></h2>
<div class="text-justify text-sm text-gray-700">
<p><%= @activity.description %></p>
<p><%= maybe_slice_string(@activity.description, 300) %></p>
</div>
<!-- Image -->
<%= if @activity.image do %>
<div class="mt-4">
<img class="max-w-screen rounded-md sm:max-w-xl" src={Uploaders.Post.url({@activity.image, @activity}, :original)} />
<img class="max-w-screen max-h-[32rem] rounded-md object-cover sm:max-w-xl" src={Uploaders.Post.url({@activity.image, @activity}, :original)} />
</div>
<% end %>
<!-- Footer -->
<div class={"#{footer_margin_top_class(@activity)} flex flex-row justify-between"}>
<div class="flex space-x-4">
<span class="inline-flex items-center text-sm">
<span class="inline-flex space-x-2 text-zinc-400">
<.icon name="hero-clock-solid" class="size-5" />
<span class="font-medium text-gray-900"><%= relative_datetime(@activity.start) %></span>
<.icon name="hero-calendar-solid" class="mr-1.5 h-5 w-5 flex-shrink-0 text-zinc-400" />
<span class="font-medium text-gray-900"><%= pretty_display_date(@activity.start) %></span>
<span class="sr-only">starting in</span>
</span>
</span>
<span class="inline-flex items-center text-sm">
<span class="inline-flex space-x-2 text-zinc-400">
<.icon name="hero-user-group-solid" class="size-5" />
<span class="font-medium text-gray-900"><%= @activity.enrolled %>/<%= @activity.maximum_entries %></span>
<span class="sr-only">enrollments</span>
<span class="sr-only text-zinc-400">enrollments</span>
</span>
</span>
<span class="inline-flex items-center text-sm">
Expand Down
4 changes: 2 additions & 2 deletions lib/atomic_web/components/announcement.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ defmodule AtomicWeb.Components.Announcement do
</div>
<h2 class="mt-3 text-base font-semibold text-gray-900"><%= @announcement.title %></h2>
<div class="space-y-4 text-justify text-sm text-gray-700">
<%= @announcement.description %>
<%= maybe_slice_string(@announcement.description, 300) %>
</div>
<!-- Image -->
<%= if @announcement.image do %>
<div class="mt-4">
<img class="max-w-screen rounded-md sm:max-w-xl" src={Uploaders.Post.url({@announcement.image, @announcement}, :original)} />
<img class="max-w-screen max-h-[32rem] rounded-md object-cover sm:max-w-xl" src={Uploaders.Post.url({@announcement.image, @announcement}, :original)} />
</div>
<% end %>
</div>
Expand Down
24 changes: 24 additions & 0 deletions lib/atomic_web/components/unauthenticated.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule AtomicWeb.Components.Unauthenticated do
@moduledoc """
A component for displaying an unauthenticated state.
"""
use AtomicWeb, :component

attr :id, :string, default: "unauthenticated-state", required: false
attr :url, :string, default: "users/sign_in", required: false
FilipeR13 marked this conversation as resolved.
Show resolved Hide resolved

def unauthenticated_state(assigns) do
~H"""
<div id={@id} class="text-center">
<.icon name="hero-user-circle" class="mx-auto h-12 w-12 text-zinc-400" />
<h3 class="mt-2 text-sm font-semibold text-zinc-900"><%= gettext("You are not authenticated") %></h3>
<p class="mt-1 text-sm text-zinc-500"><%= gettext("Please log in to view this content.") %></p>
<div class="mt-4 flex justify-center">
<.button patch={~p"/users/log_in"} icon="hero-arrow-right-end-on-rectangle-solid" icon_position={:right} class="w-fit">
FilipeR13 marked this conversation as resolved.
Show resolved Hide resolved
<%= gettext("Log In") %>
</.button>
</div>
</div>
"""
end
end
2 changes: 1 addition & 1 deletion lib/atomic_web/live/activity_live/form_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ defmodule AtomicWeb.ActivityLive.FormComponent do
consume_uploaded_entries(socket, :image, fn %{path: path}, entry ->
Activities.update_activity_image(activity, %{
"image" => %Plug.Upload{
content_type: entry.content_type,
content_type: entry.client_type,
filename: entry.client_name,
path: path
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ defmodule AtomicWeb.HomeLive.Components.FollowSuggestions do
def follow_suggestions(assigns) do
~H"""
<div class="overflow-hidden">
<p class="text-lg font-semibold text-gray-900">
Organizations to follow
<p class="font-semibold leading-6 text-zinc-400">
FilipeR13 marked this conversation as resolved.
Show resolved Hide resolved
<%= title(@current_user) %>
</p>
<div class="flow-root">
<ul role="list" class="divide-y divide-gray-200">
Expand All @@ -25,12 +25,16 @@ defmodule AtomicWeb.HomeLive.Components.FollowSuggestions do
<% end %>
</ul>
</div>
<div class="my-4">
<div class="mt-2 mb-4">
<.button patch={~p"/organizations"} color={:white} size={:md} full_width>
<%= gettext("View all") %>
</.button>
</div>
</div>
"""
end

defp title(current_user) when is_nil(current_user), do: gettext("Top organizations")

defp title(_current_user), do: gettext("Organizations you may like")
end
Original file line number Diff line number Diff line change
Expand Up @@ -38,25 +38,28 @@ defmodule AtomicWeb.HomeLive.Components.FollowSuggestions.Suggestion do
</.link>
<div class="flex-shrink-0">
<%= if @is_following do %>
<button type="button" phx-value-organization_id={@organization.id} phx-click="unfollow" phx-target={@myself} class="z-100 inline-flex items-center gap-x-1.5 text-sm font-semibold leading-6 text-gray-900">
<svg class="size-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" />
</svg>
<.button phx-value-organization_id={@organization.id} phx-click="unfollow" phx-target={@myself} color={:white} size={:xs}>
<.icon name="hero-minus-solid" class="size-5 text-gray-400" />
<span>Unfollow</span>
</button>
</.button>
<% else %>
<button type="button" phx-value-organization_id={@organization.id} phx-click="follow" phx-target={@myself} class="z-100 inline-flex items-center gap-x-1.5 text-sm font-semibold leading-6 text-gray-900">
<svg class="size-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" />
</svg>
<.button phx-value-organization_id={@organization.id} phx-click="follow" phx-target={@myself} color={:white} size={:xs}>
<.icon name="hero-plus" class="size-5 text-gray-400" />
<span>Follow</span>
</button>
</.button>
<% end %>
</div>
</li>
"""
end

def handle_event("follow", _params, socket) when is_nil(socket.assigns.current_user) do
{:noreply,
socket
|> put_flash(:info, "You must be logged in to follow organizations")
|> redirect(to: ~p"/users/log_in")}
end

@impl true
def handle_event("follow", %{"organization_id" => organization_id} = _params, socket) do
attrs = %{
Expand Down
122 changes: 98 additions & 24 deletions lib/atomic_web/live/home_live/components/schedule.ex
Original file line number Diff line number Diff line change
@@ -1,67 +1,141 @@
defmodule AtomicWeb.HomeLive.Components.Schedule do
@moduledoc false
use AtomicWeb, :component
alias Atomic.Activities

attr :schedule, :map, required: true, doc: "The schedule to display."
attr :current_user, :map, required: true, doc: "The current user."
attr :tab, :string, default: "all", values: ["all", "user"], doc: "The tab active."

def schedule(assigns) do
~H"""
<div class="overflow-hidden">
<div :if={length(@schedule.daily) != 0} class="px-4 pt-6 sm:px-0">
<p class="text-xl font-semibold text-gray-900">
<%= if length(@schedule.daily) == 0 && length(@schedule.weekly) == 0 do %>
<div class="space-y-4 px-4 pt-4 pb-2 text-center text-zinc-400 sm:px-0">
<%= show_empty(assigns) %>
</div>
<% end %>
<div :if={length(@schedule.daily) != 0} class="border-b border-gray-200 px-4 pt-4 pb-2 sm:px-0">
<p class="font-semibold text-zinc-400">
Today
</p>
<div class="flow-root">
<ul role="list" class="divide-y divide-gray-200">
<ul role="list">
<%= for entry <- @schedule.daily do %>
<.link navigate={~p"/activities/#{entry}"}>
<li class="space-y-3 py-4">
<p class="text-md font-semibold hover:underline">
<%= entry.title %>
</p>
<div class="w-[110px] flex h-6 items-center justify-center space-x-1 rounded-md bg-orange-100 text-orange-500 opacity-70">
<.icon name="hero-clock-solid" class="size-4" />
<p class="text-xs font-semibold">
<%= display_time(entry.start) %> - <%= display_time(entry.finish) %>
<li class="space-y-3 pt-4">
<div class="flex justify-between">
<p class="text-md font-semibold hover:underline">
<%= entry.title %>
</p>
<div class="w-[110px] flex h-6 items-center justify-center space-x-1 rounded-md bg-orange-100 text-orange-500 opacity-70">
<.icon name="hero-clock-solid" class="size-4" />
<p class="text-xs font-semibold">
<%= display_time(entry.start) %> - <%= display_time(entry.finish) %>
</p>
</div>
</div>
<p class="text-justify text-sm text-gray-700">
<%= entry.description %>
<%= maybe_slice_string(entry.description, 100) %>
</p>
</li>
</.link>
<%= if check_enrolled(entry, @current_user) do %>
<div class="flex justify-between pt-2">
<.icon name="hero-user-group-solid" class="size-4 font-bold text-green-500" />
<.link navigate={~p"/organizations/#{entry.organization_id}"} class="text-xs text-zinc-400 hover:underline">
<%= entry.organization.name %>
</.link>
</div>
<% else %>
<div class="pt-2 text-right">
<.link navigate={~p"/organizations/#{entry.organization_id}"} class="text-xs text-zinc-400 hover:underline">
<%= entry.organization.name %>
</.link>
</div>
<% end %>
<% end %>
</ul>
</div>
</div>
<div :if={length(@schedule.weekly) != 0} class={"#{if length(@schedule.daily) != 0, do: 'pt-3', else: 'pt-6'} px-4 sm:px-0"}>
<p class="text-xl font-semibold text-gray-900">
<div :if={length(@schedule.weekly) != 0} class={"#{if length(@schedule.daily) != 0, do: 'pt-2', else: 'pt-4'} px-4 pb-2 sm:px-0"}>
<p class="font-semibold text-zinc-400">
This week
</p>
<div class="flow-root">
<ul role="list" class="divide-y divide-gray-200">
<ul role="list">
<%= for entry <- @schedule.weekly do %>
<.link navigate={~p"/activities/#{entry}"}>
<li class="space-y-3 py-4">
<p class="text-md font-semibold hover:underline">
<%= entry.title %>
</p>
<div class="w-[110px] flex h-6 items-center justify-center space-x-1 rounded-md bg-orange-100 text-orange-500 opacity-70">
<.icon name="hero-clock-solid" class="size-4" />
<p class="text-xs font-semibold">
<%= pretty_display_date(entry.start) %>
<li class="space-y-3 pt-4">
<div class="flex justify-between">
<p class="text-md font-semibold hover:underline">
<%= entry.title %>
</p>
<div class="w-[110px] flex h-6 items-center justify-center space-x-1 rounded-md bg-orange-100 text-orange-500 opacity-70">
<.icon name="hero-calendar-solid" class="size-4" />
<p class="text-xs font-semibold">
<%= pretty_display_date(entry.start) %>
</p>
</div>
</div>
<p class="text-justify text-sm text-gray-700">
<%= entry.description %>
<%= maybe_slice_string(entry.description, 150) %>
</p>
</li>
</.link>
<%= if check_enrolled(entry, @current_user) do %>
<div class="flex justify-between pt-2">
<.icon name="hero-user-group-solid" class="size-4 font-bold text-green-500" />
<.link navigate={~p"/organizations/#{entry.organization_id}"} class="text-xs text-zinc-400 hover:underline">
<%= entry.organization.name %>
</.link>
</div>
<% else %>
<div class="pt-2 text-right">
<.link navigate={~p"/organizations/#{entry.organization.id}"} class="text-xs text-zinc-400 hover:underline">
<%= entry.organization.name %>
</.link>
</div>
<% end %>
<% end %>
</ul>
</div>
</div>
</div>
"""
end

defp check_enrolled(_entry, nil), do: false
defp check_enrolled(entry, user), do: Activities.participating?(entry.id, user.id)

defp show_empty(assigns) when assigns.tab == "user" do
~H"""
<%= if @current_user do %>
<p>
<%= gettext("Nothing to do in the next week.") %>
</p>
<p>
<%= gettext("Try enrolling in some activities.") %>
</p>
<.button patch={~p"/activities"} color={:white} size={:xs} icon="hero-academic-cap">
<%= gettext("Browse activities") %>
</.button>
<% else %>
<p>
<%= gettext("You need to be logged in to see your schedule.") %>
</p>
<.button patch={~p"/users/log_in"} icon="hero-arrow-right-end-on-rectangle-solid" color={:white} icon_position={:right} size={:xs}>
<%= gettext("Log In") %>
</.button>
<% end %>
FilipeR13 marked this conversation as resolved.
Show resolved Hide resolved
"""
end

defp show_empty(assigns) when assigns.tab == "all" do
~H"""
<p>
<%= gettext("No activities scheduled to the next week.") %>
</p>
"""
end
end
37 changes: 28 additions & 9 deletions lib/atomic_web/live/home_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule AtomicWeb.HomeLive.Index do
@moduledoc false
use AtomicWeb, :live_view

import AtomicWeb.Components.{Activity, Announcement, Tabs}
import AtomicWeb.Components.{Activity, Announcement, Tabs, Unauthenticated, Dropdown}
import AtomicWeb.HomeLive.Components.{FollowSuggestions, Schedule}

alias Atomic.Activities
Expand All @@ -26,8 +26,10 @@ defmodule AtomicWeb.HomeLive.Index do
socket
|> assign(:current_page, :home)
|> assign(:page_title, gettext("Home"))
|> assign(:schedule, fetch_schedule(socket))
|> assign(:schedule_default, fetch_default_schedule())
|> assign(:schedule_user, fetch_user_schedule(socket.assigns.current_user))
|> assign(:current_tab, current_tab(socket, params))
|> assign(:schedule, :default)
|> assign(:organizations, list_organizations_to_follow(socket.assigns))}
end

Expand Down Expand Up @@ -71,6 +73,12 @@ defmodule AtomicWeb.HomeLive.Index do
def handle_event("load-following", _, socket) when socket.assigns.current_tab == "following",
do: {:noreply, socket}

def handle_event("load-following", _, socket) when socket.assigns.is_authenticated? == false,
do:
{:noreply,
socket
|> assign(:current_tab, "following")}

def handle_event("load-following", _, socket) do
current_user = socket.assigns.current_user

Expand All @@ -93,18 +101,29 @@ defmodule AtomicWeb.HomeLive.Index do
|> assign(:current_tab, "schedule")}
end

defp fetch_schedule(socket) when socket.assigns.is_authenticated? do
def handle_event("show-schedule-default", _, socket) do
{:noreply,
socket
|> assign(:schedule, :default)}
end

def handle_event("show-schedule-user", _, socket) do
{:noreply,
socket
|> assign(:schedule, :user)}
end

defp fetch_user_schedule(nil), do: %{daily: [], weekly: []}

defp fetch_user_schedule(user) do
{daily, weekly} =
Activities.list_user_activities(socket.assigns.current_user.id,
preloads: [:organization],
order_by: [desc: :start]
)
Activities.list_user_activities(user.id, preloads: [:organization], order_by: [desc: :start])
|> Enum.reduce({[], []}, &process_activity/2)

%{daily: Enum.take(daily, 3), weekly: Enum.take(weekly, 3)}
end

defp fetch_schedule(_socket) do
defp fetch_default_schedule do
{daily, weekly} =
Activities.list_activities(preloads: [:organization], order_by: [desc: :start])
|> Enum.reduce({[], []}, &process_activity/2)
Expand All @@ -113,7 +132,7 @@ defmodule AtomicWeb.HomeLive.Index do
end

defp process_activity(activity, {daily_acc, weekly_acc}) do
case within_today_or_this_week(activity.start) do
case within_today_or_this_week(activity.start |> NaiveDateTime.to_date()) do
:today ->
{[activity | daily_acc], weekly_acc}

Expand Down
Loading
Loading