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