When the Oban queue backs up, the 60-second uniqueness window expires and duplicate jobs stack up per device. Switch to period: :infinity so only one poll/monitor job exists per device at any time. Add replace option to supersede stale scheduled jobs with updated scheduled_at. Remove :executing from unique states so self-scheduling works while the current job runs. Set max_attempts: 1 since retrying stale polls is pointless. Also fix all credo --strict issues across the codebase.
123 lines
5.4 KiB
Elixir
123 lines
5.4 KiB
Elixir
defmodule ToweropsWeb.Components.ConsentPrompt do
|
|
@moduledoc """
|
|
LiveView component for prompting users to re-consent when policies are updated.
|
|
|
|
This component displays a modal that users cannot dismiss until they accept
|
|
the updated privacy policy and/or terms of service.
|
|
"""
|
|
use Phoenix.Component
|
|
use Gettext, backend: ToweropsWeb.Gettext
|
|
|
|
import ToweropsWeb.CoreComponents
|
|
|
|
attr :user_id, :string, required: true
|
|
attr :policies, :list, required: true
|
|
|
|
def consent_modal(assigns) do
|
|
~H"""
|
|
<%= if @policies != [] do %>
|
|
<div
|
|
id="consent-modal"
|
|
class="fixed inset-0 z-50 overflow-y-auto"
|
|
aria-labelledby="consent-modal-title"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
>
|
|
<div class="flex min-h-screen items-center justify-center p-4">
|
|
<!-- Backdrop -->
|
|
<div class="fixed inset-0 bg-gray-500 dark:bg-gray-900 bg-opacity-75 dark:bg-opacity-80 transition-opacity">
|
|
</div>
|
|
|
|
<!-- Modal panel -->
|
|
<div class="relative transform overflow-hidden rounded-lg bg-white dark:bg-gray-800 px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
|
|
<div class="sm:flex sm:items-start">
|
|
<div class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900 sm:mx-0 sm:h-10 sm:w-10">
|
|
<.icon
|
|
name="hero-information-circle"
|
|
class="h-6 w-6 text-blue-600 dark:text-blue-400"
|
|
/>
|
|
</div>
|
|
<div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left w-full">
|
|
<h3
|
|
class="text-lg font-semibold leading-6 text-gray-900 dark:text-white"
|
|
id="consent-modal-title"
|
|
>
|
|
{gettext("Updated Policies - Action Required")}
|
|
</h3>
|
|
<div class="mt-4 space-y-4">
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
|
{gettext(
|
|
"We've updated our policies. Please review and accept the changes to continue using Towerops."
|
|
)}
|
|
</p>
|
|
|
|
<div class="space-y-3">
|
|
<%= for policy_type <- @policies do %>
|
|
<div class="flex items-start">
|
|
<div class="flex items-center h-5">
|
|
<input
|
|
type="checkbox"
|
|
id={"consent-#{policy_type}"}
|
|
name={"consent[#{policy_type}]"}
|
|
phx-click="toggle_consent"
|
|
phx-value-policy={policy_type}
|
|
required
|
|
class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-800 dark:checked:bg-blue-500 dark:checked:border-blue-500"
|
|
/>
|
|
</div>
|
|
<div class="ml-3 text-sm">
|
|
<label
|
|
for={"consent-#{policy_type}"}
|
|
class="text-gray-700 dark:text-gray-300"
|
|
>
|
|
{gettext("I agree to the updated")}
|
|
<.link
|
|
href={policy_link(policy_type)}
|
|
target="_blank"
|
|
class="font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
|
>
|
|
{policy_name(policy_type)}
|
|
</.link>
|
|
<span class="text-red-600 dark:text-red-400">*</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
|
|
<div class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
{gettext(
|
|
"By accepting, you acknowledge that you have read and agree to the updated policies. You can review your consent history at any time from your account settings."
|
|
)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
|
<button
|
|
type="button"
|
|
phx-click="accept_updated_policies"
|
|
phx-value-user-id={@user_id}
|
|
class="inline-flex w-full justify-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 sm:ml-3 sm:w-auto disabled:opacity-50 disabled:cursor-not-allowed"
|
|
id="accept-policies-button"
|
|
>
|
|
{gettext("Accept and Continue")}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
"""
|
|
end
|
|
|
|
defp policy_name("privacy_policy"), do: gettext("Privacy Policy")
|
|
defp policy_name("terms_of_service"), do: gettext("Terms of Service")
|
|
defp policy_name(_), do: gettext("Policy")
|
|
|
|
defp policy_link("privacy_policy"), do: "/privacy"
|
|
defp policy_link("terms_of_service"), do: "/terms"
|
|
defp policy_link(_), do: "/"
|
|
end
|