add privacy-first analytics with progressive event collection
All checks were successful
deploy / deploy (push) Successful in 3m20s

Three-layer pipeline: Plug for all HTTP requests (no JS needed), LiveView
hook for SPA navigations, JS hook for screen width. ETS-backed buffer
batches writes to SQLite every 10s. Daily-rotating salt for visitor hashing.
Includes admin dashboard with date ranges, visitor trends, top pages,
sources, devices, and e-commerce conversion funnel. Oban cron for 12-month
data retention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-22 12:50:55 +00:00
parent b0aed4c1d6
commit 2bd2e613c7
29 changed files with 2277 additions and 10 deletions

View File

@@ -1,7 +1,7 @@
defmodule BerrypodWeb.Shop.CheckoutSuccess do
use BerrypodWeb, :live_view
alias Berrypod.Orders
alias Berrypod.{Analytics, Orders}
@impl true
def mount(%{"session_id" => session_id}, _session, socket) do
@@ -12,6 +12,15 @@ defmodule BerrypodWeb.Shop.CheckoutSuccess do
Phoenix.PubSub.subscribe(Berrypod.PubSub, "order:#{order.id}:status")
end
# Track purchase event
if order && connected?(socket) && socket.assigns[:analytics_visitor_hash] do
Analytics.track_event("purchase", %{
pathname: "/checkout/success",
visitor_hash: socket.assigns.analytics_visitor_hash,
revenue: order.total
})
end
# Clear the cart after successful checkout
socket =
if order && connected?(socket) do

View File

@@ -1,7 +1,7 @@
defmodule BerrypodWeb.Shop.ProductShow do
use BerrypodWeb, :live_view
alias Berrypod.Cart
alias Berrypod.{Analytics, Cart}
alias Berrypod.Images.Optimizer
alias Berrypod.Products
alias Berrypod.Products.{Product, ProductImage}
@@ -41,6 +41,13 @@ defmodule BerrypodWeb.Shop.ProductShow do
display_price = variant_price(selected_variant, product)
gallery_images = filter_gallery_images(all_images, selected_options["Color"])
if connected?(socket) and socket.assigns[:analytics_visitor_hash] do
Analytics.track_event("product_view", %{
pathname: "/products/#{slug}",
visitor_hash: socket.assigns.analytics_visitor_hash
})
end
socket =
socket
|> assign(:page_title, product.title)
@@ -177,6 +184,13 @@ defmodule BerrypodWeb.Shop.ProductShow do
if variant do
cart = Cart.add_item(socket.assigns.raw_cart, variant.id, socket.assigns.quantity)
if socket.assigns[:analytics_visitor_hash] do
Analytics.track_event("add_to_cart", %{
pathname: "/products/#{socket.assigns.product.slug}",
visitor_hash: socket.assigns.analytics_visitor_hash
})
end
socket =
socket
|> BerrypodWeb.CartHook.broadcast_and_update(cart)