Skip to content

Commit

Permalink
Improve state management (#2266)
Browse files Browse the repository at this point in the history
  • Loading branch information
anthonyshull authored Dec 16, 2024
1 parent f2344ee commit 14b48f0
Show file tree
Hide file tree
Showing 12 changed files with 497 additions and 497 deletions.
8 changes: 4 additions & 4 deletions assets/css/_autocomplete-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,8 @@
}
}

#trip-planner-form--from,
#trip-planner-form--to {
#trip-planner-input-form--from,
#trip-planner-input-form--to {
.aa-InputWrapperPrefix {
order: unset;
}
Expand All @@ -214,10 +214,10 @@
}
}

#trip-planner-form--from .aa-SubmitButton {
#trip-planner-input-form--from .aa-SubmitButton {
@include fa-icon-solid($fa-var-a);
}

#trip-planner-form--to .aa-SubmitButton {
#trip-planner-input-form--to .aa-SubmitButton {
@include fa-icon-solid($fa-var-b);
}
5 changes: 4 additions & 1 deletion lib/dotcom/trip_plan/anti_corruption_layer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,12 @@ defmodule Dotcom.TripPlan.AntiCorruptionLayer do

modes
|> Enum.reduce(default_modes, fn {key, value}, acc ->
Map.put(acc, String.upcase(key), value)
Map.put(acc, convert_mode(key), value)
end)
end

defp convert_modes(_), do: Dotcom.TripPlan.InputForm.initial_modes()

defp convert_mode("commuter_rail"), do: "RAIL"
defp convert_mode(mode), do: String.upcase(mode)
end
33 changes: 11 additions & 22 deletions lib/dotcom/trip_plan/input_form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule Dotcom.TripPlan.InputForm do
"""

use TypedEctoSchema

import Ecto.Changeset

alias OpenTripPlannerClient.PlanParams
Expand Down Expand Up @@ -60,15 +61,10 @@ defmodule Dotcom.TripPlan.InputForm do

def changeset(form, params) do
form
|> cast(params, [:datetime_type, :datetime, :wheelchair])
|> cast(params, [:datetime, :datetime_type, :wheelchair])
|> cast_embed(:from, required: true)
|> cast_embed(:to, required: true)
|> cast_embed(:modes, required: true)
end

def validate_params(params) do
params
|> changeset()
|> update_change(:from, &update_location_change/1)
|> update_change(:to, &update_location_change/1)
|> validate_required(:from, message: error_message(:from))
Expand All @@ -77,7 +73,6 @@ defmodule Dotcom.TripPlan.InputForm do
|> validate_required([:datetime_type, :wheelchair])
|> validate_same_locations()
|> validate_chosen_datetime()
|> validate_modes()
end

# make the parent field blank if the location isn't valid
Expand All @@ -98,16 +93,6 @@ defmodule Dotcom.TripPlan.InputForm do
end
end

defp validate_modes(changeset) do
case get_change(changeset, :modes) do
%Ecto.Changeset{valid?: false} ->
add_error(changeset, :modes, error_message(:modes))

_ ->
changeset
end
end

defp validate_chosen_datetime(changeset) do
case get_field(changeset, :datetime_type) do
"now" ->
Expand Down Expand Up @@ -191,10 +176,10 @@ defmodule Dotcom.TripPlan.InputForm do

@primary_key false
typed_embedded_schema do
field(:RAIL, :boolean, default: true)
field(:SUBWAY, :boolean, default: true)
field(:BUS, :boolean, default: true)
field(:FERRY, :boolean, default: true)
field(:RAIL, :boolean, default: false)
field(:SUBWAY, :boolean, default: false)
field(:BUS, :boolean, default: false)
field(:FERRY, :boolean, default: false)
end

def fields, do: __MODULE__.__schema__(:fields)
Expand Down Expand Up @@ -244,7 +229,9 @@ defmodule Dotcom.TripPlan.InputForm do
|> selected_modes()
end

def selected_modes([]), do: "No transit modes selected"
def selected_modes(%{RAIL: true, SUBWAY: true, BUS: true, FERRY: true}), do: "All modes"

def selected_modes(%{}), do: "Walking directions only"
def selected_modes([mode]), do: mode_name(mode) <> " Only"

def selected_modes(modes) do
Expand All @@ -257,6 +244,8 @@ defmodule Dotcom.TripPlan.InputForm do
end
end

defp summarized_modes([]), do: "No transit modes selected"

defp summarized_modes([mode1, mode2]) do
mode_name(mode1) <> " and " <> mode_name(mode2)
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,78 +1,50 @@
defmodule DotcomWeb.Components.LiveComponents.TripPlannerForm do
defmodule DotcomWeb.Components.TripPlanner.InputForm do
@moduledoc """
A form to plan trips.
"""
use DotcomWeb, :live_component

use DotcomWeb, :component

import MbtaMetro.Components.InputGroup
import Phoenix.HTML.Form, only: [input_value: 2]

alias Dotcom.TripPlan.{InputForm, InputForm.Modes}
alias MbtaMetro.Live.DatePicker

@impl true
def mount(socket) do
{:ok, socket}
end

@impl true
@doc """
If form values are passed in, we merge them with the defaults and submit the form.
Otherwise, we just render the form.
"""
def update(assigns, socket) do
form_defaults = get_form_defaults(assigns)

defaults = %{
form: %InputForm{} |> InputForm.changeset(form_defaults) |> to_form(),
location_keys: InputForm.Location.fields(),
show_datepicker: false
}

new_socket =
socket
|> assign(assigns)
|> assign(defaults)

if assigns[:form_values] do
save_form(form_defaults, new_socket)
end

{:ok, new_socket}
end

@impl true
def render(assigns) do
def input_form(assigns) do
~H"""
<section class="px-10 py-8 lg:px-20 lg:py-12 mb-4 bg-gray-100">
<.form
:let={f}
class="md:grid md:grid-cols-2 gap-x-8 gap-y-2"
id={@id}
for={@form}
id="trip-planner-input-form"
for={@changeset}
method="get"
phx-submit="save_form"
phx-change="handle_change"
phx-target={@myself}
phx-change="input_form_change"
phx-submit="input_form_submit"
>
<div :for={field <- [:from, :to]} class="mb-1" id="trip-planner-locations">
<.algolia_autocomplete
config_type="trip-planner"
placeholder="Enter a location"
id={"#{@form_name}--#{field}"}
id={"trip-planner-input-form--#{field}"}
>
<.inputs_for :let={location_f} field={f[field]} skip_hidden={true}>
<input
:for={subfield <- @location_keys}
:for={subfield <- InputForm.Location.fields()}
type="hidden"
class="location-input"
id={location_f[subfield].id}
value={location_f[subfield].value}
name={location_f[subfield].name}
/>
</.inputs_for>
<.error_container :for={{msg, _} <- f[field].errors} :if={used_input?(f[field])}>
<.error_container :for={{msg, _} <- f[field].errors}>
{msg}
</.error_container>
</.algolia_autocomplete>
Expand All @@ -86,23 +58,18 @@ defmodule DotcomWeb.Components.LiveComponents.TripPlannerForm do
options={[{"Now", "now"}, {"Leave at", "leave_at"}, {"Arrive by", "arrive_by"}]}
type="radio-button"
class="mb-0"
phx-change="toggle_datepicker"
phx-update="ignore"
/>
<.error_container
:for={{msg, _} <- f[:datetime_type].errors}
:if={used_input?(f[:datetime_type])}
>
<.error_container :for={{msg, _} <- f[:datetime_type].errors}>
{msg}
</.error_container>
<.live_component
:if={@show_datepicker}
:if={show_datepicker?(f)}
module={DatePicker}
config={datepicker_config()}
field={f[:datetime]}
id={:datepicker}
/>
<.error_container :for={{msg, _} <- f[:datetime].errors} :if={used_input?(f[:datetime])}>
<.error_container :for={{msg, _} <- f[:datetime].errors}>
{msg}
</.error_container>
</div>
Expand Down Expand Up @@ -144,46 +111,6 @@ defmodule DotcomWeb.Components.LiveComponents.TripPlannerForm do
"""
end

@impl true
@doc """
If the user selects "now" for the date and time, hide the datepicker.
This will destroy the flatpickr instance.
If the user selects arrive by or leave at, then we show the datepicker and set the time to the nearest 5 minutes.
"""
def handle_event("toggle_datepicker", %{"input_form" => %{"datetime_type" => "now"}}, socket) do
new_socket =
socket
|> assign(show_datepicker: false)
|> push_event("set-datetime", %{datetime: nearest_5_minutes()})

{:noreply, new_socket}
end

def handle_event("toggle_datepicker", _, socket) do
new_socket =
socket
|> assign(show_datepicker: true)
|> push_event("set-datetime", %{datetime: nearest_5_minutes()})

{:noreply, new_socket}
end

def handle_event("handle_change", %{"input_form" => params}, socket) do
send(self(), {:changed_form, params})

form =
params
|> InputForm.validate_params()
|> Phoenix.Component.to_form()

{:noreply, assign(socket, %{form: form})}
end

def handle_event("save_form", %{"input_form" => params}, socket) do
{:noreply, save_form(params, socket)}
end

defp datepicker_config do
%{
default_date: Timex.now("America/New_York"),
Expand All @@ -193,43 +120,7 @@ defmodule DotcomWeb.Components.LiveComponents.TripPlannerForm do
}
end

defp get_form_defaults(assigns) do
assigns
|> Map.get(:form_values, %{
"modes" => InputForm.initial_modes(),
"wheelchair" => true
})
|> Map.merge(%{
"datetime_type" => "now",
"datetime" => Timex.now("America/New_York")
})
end

defp nearest_5_minutes do
datetime = Timex.now("America/New_York")
minutes = datetime.minute
rounded_minutes = Float.ceil(minutes / 5) * 5
added_minutes = Kernel.trunc(rounded_minutes - minutes)

Timex.shift(datetime, minutes: added_minutes)
end

defp save_form(params, socket) do
params
|> InputForm.validate_params()
|> Ecto.Changeset.apply_action(:update)
|> case do
{:ok, data} ->
send(self(), {:updated_form, data})

socket

{:error, changeset} ->
form =
changeset
|> Phoenix.Component.to_form()

assign(socket, %{form: form})
end
defp show_datepicker?(f) do
input_value(f, :datetime_type) != "now"
end
end
35 changes: 16 additions & 19 deletions lib/dotcom_web/components/trip_planner/itinerary_detail.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,39 +12,36 @@ defmodule DotcomWeb.Components.TripPlanner.ItineraryDetail do

alias Dotcom.TripPlan.LegToSegmentHelper

def itinerary_detail(
%{
itineraries: itineraries,
selected_itinerary_detail_index: selected_itinerary_detail_index
} = assigns
) do
assigns =
assign(assigns, :selected_itinerary, Enum.at(itineraries, selected_itinerary_detail_index))
def itinerary_detail(assigns) do
itinerary_group =
Enum.at(assigns.results.itinerary_groups, assigns.results.itinerary_group_selection || 0)

itinerary = Enum.at(itinerary_group.itineraries, assigns.results.itinerary_selection || 0)

assigns = %{
itinerary: itinerary,
itinerary_selection: assigns.results.itinerary_selection,
itineraries: itinerary_group.itineraries
}

~H"""
<div>
<.depart_at_buttons
selected_itinerary_detail_index={@selected_itinerary_detail_index}
itineraries={@itineraries}
/>
<.specific_itinerary_detail itinerary={@selected_itinerary} />
<.depart_at_buttons itineraries={@itineraries} itinerary_selection={@itinerary_selection} />
<.specific_itinerary_detail itinerary={@itinerary} />
</div>
"""
end

attr :itineraries, :list
attr :selected_itinerary_detail_index, :integer

defp depart_at_buttons(assigns) do
~H"""
<div :if={Enum.count(@itineraries) > 1}>
<p class="text-sm mb-2 mt-3">Depart at</p>
<div id="itinerary-detail-departure-times" class="flex flex-wrap gap-2">
<.depart_at_button
:for={{itinerary, index} <- Enum.with_index(@itineraries)}
active={@selected_itinerary_detail_index == index}
phx-click="set_itinerary_index"
phx-value-trip-index={index}
active={@itinerary_selection == index}
phx-click="select_itinerary"
phx-value-index={index}
>
{Timex.format!(itinerary.start, "%-I:%M%p", :strftime)}
</.depart_at_button>
Expand Down
3 changes: 1 addition & 2 deletions lib/dotcom_web/components/trip_planner/itinerary_summary.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ defmodule DotcomWeb.Components.TripPlanner.ItinerarySummary do
@moduledoc """
A component that renders the summary for a given itinerary
"""
use DotcomWeb, :component

attr :summary, :map, doc: "ItineraryGroups.summary()", required: true
use DotcomWeb, :component

def itinerary_summary(assigns) do
~H"""
Expand Down
Loading

0 comments on commit 14b48f0

Please sign in to comment.