diff --git a/lib/atomic/organizations.ex b/lib/atomic/organizations.ex index 98f23f58..e2c58e8a 100644 --- a/lib/atomic/organizations.ex +++ b/lib/atomic/organizations.ex @@ -495,19 +495,24 @@ defmodule Atomic.Organizations do end @doc """ - Returns the list of announcements belonging to an organization. + Returns the list of announcements belonging to an organization, filtered and validated by the given parameters. ## Examples - iex> list_announcements_by_organization_id("99d7c9e5-4212-4f59-a097-28aaa33c2621") - [%Announcement{}, ...] + iex> list_announcements_by_organization_id("99d7c9e5-4212-4f59-a097-28aaa33c2621", %{}) + {:ok, [%Announcement{}, ...]} + + iex> list_announcements_by_organization_id("99d7c9e5-4212-4f59-a097-28aaa33c2621", %{}, + ...> some_option: true + ...> ) + {:ok, [%Announcement{}, ...]} """ - def list_announcements_by_organization_id(id, opts \\ []) do + def list_announcements_by_organization_id(id, %{} = flop, opts \\ []) when is_list(opts) do Announcement |> where(organization_id: ^id) |> apply_filters(opts) - |> Repo.all() + |> Flop.validate_and_run(flop, for: Announcement) end @doc """ @@ -564,7 +569,7 @@ defmodule Atomic.Organizations do {:error, %Ecto.Changeset{}} """ - def create_announcement_with_post(attrs \\ %{}) do + def create_announcement_with_post(attrs \\ %{}, after_save \\ &{:ok, &1}) do Multi.new() |> Multi.insert(:post, fn _ -> %Post{} @@ -580,7 +585,7 @@ defmodule Atomic.Organizations do |> Repo.transaction() |> case do {:ok, %{announcement: announcement, post: _post}} -> - {:ok, announcement} + after_save.(announcement) {:error, _reason, changeset, _actions} -> {:error, changeset} @@ -605,10 +610,11 @@ defmodule Atomic.Organizations do {:error, %Ecto.Changeset{}} """ - def update_announcement(%Announcement{} = announcement, attrs, _after_save \\ &{:ok, &1}) do + def update_announcement(%Announcement{} = announcement, attrs, after_save \\ &{:ok, &1}) do announcement |> Announcement.changeset(attrs) |> Repo.update() + |> after_save(after_save) end @doc """ @@ -624,7 +630,14 @@ defmodule Atomic.Organizations do """ def delete_announcement(%Announcement{} = announcement) do - Repo.delete(announcement) + Ecto.Multi.new() + |> Ecto.Multi.delete(:announcement, announcement) + |> Ecto.Multi.delete(:post, Repo.get!(Post, announcement.post_id)) + |> Repo.transaction() + |> case do + {:ok, _changes} -> {:ok, announcement} + {:error, _step, reason, _changes} -> {:error, reason} + end end @doc """ @@ -639,4 +652,22 @@ defmodule Atomic.Organizations do def change_announcement(%Announcement{} = announcement, attrs \\ %{}) do Announcement.changeset(announcement, attrs) end + + @doc """ + Updates an announcement image. + + ## Examples + + iex> update_announcement_image(announcement, %{field: new_value}) + {:ok, %Announcement{}} + + iex> update_announcement_image(announcement, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_announcement_image(%Announcement{} = announcement, attrs) do + announcement + |> Announcement.image_changeset(attrs) + |> Repo.update() + end end diff --git a/lib/atomic_web/config.ex b/lib/atomic_web/config.ex index 03f60860..ef36a0ed 100644 --- a/lib/atomic_web/config.ex +++ b/lib/atomic_web/config.ex @@ -43,6 +43,13 @@ defmodule AtomicWeb.Config do url: ~p"/organizations/#{current_organization}/departments", tabs: [] }, + %{ + key: :announcements, + title: "Announcements", + icon: "hero-newspaper", + url: ~p"/organizations/#{current_organization}/announcements", + tabs: [] + }, %{ key: :partners, title: "Partners", @@ -83,13 +90,6 @@ defmodule AtomicWeb.Config do url: ~p"/activities", tabs: [] }, - %{ - key: :announcements, - title: "Announcements", - icon: "hero-newspaper", - url: ~p"/announcements", - tabs: [] - }, %{ key: :organizations, title: "Organizations", diff --git a/lib/atomic_web/live/announcement_live/components/announcement_card.ex b/lib/atomic_web/live/announcement_live/components/announcement_card.ex new file mode 100644 index 00000000..118aa457 --- /dev/null +++ b/lib/atomic_web/live/announcement_live/components/announcement_card.ex @@ -0,0 +1,41 @@ +defmodule AtomicWeb.AnnouncementLive.Components.AnnouncementCard do + @moduledoc false + + import AtomicWeb.Components.Avatar + + use AtomicWeb, :component + + def announcement_card(assigns) do + ~H""" +
+ <.link navigate={~p"/organizations/#{@organization}/announcements/#{@announcement}"} class="block"> +
+
+ <.avatar name={@announcement.organization.name} color={:light_gray} class="!h-10 !w-10" size={:xs} type={:organization} src={Uploaders.Logo.url({@announcement.organization.logo, @announcement.organization}, :original)} /> +
+
+

<%= @announcement.organization.name %>

+

+ Published on + +

+
+
+
+

+ <%= @announcement.title %> +

+

+ <%= @announcement.description %> +

+
+ <%= if @announcement.image do %> +
+ Announcement Image +
+ <% end %> + +
+ """ + end +end diff --git a/lib/atomic_web/live/announcement_live/edit.html.heex b/lib/atomic_web/live/announcement_live/edit.html.heex index c3db0c4f..6b36d4a3 100644 --- a/lib/atomic_web/live/announcement_live/edit.html.heex +++ b/lib/atomic_web/live/announcement_live/edit.html.heex @@ -5,6 +5,6 @@
- <.live_component module={AtomicWeb.AnnouncementLive.FormComponent} id={@announcement.id} organization={@current_organization} title={@page_title} action={@live_action} announcement={@announcement} return_to={~p"/announcements/#{@announcement}"} /> + <.live_component module={AtomicWeb.AnnouncementLive.FormComponent} id={@announcement.id} organization={@current_organization} title={@page_title} action={@live_action} announcement={@announcement} return_to={~p"/organizations/#{@current_organization}/announcements"} />
diff --git a/lib/atomic_web/live/announcement_live/form_component.ex b/lib/atomic_web/live/announcement_live/form_component.ex index dc264e02..feb604a8 100644 --- a/lib/atomic_web/live/announcement_live/form_component.ex +++ b/lib/atomic_web/live/announcement_live/form_component.ex @@ -2,6 +2,7 @@ defmodule AtomicWeb.AnnouncementLive.FormComponent do use AtomicWeb, :live_component alias Atomic.Organizations + alias AtomicWeb.Components.ImageUploader import AtomicWeb.Components.Forms @@ -17,7 +18,8 @@ defmodule AtomicWeb.AnnouncementLive.FormComponent do {:ok, socket |> assign(assigns) - |> assign(:changeset, changeset)} + |> assign(:changeset, changeset) + |> allow_upload(:image, accept: Uploaders.Post.extension_whitelist(), max_entries: 1)} end @impl true @@ -35,10 +37,16 @@ defmodule AtomicWeb.AnnouncementLive.FormComponent do save_announcement(socket, socket.assigns.action, announcement_params) end + @impl true + def handle_event("cancel-image", %{"ref" => ref}, socket) do + {:noreply, cancel_upload(socket, :image, ref)} + end + defp save_announcement(socket, :edit, announcement_params) do case Organizations.update_announcement( socket.assigns.announcement, - announcement_params + announcement_params, + &consume_image_data(socket, &1) ) do {:ok, _announcement} -> {:noreply, @@ -55,7 +63,10 @@ defmodule AtomicWeb.AnnouncementLive.FormComponent do announcement_params = Map.put(announcement_params, "organization_id", socket.assigns.organization.id) - case Organizations.create_announcement_with_post(announcement_params) do + case Organizations.create_announcement_with_post( + announcement_params, + &consume_image_data(socket, &1) + ) do {:ok, _announcement} -> {:noreply, socket @@ -66,4 +77,23 @@ defmodule AtomicWeb.AnnouncementLive.FormComponent do {:noreply, assign(socket, :changeset, changeset)} end end + + defp consume_image_data(socket, announcement) do + consume_uploaded_entries(socket, :image, fn %{path: path}, entry -> + Organizations.update_announcement_image(announcement, %{ + "image" => %Plug.Upload{ + content_type: entry.client_type, + filename: entry.client_name, + path: path + } + }) + end) + |> case do + [{:ok, announcement}] -> + {:ok, announcement} + + _errors -> + {:ok, announcement} + end + end end diff --git a/lib/atomic_web/live/announcement_live/form_component.html.heex b/lib/atomic_web/live/announcement_live/form_component.html.heex index 8f06c954..5f6ef756 100644 --- a/lib/atomic_web/live/announcement_live/form_component.html.heex +++ b/lib/atomic_web/live/announcement_live/form_component.html.heex @@ -1,11 +1,20 @@ -
- <.form :let={f} for={@changeset} id="announcement-form" phx-target={@myself} phx-change="validate" phx-submit="save"> - <.field field={f[:title]} help_text={gettext("The title of the announcement")} type="text" placeholder="Title" required /> +
+ <.form :let={f} for={@changeset} id="announcement-form" phx-target={@myself} phx-change="validate" phx-submit="save" class="space-y-6"> +
+
+
+ <.field field={f[:title]} type="text" placeholder="Choose a title for the announcement" required class="w-full" /> - <.field field={f[:description]} help_text={gettext("Announcement description")} type="text" placeholder="Description" required /> + <.field field={f[:description]} type="textarea" placeholder="Write a description" required class="w-full overflow-auto resize-none h-44 xl:h-64" /> +
+
+ <.live_component module={ImageUploader} id="uploader" uploads={@uploads} target={@myself} class="object-cover" /> -
- <.button size={:md} color={:white} icon="hero-cube" type="submit"><%= gettext("Save Changes") %> +
+ <.button size={:md} color={:white} icon="hero-cube" type="submit"><%= gettext("Save Changes") %> +
+
+
diff --git a/lib/atomic_web/live/announcement_live/index.ex b/lib/atomic_web/live/announcement_live/index.ex index 2c26e45b..207e00ad 100644 --- a/lib/atomic_web/live/announcement_live/index.ex +++ b/lib/atomic_web/live/announcement_live/index.ex @@ -2,6 +2,7 @@ defmodule AtomicWeb.AnnouncementLive.Index do use AtomicWeb, :live_view import AtomicWeb.Components.{Button, Empty, Pagination, Tabs} + import AtomicWeb.AnnouncementLive.Components.AnnouncementCard alias Atomic.Accounts alias Atomic.Organizations @@ -12,44 +13,24 @@ defmodule AtomicWeb.AnnouncementLive.Index do end @impl true - def handle_params(params, _, socket) do + def handle_params(%{"organization_id" => organization_id} = params, _, socket) do + organization = Organizations.get_organization!(organization_id) + {:noreply, socket |> assign(:page_title, gettext("Announcements")) |> assign(:current_page, :announcements) - |> assign(:current_tab, current_tab(socket, params)) + |> assign(:organization, organization) |> assign(:params, params) |> assign(:has_permissions?, has_permissions?(socket)) - |> assign(list_announcements(socket, params)) + |> assign(list_announcements_by_organization(socket, params, organization_id)) |> then(fn complete_socket -> assign(complete_socket, :empty?, Enum.empty?(complete_socket.assigns.announcements)) end)} end - defp list_announcements(socket, params) do - params = Map.put(params, "page_size", 6) - - case current_tab(socket, params) do - "all" -> list_all_announcements(socket, params) - "following" -> list_following_announcements(socket, params) - end - end - - defp list_all_announcements(_socket, params) do - case Organizations.list_announcements(params, preloads: [:organization]) do - {:ok, {announcements, meta}} -> - %{announcements: announcements, meta: meta} - - {:error, flop} -> - %{announcements: [], meta: flop} - end - end - - defp list_following_announcements(socket, params) do - organizations = - Organizations.list_organizations_followed_by_user(socket.assigns.current_user.id) - - case Organizations.list_organizations_announcements(organizations, params, + defp list_announcements_by_organization(_socket, params, organization_id) do + case Organizations.list_announcements_by_organization_id(organization_id, params, preloads: [:organization] ) do {:ok, {announcements, meta}} -> @@ -60,9 +41,6 @@ defmodule AtomicWeb.AnnouncementLive.Index do end end - defp current_tab(_socket, params) when is_map_key(params, "tab"), do: params["tab"] - defp current_tab(_socket, _params), do: "all" - defp has_permissions?(socket) when not socket.assigns.is_authenticated?, do: false defp has_permissions?(socket) do diff --git a/lib/atomic_web/live/announcement_live/index.html.heex b/lib/atomic_web/live/announcement_live/index.html.heex index 623e7d4b..f4d8c004 100644 --- a/lib/atomic_web/live/announcement_live/index.html.heex +++ b/lib/atomic_web/live/announcement_live/index.html.heex @@ -1,47 +1,24 @@ <.page title="Announcements"> <:actions> <%= if not @empty? and @has_permissions? do %> - <.button navigate={~p"/announcements/new"}> + <.button navigate={~p"/organizations/#{@current_organization}/announcements/new"} icon="hero-plus"> <%= gettext("New") %> <% end %> - <.tabs class="max-w-5-xl mx-auto px-4 sm:px-6 lg:px-8"> - <.link patch="?tab=all" replace={false}> - <.tab active={@current_tab == "all"}> - <%= gettext("All") %> - - - - <.link patch="?tab=following" replace={false}> - <.tab active={@current_tab == "following"}> - <%= gettext("Following") %> - - - + <.tabs> <%= if @empty? and @has_permissions? do %>
- <.empty_state url={~p"/announcements/new"} placeholder="announcement" /> + <.empty_state url={~p"/organizations/#{@organization}/announcements/new"} placeholder="announcement" />
<% else %>
    <%= for announcement <- @announcements do %>
  • - <.link navigate={~p"/announcements/#{announcement}"} class="block hover:bg-zinc-50"> -
    -
    -

    - <%= announcement.title %> -

    -
    -

    - <%= announcement.description %> -

    -
    - + <.announcement_card announcement={announcement} organization={@organization} />
  • <% end %>
diff --git a/lib/atomic_web/live/announcement_live/new.html.heex b/lib/atomic_web/live/announcement_live/new.html.heex index bda286c4..a944ab48 100644 --- a/lib/atomic_web/live/announcement_live/new.html.heex +++ b/lib/atomic_web/live/announcement_live/new.html.heex @@ -1,5 +1,5 @@ <.page title={gettext("New Announcement")}>
- <.live_component module={AtomicWeb.AnnouncementLive.FormComponent} id={:new} organization={@current_organization} title={@page_title} action={@live_action} announcement={@announcement} return_to={~p"/announcements"} /> + <.live_component module={AtomicWeb.AnnouncementLive.FormComponent} id={:new} organization={@current_organization} title={@page_title} action={@live_action} announcement={@announcement} return_to={~p"/organizations/#{@current_organization}/announcements"} />
diff --git a/lib/atomic_web/live/announcement_live/show.ex b/lib/atomic_web/live/announcement_live/show.ex index 41c8b5c7..9814e61c 100644 --- a/lib/atomic_web/live/announcement_live/show.ex +++ b/lib/atomic_web/live/announcement_live/show.ex @@ -1,8 +1,11 @@ defmodule AtomicWeb.AnnouncementLive.Show do use AtomicWeb, :live_view + import AtomicWeb.Components.Announcement + alias Atomic.Accounts alias Atomic.Organizations + alias AtomicWeb.Router.Helpers, as: Routes @impl true def mount(_params, _session, socket) do diff --git a/lib/atomic_web/live/announcement_live/show.html.heex b/lib/atomic_web/live/announcement_live/show.html.heex index 308ae58e..413995b5 100644 --- a/lib/atomic_web/live/announcement_live/show.html.heex +++ b/lib/atomic_web/live/announcement_live/show.html.heex @@ -1,22 +1,14 @@
-
-
-
-

- <%= @announcement.title %> -

-
-

- <%= @announcement.description %> -

-
+
+ <.link navigate={Routes.announcement_show_path(@socket, :show, @announcement)}> + <.announcement announcement={@announcement} /> +
+ <%= if @has_permissions? do %> + <.link patch={Routes.announcement_edit_path(@socket, :edit, @announcement.organization, @announcement)} class="px-4 button"> + + + <% end %>
- -<%= if @has_permissions? do %> - <.link patch={~p"/organizations/#{@announcement.organization}/announcements/#{@announcement}/edit"} class="px-2 button"> - - -<% end %> diff --git a/lib/atomic_web/router.ex b/lib/atomic_web/router.ex index 9843a8c8..e7d91872 100644 --- a/lib/atomic_web/router.ex +++ b/lib/atomic_web/router.ex @@ -132,6 +132,12 @@ defmodule AtomicWeb.Router do live "/", PartnerLive.Index, :index live "/:id", PartnerLive.Show, :show end + + scope "/announcements" do + pipe_through :confirm_announcement_association + live "/", AnnouncementLive.Index, :index + live "/:id", AnnouncementLive.Show, :show + end end # Only masters can create organizations