Skip to content

Commit

Permalink
refactor: activity pages to use new forms
Browse files Browse the repository at this point in the history
  • Loading branch information
ruilopesm committed Mar 20, 2024
1 parent cb20014 commit 1205dbe
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 37 deletions.
4 changes: 2 additions & 2 deletions lib/atomic_web/components/multi_select.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule AtomicWeb.Components.MultiSelect do
@target - the target to send the event to
The component events are:
toggle_option - toggles the selected state of an item. This event should be defined in the component that you passed in the @target attribute.
toggle-option - toggles the selected state of an item. This event should be defined in the component that you passed in the @target attribute.
"""

use AtomicWeb, :live_component
Expand Down Expand Up @@ -56,7 +56,7 @@ defmodule AtomicWeb.Components.MultiSelect do
style="display: none;"
>
<%= for item <- @items do %>
<li class="flex cursor-pointer select-none items-center justify-between py-2 pr-3 pl-3 text-gray-900" role="option" phx-target={@target} phx-click={JS.push("toggle_option", value: %{id: item.id})}>
<li class="flex cursor-pointer select-none items-center justify-between py-2 pr-3 pl-3 text-gray-900" role="option" phx-target={@target} phx-click={JS.push("toggle-option", value: %{id: item.id})}>
<span class="block truncate font-normal">
<%= item.label %>
</span>
Expand Down
140 changes: 138 additions & 2 deletions lib/atomic_web/live/activity_live/form_component.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,146 @@
defmodule AtomicWeb.ActivityLive.FormComponent do
use AtomicWeb, :live_component

alias Atomic.Activities
alias Atomic.Uploader
alias AtomicWeb.Components.{ImageUploader, MultiSelect}

import AtomicWeb.Components.Field

@impl true
def mount(socket) do
{:ok, socket}
def update(%{activity: activity} = assigns, socket) do
changeset = Activities.change_activity(activity)
speakers = list_speakers(assigns)

{:ok,
socket
|> assign(assigns)
|> assign_form(changeset)
|> assign(:speakers, speakers)
|> assign(:selected_speakers, Enum.count(speakers, & &1.selected))
|> allow_upload(:image, accept: Uploader.extensions_whitelist(), max_entries: 1)}
end

@impl true
def handle_event("validate", %{"activity" => activity_params}, socket) do
changeset =
socket.assigns.activity
|> Activities.change_activity(activity_params)
|> Map.put(:action, :validate)

{:noreply, assign_form(socket, changeset)}
end

@impl true
def handle_event("save", %{"activity" => activity_params}, socket) do
speakers =
List.foldl(socket.assigns.speakers, [], fn speaker, acc ->
if speaker.selected do
[speaker.id | acc]
else
acc
end
end)

activity_params =
activity_params
|> Map.put("speakers", speakers)
|> Map.put("organization_id", socket.assigns.current_organization.id)

save_activity(socket, socket.assigns.action, activity_params)
end

@impl true
def handle_event("toggle-option", %{"id" => id}, socket) do
updated_speakers =
Enum.map(socket.assigns.speakers, fn option ->
if option.id == id do
%{option | selected: !option.selected}
else
option
end
end)

{:noreply,
socket
|> assign(:speakers, updated_speakers)
|> assign(:selected_speakers, Enum.count(updated_speakers, & &1.selected))}
end

@impl true
def handle_event("cancel-image", %{"ref" => ref}, socket) do
{:noreply, cancel_upload(socket, :image, ref)}
end

defp save_activity(socket, :new, activity_params) do
# TODO: Let user choose whether to create a post
case Activities.create_activity_with_post(activity_params, &consume_image_data(socket, &1)) do
{:ok, _activity} ->
{:noreply,
socket
|> put_flash(:info, "Activity created successfully!")
|> push_navigate(to: socket.assigns.return_to)}

{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign_form(socket, changeset)}
end
end

defp save_activity(socket, :edit, activity_params) do
case Activities.update_activity(
socket.assigns.activity,
activity_params,
&consume_image_data(socket, &1)
) do
{:ok, _activity} ->
{:noreply,
socket
|> put_flash(:info, "Activity updated successfully!")
|> push_navigate(to: socket.assigns.return_to)}

{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign_form(socket, changeset)}
end
end

defp consume_image_data(socket, activity) do
consume_uploaded_entries(socket, :image, fn %{path: path}, entry ->
Activities.update_activity_image(activity, %{
"image" => %Plug.Upload{
content_type: entry.content_type,
filename: entry.client_name,
path: path
}
})
end)
|> case do
[{:ok, activity}] ->
{:ok, activity}

_errors ->
{:ok, activity}
end
end

defp list_speakers(assigns) do
activity = assigns.activity

organization_speakers =
Activities.list_speakers_by_organization_id(assigns.current_organization.id)

Enum.map(organization_speakers, fn s ->
selected =
if(is_list(activity.speakers), do: Enum.member?(activity.speakers, s), else: false)

%{
id: s.id,
label: s.name,
selected: selected
}
end)
end

defp assign_form(socket, %Ecto.Changeset{} = changeset) do
assign(socket, :form, to_form(changeset))
end
end
93 changes: 63 additions & 30 deletions lib/atomic_web/live/activity_live/form_component.html.heex
Original file line number Diff line number Diff line change
@@ -1,32 +1,65 @@
<div id="activity-form">
<h2>Simple form</h2>
<.form for={@form} phx-submit="submit">
<.field required field={@form[:text]} placeholder="Text" phx-debounce="blur" label="Text" />

<.field label="Textarea" field={@form[:field_name]} type="textarea" />

<.field type="select" field={@form[:select]} options={["Option 1", "Option 2", "Option 3"]} />

<.field type="checkbox-group" field={@form[:checkbox_group]} options={[{"Option 1", "1"}, {"Option 2", "2"}, {"Option 3", "3"}]} />

<.field type="radio-group" field={@form[:radio_group]} group_layout="row" options={[{"Option 1", "1"}, {"Option 2", "2"}, {"Option 3", "3"}]} />

<.field type="switch" field={@form[:switch]} />
<.field type="checkbox" field={@form[:checkbox]} />
<.field type="color" field={@form[:color]} />
<.field type="date" field={@form[:date]} />
<.field type="datetime-local" field={@form[:datetime_local]} />
<.field type="email" field={@form[:email]} />
<.field type="file" field={@form[:file]} />
<.field type="hidden" field={@form[:hidden]} />
<.field type="month" field={@form[:month]} />
<.field type="number" field={@form[:number]} />
<.field type="password" field={@form[:password]} />
<.field type="range" field={@form[:range]} />
<.field type="search" field={@form[:search]} />
<.field type="tel" field={@form[:tel]} />
<.field type="time" field={@form[:time]} />
<.field type="url" field={@form[:url]} />
<.field type="week" field={@form[:week]} />
<div>
<.form id="activity-form" for={@form} phx-change="validate" phx-submit="save" phx-target={@myself}>
<div class="relative py-5 border-b border-gray-200 sm:py-6">
<div class="flex flex-col gap-y-3 lg:self-end">
<div class="w-full">
<.field type="text" field={@form[:title]} placeholder="Choose a title" phx-debounce="blur" />
</div>
</div>
</div>

<div class="flex flex-col-reverse xl:flex-row">
<div class="w-full xl:w-1/3">
<.live_component module={ImageUploader} id="uploader" uploads={@uploads} target={@myself} />
</div>

<div class="flex flex-col">
<div class="flex pt-6 flex-col md:flex-row justify-center md:justify-start">
<div class="flex flex-col gap-y-1">
<.field type="datetime-local" field={@form[:start]} label="Starting date" />
</div>

<div class="flex flex-col md:ml-8 gap-y-1 mt-4 sm:mt-0">
<.field type="datetime-local" field={@form[:finish]} label="Ending date" />
</div>
</div>

<div class="flex mt-4 flex-col md:flex-row justify-center md:justify-start">
<div class="flex flex-col gap-y-1">
<.field type="number" field={@form[:minimum_entries]} label="Minimum entries" placeholder="Choose minimum entries" />
</div>

<div class="flex flex-col md:ml-8 gap-y-1 mt-4 sm:mt-0">
<.field type="number" field={@form[:maximum_entries]} label="Maximum entries" placeholder="Choose maximum entries" />
</div>
</div>

<div class="flex mt-4 flex-col md:flex-row justify-center md:justify-start">
<.inputs_for :let={fl} field={@form[:location]}>
<div class="flex flex-col gap-y-1">
<.field type="text" field={fl[:name]} label="Location" placeholder="Choose location name" required />
</div>

<div class="flex flex-col md:ml-8 gap-y-1 mt-4 sm:mt-0">
<.field type="url" field={fl[:url]} label="URL" placeholder="Choose an URL" />
</div>
</.inputs_for>
</div>

<div class="flex mt-4 flex-col md:flex-row justify-center md:justify-start">
<.live_component module={MultiSelect} id="speakers" items={@speakers} selected_items={@selected_speakers} target={@myself} />
</div>

<div class="flex-grow mt-4">
<.field type="textarea" field={@form[:description]} label="Description" placeholder="Choose description" rows={15} />

Check warning on line 54 in lib/atomic_web/live/activity_live/form_component.html.heex

View workflow job for this annotation

GitHub Actions / OTP 26.x / Elixir 1.14.x

attribute "rows" in component AtomicWeb.Components.Field.field/1 must be a :string, got: 15

Check warning on line 54 in lib/atomic_web/live/activity_live/form_component.html.heex

View workflow job for this annotation

GitHub Actions / Code Quality (26.x, 1.14.x)

attribute "rows" in component AtomicWeb.Components.Field.field/1 must be a :string, got: 15
</div>
</div>
</div>

<div class="flex-grow mt-4">
<div class="flex justify-center lg:justify-end">
<.button type="submit" phx-disable-with="Saving...">Save</.button>
</div>
</div>
</.form>
</div>
3 changes: 1 addition & 2 deletions lib/atomic_web/live/activity_live/new.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ defmodule AtomicWeb.ActivityLive.New do

@impl true
def mount(_params, _session, socket) do
changeset = Activity.changeset(%Activity{})
{:ok, assign(socket, form: to_form(changeset))}
{:ok, assign(socket, activity: %Activity{})}
end

@impl true
Expand Down
2 changes: 1 addition & 1 deletion lib/atomic_web/live/activity_live/new.html.heex
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div id="atomic-new">
<.live_component module={AtomicWeb.ActivityLive.FormComponent} id={:new} action={@live_action} form={@form} current_organization={@current_organization} return_to={Routes.activity_index_path(@socket, :index)} />
<.live_component module={AtomicWeb.ActivityLive.FormComponent} id={:new} title={@page_title} action={@live_action} activity={@activity} current_organization={@current_organization} return_to={Routes.activity_index_path(@socket, :index)} />
</div>

0 comments on commit 1205dbe

Please sign in to comment.