diff --git a/assets/js/hooks.js b/assets/js/hooks.js
index 9cacc1f..1f20179 100644
--- a/assets/js/hooks.js
+++ b/assets/js/hooks.js
@@ -207,6 +207,7 @@ export const Hooks = {
DateTimePicker: {
mounted() {
this.hiddenInput = this.el.querySelector("[data-datetime-value]")
+ this.unusedInput = this.el.querySelector("[data-datetime-unused]")
this.dateInput = this.el.querySelector("[data-datetime-date]")
this.timeInput = this.el.querySelector("[data-datetime-time]")
this.timezoneLabel = this.el.querySelector("[data-datetime-timezone]")
@@ -264,6 +265,11 @@ export const Hooks = {
this.hiddenInput.dispatchEvent(new Event("input", {bubbles: true}))
}
+ this.syncUserInput = () => {
+ if (this.unusedInput) this.unusedInput.disabled = true
+ this.syncHidden()
+ }
+
this.setQuickTime = event => {
const minutes = Number(event.currentTarget.dataset.datetimeOffset)
if (!Number.isFinite(minutes)) return
@@ -271,11 +277,11 @@ export const Hooks = {
const date = new Date(Date.now() + minutes * 60_000)
date.setSeconds(0, 0)
this.applyLocalDate(date)
- this.syncHidden()
+ this.syncUserInput()
}
- this.dateInput.addEventListener("input", this.syncHidden)
- this.timeInput.addEventListener("input", this.syncHidden)
+ this.dateInput.addEventListener("input", this.syncUserInput)
+ this.timeInput.addEventListener("input", this.syncUserInput)
this.quickButtons.forEach(button => button.addEventListener("click", this.setQuickTime))
const initial = this.parseUtc(this.hiddenInput.value)
@@ -288,8 +294,8 @@ export const Hooks = {
}
},
destroyed() {
- this.dateInput?.removeEventListener("input", this.syncHidden)
- this.timeInput?.removeEventListener("input", this.syncHidden)
+ this.dateInput?.removeEventListener("input", this.syncUserInput)
+ this.timeInput?.removeEventListener("input", this.syncUserInput)
this.quickButtons?.forEach(button => button.removeEventListener("click", this.setQuickTime))
}
},
diff --git a/lib/who_need_help_web/components/core_components.ex b/lib/who_need_help_web/components/core_components.ex
index fcf520c..f5f19cf 100644
--- a/lib/who_need_help_web/components/core_components.ex
+++ b/lib/who_need_help_web/components/core_components.ex
@@ -394,6 +394,7 @@ defmodule WhoNeedHelpWeb.CoreComponents do
assigns
|> assign(:id, field.id)
|> assign(:name, field.name)
+ |> assign(:unused_name, unused_input_name(field.name))
|> assign(:value, normalized_input_value("datetime-local", safe_form_value(field.value)))
|> assign(:errors, Enum.map(errors, &translate_error(&1)))
@@ -418,6 +419,7 @@ defmodule WhoNeedHelpWeb.CoreComponents do
value={@value}
data-datetime-value
/>
+
{gettext("Date")}
@@ -464,6 +466,13 @@ defmodule WhoNeedHelpWeb.CoreComponents do
"""
end
+ defp unused_input_name(name) do
+ case Regex.run(~r/^(.*)\[([^\[\]]+)\]$/, name, capture: :all_but_first) do
+ [prefix, field] -> "#{prefix}[_unused_#{field}]"
+ _other -> "_unused_#{name}"
+ end
+ end
+
# Helper used by inputs to generate form errors
defp error(assigns) do
~H"""
diff --git a/lib/who_need_help_web/live/activity_live/show.ex b/lib/who_need_help_web/live/activity_live/show.ex
index b0c7eb0..41bd7ae 100644
--- a/lib/who_need_help_web/live/activity_live/show.ex
+++ b/lib/who_need_help_web/live/activity_live/show.ex
@@ -492,6 +492,14 @@ defmodule WhoNeedHelpWeb.ActivityLive.Show do
{message.body}
+
+ {gettext(
+ "This activity is closed. The group chat remains available as read-only history."
+ )}
+
<.form
:if={@activity.status == :open}
for={@message_form}
@@ -548,7 +556,9 @@ defmodule WhoNeedHelpWeb.ActivityLive.Show do
phx-click="join"
class="btn btn-info w-full"
>
- {gettext("Request to join")}
+ {if @viewer_participant,
+ do: gettext("Request to join again"),
+ else: gettext("Request to join")}
{gettext("Approval pending")}
@@ -556,6 +566,28 @@ defmodule WhoNeedHelpWeb.ActivityLive.Show do
{gettext("The organizer has not approved your request yet.")}
+
+
{gettext("You left this activity")}
+
+ {gettext(
+ "You are no longer a participant and cannot access the group chat or exact meeting point. You can request to join again while registration is open."
+ )}
+
+
+
+
{gettext("Your join request was declined")}
+
+ {gettext(
+ "You are not a participant. You can send a new request while registration is open."
+ )}
+
+
"Visible before leaving"
+ })
+
+ assert {:ok, left} = Activities.leave_activity(context.participant_scope, activity.id)
+ assert left.status == :left
+
+ assert {:ok, left_activity} = Activities.get_activity(context.participant_scope, activity.id)
+ assert Activities.coordinates_for(context.participant_scope, left_activity).exact == false
+ assert Activities.paginate_messages(context.participant_scope, left_activity).entries == []
+
+ assert {:error, :forbidden} =
+ Activities.send_message(context.participant_scope, activity.id, %{
+ "body" => "Must not be visible after leaving"
+ })
+
+ assert {:ok, rejoined} =
+ Activities.request_to_join(context.participant_scope, activity.id)
+
+ assert rejoined.id == request.id
+ assert rejoined.status == :requested
+ assert is_nil(rejoined.reviewed_at)
+ assert is_nil(rejoined.left_at)
+
+ assert Enum.map(Activities.list_my_activities(context.participant_scope), & &1.id) == [
+ activity.id
+ ]
+ end
+
test "my activities exclude organizers blocked in either direction", context do
{:ok, activity} = Activities.create_activity(context.organizer_scope, context.attrs)
{:ok, _request} = Activities.request_to_join(context.participant_scope, activity.id)
diff --git a/test/who_need_help_web/live/mutual_aid_live_test.exs b/test/who_need_help_web/live/mutual_aid_live_test.exs
index 8d3e1af..9c5e481 100644
--- a/test/who_need_help_web/live/mutual_aid_live_test.exs
+++ b/test/who_need_help_web/live/mutual_aid_live_test.exs
@@ -276,6 +276,7 @@ defmodule WhoNeedHelpWeb.MutualAidLiveTest do
)
assert has_element?(view, "#help_request_expires_at[type='hidden']")
+ assert has_element?(view, "input[name='help_request[_unused_expires_at]']")
assert has_element?(view, "#help_request_expires_at-date[type='date']")
assert has_element?(view, "#help_request_expires_at-time[type='time']")
assert has_element?(view, "#help_request_expires_at-picker[phx-hook='DateTimePicker']")
@@ -319,13 +320,22 @@ defmodule WhoNeedHelpWeb.MutualAidLiveTest do
html =
render_change(view, "validate", %{
- "activity" => %{"category_id" => hiking.id}
+ "activity" => %{
+ "category_id" => hiking.id,
+ "starts_at" => "",
+ "_unused_starts_at" => "",
+ "join_deadline" => "",
+ "_unused_join_deadline" => ""
+ }
})
assert html =~ "Route difficulty"
assert html =~ "activity[structured_data][route_plan]"
assert html =~ "activity[structured_data][safety_plan_confirmed]"
assert html =~ "approve participants individually"
+ refute html =~ "can't be blank"
+ assert has_element?(view, "input[name='activity[_unused_starts_at]']")
+ assert has_element?(view, "input[name='activity[_unused_join_deadline]']")
html =
render_change(view, "validate", %{
@@ -424,6 +434,33 @@ defmodule WhoNeedHelpWeb.MutualAidLiveTest do
assert_push_event(participant_view, "reset-message-form", %{id: "activity-message-form"})
assert render(organizer_view) =~ "I will be there."
+ participant_view
+ |> element("button[phx-click='leave']")
+ |> render_click()
+
+ participant_html = render(participant_view)
+ assert participant_html =~ "You left this activity"
+ assert participant_html =~ "You are no longer a participant"
+ assert participant_html =~ "Request to join again"
+ refute participant_html =~ "Approved group chat"
+ refute participant_html =~ "I will be there."
+ refute has_element?(participant_view, "#activity-message-form")
+
+ participant_view
+ |> element("button[phx-click='join']")
+ |> render_click()
+
+ assert render(participant_view) =~ "Approval pending"
+ assert render(organizer_view) =~ "Activity participant"
+
+ organizer_view
+ |> element("button[phx-click='approve']")
+ |> render_click()
+
+ participant_html = render(participant_view)
+ assert participant_html =~ "Approved group chat"
+ assert participant_html =~ "I will be there."
+
participant_view
|> form("#activity-report-form",
report: %{reason: "harassment", details: "Review this activity context."}
@@ -466,6 +503,8 @@ defmodule WhoNeedHelpWeb.MutualAidLiveTest do
assert render(participant_view) =~ "Completed"
refute has_element?(participant_view, "button[phx-click='leave']")
+ refute has_element?(participant_view, "#activity-message-form")
+ assert render(participant_view) =~ "read-only history"
stop_live_view(organizer_view)
stop_live_view(participant_view)