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
+ <%= relative_datetime(@announcement.inserted_at) %>
+
+
+
+
+
+ <%= @announcement.title %>
+
+
+ <%= @announcement.description %>
+
+
+ <%= if @announcement.image do %>
+
+
+
+ <% 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">
+
+ <.icon name="hero-pencil-solid" class="mr-3 w-5 h-5 text-zinc-400" /> Edit
+
+
+ <% end %>
-
-<%= if @has_permissions? do %>
- <.link patch={~p"/organizations/#{@announcement.organization}/announcements/#{@announcement}/edit"} class="px-2 button">
-
- <.icon name="hero-pencil-solid" class="mr-3 w-5 h-5 text-zinc-400" /> Edit
-
-
-<% 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