diff --git a/assets/css/_trip-plan-form.scss b/assets/css/_trip-plan-form.scss index fa22eba50d..54b169405a 100644 --- a/assets/css/_trip-plan-form.scss +++ b/assets/css/_trip-plan-form.scss @@ -145,7 +145,7 @@ .m-trip-plan__optimize-for { @include icon-size-inline(1em); - margin-bottom: 0; + margin-bottom: .75rem; margin-top: $base-spacing; .c-svg__icon-accessible-default { @@ -173,3 +173,180 @@ .m-trip-plan__hidden { display: none; } + +.c-trip-plan-widget__inputs { + label { + margin-bottom: .75rem; + } +} + +#trip-planner-form { + .c-accordion-ui { + border-radius: $border-radius; + margin-top: .75rem; + } + + .c-accordion-ui__content { + background: white; + border-bottom: solid 1px $brand-primary; + border-bottom-left-radius: $border-radius; + border-bottom-right-radius: $border-radius; + } + + .c-accordion-ui__target { + border-bottom: none; + } + + .c-accordion-ui__trigger { + border-top-left-radius: $border-radius; + border-top-right-radius: $border-radius; + + &.collapsed { + border-bottom-left-radius: $border-radius; + border-bottom-right-radius: $border-radius; + } + } +} + +.trip-planner-form { + padding-top: 1rem; + + h2 { + font-size: 16px; + font-weight: bold; + line-height: 1.5; + margin-bottom: .5rem; + margin-top: .5rem; + } +} + +#trip-planner-inputs { + margin: .75rem 0; + width: 100%; + + #trip-plan-datepicker { + margin-bottom: 1rem; + margin-top: .75rem; + } + + .btn-group { + width: 100%; + + .btn+.btn { + margin-left: 0; + } + + label:has(:checked) { + @extend .active; + } + } + + input { + padding: calc(#{$base-spacing} / 2); + } + + label { + border-color: $brand-primary; + border-width: 1px 0; + font-weight: 400; + padding: calc(#{$base-spacing} / 2); + text-transform: capitalize; + width: 33.33%; + } + + label.active { + background-color: $brand-primary-lightest; + color: $brand-primary; + font-weight: 700; + } + + label:first-child, + label:last-child { + border-left-width: 1px; + border-right-width: 1px; + } + + input[type='text'] { + border: 1px solid $brand-primary; + border-radius: $border-radius; + width: 100%; + } + + i.fa-calendar { + color: $brand-primary; + } + + .flatpickr { + position: relative; + + .form-control[readonly] { + background-color: $body-bg; + } + + a[data-toggle] { + position: absolute; + right: .75rem; + top: .5rem; + } + } + + .flatpickr-mobile { + border: 1px solid $brand-primary; + border-radius: $border-radius; + width: calc(100% - 2.5rem); + + & ~ a[data-toggle] { + position: unset; + } + } +} + +.flatpickr-months { + font-family: $headings-font-family; + font-weight: $headings-font-weight; + line-height: $headings-line-height; + + .flatpickr-month { + color: inherit; + } +} + +.flatpickr-current-month { + font-size: $font-size-base-xxl; + font-weight: inherit; + + .flatpickr-monthDropdown-months { + font-weight: inherit; + } + + input.cur-year[disabled], + input.cur-year[disabled]:hover { + color: inherit; + } +} + +.flatpickr-time input { + font-size: inherit; +} + +.flatpickr-day { + font-weight: $font-weight-medium; +} + +.flatpickr-calendar { + font-family: $font-family-base; + font-size: inherit; + line-height: inherit; +} + +.flatpickr-day.selected { + background-color: $brand-primary; + border-color: $white; + color: $white; + + &:hover { + background-color: $brand-primary-lightest; + border-color: $white; + color: $brand-primary; + } +} diff --git a/assets/js/app.js b/assets/js/app.js index 2d8ab60184..3fa94abdb8 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -44,6 +44,7 @@ import tabbedNav from "./tabbed-nav.js"; import { accordionInit } from "../ts/ui/accordion"; import initializeSentry from "../ts/sentry"; import setupAlgoliaAutocomplete from "../ts/ui/autocomplete/index"; +import setupTripPlannerForm from "./trip-planner-form.js"; // Establish Phoenix Socket and LiveView configuration. import { Socket } from "phoenix"; @@ -52,17 +53,27 @@ import { LiveSocket } from "phoenix_live_view"; let csrfToken = document .querySelector("meta[name='csrf-token']") .getAttribute("content"); + let Hooks = {}; + Hooks.AlgoliaAutocomplete = { mounted() { setupAlgoliaAutocomplete(this.el); } }; + Hooks.ScrollIntoView = { mounted() { this.el.scrollIntoView({ behavior: "smooth" }); } }; + +Hooks.TripPlannerForm = { + mounted() { + setupTripPlannerForm(this.el); + } +}; + let liveSocket = new LiveSocket("/live", Socket, { params: { _csrf_token: csrfToken }, hooks: Hooks diff --git a/assets/js/test/time-controls-test.js b/assets/js/test/time-controls-test.js deleted file mode 100644 index 5f7abac323..0000000000 --- a/assets/js/test/time-controls-test.js +++ /dev/null @@ -1,46 +0,0 @@ -import { assert } from "chai"; -import jsdom from "mocha-jsdom"; -import { - TimeControls, - getSelectorFields -} from "../time-controls/time-controls"; -import testConfig from "../../ts/jest.config"; - -const { testURL } = testConfig; - -describe("getSelectorFields", () => { - let $; - jsdom({ - url: testURL - }); - beforeEach(() => { - $ = jsdom.rerequire("jquery"); - window.$ = $; - window.jQuery = $; - }); -}); -describe("TimeControls.getFriendlyTime", () => { - it("returns a friendly string given a JavaScript date", () => { - const date = new Date(2017, 10, 9, 8, 7); - - assert.equal(TimeControls.getFriendlyTime(date), "8:07 AM"); - }); - - it("converts times after 13:00 to PM", () => { - const date = new Date(2017, 10, 9, 18, 19); - - assert.equal(TimeControls.getFriendlyTime(date), "6:19 PM"); - }); - - it("interprets 12:00 as 12:00 PM", () => { - const date = new Date(2017, 10, 9, 12, 7); - - assert.equal(TimeControls.getFriendlyTime(date), "12:07 PM"); - }); - - it("interprets 0:00 as 12:00 AM", () => { - const date = new Date(2017, 10, 9, 0, 7); - - assert.equal(TimeControls.getFriendlyTime(date), "12:07 AM"); - }); -}); diff --git a/assets/js/trip-planner-form.js b/assets/js/trip-planner-form.js new file mode 100644 index 0000000000..1ac2b17822 --- /dev/null +++ b/assets/js/trip-planner-form.js @@ -0,0 +1,97 @@ +/* eslint no-unused-vars: ["error", { "args": "none" }] */ + +import flatpickr from "flatpickr"; +import { format } from "date-fns"; + +/** + * Formats a date into a string in the user's locale. + */ +function i18nDate(date, locale = navigator.language) { + const formatter = new Intl.DateTimeFormat(locale, { + month: "long", + weekday: "long", + day: "numeric", + hour: "numeric", + minute: "numeric" + }); + + return formatter.format(date); +} + +/** + * Updates the title of the accordion based on the mode selections. + */ +function updateAccordionTitle(elem, modeCheckboxes) { + const checkedModes = Array.prototype.slice + .call(modeCheckboxes) + .filter(checkbox => checkbox.checked) + .map(checkbox => checkbox.labels[0].textContent); + + const title = elem.querySelector(".c-accordion-ui__title"); + + if (checkedModes.length === 0) { + title.textContent = "Walking Only"; + } else if (checkedModes.length === modeCheckboxes.length) { + title.textContent = "All Modes"; + } else { + title.textContent = checkedModes.join(", "); + } +} + +const SERVER_FORMAT = "Y-m-d G:i K"; + +/** + * Initializes the trip planner inputs and sets all listeners. + */ +export default function setupTripPlannerForm(elem) { + // Gets data that is injected into the template. + // This is how we get the user selections from the query params to set controls. + let data = elem.querySelector("#data").innerHTML; + data = JSON.parse(data); + + const maxDate = new Date(data.maxDate); + const minDate = new Date(data.minDate); + + // Sets the initial value of the date input display. + // Default to 'now' and uses the server date if 'now.' + // Otherwise, we use the chosen time. + const time = data.chosenTime || "now"; + const dateTime = new Date( + time === "now" ? data.dateTime : data.chosenDateTime + ); + + // Initializes the date picker. + flatpickr(elem.querySelector("#trip-plan-datepicker .flatpickr"), { + allowInvalidPreload: true, // needed on mobile to prevent the input from becoming blank when selecting a date outside the min/max + altInput: true, // allow different format to be sent to server + dateFormat: "Y-m-d G:i K", // this gets sent to the server + defaultDate: dateTime, + enableTime: true, + maxDate, + minDate, + formatDate: (date, formatString, locale) => { + if (formatString === SERVER_FORMAT) { + // Formats a date into a string in the format util.ex parse/1 expects. + return format(date, "yyyy-MM-dd HH:mm aa"); + } + + // if not being sent to the server, use localized format + return i18nDate(date, locale); + }, + wrap: true // works with adjacent icon + }); + + // When someone makes mode selections, we update the title of the accordion. + const modeCheckboxes = elem.querySelectorAll( + ".c-accordion-ui input[type='checkbox']" + ); + + modeCheckboxes.forEach(checkbox => { + checkbox.addEventListener("click", _event => { + updateAccordionTitle(elem, modeCheckboxes); + }); + }); + + // Setup the correct title when the page is loaded. + updateAccordionTitle(elem, modeCheckboxes); +} diff --git a/assets/package-lock.json b/assets/package-lock.json index 5aa99f3410..016351bf11 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -26,6 +26,7 @@ "dot-prop-immutable": "^1.5.0", "fast-deep-equal": "^3.1.3", "filesize": "^3.3.0", + "flatpickr": "^4.6.13", "focus-trap": "^7.5.2", "form-data": "^1.0.1", "formdata-polyfill": "^3.0.20", @@ -11100,6 +11101,11 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flatpickr": { + "version": "4.6.13", + "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz", + "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==" + }, "node_modules/flatted": { "version": "3.2.7", "dev": true, diff --git a/assets/package.json b/assets/package.json index 3ddffd6d95..875d1886ea 100644 --- a/assets/package.json +++ b/assets/package.json @@ -21,6 +21,7 @@ "dot-prop-immutable": "^1.5.0", "fast-deep-equal": "^3.1.3", "filesize": "^3.3.0", + "flatpickr": "^4.6.13", "focus-trap": "^7.5.2", "form-data": "^1.0.1", "formdata-polyfill": "^3.0.20", diff --git a/cypress/e2e/smoke.cy.js b/cypress/e2e/smoke.cy.js index bd0f9ca663..fe26317ba6 100644 --- a/cypress/e2e/smoke.cy.js +++ b/cypress/e2e/smoke.cy.js @@ -167,14 +167,8 @@ describe("passes smoke test", () => { // opens the date picker cy.contains("#trip-plan-datepicker").should("not.exist"); - cy.get("#trip-plan-departure-title").click(); - cy.get("#trip-plan-datepicker"); - - // updates title with selected departure option cy.get('label[for="arrive"]').click(); - cy.get("#trip-plan-departure-title").should("include.text", "Arrive by"); - cy.get('label[for="depart"]').click(); - cy.get("#trip-plan-departure-title").should("include.text", "Depart at"); + cy.get("#trip-plan-datepicker"); // shortcut /from/ - marker A prepopulated cy.visit("/trip-planner/from/North+Station"); diff --git a/lib/dotcom_web.ex b/lib/dotcom_web.ex index f35a789185..30ce05a33a 100644 --- a/lib/dotcom_web.ex +++ b/lib/dotcom_web.ex @@ -137,6 +137,7 @@ defmodule DotcomWeb do # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) import Phoenix.LiveView.Helpers + alias Phoenix.LiveView.JS # Import basic rendering functionality (render, render_layout, etc) import Phoenix.View diff --git a/lib/dotcom_web/controllers/trip_plan_controller.ex b/lib/dotcom_web/controllers/trip_plan_controller.ex index ff7f9a0ba9..3c0b8b02f7 100644 --- a/lib/dotcom_web/controllers/trip_plan_controller.ex +++ b/lib/dotcom_web/controllers/trip_plan_controller.ex @@ -16,38 +16,13 @@ defmodule DotcomWeb.TripPlanController do plug(:modes) plug(:wheelchair) plug(:meta_description) - plug(:assign_datetime_selector_fields) + plug(:assign_params) @type route_map :: %{optional(Route.id_t()) => Route.t()} @type route_mapper :: (Route.id_t() -> Route.t() | nil) @location_service Application.compile_env!(:dotcom, :location_service) - @plan_datetime_selector_fields %{ - depart: "depart", - leaveNow: "leave-now", - arrive: "arrive", - controls: "trip-plan-datepicker", - year: "plan_date_time_year", - month: "plan_date_time_month", - day: "plan_date_time_day", - hour: "plan_date_time_hour", - minute: "plan_date_time_minute", - amPm: "plan_date_time_am_pm", - dateEl: %{ - container: "plan-date", - input: "plan-date-input", - select: "plan-date-select", - label: "plan-date-label" - }, - timeEl: %{ - container: "plan-time", - select: "plan-time-select", - label: "plan-time-label" - }, - title: "trip-plan-departure-title" - } - def index(conn, %{"plan" => %{"to" => _to, "from" => _fr} = plan}) do conn |> assign(:expanded, conn.query_params["expanded"]) @@ -198,6 +173,12 @@ defmodule DotcomWeb.TripPlanController do |> render(:index) end + defp assign_params(conn, _) do + conn + |> assign(:chosen_date_time, conn.params["plan"]["date_time"]) + |> assign(:chosen_time, conn.params["plan"]["time"]) + end + @spec check_address(String.t()) :: String.t() defp check_address(address) do # address can be a String containing "lat,lon" so we check for that case @@ -271,12 +252,6 @@ defmodule DotcomWeb.TripPlanController do ) end - @spec assign_datetime_selector_fields(Plug.Conn.t(), Keyword.t()) :: Plug.Conn.t() - defp assign_datetime_selector_fields(conn, _) do - conn - |> assign(:plan_datetime_selector_fields, @plan_datetime_selector_fields) - end - @spec with_fares_and_passes([Itinerary.t()]) :: [Itinerary.t()] defp with_fares_and_passes(itineraries) do Enum.map(itineraries, fn itinerary -> @@ -435,11 +410,7 @@ defmodule DotcomWeb.TripPlanController do ) end - @doc """ - if other plan params are filled, such as from or to, but no modes, set all - modes to true. this can happen when getting trip plans from the homepage. - """ - def modes(%Plug.Conn{params: %{"plan" => _}} = conn, _) do + def modes(%Plug.Conn{} = conn, _) do assign( conn, :modes, @@ -447,10 +418,6 @@ defmodule DotcomWeb.TripPlanController do ) end - def modes(%Plug.Conn{} = conn, _) do - assign(conn, :modes, %{}) - end - @spec breadcrumbs(Plug.Conn.t(), Keyword.t()) :: Plug.Conn.t() defp breadcrumbs(conn, _) do assign(conn, :breadcrumbs, [Breadcrumb.build("Trip Planner")]) diff --git a/lib/dotcom_web/templates/layout/root.html.eex b/lib/dotcom_web/templates/layout/root.html.eex index 5b8e525634..cbd1103ce8 100644 --- a/lib/dotcom_web/templates/layout/root.html.eex +++ b/lib/dotcom_web/templates/layout/root.html.eex @@ -27,6 +27,7 @@ " type="image/png"> " sizes="32x32" type="image/png"> " sizes="16x16" type="image/vnd.microsoft.icon"> + <%= if google_tag_manager_id() do %> diff --git a/lib/dotcom_web/templates/trip_plan/_departure.html.eex b/lib/dotcom_web/templates/trip_plan/_departure.html.eex deleted file mode 100644 index 9978744249..0000000000 --- a/lib/dotcom_web/templates/trip_plan/_departure.html.eex +++ /dev/null @@ -1,29 +0,0 @@ -
- Schedule By Arrival or Departure: -
- <%= label @parent_form, :time, for: "depart", class: "form-check-label m-trip-plan__form-label" do %> - <%= radio_button @parent_form, :time, "depart", id: "depart", checked: true, class: "form-check-input c-radio" %> -
- Depart at -
- <% end %> -
-
- <%= label @parent_form, :time, for: "arrive", class: "form-check-label m-trip-plan__form-label" do %> - <%= radio_button @parent_form, :time, "arrive", id: "arrive", class: "form-check-input c-radio" %> -
- Arrive by -
- <% end %> -
-
-
- - <% date_ranges = %{min_date: Util.now(), max_date: end_of_rating()} %> - <%= DateTimeSelector.custom_date_time_select(@parent_form, date_ranges, @datetime) %> -
- <%= date_error(@errors) %> -
-
diff --git a/lib/dotcom_web/templates/trip_plan/_mobile_trip_summary.html.eex b/lib/dotcom_web/templates/trip_plan/_mobile_trip_summary.html.heex similarity index 69% rename from lib/dotcom_web/templates/trip_plan/_mobile_trip_summary.html.eex rename to lib/dotcom_web/templates/trip_plan/_mobile_trip_summary.html.heex index e6f1ebd599..708bac0295 100644 --- a/lib/dotcom_web/templates/trip_plan/_mobile_trip_summary.html.eex +++ b/lib/dotcom_web/templates/trip_plan/_mobile_trip_summary.html.heex @@ -1,12 +1,12 @@ <%= if @itineraries? do %> -
+
Start from: <%= Query.location_name(assigns[:query], :from) %>
End here: <%= Query.location_name(assigns[:query], :to) %>
- + Edit trip
diff --git a/lib/dotcom_web/templates/trip_plan/_more_options.html.eex b/lib/dotcom_web/templates/trip_plan/_modes.html.eex similarity index 54% rename from lib/dotcom_web/templates/trip_plan/_more_options.html.eex rename to lib/dotcom_web/templates/trip_plan/_modes.html.eex index adf901978a..dbc80a478b 100644 --- a/lib/dotcom_web/templates/trip_plan/_more_options.html.eex +++ b/lib/dotcom_web/templates/trip_plan/_modes.html.eex @@ -1,4 +1,4 @@ -
+
<%= inputs_for(@form, :modes, [], fn mode_f -> for mode <- [:subway, :commuter_rail, :bus, :ferry] do @@ -9,22 +9,15 @@ else Map.get(@modes, mode, false) end + + mode_name = mode |> Atom.to_string() |> Recase.to_title() + content_tag(:div, [ checkbox(mode_f, mode, checked: active?, class: "c-checkbox__input", hidden_input: false), - label(mode_f, mode, class: "c-checkbox__label") + label(mode_f, mode, mode_name, class: "c-checkbox__label") ], class: "c-checkbox") end end) %>
- -
- <%= - - content_tag(:div, [ - checkbox(@form, :wheelchair, checked: @wheelchair == true, class: "c-checkbox__input", hidden_input: false), - label(@form, :wheelchair, ["Wheelchair accessible", svg_icon_with_circle(%SvgIconWithCircle{icon: :access})], class: "c-checkbox__label") - ], class: "c-checkbox") - %> -
diff --git a/lib/dotcom_web/templates/trip_plan/_optimize_for.html.eex b/lib/dotcom_web/templates/trip_plan/_optimize_for.html.eex new file mode 100644 index 0000000000..abd52b2af3 --- /dev/null +++ b/lib/dotcom_web/templates/trip_plan/_optimize_for.html.eex @@ -0,0 +1,9 @@ +
+ <%= + + content_tag(:div, [ + checkbox(@form, :wheelchair, checked: @wheelchair == true, class: "c-checkbox__input", hidden_input: false), + label(@form, :wheelchair, ["Wheelchair accessible", svg_icon_with_circle(%SvgIconWithCircle{icon: :access})], class: "c-checkbox__label") + ], class: "c-checkbox") + %> +
diff --git a/lib/dotcom_web/templates/trip_plan/_options.html.eex b/lib/dotcom_web/templates/trip_plan/_options.html.eex deleted file mode 100644 index 691868f747..0000000000 --- a/lib/dotcom_web/templates/trip_plan/_options.html.eex +++ /dev/null @@ -1,20 +0,0 @@ -<%= - DotcomWeb.PartialView.render("_accordion_ui.html", Map.merge(assigns, %{ - multiselectable: true, - parent_view: DotcomWeb.TripPlanView, - sections: [ - %{ - content_template: "_departure.html", - title: format_plan_type_for_title(@query), - prefix: "trip-plan-departure", - datetime: datetime_from_query(@query) - }, - %{ - content_template: "_more_options.html", - title: "See more options", - prefix: "trip-plan-options", - form: @parent_form - } - ] - })) -%> diff --git a/lib/dotcom_web/templates/trip_plan/_options.html.heex b/lib/dotcom_web/templates/trip_plan/_options.html.heex new file mode 100644 index 0000000000..6d3ef21f38 --- /dev/null +++ b/lib/dotcom_web/templates/trip_plan/_options.html.heex @@ -0,0 +1,20 @@ +
+

When

+ <%= render("_time_inputs.html", assigns) %> +

Modes

+ <%= + DotcomWeb.PartialView.render("_accordion_ui.html", Map.merge(assigns, %{ + multiselectable: true, + parent_view: DotcomWeb.TripPlanView, + sections: [ + %{ + content_template: "_modes.html", + form: @parent_form, + prefix: "modes", + title: "All Modes" + } + ] + })) + %> + <%= render("_optimize_for.html", Map.merge(assigns, %{form: @parent_form})) %> +
diff --git a/lib/dotcom_web/templates/trip_plan/_sidebar.html.eex b/lib/dotcom_web/templates/trip_plan/_sidebar.html.eex index 8bd68fc8d1..077f5dca44 100644 --- a/lib/dotcom_web/templates/trip_plan/_sidebar.html.eex +++ b/lib/dotcom_web/templates/trip_plan/_sidebar.html.eex @@ -6,8 +6,7 @@ form_options = [ as: :plan, method: :get, - toggle_element_collapse: itineraries?, - class: "trip-planner-form", + class: "trip-planner-form #{if(itineraries?, do: "hidden-sm-down")}", id: "plan" ] @@ -32,7 +31,7 @@ <%= render "_mobile_trip_summary.html", query: query, itineraries?: itineraries? %> <%= form_for @conn, @conn.request_path, form_options, fn f -> %> <%= render "_to_from_inputs.html", Map.merge(assigns, to_from_assigns) %> - <%= render "_options.html", conn: @conn, parent_form: f, errors: plan_error, query: query, modes: @modes, wheelchair: @wheelchair, plan_datetime_selector_fields: @plan_datetime_selector_fields %> + <%= render "_options.html", conn: @conn, parent_form: f, errors: plan_error, query: query, chosen_date_time: @chosen_date_time, chosen_time: @chosen_time, date_time: @date_time, modes: @modes, wheelchair: @wheelchair %> <%= render "_submit_button.html" %>
Reset form diff --git a/lib/dotcom_web/templates/trip_plan/_time_inputs.html.heex b/lib/dotcom_web/templates/trip_plan/_time_inputs.html.heex new file mode 100644 index 0000000000..0be8a1aa33 --- /dev/null +++ b/lib/dotcom_web/templates/trip_plan/_time_inputs.html.heex @@ -0,0 +1,39 @@ +
+ + + <% select_now? = @chosen_time == "now" or is_nil(@chosen_time) %> + +
+
+ + + +
+
+
+ + + + +
+
+
+
+ <%= date_error(@errors) %> +
+
diff --git a/lib/dotcom_web/templates/trip_plan/index.html.eex b/lib/dotcom_web/templates/trip_plan/index.html.eex index c4a8cb032a..c7d1ede4b5 100644 --- a/lib/dotcom_web/templates/trip_plan/index.html.eex +++ b/lib/dotcom_web/templates/trip_plan/index.html.eex @@ -6,7 +6,7 @@ "> <%= case assigns[:query] do %> <% %{itineraries: {:ok, _}} -> %> -

+

<% l = length(@itineraries) %> <%= "We found #{l} #{Inflex.inflect("trip", l)} for you" %>

diff --git a/lib/dotcom_web/views/trip_plan_view.ex b/lib/dotcom_web/views/trip_plan_view.ex index 5436661002..caa5fbca44 100644 --- a/lib/dotcom_web/views/trip_plan_view.ex +++ b/lib/dotcom_web/views/trip_plan_view.ex @@ -402,6 +402,7 @@ defmodule DotcomWeb.TripPlanView do def format_plan_type_for_title(nil) do time = Dotcom.TripPlan.DateTime.round_minute(Util.now()) + ["Depart at ", Timex.format!(time, "{h12}:{m} {AM}, {M}/{D}/{YY}")] end diff --git a/lib/util/util.ex b/lib/util/util.ex index 53594b12da..5eb4f2c0c0 100644 --- a/lib/util/util.ex +++ b/lib/util/util.ex @@ -2,6 +2,7 @@ defmodule Util do @moduledoc "Utilities module" require Logger + use Timex {:ok, endpoint} = Application.compile_env(:dotcom, :util_endpoint) @@ -22,6 +23,18 @@ defmodule Util do # to_local_time(utc_now_fn.()) end + def date_as_js_string(%DateTime{} = date_time) do + Timex.format!(date_time, "%FT%H:%M", :strftime) + end + + def date_as_js_string(%Date{} = date) do + Timex.format!(date, "%FT%H:%M", :strftime) + end + + def date_as_js_string(%NaiveDateTime{} = naive_date_time) do + Timex.format!(naive_date_time, "%FT%H:%M", :strftime) + end + @doc "Today's date in the America/New_York timezone." def today do now() |> Timex.to_date() @@ -48,6 +61,20 @@ defmodule Util do end end + def parse_date_time(%DateTime{} = date_time) do + date_time + end + + def parse_date_time(%NaiveDateTime{} = date_time) do + date_time + end + + def parse_date_time(string) when is_binary(string) do + Timex.parse!(string, "{YYYY}-{M}-{D} {_h24}:{_m} {AM}") + end + + def parse_date_time(_), do: Timex.now() + @spec parse(map | DateTime.t()) :: NaiveDateTime.t() | DateTime.t() | {:error, :invalid_date} def parse(date_params) do case date_to_string(date_params) do @@ -83,6 +110,8 @@ defmodule Util do "#{year}-#{month}-#{day} #{hour}:#{minute} #{am_pm}" end + defp date_to_string(date) when is_binary(date), do: date + defp date_to_string(%DateTime{} = date) do date end diff --git a/test/dotcom_web/controllers/trip_plan_controller_test.exs b/test/dotcom_web/controllers/trip_plan_controller_test.exs index c6e5075646..75a15c2ddd 100644 --- a/test/dotcom_web/controllers/trip_plan_controller_test.exs +++ b/test/dotcom_web/controllers/trip_plan_controller_test.exs @@ -195,11 +195,6 @@ defmodule DotcomWeb.TripPlanControllerTest do assert conn.assigns.map_data end - test "assigns modes to empty map", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index)) - assert conn.assigns.modes == %{} - end - test "sets a custom meta description", %{conn: conn} do conn = get(conn, trip_plan_path(conn, :index)) assert conn.assigns.meta_description @@ -895,19 +890,4 @@ defmodule DotcomWeb.TripPlanControllerTest do } end end - - describe "Date and time selector" do - test "renders a date and time selector", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index)) - rendered = html_response(conn, 200) - - # check there are date and time selectors: - refute Floki.find(rendered, "div[id=\"plan-time-select\"]") == [] - refute Floki.find(rendered, "select[id=\"plan_date_time_hour\"]") == [] - refute Floki.find(rendered, "select[id=\"plan_date_time_minute\"]") == [] - refute Floki.find(rendered, "select[id=\"plan_date_time_am_pm\"]") == [] - - refute Floki.find(rendered, "div[id=\"plan-date\"]") == [] - end - end end