add newsletter and email campaigns
Subscribers with double opt-in confirmation, campaign composer with draft/scheduled/sent lifecycle, admin dashboard with overview stats, CSV export, and shop signup form wired into page builder blocks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
60
lib/berrypod_web/newsletter_hook.ex
Normal file
60
lib/berrypod_web/newsletter_hook.ex
Normal file
@@ -0,0 +1,60 @@
|
||||
defmodule BerrypodWeb.NewsletterHook do
|
||||
@moduledoc """
|
||||
LiveView on_mount hook for newsletter signup across all shop pages.
|
||||
|
||||
Uses `attach_hook/4` to intercept `newsletter_subscribe` events globally
|
||||
without modifying individual shop LiveViews.
|
||||
"""
|
||||
|
||||
import Phoenix.Component, only: [assign: 3]
|
||||
import Phoenix.LiveView, only: [attach_hook: 4]
|
||||
|
||||
alias Berrypod.Newsletter
|
||||
|
||||
def on_mount(:mount_newsletter, _params, _session, socket) do
|
||||
enabled = Newsletter.newsletter_enabled?()
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:newsletter_enabled, enabled)
|
||||
|> assign(:newsletter_state, :idle)
|
||||
|> assign(:newsletter_ip_hash, hash_ip(socket))
|
||||
|> attach_hook(:newsletter, :handle_event, &handle_newsletter_event/3)
|
||||
|
||||
{:cont, socket}
|
||||
end
|
||||
|
||||
defp handle_newsletter_event("newsletter_subscribe", %{"email" => email}, socket) do
|
||||
if socket.assigns.newsletter_enabled do
|
||||
case Newsletter.subscribe(email,
|
||||
consent_text: "Newsletter signup on website",
|
||||
ip_hash: socket.assigns.newsletter_ip_hash
|
||||
) do
|
||||
{:ok, _} ->
|
||||
{:halt, assign(socket, :newsletter_state, :submitted)}
|
||||
|
||||
{:already_confirmed, _} ->
|
||||
{:halt, assign(socket, :newsletter_state, :submitted)}
|
||||
|
||||
{:error, _} ->
|
||||
{:halt, assign(socket, :newsletter_state, :error)}
|
||||
end
|
||||
else
|
||||
{:halt, assign(socket, :newsletter_state, :disabled)}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_newsletter_event(_event, _params, socket), do: {:cont, socket}
|
||||
|
||||
# connect_info is only available during mount, so we hash and store it early
|
||||
defp hash_ip(socket) do
|
||||
case Phoenix.LiveView.get_connect_info(socket, :peer_data) do
|
||||
%{address: ip} ->
|
||||
daily_salt = Date.utc_today() |> Date.to_iso8601()
|
||||
:crypto.hash(:sha256, :inet.ntoa(ip) ++ [daily_salt]) |> Base.encode16(case: :lower)
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user