berrypod/lib/berrypod_web/router.ex
jamey a039c8d53c
All checks were successful
deploy / deploy (push) Successful in 6m49s
add live page editor sidebar with collapsible UI
Admins can now edit pages directly on the live shop by clicking the
pencil icon in the header. A sidebar slides in with block management
controls (add, remove, reorder, edit settings, save, reset, done).

Key features:
- PageEditorHook on_mount with handle_params/event/info hooks
- BlockEditor pure functions extracted from admin editor
- Shared BlockEditorComponents with event_prefix namespacing
- Collapsible sidebar: X closes it, header pencil reopens it
- Backdrop overlay dismisses sidebar on tap
- Conditional admin.css loading for logged-in users
- content_body block now portable (textarea setting + rich text fallback)

13 integration tests, 26 unit tests, 1370 total passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 16:22:35 +00:00

272 lines
8.4 KiB
Elixir

defmodule BerrypodWeb.Router do
use BerrypodWeb, :router
import BerrypodWeb.UserAuth
import Phoenix.LiveDashboard.Router
import ErrorTracker.Web.Router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {BerrypodWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :fetch_current_scope_for_user
plug BerrypodWeb.Plugs.CountryDetect
plug BerrypodWeb.Plugs.LoadTheme
end
pipeline :api do
plug :accepts, ["json"]
end
# Lightweight pipeline for SVG recoloring — no session, CSRF, auth, or layout
pipeline :image do
plug :put_secure_browser_headers
end
# Minimal pipeline for robots.txt and sitemap.xml
pipeline :seo do
plug :put_secure_browser_headers
end
pipeline :printify_webhook do
plug BerrypodWeb.Plugs.VerifyPrintifyWebhook
end
pipeline :printful_webhook do
plug BerrypodWeb.Plugs.VerifyPrintfulWebhook
end
pipeline :shop do
plug :put_root_layout, html: {BerrypodWeb.Layouts, :shop_root}
plug BerrypodWeb.Plugs.LoadTheme
plug BerrypodWeb.Plugs.Analytics
end
pipeline :admin do
plug :put_root_layout, html: {BerrypodWeb.Layouts, :admin_root}
plug BerrypodWeb.Plugs.LoadTheme
end
# Public storefront (root level)
scope "/", BerrypodWeb do
pipe_through [:browser, :shop]
live_session :coming_soon,
layout: {BerrypodWeb.Layouts, :shop},
on_mount: [
{BerrypodWeb.ThemeHook, :mount_theme}
] do
live "/coming-soon", Shop.ComingSoon, :index
end
live_session :public_shop,
layout: {BerrypodWeb.Layouts, :shop},
on_mount: [
{BerrypodWeb.UserAuth, :mount_current_scope},
{BerrypodWeb.ThemeHook, :mount_theme},
{BerrypodWeb.ThemeHook, :require_site_live},
{BerrypodWeb.CartHook, :mount_cart},
{BerrypodWeb.SearchHook, :mount_search},
{BerrypodWeb.AnalyticsHook, :track},
{BerrypodWeb.PageEditorHook, :mount_page_editor}
] do
live "/", Shop.Home, :index
live "/about", Shop.Content, :about
live "/delivery", Shop.Content, :delivery
live "/privacy", Shop.Content, :privacy
live "/terms", Shop.Content, :terms
live "/contact", Shop.Contact, :index
live "/collections/:slug", Shop.Collection, :show
live "/products/:id", Shop.ProductShow, :show
live "/cart", Shop.Cart, :index
live "/search", Shop.Search, :index
live "/checkout/success", Shop.CheckoutSuccess, :show
live "/orders", Shop.Orders, :index
live "/orders/:order_number", Shop.OrderDetail, :show
end
# Checkout (POST — creates Stripe session and redirects)
post "/checkout", CheckoutController, :create
# Order lookup (no-JS fallback for contact page form)
post "/contact/lookup", OrderLookupController, :lookup
# Cart form actions (no-JS fallbacks for LiveView cart events)
post "/cart/add", CartController, :add
post "/cart/remove", CartController, :remove
post "/cart/update", CartController, :update_item
post "/cart/country", CartController, :update_country
end
# Health check (no auth, no theme loading — for load balancers and uptime monitors)
scope "/", BerrypodWeb do
pipe_through [:api]
get "/health", HealthController, :show
end
# SEO — crawlers need these without any session/auth overhead
scope "/", BerrypodWeb do
pipe_through [:seo]
get "/robots.txt", SeoController, :robots
get "/sitemap.xml", SeoController, :sitemap
end
# Favicon & PWA manifest — served from DB, minimal pipeline
scope "/", BerrypodWeb do
pipe_through [:seo]
get "/favicon.svg", FaviconController, :favicon_svg
get "/favicon-32x32.png", FaviconController, :favicon_32
get "/apple-touch-icon.png", FaviconController, :apple_touch_icon
get "/icon-192.png", FaviconController, :icon_192
get "/icon-512.png", FaviconController, :icon_512
get "/site.webmanifest", FaviconController, :webmanifest
end
# Cart API (session persistence for LiveView)
scope "/api", BerrypodWeb do
pipe_through [:browser]
post "/cart", CartController, :update
end
# SVG recoloring (dynamic — can't be pre-generated to disk)
scope "/images", BerrypodWeb do
pipe_through :image
get "/:id/recolored/:color", ImageController, :recolored_svg
end
# Webhook endpoints (no CSRF, signature verified)
scope "/webhooks", BerrypodWeb do
pipe_through [:api, :printify_webhook]
post "/printify", WebhookController, :printify
end
scope "/webhooks", BerrypodWeb do
pipe_through [:api, :printful_webhook]
post "/printful", WebhookController, :printful
end
scope "/webhooks", BerrypodWeb do
pipe_through [:api]
post "/stripe", StripeWebhookController, :handle
end
# LiveDashboard and ErrorTracker behind admin auth (available in all environments)
scope "/admin" do
pipe_through [:browser, :require_authenticated_user]
live_dashboard "/dashboard", metrics: BerrypodWeb.Telemetry
error_tracker_dashboard("/errors")
end
# Dev-only routes (mailbox preview, error previews)
if Application.compile_env(:berrypod, :dev_routes) do
scope "/dev" do
pipe_through :browser
forward "/mailbox", Plug.Swoosh.MailboxPreview
# Preview error pages
get "/errors/404", BerrypodWeb.ErrorPreviewController, :not_found
get "/errors/500", BerrypodWeb.ErrorPreviewController, :server_error
end
end
# Order lookup verification — sets session email then redirects to /orders
scope "/", BerrypodWeb do
pipe_through [:browser]
get "/orders/verify/:token", OrderLookupController, :verify
get "/unsubscribe/:token", UnsubscribeController, :unsubscribe
end
# Setup page — minimal live_session, no theme/cart/search hooks
scope "/", BerrypodWeb do
pipe_through [:browser]
# Token-based auto-login after setup/recovery
get "/setup/login/:token", SetupController, :login
get "/recover/login/:token", SetupController, :recover_login
live_session :setup,
on_mount: [{BerrypodWeb.UserAuth, :mount_current_scope}] do
live "/setup", Setup.Onboarding, :index
live "/recover", Setup.Recover, :index
end
end
## Authentication routes
# Admin pages with sidebar layout
scope "/admin", BerrypodWeb do
pipe_through [:browser, :require_authenticated_user, :admin]
get "/analytics/export", AnalyticsExportController, :export
live_session :admin,
layout: {BerrypodWeb.Layouts, :admin},
on_mount: [
{BerrypodWeb.UserAuth, :require_authenticated},
{BerrypodWeb.AdminLayoutHook, :assign_current_path}
] do
live "/", Admin.Dashboard, :index
live "/analytics", Admin.Analytics, :index
live "/orders", Admin.Orders, :index
live "/orders/:id", Admin.OrderShow, :show
live "/products", Admin.Products, :index
live "/products/:id", Admin.ProductShow, :show
live "/providers", Admin.Providers.Index, :index
live "/providers/new", Admin.Providers.Form, :new
live "/providers/:id/edit", Admin.Providers.Form, :edit
live "/settings", Admin.Settings, :index
live "/settings/email", Admin.EmailSettings, :index
live "/pages", Admin.Pages.Index, :index
live "/pages/:slug", Admin.Pages.Editor, :edit
live "/redirects", Admin.Redirects, :index
end
# Theme editor: admin root layout but full-screen (no sidebar)
live_session :admin_theme,
on_mount: [{BerrypodWeb.UserAuth, :require_authenticated}] do
live "/theme", Admin.Theme.Index, :index
end
end
# User account settings
scope "/", BerrypodWeb do
pipe_through [:browser, :require_authenticated_user]
live_session :user_settings,
on_mount: [{BerrypodWeb.UserAuth, :require_authenticated}] do
live "/users/settings", Auth.Settings, :edit
live "/users/settings/confirm-email/:token", Auth.Settings, :confirm_email
end
post "/users/update-password", UserSessionController, :update_password
end
scope "/", BerrypodWeb do
pipe_through [:browser]
live_session :current_user,
on_mount: [{BerrypodWeb.UserAuth, :mount_current_scope}] do
live "/users/register", Auth.Registration, :new
live "/users/log-in", Auth.Login, :new
live "/users/log-in/:token", Auth.Confirmation, :new
end
post "/users/log-in", UserSessionController, :create
delete "/users/log-out", UserSessionController, :delete
end
end