Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 35 additions & 5 deletions lib/modules/legal/legal.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ defmodule PhoenixKit.Modules.Legal do
alias PhoenixKit.Modules.Legal.PageType
alias PhoenixKit.Modules.Legal.TemplateGenerator
alias PhoenixKit.Settings
alias PhoenixKit.Utils.Routes

@enabled_key "legal_enabled"
@module_name "legal"
Expand Down Expand Up @@ -551,12 +552,26 @@ defmodule PhoenixKit.Modules.Legal do
- google_consent_mode: boolean
- hide_for_authenticated: boolean
- frameworks: list of framework IDs
- cookie_policy_url: string
- privacy_policy_url: string
- cookie_policy_url: string (backward compat, derived from published pages)
- privacy_policy_url: string (backward compat, derived from published pages)
- legal_links: list of %{title: string, url: string} for all published legal pages
- legal_index_url: string
"""
@spec get_consent_widget_config() :: map()
def get_consent_widget_config do
prefix = PhoenixKit.Config.get_url_prefix()
legal_links = get_published_legal_links()

cookie_policy_url =
case Enum.find(legal_links, &String.ends_with?(&1.url, "/cookie-policy")) do
%{url: url} -> url
nil -> Routes.path("/legal/cookie-policy")
end

privacy_policy_url =
case Enum.find(legal_links, &String.ends_with?(&1.url, "/privacy-policy")) do
%{url: url} -> url
nil -> Routes.path("/legal/privacy-policy")
end

%{
enabled: consent_widget_enabled?(),
Expand All @@ -567,11 +582,26 @@ defmodule PhoenixKit.Modules.Legal do
policy_version: get_auto_policy_version(),
google_consent_mode: google_consent_mode_enabled?(),
frameworks: get_selected_frameworks(),
cookie_policy_url: "#{prefix}/legal/cookie-policy",
privacy_policy_url: "#{prefix}/legal/privacy-policy"
cookie_policy_url: cookie_policy_url,
privacy_policy_url: privacy_policy_url,
legal_links: legal_links,
legal_index_url: Routes.path("/legal")
}
end

@doc """
Returns a list of all published legal pages as link maps.

Each map has `:title` and `:url` keys. Used by the cookie consent widget
to render dynamic links to all published legal pages.
"""
@spec get_published_legal_links() :: list(%{title: String.t(), url: String.t()})
def get_published_legal_links do
list_generated_pages()
|> Enum.filter(&(&1.status == "published"))
|> Enum.map(&%{title: &1.title, url: Routes.path("/legal/#{&1.slug}")})
end

@doc """
Check if there are unpublished legal pages that are required.

Expand Down
3 changes: 3 additions & 0 deletions lib/modules/sync/web/connections_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,7 @@ defmodule PhoenixKit.Modules.Sync.Web.ConnectionsLive do
end

# Get all FK dependencies for a table (recursive)
@dialyzer [{:no_opaque, get_table_dependencies: 2}, {:no_opaque, get_table_dependencies: 3}]
defp get_table_dependencies(table_name, tables) do
get_table_dependencies(table_name, tables, MapSet.new())
|> MapSet.to_list()
Expand Down Expand Up @@ -1165,6 +1166,7 @@ defmodule PhoenixKit.Modules.Sync.Web.ConnectionsLive do

defp topo_sort(_graph, [], sorted, _visited), do: sorted

@dialyzer {:no_opaque, topo_sort: 4}
defp topo_sort(graph, [node | rest], sorted, visited) do
if MapSet.member?(visited, node) do
topo_sort(graph, rest, sorted, visited)
Expand All @@ -1174,6 +1176,7 @@ defmodule PhoenixKit.Modules.Sync.Web.ConnectionsLive do
end
end

@dialyzer {:no_opaque, visit_node: 5}
defp visit_node(graph, node, sorted, visited, path) do
if MapSet.member?(visited, node) do
{sorted, visited}
Expand Down
89 changes: 27 additions & 62 deletions lib/phoenix_kit_web/components/core/cookie_consent.ex
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ defmodule PhoenixKitWeb.Components.Core.CookieConsent do
attr :policy_version, :string, default: "1.0", doc: "Policy version for consent tracking"
attr :cookie_policy_url, :string, default: "/legal/cookie-policy"
attr :privacy_policy_url, :string, default: "/legal/privacy-policy"

attr :legal_links, :list,
default: [],
doc: "Dynamic list of %{title, url} for published legal pages"

attr :legal_index_url, :string, default: "/legal", doc: "URL to legal pages index"

attr :google_consent_mode, :boolean, default: false, doc: "Enable Google Consent Mode v2"
attr :class, :string, default: ""

Expand Down Expand Up @@ -173,7 +180,7 @@ defmodule PhoenixKitWeb.Components.Core.CookieConsent do
}

.pk-glass {
background: oklch(var(--b1) / 0.95);
background: oklch(var(--b1) / 0.98);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid var(--pk-border);
Expand All @@ -190,25 +197,6 @@ defmodule PhoenixKitWeb.Components.Core.CookieConsent do
transform: translateY(-2px);
box-shadow: 0 4px 12px oklch(var(--bc) / 0.1);
}

.pk-toggle-track {
background: var(--pk-border);
transition: background-color 0.2s ease;
}

.pk-toggle-track.active {
background: var(--pk-primary);
}

.pk-toggle-thumb {
background: var(--pk-bg);
box-shadow: 0 1px 3px oklch(var(--bc) / 0.2);
transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}

input:checked + .pk-toggle-track .pk-toggle-thumb {
transform: translateX(20px);
}
</style>

<%!-- Floating Icon (only for opt-in frameworks) --%>
Expand Down Expand Up @@ -254,17 +242,16 @@ defmodule PhoenixKitWeb.Components.Core.CookieConsent do
<h3 class="font-semibold text-base-content text-sm sm:text-base">
{gettext("We value your privacy")}
</h3>
<p class="text-base-content/70 text-xs sm:text-sm mt-0.5 leading-relaxed max-w-xl">
<p class="text-base-content/80 text-xs sm:text-sm mt-0.5 leading-relaxed max-w-xl">
{gettext(
"We use cookies to enhance your browsing experience and analyze our traffic."
)}
{" "}
<a
href={@cookie_policy_url}
href={@legal_index_url}
class="link link-primary text-xs sm:text-sm"
target="_blank"
>
{gettext("Cookie Policy")}
{gettext("Legal")}
</a>
</p>
</div>
Expand Down Expand Up @@ -308,7 +295,7 @@ defmodule PhoenixKitWeb.Components.Core.CookieConsent do
>
<%!-- Backdrop --%>
<div
class="pk-modal-backdrop absolute inset-0 bg-black/40 backdrop-blur-sm"
class="pk-modal-backdrop absolute inset-0 bg-base-100/70 backdrop-blur-sm"
onclick="window.PhoenixKitConsent?.closePreferences()"
>
</div>
Expand All @@ -328,7 +315,7 @@ defmodule PhoenixKitWeb.Components.Core.CookieConsent do
<h2 class="font-semibold text-lg text-base-content">
{gettext("Privacy Preferences")}
</h2>
<p class="text-xs text-base-content/60">
<p class="text-xs text-base-content/70">
{gettext("Manage your cookie settings")}
</p>
</div>
Expand Down Expand Up @@ -356,7 +343,7 @@ defmodule PhoenixKitWeb.Components.Core.CookieConsent do
<%= for category <- @categories do %>
<div class={[
"pk-category-card rounded-xl p-4",
"bg-base-200/50 border border-base-300/30"
"bg-base-200/80 border border-base-300/30"
]}>
<div class="flex items-start justify-between gap-3">
<div class="flex items-start gap-3 flex-1 min-w-0">
Expand All @@ -374,34 +361,21 @@ defmodule PhoenixKitWeb.Components.Core.CookieConsent do
</span>
<% end %>
</div>
<p class="text-xs text-base-content/60 mt-1 leading-relaxed">
<p class="text-xs text-base-content/70 mt-1 leading-relaxed">
{category.description}
</p>
</div>
</div>

<%!-- Custom Toggle --%>
<label class="relative inline-flex items-center cursor-pointer flex-shrink-0">
<input
type="checkbox"
id={"pk-consent-#{category.id}"}
class="sr-only peer"
checked={Map.get(category, :always_enabled, false)}
disabled={Map.get(category, :always_enabled, false)}
data-category={category.id}
/>
<div class={[
"pk-toggle-track relative w-11 h-6 rounded-full",
"bg-base-300 peer-checked:bg-primary",
"peer-disabled:opacity-60 peer-disabled:cursor-not-allowed",
"after:content-[''] after:absolute after:top-0.5 after:left-0.5",
"after:bg-white after:rounded-full after:h-5 after:w-5",
"after:shadow-sm after:transition-transform after:duration-200",
"peer-checked:after:translate-x-5",
"peer-focus:ring-2 peer-focus:ring-primary/30"
]}>
</div>
</label>
<%!-- Toggle --%>
<input
type="checkbox"
id={"pk-consent-#{category.id}"}
class="toggle toggle-primary"
checked={Map.get(category, :always_enabled, false)}
disabled={Map.get(category, :always_enabled, false)}
data-category={category.id}
/>
</div>
</div>
<% end %>
Expand All @@ -411,21 +385,12 @@ defmodule PhoenixKitWeb.Components.Core.CookieConsent do
<div class="px-6 py-4 border-t border-base-300/50 bg-base-200/30">
<div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
<%!-- Policy Links --%>
<div class="flex items-center gap-3 text-xs text-base-content/60">
<a
href={@privacy_policy_url}
class="link hover:text-primary transition-colors"
target="_blank"
>
{gettext("Privacy Policy")}
</a>
<span class="text-base-300">•</span>
<div class="flex items-center gap-3 text-xs text-base-content/70">
<a
href={@cookie_policy_url}
href={@legal_index_url}
class="link hover:text-primary transition-colors"
target="_blank"
>
{gettext("Cookie Policy")}
{gettext("Legal")}
</a>
</div>

Expand Down
16 changes: 12 additions & 4 deletions lib/phoenix_kit_web/components/layout_wrapper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ defmodule PhoenixKitWeb.Components.LayoutWrapper do
alias PhoenixKit.Modules.Languages.DialectMapper
alias PhoenixKit.Modules.Legal
alias PhoenixKit.Modules.SEO
alias PhoenixKit.Modules.Storage.URLSigner
alias PhoenixKit.ThemeConfig
alias PhoenixKit.Users.Auth.Scope
alias PhoenixKit.Utils.PhoenixVersion
Expand Down Expand Up @@ -236,7 +237,12 @@ defmodule PhoenixKitWeb.Components.LayoutWrapper do
current_locale: assigns[:current_locale],
current_locale_base:
assigns[:current_locale] && DialectMapper.extract_base(assigns[:current_locale]),
scope: assigns[:phoenix_kit_current_scope]
scope: assigns[:phoenix_kit_current_scope],
auth_logo_url:
case PhoenixKit.Settings.get_setting("auth_logo_file_uuid", "") do
uuid when is_binary(uuid) and uuid != "" -> URLSigner.signed_url(uuid, "medium")
_ -> nil
end
}

assigns = template_assigns
Expand Down Expand Up @@ -281,9 +287,9 @@ defmodule PhoenixKitWeb.Components.LayoutWrapper do
</label>

<%!-- Logo --%>
<div class="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
<PhoenixKitWeb.Components.Core.Icons.icon_shield />
</div>
<%= if @auth_logo_url do %>
<img src={@auth_logo_url} alt={@project_title} class="h-8 w-8 object-contain rounded-lg" />
<% end %>

<%!-- Project title and Admin label grouped together --%>
<div class="flex items-center gap-1 min-w-0">
Expand Down Expand Up @@ -700,6 +706,8 @@ defmodule PhoenixKitWeb.Components.LayoutWrapper do
policy_version={config.policy_version}
cookie_policy_url={config.cookie_policy_url}
privacy_policy_url={config.privacy_policy_url}
legal_links={config.legal_links}
legal_index_url={config.legal_index_url}
google_consent_mode={config.google_consent_mode}
/>
<% end %>
Expand Down
Loading