diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0b00cfc6f..0b66bfc9c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,7 +11,7 @@
## \#3 unreleased
-* -
+* Added: Confirmation dialog when skipping content pages or consent pages in assignment configuration.
## \#2 2024-10-04
diff --git a/core/frameworks/pixel/components/confirmation_modal.ex b/core/frameworks/pixel/components/confirmation_modal.ex
new file mode 100644
index 000000000..597089c6a
--- /dev/null
+++ b/core/frameworks/pixel/components/confirmation_modal.ex
@@ -0,0 +1,66 @@
+defmodule Frameworks.Pixel.ConfirmationModal do
+ use CoreWeb, :live_component
+
+ @impl true
+ def update(%{assigns: assigns}, socket) do
+ {
+ :ok,
+ socket
+ |> assign_new(:title, fn ->
+ Map.get(assigns, :title, dgettext("eyra-ui", "confirmation_modal.title"))
+ end)
+ |> assign_new(:body, fn ->
+ Map.get(assigns, :body, dgettext("eyra-ui", "confirmation_modal.body"))
+ end)
+ |> update_buttons()
+ }
+ end
+
+ defp update_buttons(%{assigns: %{myself: myself}} = socket) do
+ confirm = %{
+ action: %{type: :send, target: myself, event: "confirm"},
+ face: %{type: :primary, label: dgettext("eyra-ui", "confirm.button")}
+ }
+
+ cancel = %{
+ action: %{type: :send, target: myself, event: "cancel"},
+ face: %{type: :secondary, label: dgettext("eyra-ui", "cancel.button")}
+ }
+
+ assign(socket, buttons: [confirm, cancel])
+ end
+
+ def handle_event("confirm", _, socket) do
+ {:noreply, socket |> send_event(:parent, "confirmed")}
+ end
+
+ def handle_event("cancel", _, socket) do
+ {:noreply, socket |> send_event(:parent, "cancelled")}
+ end
+
+ @impl true
+ @spec render(any()) :: Phoenix.LiveView.Rendered.t()
+ def render(assigns) do
+ ~H"""
+
+
+ <%= @title %>
+
+
+ <.spacing value="M" />
+
+
+ <%= @body %>
+
+
+ <.spacing value="M" />
+
+
+ <%= for button <- @buttons do %>
+
+ <% end %>
+
+
+ """
+ end
+end
diff --git a/core/frameworks/pixel/components/modal_view.ex b/core/frameworks/pixel/components/modal_view.ex
index ff5a6ce48..a1941ea00 100644
--- a/core/frameworks/pixel/components/modal_view.ex
+++ b/core/frameworks/pixel/components/modal_view.ex
@@ -39,7 +39,14 @@ defmodule Frameworks.Pixel.ModalView do
attr(:style, :atom, required: true)
attr(:live_component, :map, required: true)
- def container(assigns) do
+ def container(%{style: style} = assigns) do
+ allowed_styles = [:full, :page, :sheet, :dialog, :notification]
+
+ unless style in allowed_styles do
+ raise ArgumentError,
+ "Invalid style: #{style}. Allowed styles are: #{Enum.join(allowed_styles, ", ")}"
+ end
+
~H"""
<%= if @style == :full do %>
<.full live_component={@live_component} />
diff --git a/core/priv/gettext/de/LC_MESSAGES/eyra-assignment.po b/core/priv/gettext/de/LC_MESSAGES/eyra-assignment.po
index b0204650b..bdd03b7b3 100644
--- a/core/priv/gettext/de/LC_MESSAGES/eyra-assignment.po
+++ b/core/priv/gettext/de/LC_MESSAGES/eyra-assignment.po
@@ -430,6 +430,10 @@ msgid "atom.tag"
msgstr ""
#, elixir-autogen, elixir-format
+msgid "gdpr_form.confirmation_modal.body"
+msgstr ""
+
+#, elixir-autogen
msgid "export.progress.button"
msgstr ""
diff --git a/core/priv/gettext/de/LC_MESSAGES/eyra-ui.po b/core/priv/gettext/de/LC_MESSAGES/eyra-ui.po
index 665710114..af13d4d95 100644
--- a/core/priv/gettext/de/LC_MESSAGES/eyra-ui.po
+++ b/core/priv/gettext/de/LC_MESSAGES/eyra-ui.po
@@ -190,3 +190,15 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "menu.item.workspace"
msgstr ""
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "confirm.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "confirmation_modal.body"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "confirmation_modal.title"
+msgstr ""
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po b/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po
index 59948831b..ac87e6265 100644
--- a/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-assignment.po
@@ -429,6 +429,10 @@ msgid "atom.tag"
msgstr "Assignment"
#, elixir-autogen, elixir-format
+msgid "gdpr_form.confirmation_modal.body"
+msgstr "By skipping the consent, not only will the text be deleted, but the underlying consent agreement and all associated signatures will also be removed."
+
+#, elixir-autogen
msgid "export.progress.button"
msgstr "Progress report"
diff --git a/core/priv/gettext/en/LC_MESSAGES/eyra-ui.po b/core/priv/gettext/en/LC_MESSAGES/eyra-ui.po
index 3fdccf1b1..3aa64324a 100644
--- a/core/priv/gettext/en/LC_MESSAGES/eyra-ui.po
+++ b/core/priv/gettext/en/LC_MESSAGES/eyra-ui.po
@@ -189,3 +189,15 @@ msgstr "Desktop"
#, elixir-autogen, elixir-format, fuzzy
msgid "menu.item.workspace"
msgstr "My workspace"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "confirm.button"
+msgstr "Confirm"
+
+#, elixir-autogen, elixir-format
+msgid "confirmation_modal.body"
+msgstr "Are you sure you would like to skip this page? Your text will be deleted."
+
+#, elixir-autogen, elixir-format
+msgid "confirmation_modal.title"
+msgstr "Are you sure?"
diff --git a/core/priv/gettext/eyra-assignment.pot b/core/priv/gettext/eyra-assignment.pot
index 0b007f588..7dec3a4ea 100644
--- a/core/priv/gettext/eyra-assignment.pot
+++ b/core/priv/gettext/eyra-assignment.pot
@@ -429,6 +429,10 @@ msgid "atom.tag"
msgstr ""
#, elixir-autogen, elixir-format
+msgid "gdpr_form.confirmation_modal.body"
+msgstr ""
+
+#, elixir-autogen
msgid "export.progress.button"
msgstr ""
diff --git a/core/priv/gettext/eyra-ui.pot b/core/priv/gettext/eyra-ui.pot
index a5331a53a..de552afb7 100644
--- a/core/priv/gettext/eyra-ui.pot
+++ b/core/priv/gettext/eyra-ui.pot
@@ -189,3 +189,15 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "menu.item.workspace"
msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "confirm.button"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "confirmation_modal.body"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "confirmation_modal.title"
+msgstr ""
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po
index a49eb6c92..002bd1ca1 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-assignment.po
@@ -429,6 +429,10 @@ msgid "atom.tag"
msgstr ""
#, elixir-autogen, elixir-format
+msgid "gdpr_form.confirmation_modal.body"
+msgstr ""
+
+#, elixir-autogen
msgid "export.progress.button"
msgstr ""
diff --git a/core/priv/gettext/nl/LC_MESSAGES/eyra-ui.po b/core/priv/gettext/nl/LC_MESSAGES/eyra-ui.po
index 2e22db9d7..8e194fb2d 100644
--- a/core/priv/gettext/nl/LC_MESSAGES/eyra-ui.po
+++ b/core/priv/gettext/nl/LC_MESSAGES/eyra-ui.po
@@ -189,3 +189,15 @@ msgstr "To-do"
#, elixir-autogen, elixir-format, fuzzy
msgid "menu.item.workspace"
msgstr "Mijn werkplek"
+
+#, elixir-autogen, elixir-format, fuzzy
+msgid "confirm.button"
+msgstr "Klaar"
+
+#, elixir-autogen, elixir-format
+msgid "confirmation_modal.body"
+msgstr ""
+
+#, elixir-autogen, elixir-format
+msgid "confirmation_modal.title"
+msgstr ""
diff --git a/core/systems/assignment/_public.ex b/core/systems/assignment/_public.ex
index a2581a651..f8a7cc805 100644
--- a/core/systems/assignment/_public.ex
+++ b/core/systems/assignment/_public.ex
@@ -259,6 +259,10 @@ defmodule Systems.Assignment.Public do
assignment
end
+ def delete_consent_agreement(assignment) do
+ update_consent_agreement(assignment, nil)
+ end
+
def copy(
%Assignment.Model{} = assignment,
%Assignment.InfoModel{} = info,
diff --git a/core/systems/assignment/content_page_form.ex b/core/systems/assignment/content_page_form.ex
index efc0bc370..32a3375f0 100644
--- a/core/systems/assignment/content_page_form.ex
+++ b/core/systems/assignment/content_page_form.ex
@@ -64,6 +64,18 @@ defmodule Systems.Assignment.ContentPageForm do
}
end
+ @impl true
+ def compose(:confirmation_modal, %{page_ref: page_ref}) do
+ %{
+ module: Pixel.ConfirmationModal,
+ params: %{
+ assigns: %{
+ page_ref: page_ref
+ }
+ }
+ }
+ end
+
@impl true
def compose(:content_page_form, %{page_ref: nil}) do
%{
@@ -99,16 +111,39 @@ defmodule Systems.Assignment.ContentPageForm do
end
@impl true
- def handle_event("update", %{status: :off}, %{assigns: %{page_ref: page_ref}} = socket) do
- {:ok, _} = Assignment.Public.delete_page_ref(page_ref)
+ def handle_event("update", %{status: :off}, socket) do
+ if socket.assigns.page_ref.page.body != nil do
+ {
+ :noreply,
+ socket
+ |> compose_child(:confirmation_modal)
+ |> show_modal(:confirmation_modal, :dialog)
+ }
+ else
+ {:ok, _} = Assignment.Public.delete_page_ref(socket.assigns.page_ref)
+ {:noreply, socket}
+ end
+ end
- {
- :noreply,
- socket |> assign(page_ref: page_ref)
- }
+ @impl true
+ def handle_event("cancelled", %{source: %{name: :confirmation_modal}}, socket) do
+ {:noreply,
+ socket
+ |> hide_modal(:confirmation_modal)}
+ end
+
+ @impl true
+ def handle_event(
+ "confirmed",
+ %{source: %{name: :confirmation_modal}},
+ %{assigns: %{page_ref: page_ref}} = socket
+ ) do
+ {:ok, _} = Assignment.Public.delete_page_ref(page_ref)
+ {:noreply, socket |> hide_modal(:confirmation_modal)}
end
@impl true
+ @spec render(any()) :: Phoenix.LiveView.Rendered.t()
def render(assigns) do
~H"""
diff --git a/core/systems/assignment/gdpr_form.ex b/core/systems/assignment/gdpr_form.ex
index ff3a4c66f..21efc149c 100644
--- a/core/systems/assignment/gdpr_form.ex
+++ b/core/systems/assignment/gdpr_form.ex
@@ -55,6 +55,26 @@ defmodule Systems.Assignment.GdprForm do
}
end
+ @impl true
+ def compose(:confirmation_modal, %{entity: %{consent_agreement: consent_agreement}}) do
+ assigns = %{consent_agreement: consent_agreement}
+ signatures = Consent.Public.list_signatures(consent_agreement)
+
+ assigns =
+ if signatures != [] do
+ Map.put(assigns, :body, dgettext("eyra-assignment", "gdpr_form.confirmation_modal.body"))
+ else
+ assigns
+ end
+
+ %{
+ module: Pixel.ConfirmationModal,
+ params: %{
+ assigns: assigns
+ }
+ }
+ end
+
@impl true
def handle_event(
"update",
@@ -71,15 +91,49 @@ defmodule Systems.Assignment.GdprForm do
end
@impl true
- def handle_event("update", %{status: :off}, %{assigns: %{entity: assignment}} = socket) do
- {:ok, _} = Assignment.Public.update_consent_agreement(assignment, nil)
+ def handle_event(
+ "update",
+ %{status: :off},
+ %{assigns: %{entity: assignment}} = socket
+ ) do
+ revision = Consent.Public.latest_revision(assignment.consent_agreement)
+ localized_default_text = dgettext("eyra-consent", "default.consent.text")
{
:noreply,
socket
+ |> handle_off_state(revision, revision.source == localized_default_text)
}
end
+ @impl true
+ def handle_event("cancelled", %{source: %{name: :confirmation_modal}}, socket) do
+ {:noreply,
+ socket
+ |> hide_modal(:confirmation_modal)}
+ end
+
+ @impl true
+ def handle_event(
+ "confirmed",
+ %{source: %{name: :confirmation_modal}},
+ %{assigns: %{entity: assignment}} = socket
+ ) do
+ {:ok, _} = Assignment.Public.delete_consent_agreement(assignment)
+ {:noreply, socket |> hide_modal(:confirmation_modal)}
+ end
+
+ defp handle_off_state(socket, %{source: source}, false) when not is_nil(source) do
+ socket
+ |> compose_child(:confirmation_modal)
+ |> show_modal(:confirmation_modal, :dialog)
+ end
+
+ defp handle_off_state(socket, _, _) do
+ {:ok, _} = Assignment.Public.delete_consent_agreement(socket.assigns.entity)
+ socket
+ end
+
@impl true
def render(assigns) do
~H"""
diff --git a/core/systems/consent/_public.ex b/core/systems/consent/_public.ex
index 0d76d9cc5..cb3ead5fd 100644
--- a/core/systems/consent/_public.ex
+++ b/core/systems/consent/_public.ex
@@ -1,6 +1,7 @@
defmodule Systems.Consent.Public do
import CoreWeb.Gettext
import Ecto.Query
+ import Systems.Consent.Queries
alias Ecto.Multi
alias Core.Repo
@@ -186,6 +187,11 @@ defmodule Systems.Consent.Public do
|> Signal.Public.multi_dispatch({:consent_revision, :updated})
|> Repo.transaction()
end
+
+ def list_signatures(%Consent.AgreementModel{} = consent_agreement) do
+ signature_query(consent_agreement)
+ |> Repo.all()
+ end
end
defimpl Core.Persister, for: Systems.Consent.RevisionModel do
diff --git a/core/systems/consent/_queries.ex b/core/systems/consent/_queries.ex
new file mode 100644
index 000000000..67bd3e890
--- /dev/null
+++ b/core/systems/consent/_queries.ex
@@ -0,0 +1,23 @@
+defmodule Systems.Consent.Queries do
+ require Ecto.Query
+ require Frameworks.Utility.Query
+
+ import Ecto.Query, warn: false
+ import Frameworks.Utility.Query, only: [build: 3]
+
+ alias Systems.Consent
+
+ def signature_query() do
+ from(Consent.SignatureModel, as: :signature)
+ end
+
+ def signature_query(%Consent.AgreementModel{id: consent_agreement_id}) do
+ build(signature_query(), :signature,
+ revision: [
+ agreement: [
+ id == ^consent_agreement_id
+ ]
+ ]
+ )
+ end
+end