diff --git a/lib/bike_brigade/locations.ex b/lib/bike_brigade/locations.ex
index e4568a08..8a42c904 100644
--- a/lib/bike_brigade/locations.ex
+++ b/lib/bike_brigade/locations.ex
@@ -21,7 +21,10 @@ defmodule BikeBrigade.Locations do
Returns the list of community_fridges.
"""
def list_community_fridges do
- Repo.all(from cf in CommunityFridge, order_by: [desc: cf.active, asc: cf.name])
+ CommunityFridge
+ |> from(order_by: [desc: :active, asc: :name])
+ |> preload(:location)
+ |> Repo.all()
end
@doc """
@@ -29,13 +32,18 @@ defmodule BikeBrigade.Locations do
Raises `Ecto.NoResultsError` if the Community fridge does not exist.
"""
- def get_community_fridge!(id), do: Repo.get!(CommunityFridge, id)
+ def get_community_fridge!(id) do
+ CommunityFridge
+ |> where([cf], cf.id == ^id)
+ |> preload(:location)
+ |> Repo.one!()
+ end
@doc """
Creates a community_fridge.
"""
def create_community_fridge(attrs \\ %{}) do
- %CommunityFridge{}
+ %CommunityFridge{location: nil}
|> CommunityFridge.changeset(attrs)
|> Repo.insert()
end
@@ -45,6 +53,7 @@ defmodule BikeBrigade.Locations do
"""
def update_community_fridge(%CommunityFridge{} = community_fridge, attrs) do
community_fridge
+ |> Repo.preload(:location)
|> CommunityFridge.changeset(attrs)
|> Repo.update()
end
diff --git a/lib/bike_brigade/locations/community_fridge.ex b/lib/bike_brigade/locations/community_fridge.ex
index fb00feba..1515556d 100644
--- a/lib/bike_brigade/locations/community_fridge.ex
+++ b/lib/bike_brigade/locations/community_fridge.ex
@@ -19,16 +19,15 @@ defmodule BikeBrigade.Locations.CommunityFridge do
field :active, :boolean, default: true
field :pair_preferred, :boolean, default: false
- belongs_to :location, Location
+ belongs_to :location, Location, on_replace: :update
timestamps()
end
def changeset(community_fridge, params) do
community_fridge
- |> cast(params, @fields ++ [:location_id])
- |> validate_required([:name, :location_id])
- |> unique_constraint(:location_id)
- |> assoc_constraint(:location)
+ |> cast(params, @fields)
+ |> cast_assoc(:location)
+ |> validate_required([:name])
end
end
diff --git a/lib/bike_brigade_web/components/layouts.ex b/lib/bike_brigade_web/components/layouts.ex
index 7771b87d..9685aa26 100644
--- a/lib/bike_brigade_web/components/layouts.ex
+++ b/lib/bike_brigade_web/components/layouts.ex
@@ -32,6 +32,7 @@ defmodule BikeBrigadeWeb.Layouts do
:campaigns,
:opportunities,
:riders,
+ :community_fridges,
:stats,
:users,
:messages,
@@ -69,6 +70,15 @@ defmodule BikeBrigadeWeb.Layouts do
Riders
+ <.sidebar_link
+ selected={@current_page == :community_fridges}
+ navigate={~p"/community_fridges"}
+ >
+ <:icon>
+
+
+ Community Fridges
+
<.sidebar_link selected={@current_page == :stats} navigate={~p"/stats"}>
<:icon>
diff --git a/lib/bike_brigade_web/live/community_fridge_live/form_component.ex b/lib/bike_brigade_web/live/community_fridge_live/form_component.ex
new file mode 100644
index 00000000..518e8bd3
--- /dev/null
+++ b/lib/bike_brigade_web/live/community_fridge_live/form_component.ex
@@ -0,0 +1,96 @@
+defmodule BikeBrigadeWeb.CommunityFridgeLive.FormComponent do
+ use BikeBrigadeWeb, :live_component
+
+ alias BikeBrigade.{Locations, MediaStorage}
+ alias BikeBrigadeWeb.Components.LiveLocation
+
+ @impl true
+ def mount(socket) do
+ {:ok,
+ allow_upload(socket, :photo,
+ accept: ~w(.gif .png .jpg .jpeg),
+ max_entries: 1,
+ max_file_size: 10_000_000
+ )}
+ end
+
+ @impl true
+ def update(%{community_fridge: community_fridge} = assigns, socket) do
+ changeset = Locations.change_community_fridge(community_fridge)
+
+ {:ok,
+ socket
+ |> assign(assigns)
+ |> assign(:changeset, changeset)}
+ end
+
+ @impl true
+ def handle_event("validate", %{"community_fridge" => params}, socket) do
+ changeset =
+ socket.assigns.community_fridge
+ |> Locations.change_community_fridge(params)
+ |> Map.put(:action, :validate)
+
+ {:noreply, assign(socket, :changeset, changeset)}
+ end
+
+ def handle_event("save", %{"community_fridge" => params}, socket) do
+ params =
+ case consume_uploaded_entries(socket, :photo, fn %{path: path},
+ %{client_type: content_type} ->
+ {:ok, MediaStorage.upload_file!(path, content_type)}
+ end) do
+ [%{url: url}] -> Map.put(params, "photo", url)
+ [] -> Map.update(params, "photo", nil, &if(&1 == "", do: nil, else: &1))
+ end
+
+ save_community_fridge(socket, socket.assigns.action, params)
+ end
+
+ def handle_event("delete_photo", _params, socket) do
+ params = socket.assigns.changeset.params || %{}
+
+ {:noreply,
+ socket
+ |> assign(
+ :changeset,
+ socket.assigns.community_fridge
+ |> Locations.change_community_fridge(Map.put(params, "photo", nil))
+ |> Map.put(:action, :validate)
+ )}
+ end
+
+ def handle_event("cancel_upload", %{"ref" => ref}, socket) do
+ {:noreply, cancel_upload(socket, :photo, ref)}
+ end
+
+ defp error_to_string(:too_large), do: "Too large"
+ defp error_to_string(:too_many_files), do: "You have selected too many files"
+ defp error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
+
+ defp save_community_fridge(socket, :edit, params) do
+ case Locations.update_community_fridge(socket.assigns.community_fridge, params) do
+ {:ok, _community_fridge} ->
+ {:noreply,
+ socket
+ |> put_flash(:info, "Community fridge updated successfully")
+ |> push_patch(to: socket.assigns.navigate)}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign(socket, :changeset, changeset)}
+ end
+ end
+
+ defp save_community_fridge(socket, :new, params) do
+ case Locations.create_community_fridge(params) do
+ {:ok, _community_fridge} ->
+ {:noreply,
+ socket
+ |> put_flash(:info, "Community fridge created successfully")
+ |> push_patch(to: socket.assigns.navigate)}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign(socket, changeset: changeset)}
+ end
+ end
+end
diff --git a/lib/bike_brigade_web/live/community_fridge_live/form_component.html.heex b/lib/bike_brigade_web/live/community_fridge_live/form_component.html.heex
new file mode 100644
index 00000000..801babaa
--- /dev/null
+++ b/lib/bike_brigade_web/live/community_fridge_live/form_component.html.heex
@@ -0,0 +1,100 @@
+
+ <.header>{@title}
+ <.simple_form
+ :let={f}
+ for={@changeset}
+ id="community-fridge-form"
+ phx-target={@myself}
+ phx-change="validate"
+ phx-submit="save"
+ >
+ <.input type="text" field={f[:name]} label="Name" />
+
+ <.input type="textarea" field={f[:description]} label="Description" />
+
+ <.live_component
+ module={LiveLocation}
+ id="location-form"
+ field={f[:location]}
+ label="Location"
+ />
+
+
+
+
+
+
+
+ <.button
+ color={:lightred}
+ size={:xsmall}
+ phx-click={JS.push("delete_photo", target: @myself)}
+ type="button"
+ >
+ Delete
+
+
+
+
+
+
+
+
+
or drag and drop
+
+
PNG, JPG, GIF up to 10MB
+
+
+
+
+ <.live_img_preview entry={entry} class="w-16 rounded" />
+ {entry.client_name}
+
+
+ {error_to_string(err)}
+
+
+
+
+
+ <.input
+ type="checkbox"
+ field={f[:active]}
+ label="Active"
+ />
+
+ <.input
+ type="checkbox"
+ field={f[:pair_preferred]}
+ label="Pair Preferred"
+ help_text="Deliveries to this fridge prefer a rider pair"
+ />
+
+ <:actions>
+ <.button navigate={~p"/community_fridges"} color={:white}>
+ Cancel
+
+ <.button type="submit" phx-disable-with="Saving...">Save
+
+
+
diff --git a/lib/bike_brigade_web/live/community_fridge_live/index.ex b/lib/bike_brigade_web/live/community_fridge_live/index.ex
new file mode 100644
index 00000000..e1d0ddb8
--- /dev/null
+++ b/lib/bike_brigade_web/live/community_fridge_live/index.ex
@@ -0,0 +1,93 @@
+defmodule BikeBrigadeWeb.CommunityFridgeLive.Index do
+ use BikeBrigadeWeb, :live_view
+
+ alias BikeBrigade.Locations
+ alias BikeBrigade.Locations.CommunityFridge
+
+ # Toronto city hall as fallback centre
+ @default_coords %Geo.Point{coordinates: {-79.3832, 43.6532}}
+
+ @impl Phoenix.LiveView
+ def mount(_params, _session, socket) do
+ {:ok,
+ socket
+ |> assign(:page, :community_fridges)
+ |> assign(:page_title, "Community Fridges")
+ |> assign(:community_fridge, nil)
+ |> assign(:community_fridges, Locations.list_community_fridges())
+ |> assign(:map_layers, [])
+ |> assign(:map_coords, @default_coords)
+ |> assign(:mode, :list)}
+ end
+
+ @impl Phoenix.LiveView
+ def handle_params(params, _url, socket) do
+ {:noreply, apply_action(socket, socket.assigns.live_action, params)}
+ end
+
+ @impl Phoenix.LiveView
+ def handle_event("set_mode", %{"mode" => mode}, socket) do
+ socket = assign(socket, :mode, String.to_existing_atom(mode))
+
+ socket =
+ if socket.assigns.mode == :map do
+ push_event(socket, "leaflet:redraw_map", %{recenter: false})
+ else
+ socket
+ end
+
+ {:noreply, socket}
+ end
+
+ defp apply_action(socket, :index, _params) do
+ community_fridges = Locations.list_community_fridges()
+
+ socket
+ |> assign(:page_title, "Community Fridges")
+ |> assign(:community_fridge, nil)
+ |> assign(:community_fridges, community_fridges)
+ |> assign(:map_layers, fridge_markers(community_fridges))
+ |> assign(:map_coords, map_center(community_fridges))
+ end
+
+ defp apply_action(socket, :new, _params) do
+ socket
+ |> assign(:page_title, "New Fridge")
+ |> assign(:community_fridge, %CommunityFridge{location: nil})
+ end
+
+ defp apply_action(socket, :edit, %{"id" => id}) do
+ socket
+ |> assign(:page_title, "Edit Fridge")
+ |> assign(:community_fridge, Locations.get_community_fridge!(id))
+ end
+
+ defp fridge_markers(community_fridges) do
+ for %{id: id, name: name, location: location} <- community_fridges,
+ not is_nil(location),
+ not is_nil(location.coords) do
+ %{
+ id: "fridge-#{id}",
+ type: :marker,
+ data: %{
+ lat: lat(location),
+ lng: lng(location),
+ icon: "warehouse",
+ color: "#1c64f2",
+ tooltip: name
+ }
+ }
+ end
+ end
+
+ defp map_center(community_fridges) do
+ community_fridges
+ |> Enum.find_value(fn %{location: location} ->
+ location && location.coords
+ end)
+ |> case do
+ nil -> @default_coords
+ coords -> coords
+ end
+ end
+end
diff --git a/lib/bike_brigade_web/live/community_fridge_live/index.html.heex b/lib/bike_brigade_web/live/community_fridge_live/index.html.heex
new file mode 100644
index 00000000..92cd6840
--- /dev/null
+++ b/lib/bike_brigade_web/live/community_fridge_live/index.html.heex
@@ -0,0 +1,80 @@
+<.header>
+ Community Fridges
+ <:subtitle>
+ Community fridges are neighbourhood food-sharing locations. Dispatchers can manage fridge details here.
+
+
+
+
+
+
+
+ <.button patch={~p"/community_fridges/new"}>
+ New Community Fridge
+
+
+
+<%= if @mode == :map do %>
+
+
+ <.map
+ id="community-fridges-map"
+ coords={@map_coords}
+ initial_layers={@map_layers}
+ class="h-full"
+ />
+
+
+<% else %>
+ <.table id="community_fridges" rows={@community_fridges}>
+ <:col :let={community_fridge} label="Name">
+ {community_fridge.name}
+
+ <:col :let={community_fridge} label="Address">
+ {community_fridge.location}
+
+ <:col :let={community_fridge} label="Description">
+ {community_fridge.description}
+
+ <:col :let={community_fridge} label="Pair Preferred">
+ {if community_fridge.pair_preferred, do: "Yes", else: "No"}
+
+ <:col :let={community_fridge} label="Active">
+ {if community_fridge.active, do: "Yes", else: "No"}
+
+
+ <:action :let={community_fridge}>
+ <.link patch={~p"/community_fridges/#{community_fridge.id}/edit"} class="link">
+ Edit
+
+
+
+<% end %>
+
+<.modal
+ :if={@live_action in [:new, :edit]}
+ id="community-fridge-modal"
+ show
+ on_cancel={JS.navigate(~p"/community_fridges")}
+>
+ <.live_component
+ module={BikeBrigadeWeb.CommunityFridgeLive.FormComponent}
+ id={@community_fridge.id || :new}
+ action={@live_action}
+ title={@page_title}
+ community_fridge={@community_fridge}
+ navigate={~p"/community_fridges"}
+ />
+
diff --git a/lib/bike_brigade_web/router.ex b/lib/bike_brigade_web/router.ex
index aa288e6a..3080f591 100644
--- a/lib/bike_brigade_web/router.ex
+++ b/lib/bike_brigade_web/router.ex
@@ -180,6 +180,10 @@ defmodule BikeBrigadeWeb.Router do
live "/tags/:id/edit", TagLive.Index, :edit
live "/delivery_notes", DeliveryNoteLive.Index, :index
+
+ live "/community_fridges", CommunityFridgeLive.Index, :index
+ live "/community_fridges/new", CommunityFridgeLive.Index, :new
+ live "/community_fridges/:id/edit", CommunityFridgeLive.Index, :edit
end
get "/stats/leaderboard/download", ExportStatsController, :leaderboard
diff --git a/priv/repo/migrations/20261016222054_remove_location_id_from_community_fridges.exs b/priv/repo/migrations/20261016222054_remove_location_id_from_community_fridges.exs
new file mode 100644
index 00000000..9572ee60
--- /dev/null
+++ b/priv/repo/migrations/20261016222054_remove_location_id_from_community_fridges.exs
@@ -0,0 +1,7 @@
+defmodule BikeBrigade.Repo.Migrations.RemoveLocationIdFromCommunityFridges do
+ use Ecto.Migration
+
+ def change do
+ drop unique_index(:community_fridges, [:location_id])
+ end
+end
diff --git a/priv/repo/seeds/toronto.ex b/priv/repo/seeds/toronto.ex
index bd5252bb..a1e45341 100644
--- a/priv/repo/seeds/toronto.ex
+++ b/priv/repo/seeds/toronto.ex
@@ -30,7 +30,7 @@ defmodule BikeBrigade.Repo.Seeds.Toronto do
city: city,
country: "Canada",
province: "Ontario",
- coords: %Geo.Point{coordinates: {lon, lat}}
+ coords: %Geo.Point{coordinates: {lon, lat}, srid: 4326}
}
end
end
diff --git a/test/bike_brigade/locations/community_fridge_test.exs b/test/bike_brigade/locations/community_fridge_test.exs
index c8b3dc46..e2e4004c 100644
--- a/test/bike_brigade/locations/community_fridge_test.exs
+++ b/test/bike_brigade/locations/community_fridge_test.exs
@@ -1,69 +1,62 @@
defmodule BikeBrigade.Locations.CommunityFridgeTest do
use BikeBrigade.DataCase
- alias BikeBrigade.Locations.Location
+ alias BikeBrigade.Locations
alias BikeBrigade.Locations.CommunityFridge
-
- setup do
- location_data = fixture(:location)
-
- {:ok, location} =
- %Location{}
- |> Location.changeset(location_data)
- |> Repo.insert()
-
- %{location: location}
- end
+ alias BikeBrigade.Repo.Seeds.Toronto
describe "CommunityFridge changeset" do
- test "changeset with valid attributes", %{location: location} do
+ test "valid with nested location params" do
attrs = %{
name: "Downtown Community Fridge",
description: "A fridge for the community",
- photo: "https://example.com/photo.jpg",
active: true,
pair_preferred: false,
- location_id: location.id
+ location: Toronto.random_location()
}
- changeset = CommunityFridge.changeset(%CommunityFridge{}, attrs)
+ changeset = CommunityFridge.changeset(%CommunityFridge{location: nil}, attrs)
assert changeset.valid?
end
- test "changeset with minimal required attributes", %{location: location} do
- attrs = %{
- name: "Community Fridge",
- location_id: location.id
- }
-
- changeset = CommunityFridge.changeset(%CommunityFridge{}, attrs)
+ test "valid without a location" do
+ changeset =
+ CommunityFridge.changeset(%CommunityFridge{location: nil}, %{name: "Bare Fridge"})
assert changeset.valid?
end
- test "changeset requires name", %{location: location} do
- attrs = %{
- description: "A fridge without a name",
- location_id: location.id
- }
-
- changeset = CommunityFridge.changeset(%CommunityFridge{}, attrs)
+ test "requires name" do
+ changeset =
+ CommunityFridge.changeset(%CommunityFridge{location: nil}, %{
+ location: Toronto.random_location()
+ })
refute changeset.valid?
assert "can't be blank" in errors_on(changeset).name
end
- test "changeset rejects empty name", %{location: location} do
- attrs = %{
- name: "",
- location_id: location.id
- }
-
- changeset = CommunityFridge.changeset(%CommunityFridge{}, attrs)
+ test "rejects empty name" do
+ changeset =
+ CommunityFridge.changeset(%CommunityFridge{location: nil}, %{
+ name: "",
+ location: Toronto.random_location()
+ })
refute changeset.valid?
assert "can't be blank" in errors_on(changeset).name
end
+
+ test "updating location updates the associated record in place" do
+ fridge = fixture(:community_fridge)
+ original_location_id = fridge.location.id
+
+ new_location = Toronto.random_location()
+ {:ok, updated} = Locations.update_community_fridge(fridge, %{location: new_location})
+
+ assert updated.location.id == original_location_id
+ assert updated.location.address == new_location.address
+ end
end
end
diff --git a/test/bike_brigade/locations_test.exs b/test/bike_brigade/locations_test.exs
index 3e647d18..bf7aa264 100644
--- a/test/bike_brigade/locations_test.exs
+++ b/test/bike_brigade/locations_test.exs
@@ -26,23 +26,30 @@ defmodule BikeBrigade.LocationsTest do
test "create_community_fridge/1 with valid data creates a community_fridge" do
location = fixture(:persisted_location, %{})
- valid_attrs = %{
- active: true,
- name: "some name",
- description: "some description",
- photo: "some photo",
- pair_preferred: true,
- location_id: location.id
- }
-
- assert {:ok, %CommunityFridge{} = community_fridge} =
- Locations.create_community_fridge(valid_attrs)
-
- assert community_fridge.active == true
- assert community_fridge.name == "some name"
- assert community_fridge.description == "some description"
- assert community_fridge.photo == "some photo"
- assert community_fridge.pair_preferred == true
+ %{
+ active: active,
+ name: name,
+ description: description,
+ photo: photo,
+ pair_preferred: pair_preferred
+ } =
+ valid_attrs = %{
+ active: true,
+ name: "some name",
+ description: "some description",
+ photo: "some photo",
+ pair_preferred: true,
+ location_id: location.id
+ }
+
+ assert {:ok,
+ %CommunityFridge{
+ active: ^active,
+ name: ^name,
+ description: ^description,
+ photo: ^photo,
+ pair_preferred: ^pair_preferred
+ }} = Locations.create_community_fridge(valid_attrs)
end
test "create_community_fridge/1 with invalid data returns error changeset" do
@@ -52,19 +59,20 @@ defmodule BikeBrigade.LocationsTest do
test "update_community_fridge/2 with valid data updates the community_fridge", %{
community_fridge: community_fridge
} do
- active = false
- name = "some updated name"
- description = "some updated description"
- photo = "some updated photo"
- pair_preferred = false
-
- update_attrs = %{
+ %{
active: active,
name: name,
description: description,
photo: photo,
pair_preferred: pair_preferred
- }
+ } =
+ update_attrs = %{
+ active: false,
+ name: "some updated name",
+ description: "some updated description",
+ photo: "some updated photo",
+ pair_preferred: false
+ }
assert {:ok,
%CommunityFridge{
@@ -76,15 +84,6 @@ defmodule BikeBrigade.LocationsTest do
}} = Locations.update_community_fridge(community_fridge, update_attrs)
end
- test "update_community_fridge/2 with invalid data returns error changeset", %{
- community_fridge: community_fridge
- } do
- assert {:error, %Ecto.Changeset{}} =
- Locations.update_community_fridge(community_fridge, @invalid_attrs)
-
- assert community_fridge == Locations.get_community_fridge!(community_fridge.id)
- end
-
test "delete_community_fridge/1 deletes the community_fridge", %{
community_fridge: community_fridge
} do
diff --git a/test/bike_brigade_web/live/campaign_live_test.exs b/test/bike_brigade_web/live/campaign_live_test.exs
index 48b9c557..023bb349 100644
--- a/test/bike_brigade_web/live/campaign_live_test.exs
+++ b/test/bike_brigade_web/live/campaign_live_test.exs
@@ -78,7 +78,7 @@ defmodule BikeBrigadeWeb.CampaignLiveTest do
coords:
%Geo.Point{
coordinates: {-79.37761739999999, 43.6459904},
- srid: nil,
+ srid: 4326,
properties: %{}
}
|> Geo.JSON.encode!()
diff --git a/test/bike_brigade_web/live/community_fridge_live_test.exs b/test/bike_brigade_web/live/community_fridge_live_test.exs
new file mode 100644
index 00000000..9710d501
--- /dev/null
+++ b/test/bike_brigade_web/live/community_fridge_live_test.exs
@@ -0,0 +1,174 @@
+defmodule BikeBrigadeWeb.CommunityFridgeLiveTest do
+ use BikeBrigadeWeb.ConnCase
+ import Phoenix.LiveViewTest
+
+ describe "Community Fridges index (dispatcher)" do
+ setup [:login]
+
+ test "lists all community fridges", ctx do
+ fridge = fixture(:community_fridge, %{name: "Test Fridge"})
+ {:ok, _view, html} = live(ctx.conn, ~p"/community_fridges")
+
+ assert html =~ "Community Fridges"
+ assert html =~ fridge.name
+ end
+
+ test "shows address column for fridge with a location", ctx do
+ fridge = fixture(:community_fridge, %{name: "Located Fridge"})
+ {:ok, _view, html} = live(ctx.conn, ~p"/community_fridges")
+
+ assert html =~ fridge.location.address
+ end
+
+ test "renders map tab and displays map on click", ctx do
+ {:ok, view, html} = live(ctx.conn, ~p"/community_fridges")
+
+ refute html =~ "community-fridges-map"
+ assert html =~ "Map"
+
+ html = view |> element("button", "Map") |> render_click()
+
+ assert html =~ "community-fridges-map"
+ end
+
+ test "can create a new community fridge", ctx do
+ location = fixture(:persisted_location, %{})
+
+ {:ok, view, _html} = live(ctx.conn, ~p"/community_fridges")
+
+ view |> element("a", "New Community Fridge") |> render_click()
+ assert_patched(view, ~p"/community_fridges/new")
+
+ view
+ |> form("#community-fridge-form", community_fridge: %{name: "Brand New Fridge"})
+ |> render_submit(%{
+ community_fridge: %{
+ location: %{
+ id: location.id,
+ address: location.address,
+ coords: location.coords |> Geo.JSON.encode!() |> Jason.encode!()
+ }
+ }
+ })
+
+ assert_patch(view, ~p"/community_fridges")
+ html = render(view)
+ assert html =~ "Community fridge created successfully"
+ assert html =~ "Brand New Fridge"
+ end
+
+ test "validates name is required", ctx do
+ {:ok, view, _html} = live(ctx.conn, ~p"/community_fridges/new")
+
+ html =
+ view
+ |> form("#community-fridge-form", community_fridge: %{name: ""})
+ |> render_submit()
+
+ assert html =~ "can't be blank"
+ end
+
+ test "can edit a community fridge", ctx do
+ fridge = fixture(:community_fridge, %{name: "Original Fridge"})
+ {:ok, view, _html} = live(ctx.conn, ~p"/community_fridges")
+
+ view |> element("a", "Edit") |> render_click()
+ assert_patched(view, ~p"/community_fridges/#{fridge.id}/edit")
+
+ view
+ |> form("#community-fridge-form", community_fridge: %{name: "Updated Fridge"})
+ |> render_submit()
+
+ assert_patch(view, ~p"/community_fridges")
+ html = render(view)
+ assert html =~ "Community fridge updated successfully"
+ assert html =~ "Updated Fridge"
+ refute html =~ "Original Fridge"
+ end
+
+ test "can navigate directly to edit page", ctx do
+ fridge = fixture(:community_fridge, %{name: "Edit Me"})
+ {:ok, _view, html} = live(ctx.conn, ~p"/community_fridges/#{fridge.id}/edit")
+
+ assert html =~ "Edit Fridge"
+ end
+
+ test "edit preserves existing values", ctx do
+ fridge =
+ fixture(:community_fridge, %{
+ name: "Preserve Me",
+ description: "Some description",
+ active: true,
+ pair_preferred: true
+ })
+
+ {:ok, _view, html} = live(ctx.conn, ~p"/community_fridges/#{fridge.id}/edit")
+
+ assert html =~ "Preserve Me"
+ assert html =~ "Some description"
+ end
+ end
+
+ describe "Community Fridge photo upload" do
+ setup [:login]
+
+ test "shows existing photo in edit form", ctx do
+ fridge =
+ fixture(:community_fridge, %{
+ name: "Photo Fridge",
+ photo: "https://example.com/fridge.jpg"
+ })
+
+ {:ok, _view, html} = live(ctx.conn, ~p"/community_fridges/#{fridge.id}/edit")
+
+ assert html =~ "https://example.com/fridge.jpg"
+ end
+
+ test "renders upload dropzone", ctx do
+ fixture(:community_fridge, %{name: "Upload Fridge"})
+ {:ok, view, _html} = live(ctx.conn, ~p"/community_fridges/new")
+
+ html = render(view)
+ assert html =~ "Upload a file"
+ assert html =~ "or drag and drop"
+ assert html =~ "PNG, JPG, GIF"
+ end
+
+ test "shows pending upload preview after file is selected", ctx do
+ {:ok, view, _html} = live(ctx.conn, ~p"/community_fridges/new")
+
+ upload =
+ file_input(view, "#community-fridge-form", :photo, [
+ %{name: "fridge.jpg", content: <<0, 1, 2>>, type: "image/jpeg"}
+ ])
+
+ render_upload(upload, "fridge.jpg", 100)
+
+ html = render(view)
+ assert html =~ "fridge.jpg"
+ end
+
+ test "can cancel a pending upload", ctx do
+ {:ok, view, _html} = live(ctx.conn, ~p"/community_fridges/new")
+
+ upload =
+ file_input(view, "#community-fridge-form", :photo, [
+ %{name: "fridge.jpg", content: <<0, 1, 2>>, type: "image/jpeg"}
+ ])
+
+ render_upload(upload, "fridge.jpg", 100)
+ assert render(view) =~ "fridge.jpg"
+
+ view |> element("button[aria-label='cancel']") |> render_click()
+ refute render(view) =~ "fridge.jpg"
+ end
+ end
+
+ describe "Community Fridges access control" do
+ setup [:login_as_rider]
+
+ test "non-dispatchers cannot access community fridges page", ctx do
+ assert {:error, {:redirect, %{to: "/login"}}} = live(ctx.conn, ~p"/community_fridges")
+ end
+ end
+end
diff --git a/test/support/fixtures.ex b/test/support/fixtures.ex
index e392f86e..d96147d3 100644
--- a/test/support/fixtures.ex
+++ b/test/support/fixtures.ex
@@ -193,15 +193,10 @@ defmodule BikeBrigade.Fixtures do
end
def fixture(:community_fridge, attrs) do
- attrs =
- attrs
- |> Map.put_new_lazy(:location_id, fn -> fixture(:persisted_location, %{}).id end)
+ attrs = Map.put_new_lazy(attrs, :location, fn -> Toronto.random_location() end)
{:ok, community_fridge} =
- %{
- name: Faker.Company.name(),
- active: true
- }
+ %{name: Faker.Company.name(), active: true}
|> Map.merge(attrs)
|> Locations.create_community_fridge()