2026-02-18 21:23:15 +00:00
|
|
|
defmodule BerrypodWeb.Admin.Settings do
|
|
|
|
|
use BerrypodWeb, :live_view
|
2026-02-07 17:12:53 +00:00
|
|
|
|
2026-02-18 21:23:15 +00:00
|
|
|
alias Berrypod.Accounts
|
|
|
|
|
alias Berrypod.Products
|
|
|
|
|
alias Berrypod.Settings
|
|
|
|
|
alias Berrypod.Stripe.Setup, as: StripeSetup
|
2026-02-07 17:12:53 +00:00
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
|
def mount(_params, _session, socket) do
|
2026-02-12 09:04:51 +00:00
|
|
|
user = socket.assigns.current_scope.user
|
|
|
|
|
|
2026-02-11 22:58:58 +00:00
|
|
|
{:ok,
|
|
|
|
|
socket
|
|
|
|
|
|> assign(:page_title, "Settings")
|
|
|
|
|
|> assign(:site_live, Settings.site_live?())
|
add abandoned cart recovery
When a Stripe checkout session expires without payment, if the customer
entered their email, we record an AbandonedCart and schedule a single
plain-text recovery email (1h delay via Oban).
Privacy design:
- feature is off by default; shop owner opts in via admin settings
- only contacts customers who entered their email at Stripe checkout
- single email, never more (emailed_at timestamp gate)
- suppression list blocks repeat contact; one-click unsubscribe via
signed token (/unsubscribe/:token)
- records pruned after 30 days (nightly Oban cron)
- no tracking pixels, no redirected links, no HTML
Legal notes:
- custom_text added to Stripe session footer when recovery is on
- UK PECR soft opt-in; EU legitimate interests both satisfied by this design
Files:
- migration: abandoned_carts + email_suppressions tables
- schemas: AbandonedCart, EmailSuppression
- context: Orders.create_abandoned_cart, check_suppression, add_suppression,
has_recent_paid_order?, get_abandoned_cart_by_session, mark_abandoned_cart_emailed
- workers: AbandonedCartEmailWorker (checkout queue), AbandonedCartPruneWorker (cron)
- notifier: OrderNotifier.deliver_cart_recovery/3
- webhook: extended checkout.session.expired handler
- controller: UnsubscribeController, admin settings toggle
- tests: 28 new tests across context, workers, and controller
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:02:37 +00:00
|
|
|
|> assign(:cart_recovery_enabled, Settings.abandoned_cart_recovery_enabled?())
|
2026-03-04 17:12:10 +00:00
|
|
|
|> assign(:from_address, Settings.get_setting("email_from_address") || user.email)
|
2026-02-12 09:04:51 +00:00
|
|
|
|> assign_stripe_state()
|
|
|
|
|
|> assign_products_state()
|
|
|
|
|
|> assign_account_state(user)}
|
2026-02-07 17:12:53 +00:00
|
|
|
end
|
|
|
|
|
|
2026-02-12 09:04:51 +00:00
|
|
|
# -- Stripe assigns --
|
|
|
|
|
|
2026-02-07 17:12:53 +00:00
|
|
|
defp assign_stripe_state(socket) do
|
|
|
|
|
has_key = Settings.has_secret?("stripe_api_key")
|
|
|
|
|
has_signing = Settings.has_secret?("stripe_signing_secret")
|
|
|
|
|
|
|
|
|
|
status =
|
|
|
|
|
cond do
|
|
|
|
|
!has_key -> :not_configured
|
|
|
|
|
has_key && StripeSetup.localhost?() -> :connected_localhost
|
|
|
|
|
true -> :connected
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
socket
|
|
|
|
|
|> assign(:stripe_status, status)
|
|
|
|
|
|> assign(:stripe_api_key_hint, Settings.secret_hint("stripe_api_key"))
|
|
|
|
|
|> assign(:stripe_signing_secret_hint, Settings.secret_hint("stripe_signing_secret"))
|
|
|
|
|
|> assign(:stripe_webhook_url, StripeSetup.webhook_url())
|
|
|
|
|
|> assign(:stripe_has_signing_secret, has_signing)
|
|
|
|
|
|> assign(:connect_form, to_form(%{"api_key" => ""}, as: :stripe))
|
|
|
|
|
|> assign(:secret_form, to_form(%{"signing_secret" => ""}, as: :webhook))
|
2026-02-12 09:04:51 +00:00
|
|
|
|> assign(:stripe_advanced_open, false)
|
2026-02-07 17:12:53 +00:00
|
|
|
|> assign(:connecting, false)
|
|
|
|
|
end
|
|
|
|
|
|
2026-02-12 09:04:51 +00:00
|
|
|
# -- Products assigns --
|
|
|
|
|
|
|
|
|
|
defp assign_products_state(socket) do
|
|
|
|
|
connections = Products.list_provider_connections()
|
|
|
|
|
|
|
|
|
|
connection_info =
|
|
|
|
|
case connections do
|
|
|
|
|
[conn | _] ->
|
|
|
|
|
product_count = Products.count_products_for_connection(conn.id)
|
|
|
|
|
%{connection: conn, product_count: product_count}
|
|
|
|
|
|
|
|
|
|
[] ->
|
|
|
|
|
nil
|
|
|
|
|
end
|
|
|
|
|
|
2026-02-15 10:53:15 +00:00
|
|
|
assign(socket, :provider, connection_info)
|
2026-02-12 09:04:51 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# -- Account assigns --
|
|
|
|
|
|
|
|
|
|
defp assign_account_state(socket, user) do
|
|
|
|
|
email_changeset = Accounts.change_user_email(user, %{}, validate_unique: false)
|
|
|
|
|
password_changeset = Accounts.change_user_password(user, %{}, hash_password: false)
|
|
|
|
|
|
|
|
|
|
socket
|
|
|
|
|
|> assign(:current_email, user.email)
|
|
|
|
|
|> assign(:email_form, to_form(email_changeset))
|
|
|
|
|
|> assign(:password_form, to_form(password_changeset))
|
|
|
|
|
|> assign(:trigger_submit, false)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# -- Events: shop status --
|
|
|
|
|
|
2026-02-07 17:12:53 +00:00
|
|
|
@impl true
|
2026-02-12 09:04:51 +00:00
|
|
|
def handle_event("toggle_site_live", _params, socket) do
|
|
|
|
|
new_value = !socket.assigns.site_live
|
|
|
|
|
{:ok, _} = Settings.set_site_live(new_value)
|
|
|
|
|
|
|
|
|
|
message = if new_value, do: "Shop is now live", else: "Shop taken offline"
|
|
|
|
|
|
|
|
|
|
{:noreply,
|
|
|
|
|
socket
|
|
|
|
|
|> assign(:site_live, new_value)
|
|
|
|
|
|> put_flash(:info, message)}
|
|
|
|
|
end
|
|
|
|
|
|
add abandoned cart recovery
When a Stripe checkout session expires without payment, if the customer
entered their email, we record an AbandonedCart and schedule a single
plain-text recovery email (1h delay via Oban).
Privacy design:
- feature is off by default; shop owner opts in via admin settings
- only contacts customers who entered their email at Stripe checkout
- single email, never more (emailed_at timestamp gate)
- suppression list blocks repeat contact; one-click unsubscribe via
signed token (/unsubscribe/:token)
- records pruned after 30 days (nightly Oban cron)
- no tracking pixels, no redirected links, no HTML
Legal notes:
- custom_text added to Stripe session footer when recovery is on
- UK PECR soft opt-in; EU legitimate interests both satisfied by this design
Files:
- migration: abandoned_carts + email_suppressions tables
- schemas: AbandonedCart, EmailSuppression
- context: Orders.create_abandoned_cart, check_suppression, add_suppression,
has_recent_paid_order?, get_abandoned_cart_by_session, mark_abandoned_cart_emailed
- workers: AbandonedCartEmailWorker (checkout queue), AbandonedCartPruneWorker (cron)
- notifier: OrderNotifier.deliver_cart_recovery/3
- webhook: extended checkout.session.expired handler
- controller: UnsubscribeController, admin settings toggle
- tests: 28 new tests across context, workers, and controller
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:02:37 +00:00
|
|
|
# -- Events: cart recovery --
|
|
|
|
|
|
|
|
|
|
def handle_event("toggle_cart_recovery", _params, socket) do
|
|
|
|
|
new_value = !socket.assigns.cart_recovery_enabled
|
|
|
|
|
{:ok, _} = Settings.set_abandoned_cart_recovery(new_value)
|
|
|
|
|
|
|
|
|
|
message =
|
|
|
|
|
if new_value,
|
|
|
|
|
do: "Cart recovery emails enabled",
|
|
|
|
|
else: "Cart recovery emails disabled"
|
|
|
|
|
|
|
|
|
|
{:noreply,
|
|
|
|
|
socket
|
|
|
|
|
|> assign(:cart_recovery_enabled, new_value)
|
|
|
|
|
|> put_flash(:info, message)}
|
|
|
|
|
end
|
|
|
|
|
|
2026-03-04 17:12:10 +00:00
|
|
|
# -- Events: from address --
|
|
|
|
|
|
|
|
|
|
def handle_event("save_from_address", %{"from_address" => address}, socket) do
|
|
|
|
|
address = String.trim(address)
|
|
|
|
|
|
|
|
|
|
if address != "" do
|
|
|
|
|
Settings.put_setting("email_from_address", address)
|
|
|
|
|
|
|
|
|
|
{:noreply,
|
|
|
|
|
socket
|
|
|
|
|
|> assign(:from_address, address)
|
|
|
|
|
|> put_flash(:info, "From address saved")}
|
|
|
|
|
else
|
|
|
|
|
{:noreply, put_flash(socket, :error, "From address can't be blank")}
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2026-02-12 09:04:51 +00:00
|
|
|
# -- Events: Stripe --
|
|
|
|
|
|
2026-02-07 17:12:53 +00:00
|
|
|
def handle_event("connect_stripe", %{"stripe" => %{"api_key" => api_key}}, socket) do
|
|
|
|
|
if api_key == "" do
|
|
|
|
|
{:noreply, put_flash(socket, :error, "Please enter your Stripe secret key")}
|
|
|
|
|
else
|
|
|
|
|
socket = assign(socket, :connecting, true)
|
|
|
|
|
|
|
|
|
|
case StripeSetup.connect(api_key) do
|
|
|
|
|
{:ok, :webhook_created} ->
|
|
|
|
|
socket =
|
|
|
|
|
socket
|
|
|
|
|
|> assign_stripe_state()
|
|
|
|
|
|> put_flash(:info, "Stripe connected and webhook endpoint created")
|
|
|
|
|
|
|
|
|
|
{:noreply, socket}
|
|
|
|
|
|
|
|
|
|
{:ok, :localhost} ->
|
|
|
|
|
socket =
|
|
|
|
|
socket
|
|
|
|
|
|> assign_stripe_state()
|
|
|
|
|
|> put_flash(
|
|
|
|
|
:info,
|
|
|
|
|
"API key saved. Enter a webhook signing secret below for local testing."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
{:noreply, socket}
|
|
|
|
|
|
|
|
|
|
{:error, message} ->
|
|
|
|
|
socket =
|
|
|
|
|
socket
|
|
|
|
|
|> assign(:connecting, false)
|
|
|
|
|
|> put_flash(:error, "Stripe connection failed: #{message}")
|
|
|
|
|
|
|
|
|
|
{:noreply, socket}
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def handle_event("disconnect_stripe", _params, socket) do
|
|
|
|
|
StripeSetup.disconnect()
|
|
|
|
|
|
|
|
|
|
socket =
|
|
|
|
|
socket
|
|
|
|
|
|> assign_stripe_state()
|
|
|
|
|
|> put_flash(:info, "Stripe disconnected")
|
|
|
|
|
|
|
|
|
|
{:noreply, socket}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def handle_event("save_signing_secret", %{"webhook" => %{"signing_secret" => secret}}, socket) do
|
|
|
|
|
if secret == "" do
|
|
|
|
|
{:noreply, put_flash(socket, :error, "Please enter a signing secret")}
|
|
|
|
|
else
|
|
|
|
|
StripeSetup.save_signing_secret(secret)
|
|
|
|
|
|
|
|
|
|
socket =
|
|
|
|
|
socket
|
|
|
|
|
|> assign_stripe_state()
|
|
|
|
|
|> put_flash(:info, "Webhook signing secret saved")
|
|
|
|
|
|
|
|
|
|
{:noreply, socket}
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2026-02-12 09:04:51 +00:00
|
|
|
def handle_event("toggle_stripe_advanced", _params, socket) do
|
|
|
|
|
{:noreply, assign(socket, :stripe_advanced_open, !socket.assigns.stripe_advanced_open)}
|
|
|
|
|
end
|
2026-02-11 22:58:58 +00:00
|
|
|
|
2026-02-12 09:04:51 +00:00
|
|
|
# -- Events: products --
|
|
|
|
|
|
|
|
|
|
def handle_event("sync", %{"id" => id}, socket) do
|
|
|
|
|
connection = Products.get_provider_connection!(id)
|
|
|
|
|
|
|
|
|
|
case Products.enqueue_sync(connection) do
|
|
|
|
|
{:ok, _job} ->
|
|
|
|
|
{:noreply,
|
|
|
|
|
socket
|
|
|
|
|
|> assign_products_state()
|
|
|
|
|
|> put_flash(:info, "Sync started for #{connection.name}")}
|
|
|
|
|
|
|
|
|
|
{:error, _reason} ->
|
|
|
|
|
{:noreply, put_flash(socket, :error, "Failed to start sync")}
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def handle_event("delete_connection", %{"id" => id}, socket) do
|
|
|
|
|
connection = Products.get_provider_connection!(id)
|
|
|
|
|
{:ok, _} = Products.delete_provider_connection(connection)
|
2026-02-11 22:58:58 +00:00
|
|
|
|
|
|
|
|
{:noreply,
|
|
|
|
|
socket
|
2026-02-12 09:04:51 +00:00
|
|
|
|> assign_products_state()
|
|
|
|
|
|> put_flash(:info, "Provider connection deleted")}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# -- Events: account --
|
|
|
|
|
|
|
|
|
|
def handle_event("validate_email", %{"user" => user_params}, socket) do
|
|
|
|
|
email_form =
|
|
|
|
|
socket.assigns.current_scope.user
|
|
|
|
|
|> Accounts.change_user_email(user_params, validate_unique: false)
|
|
|
|
|
|> Map.put(:action, :validate)
|
|
|
|
|
|> to_form()
|
|
|
|
|
|
|
|
|
|
{:noreply, assign(socket, email_form: email_form)}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def handle_event("update_email", %{"user" => user_params}, socket) do
|
|
|
|
|
user = socket.assigns.current_scope.user
|
|
|
|
|
|
|
|
|
|
unless Accounts.sudo_mode?(user) do
|
|
|
|
|
{:noreply,
|
|
|
|
|
socket
|
|
|
|
|
|> put_flash(:error, "Please log in again to change account settings.")
|
|
|
|
|
|> redirect(to: ~p"/users/log-in")}
|
|
|
|
|
else
|
|
|
|
|
case Accounts.change_user_email(user, user_params) do
|
|
|
|
|
%{valid?: true} = changeset ->
|
|
|
|
|
Accounts.deliver_user_update_email_instructions(
|
|
|
|
|
Ecto.Changeset.apply_action!(changeset, :insert),
|
|
|
|
|
user.email,
|
|
|
|
|
&url(~p"/users/settings/confirm-email/#{&1}")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
info = "A link to confirm your email change has been sent to the new address."
|
|
|
|
|
{:noreply, put_flash(socket, :info, info)}
|
|
|
|
|
|
|
|
|
|
changeset ->
|
|
|
|
|
{:noreply, assign(socket, :email_form, to_form(changeset, action: :insert))}
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def handle_event("validate_password", %{"user" => user_params}, socket) do
|
|
|
|
|
password_form =
|
|
|
|
|
socket.assigns.current_scope.user
|
|
|
|
|
|> Accounts.change_user_password(user_params, hash_password: false)
|
|
|
|
|
|> Map.put(:action, :validate)
|
|
|
|
|
|> to_form()
|
|
|
|
|
|
|
|
|
|
{:noreply, assign(socket, password_form: password_form)}
|
2026-02-11 22:58:58 +00:00
|
|
|
end
|
|
|
|
|
|
2026-02-12 09:04:51 +00:00
|
|
|
def handle_event("update_password", %{"user" => user_params}, socket) do
|
|
|
|
|
user = socket.assigns.current_scope.user
|
|
|
|
|
|
|
|
|
|
unless Accounts.sudo_mode?(user) do
|
|
|
|
|
{:noreply,
|
|
|
|
|
socket
|
|
|
|
|
|> put_flash(:error, "Please log in again to change account settings.")
|
|
|
|
|
|> redirect(to: ~p"/users/log-in")}
|
|
|
|
|
else
|
|
|
|
|
case Accounts.change_user_password(user, user_params) do
|
|
|
|
|
%{valid?: true} = changeset ->
|
|
|
|
|
{:noreply, assign(socket, trigger_submit: true, password_form: to_form(changeset))}
|
|
|
|
|
|
|
|
|
|
changeset ->
|
|
|
|
|
{:noreply, assign(socket, password_form: to_form(changeset, action: :insert))}
|
|
|
|
|
end
|
|
|
|
|
end
|
2026-02-07 17:12:53 +00:00
|
|
|
end
|
|
|
|
|
|
2026-02-12 09:04:51 +00:00
|
|
|
# -- Render --
|
|
|
|
|
|
2026-02-07 17:12:53 +00:00
|
|
|
@impl true
|
|
|
|
|
def render(assigns) do
|
|
|
|
|
~H"""
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-settings">
|
2026-02-12 08:35:22 +00:00
|
|
|
<.header>
|
|
|
|
|
Settings
|
|
|
|
|
</.header>
|
|
|
|
|
|
2026-02-12 09:04:51 +00:00
|
|
|
<%!-- Shop status --%>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<section class="admin-section">
|
|
|
|
|
<div class="admin-section-header">
|
|
|
|
|
<h2 class="admin-section-title">Shop status</h2>
|
2026-02-12 08:35:22 +00:00
|
|
|
<%= if @site_live do %>
|
2026-02-12 09:04:51 +00:00
|
|
|
<.status_pill color="green">
|
2026-02-12 08:35:22 +00:00
|
|
|
<.icon name="hero-check-circle-mini" class="size-3" /> Live
|
2026-02-12 09:04:51 +00:00
|
|
|
</.status_pill>
|
2026-02-12 08:35:22 +00:00
|
|
|
<% else %>
|
2026-02-12 09:04:51 +00:00
|
|
|
<.status_pill color="zinc">Offline</.status_pill>
|
2026-02-12 08:35:22 +00:00
|
|
|
<% end %>
|
|
|
|
|
</div>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<p class="admin-section-desc">
|
2026-02-12 08:35:22 +00:00
|
|
|
<%= if @site_live do %>
|
|
|
|
|
Your shop is visible to the public.
|
|
|
|
|
<% else %>
|
|
|
|
|
Your shop is offline. Visitors see a "coming soon" page.
|
|
|
|
|
<% end %>
|
|
|
|
|
</p>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-section-body">
|
2026-02-12 08:35:22 +00:00
|
|
|
<button
|
|
|
|
|
phx-click="toggle_site_live"
|
|
|
|
|
class={[
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
"admin-btn admin-btn-sm",
|
|
|
|
|
if(@site_live, do: "admin-btn-outline", else: "admin-btn-primary")
|
2026-02-12 08:35:22 +00:00
|
|
|
]}
|
|
|
|
|
>
|
2026-02-11 22:58:58 +00:00
|
|
|
<%= if @site_live do %>
|
2026-02-12 08:35:22 +00:00
|
|
|
<.icon name="hero-eye-slash-mini" class="size-4" /> Take offline
|
|
|
|
|
<% else %>
|
|
|
|
|
<.icon name="hero-eye-mini" class="size-4" /> Go live
|
|
|
|
|
<% end %>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
2026-02-12 09:04:51 +00:00
|
|
|
<%!-- Payments --%>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<section class="admin-section">
|
|
|
|
|
<div class="admin-section-header">
|
|
|
|
|
<h2 class="admin-section-title">Payments</h2>
|
2026-02-12 08:35:22 +00:00
|
|
|
<%= case @stripe_status do %>
|
|
|
|
|
<% :connected -> %>
|
2026-02-12 09:04:51 +00:00
|
|
|
<.status_pill color="green">
|
2026-02-12 08:35:22 +00:00
|
|
|
<.icon name="hero-check-circle-mini" class="size-3" /> Connected
|
2026-02-12 09:04:51 +00:00
|
|
|
</.status_pill>
|
2026-02-12 08:35:22 +00:00
|
|
|
<% :connected_localhost -> %>
|
2026-02-12 09:04:51 +00:00
|
|
|
<.status_pill color="amber">
|
2026-02-12 08:35:22 +00:00
|
|
|
<.icon name="hero-exclamation-triangle-mini" class="size-3" /> Dev mode
|
2026-02-12 09:04:51 +00:00
|
|
|
</.status_pill>
|
2026-02-12 08:35:22 +00:00
|
|
|
<% :not_configured -> %>
|
2026-02-12 09:04:51 +00:00
|
|
|
<.status_pill color="zinc">Not connected</.status_pill>
|
2026-02-07 17:12:53 +00:00
|
|
|
<% end %>
|
2026-02-12 08:35:22 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<%= if @stripe_status == :not_configured do %>
|
|
|
|
|
<.stripe_setup_form connect_form={@connect_form} connecting={@connecting} />
|
|
|
|
|
<% else %>
|
|
|
|
|
<.stripe_connected_view
|
|
|
|
|
stripe_status={@stripe_status}
|
|
|
|
|
stripe_api_key_hint={@stripe_api_key_hint}
|
|
|
|
|
stripe_webhook_url={@stripe_webhook_url}
|
|
|
|
|
stripe_signing_secret_hint={@stripe_signing_secret_hint}
|
|
|
|
|
stripe_has_signing_secret={@stripe_has_signing_secret}
|
|
|
|
|
secret_form={@secret_form}
|
2026-02-12 09:04:51 +00:00
|
|
|
advanced_open={@stripe_advanced_open}
|
2026-02-12 08:35:22 +00:00
|
|
|
/>
|
|
|
|
|
<% end %>
|
|
|
|
|
</section>
|
2026-02-12 09:04:51 +00:00
|
|
|
|
|
|
|
|
<%!-- Products --%>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<section class="admin-section">
|
|
|
|
|
<div class="admin-section-header">
|
|
|
|
|
<h2 class="admin-section-title">Products</h2>
|
2026-02-15 10:53:15 +00:00
|
|
|
<%= if @provider do %>
|
2026-02-12 09:04:51 +00:00
|
|
|
<.status_pill color="green">
|
|
|
|
|
<.icon name="hero-check-circle-mini" class="size-3" /> Connected
|
|
|
|
|
</.status_pill>
|
|
|
|
|
<% else %>
|
|
|
|
|
<.status_pill color="zinc">Not connected</.status_pill>
|
|
|
|
|
<% end %>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-15 10:53:15 +00:00
|
|
|
<%= if @provider do %>
|
|
|
|
|
<.provider_connected provider={@provider} />
|
2026-02-12 09:04:51 +00:00
|
|
|
<% else %>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-section-body">
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<p class="admin-section-desc admin-section-desc-flush">
|
2026-02-12 09:04:51 +00:00
|
|
|
Connect a print-on-demand provider to import products into your shop.
|
|
|
|
|
</p>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-section-body">
|
|
|
|
|
<.link navigate={~p"/admin/providers"} class="admin-btn admin-btn-primary admin-btn-sm">
|
2026-02-15 10:53:15 +00:00
|
|
|
<.icon name="hero-plus-mini" class="size-4" /> Connect a provider
|
2026-02-12 09:04:51 +00:00
|
|
|
</.link>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<% end %>
|
|
|
|
|
</section>
|
|
|
|
|
|
add abandoned cart recovery
When a Stripe checkout session expires without payment, if the customer
entered their email, we record an AbandonedCart and schedule a single
plain-text recovery email (1h delay via Oban).
Privacy design:
- feature is off by default; shop owner opts in via admin settings
- only contacts customers who entered their email at Stripe checkout
- single email, never more (emailed_at timestamp gate)
- suppression list blocks repeat contact; one-click unsubscribe via
signed token (/unsubscribe/:token)
- records pruned after 30 days (nightly Oban cron)
- no tracking pixels, no redirected links, no HTML
Legal notes:
- custom_text added to Stripe session footer when recovery is on
- UK PECR soft opt-in; EU legitimate interests both satisfied by this design
Files:
- migration: abandoned_carts + email_suppressions tables
- schemas: AbandonedCart, EmailSuppression
- context: Orders.create_abandoned_cart, check_suppression, add_suppression,
has_recent_paid_order?, get_abandoned_cart_by_session, mark_abandoned_cart_emailed
- workers: AbandonedCartEmailWorker (checkout queue), AbandonedCartPruneWorker (cron)
- notifier: OrderNotifier.deliver_cart_recovery/3
- webhook: extended checkout.session.expired handler
- controller: UnsubscribeController, admin settings toggle
- tests: 28 new tests across context, workers, and controller
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:02:37 +00:00
|
|
|
<%!-- Cart recovery --%>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<section class="admin-section">
|
|
|
|
|
<div class="admin-section-header">
|
|
|
|
|
<h2 class="admin-section-title">Cart recovery</h2>
|
add abandoned cart recovery
When a Stripe checkout session expires without payment, if the customer
entered their email, we record an AbandonedCart and schedule a single
plain-text recovery email (1h delay via Oban).
Privacy design:
- feature is off by default; shop owner opts in via admin settings
- only contacts customers who entered their email at Stripe checkout
- single email, never more (emailed_at timestamp gate)
- suppression list blocks repeat contact; one-click unsubscribe via
signed token (/unsubscribe/:token)
- records pruned after 30 days (nightly Oban cron)
- no tracking pixels, no redirected links, no HTML
Legal notes:
- custom_text added to Stripe session footer when recovery is on
- UK PECR soft opt-in; EU legitimate interests both satisfied by this design
Files:
- migration: abandoned_carts + email_suppressions tables
- schemas: AbandonedCart, EmailSuppression
- context: Orders.create_abandoned_cart, check_suppression, add_suppression,
has_recent_paid_order?, get_abandoned_cart_by_session, mark_abandoned_cart_emailed
- workers: AbandonedCartEmailWorker (checkout queue), AbandonedCartPruneWorker (cron)
- notifier: OrderNotifier.deliver_cart_recovery/3
- webhook: extended checkout.session.expired handler
- controller: UnsubscribeController, admin settings toggle
- tests: 28 new tests across context, workers, and controller
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:02:37 +00:00
|
|
|
<%= if @cart_recovery_enabled do %>
|
|
|
|
|
<.status_pill color="green">
|
|
|
|
|
<.icon name="hero-check-circle-mini" class="size-3" /> On
|
|
|
|
|
</.status_pill>
|
|
|
|
|
<% else %>
|
|
|
|
|
<.status_pill color="zinc">Off</.status_pill>
|
|
|
|
|
<% end %>
|
|
|
|
|
</div>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<p class="admin-section-desc">
|
add abandoned cart recovery
When a Stripe checkout session expires without payment, if the customer
entered their email, we record an AbandonedCart and schedule a single
plain-text recovery email (1h delay via Oban).
Privacy design:
- feature is off by default; shop owner opts in via admin settings
- only contacts customers who entered their email at Stripe checkout
- single email, never more (emailed_at timestamp gate)
- suppression list blocks repeat contact; one-click unsubscribe via
signed token (/unsubscribe/:token)
- records pruned after 30 days (nightly Oban cron)
- no tracking pixels, no redirected links, no HTML
Legal notes:
- custom_text added to Stripe session footer when recovery is on
- UK PECR soft opt-in; EU legitimate interests both satisfied by this design
Files:
- migration: abandoned_carts + email_suppressions tables
- schemas: AbandonedCart, EmailSuppression
- context: Orders.create_abandoned_cart, check_suppression, add_suppression,
has_recent_paid_order?, get_abandoned_cart_by_session, mark_abandoned_cart_emailed
- workers: AbandonedCartEmailWorker (checkout queue), AbandonedCartPruneWorker (cron)
- notifier: OrderNotifier.deliver_cart_recovery/3
- webhook: extended checkout.session.expired handler
- controller: UnsubscribeController, admin settings toggle
- tests: 28 new tests across context, workers, and controller
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:02:37 +00:00
|
|
|
When on, customers who entered their email at Stripe checkout but didn't complete
|
|
|
|
|
payment receive a single plain-text recovery email one hour later.
|
|
|
|
|
No tracking pixels. One email, never more.
|
|
|
|
|
</p>
|
|
|
|
|
<%= if @cart_recovery_enabled do %>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<p class="admin-section-desc admin-text-warning">
|
add abandoned cart recovery
When a Stripe checkout session expires without payment, if the customer
entered their email, we record an AbandonedCart and schedule a single
plain-text recovery email (1h delay via Oban).
Privacy design:
- feature is off by default; shop owner opts in via admin settings
- only contacts customers who entered their email at Stripe checkout
- single email, never more (emailed_at timestamp gate)
- suppression list blocks repeat contact; one-click unsubscribe via
signed token (/unsubscribe/:token)
- records pruned after 30 days (nightly Oban cron)
- no tracking pixels, no redirected links, no HTML
Legal notes:
- custom_text added to Stripe session footer when recovery is on
- UK PECR soft opt-in; EU legitimate interests both satisfied by this design
Files:
- migration: abandoned_carts + email_suppressions tables
- schemas: AbandonedCart, EmailSuppression
- context: Orders.create_abandoned_cart, check_suppression, add_suppression,
has_recent_paid_order?, get_abandoned_cart_by_session, mark_abandoned_cart_emailed
- workers: AbandonedCartEmailWorker (checkout queue), AbandonedCartPruneWorker (cron)
- notifier: OrderNotifier.deliver_cart_recovery/3
- webhook: extended checkout.session.expired handler
- controller: UnsubscribeController, admin settings toggle
- tests: 28 new tests across context, workers, and controller
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:02:37 +00:00
|
|
|
Make sure your privacy policy mentions that a single recovery email may be sent,
|
|
|
|
|
and that customers can unsubscribe at any time.
|
|
|
|
|
</p>
|
|
|
|
|
<% end %>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-section-body">
|
add abandoned cart recovery
When a Stripe checkout session expires without payment, if the customer
entered their email, we record an AbandonedCart and schedule a single
plain-text recovery email (1h delay via Oban).
Privacy design:
- feature is off by default; shop owner opts in via admin settings
- only contacts customers who entered their email at Stripe checkout
- single email, never more (emailed_at timestamp gate)
- suppression list blocks repeat contact; one-click unsubscribe via
signed token (/unsubscribe/:token)
- records pruned after 30 days (nightly Oban cron)
- no tracking pixels, no redirected links, no HTML
Legal notes:
- custom_text added to Stripe session footer when recovery is on
- UK PECR soft opt-in; EU legitimate interests both satisfied by this design
Files:
- migration: abandoned_carts + email_suppressions tables
- schemas: AbandonedCart, EmailSuppression
- context: Orders.create_abandoned_cart, check_suppression, add_suppression,
has_recent_paid_order?, get_abandoned_cart_by_session, mark_abandoned_cart_emailed
- workers: AbandonedCartEmailWorker (checkout queue), AbandonedCartPruneWorker (cron)
- notifier: OrderNotifier.deliver_cart_recovery/3
- webhook: extended checkout.session.expired handler
- controller: UnsubscribeController, admin settings toggle
- tests: 28 new tests across context, workers, and controller
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:02:37 +00:00
|
|
|
<button
|
|
|
|
|
phx-click="toggle_cart_recovery"
|
|
|
|
|
class={[
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
"admin-btn admin-btn-sm",
|
|
|
|
|
if(@cart_recovery_enabled, do: "admin-btn-outline", else: "admin-btn-primary")
|
add abandoned cart recovery
When a Stripe checkout session expires without payment, if the customer
entered their email, we record an AbandonedCart and schedule a single
plain-text recovery email (1h delay via Oban).
Privacy design:
- feature is off by default; shop owner opts in via admin settings
- only contacts customers who entered their email at Stripe checkout
- single email, never more (emailed_at timestamp gate)
- suppression list blocks repeat contact; one-click unsubscribe via
signed token (/unsubscribe/:token)
- records pruned after 30 days (nightly Oban cron)
- no tracking pixels, no redirected links, no HTML
Legal notes:
- custom_text added to Stripe session footer when recovery is on
- UK PECR soft opt-in; EU legitimate interests both satisfied by this design
Files:
- migration: abandoned_carts + email_suppressions tables
- schemas: AbandonedCart, EmailSuppression
- context: Orders.create_abandoned_cart, check_suppression, add_suppression,
has_recent_paid_order?, get_abandoned_cart_by_session, mark_abandoned_cart_emailed
- workers: AbandonedCartEmailWorker (checkout queue), AbandonedCartPruneWorker (cron)
- notifier: OrderNotifier.deliver_cart_recovery/3
- webhook: extended checkout.session.expired handler
- controller: UnsubscribeController, admin settings toggle
- tests: 28 new tests across context, workers, and controller
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:02:37 +00:00
|
|
|
]}
|
|
|
|
|
>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
{if @cart_recovery_enabled, do: "Turn off", else: "Turn on"}
|
add abandoned cart recovery
When a Stripe checkout session expires without payment, if the customer
entered their email, we record an AbandonedCart and schedule a single
plain-text recovery email (1h delay via Oban).
Privacy design:
- feature is off by default; shop owner opts in via admin settings
- only contacts customers who entered their email at Stripe checkout
- single email, never more (emailed_at timestamp gate)
- suppression list blocks repeat contact; one-click unsubscribe via
signed token (/unsubscribe/:token)
- records pruned after 30 days (nightly Oban cron)
- no tracking pixels, no redirected links, no HTML
Legal notes:
- custom_text added to Stripe session footer when recovery is on
- UK PECR soft opt-in; EU legitimate interests both satisfied by this design
Files:
- migration: abandoned_carts + email_suppressions tables
- schemas: AbandonedCart, EmailSuppression
- context: Orders.create_abandoned_cart, check_suppression, add_suppression,
has_recent_paid_order?, get_abandoned_cart_by_session, mark_abandoned_cart_emailed
- workers: AbandonedCartEmailWorker (checkout queue), AbandonedCartPruneWorker (cron)
- notifier: OrderNotifier.deliver_cart_recovery/3
- webhook: extended checkout.session.expired handler
- controller: UnsubscribeController, admin settings toggle
- tests: 28 new tests across context, workers, and controller
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:02:37 +00:00
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
2026-03-04 17:12:10 +00:00
|
|
|
<%!-- From address --%>
|
|
|
|
|
<section class="admin-section">
|
|
|
|
|
<h2 class="admin-section-title">From address</h2>
|
|
|
|
|
<p class="admin-section-desc">
|
|
|
|
|
The sender address on all emails from your shop.
|
|
|
|
|
</p>
|
|
|
|
|
<div class="admin-section-body">
|
|
|
|
|
<form phx-submit="save_from_address" class="admin-row admin-row-lg">
|
|
|
|
|
<.input
|
|
|
|
|
name="from_address"
|
|
|
|
|
value={@from_address}
|
|
|
|
|
type="email"
|
|
|
|
|
placeholder="noreply@yourshop.com"
|
|
|
|
|
/>
|
|
|
|
|
<.button phx-disable-with="Saving...">Save</.button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
2026-02-12 09:04:51 +00:00
|
|
|
<%!-- Account --%>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<section class="admin-section">
|
|
|
|
|
<h2 class="admin-section-title">Account</h2>
|
2026-02-12 09:04:51 +00:00
|
|
|
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-stack admin-stack-lg admin-section-body">
|
2026-02-12 09:04:51 +00:00
|
|
|
<.form
|
|
|
|
|
for={@email_form}
|
|
|
|
|
id="email_form"
|
|
|
|
|
phx-submit="update_email"
|
|
|
|
|
phx-change="validate_email"
|
|
|
|
|
>
|
|
|
|
|
<.input
|
|
|
|
|
field={@email_form[:email]}
|
|
|
|
|
type="email"
|
|
|
|
|
label="Email"
|
|
|
|
|
autocomplete="username"
|
|
|
|
|
required
|
|
|
|
|
/>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-form-actions-sm">
|
2026-02-12 09:04:51 +00:00
|
|
|
<.button phx-disable-with="Saving...">Change email</.button>
|
|
|
|
|
</div>
|
|
|
|
|
</.form>
|
|
|
|
|
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-separator-xl">
|
2026-02-12 09:04:51 +00:00
|
|
|
<.form
|
|
|
|
|
for={@password_form}
|
|
|
|
|
id="password_form"
|
|
|
|
|
action={~p"/users/update-password"}
|
|
|
|
|
method="post"
|
|
|
|
|
phx-change="validate_password"
|
|
|
|
|
phx-submit="update_password"
|
|
|
|
|
phx-trigger-action={@trigger_submit}
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
name={@password_form[:email].name}
|
|
|
|
|
type="hidden"
|
|
|
|
|
id="hidden_user_email"
|
|
|
|
|
autocomplete="username"
|
|
|
|
|
value={@current_email}
|
|
|
|
|
/>
|
|
|
|
|
<.input
|
|
|
|
|
field={@password_form[:password]}
|
|
|
|
|
type="password"
|
|
|
|
|
label="New password"
|
|
|
|
|
autocomplete="new-password"
|
|
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
<.input
|
|
|
|
|
field={@password_form[:password_confirmation]}
|
|
|
|
|
type="password"
|
|
|
|
|
label="Confirm new password"
|
|
|
|
|
autocomplete="new-password"
|
|
|
|
|
/>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-form-actions-sm">
|
2026-02-12 09:04:51 +00:00
|
|
|
<.button phx-disable-with="Saving...">Change password</.button>
|
|
|
|
|
</div>
|
|
|
|
|
</.form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<%!-- Advanced --%>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<section class="admin-section">
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<h2 class="admin-section-title">Advanced</h2>
|
2026-02-12 09:04:51 +00:00
|
|
|
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-stack admin-stack-sm admin-section-body">
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<.link href={~p"/admin/dashboard"} class="admin-link-subtle">
|
2026-02-12 09:04:51 +00:00
|
|
|
<.icon name="hero-chart-bar" class="size-4 inline" /> System dashboard
|
|
|
|
|
</.link>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<.link href={~p"/admin/errors"} class="admin-link-subtle">
|
2026-02-12 09:04:51 +00:00
|
|
|
<.icon name="hero-bug-ant" class="size-4 inline" /> Error tracker
|
|
|
|
|
</.link>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# -- Function components --
|
|
|
|
|
|
|
|
|
|
attr :color, :string, required: true
|
|
|
|
|
slot :inner_block, required: true
|
|
|
|
|
|
|
|
|
|
defp status_pill(assigns) do
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
modifier =
|
2026-02-12 09:04:51 +00:00
|
|
|
case assigns.color do
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
"green" -> "admin-status-pill-green"
|
|
|
|
|
"amber" -> "admin-status-pill-amber"
|
|
|
|
|
_ -> "admin-status-pill-zinc"
|
2026-02-12 09:04:51 +00:00
|
|
|
end
|
|
|
|
|
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
assigns = assign(assigns, :modifier, modifier)
|
2026-02-12 09:04:51 +00:00
|
|
|
|
|
|
|
|
~H"""
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<span class={["admin-status-pill", @modifier]}>
|
2026-02-12 09:04:51 +00:00
|
|
|
{render_slot(@inner_block)}
|
|
|
|
|
</span>
|
|
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
2026-02-15 10:53:15 +00:00
|
|
|
attr :provider, :map, required: true
|
2026-02-12 09:04:51 +00:00
|
|
|
|
2026-02-15 10:53:15 +00:00
|
|
|
defp provider_connected(assigns) do
|
|
|
|
|
conn = assigns.provider.connection
|
2026-02-12 09:04:51 +00:00
|
|
|
|
|
|
|
|
assigns =
|
|
|
|
|
assigns
|
|
|
|
|
|> assign(:connection, conn)
|
2026-02-15 10:53:15 +00:00
|
|
|
|> assign(:product_count, assigns.provider.product_count)
|
2026-02-12 09:04:51 +00:00
|
|
|
|> assign(:syncing, conn.sync_status == "syncing")
|
2026-02-15 10:53:15 +00:00
|
|
|
|> assign(:provider_label, String.capitalize(conn.provider_type))
|
2026-02-12 09:04:51 +00:00
|
|
|
|
|
|
|
|
~H"""
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-section-body">
|
|
|
|
|
<dl class="admin-dl">
|
|
|
|
|
<div class="admin-dl-row">
|
|
|
|
|
<dt class="admin-dl-term">Provider</dt>
|
|
|
|
|
<dd class="admin-dl-value">{@provider_label}</dd>
|
2026-02-12 09:04:51 +00:00
|
|
|
</div>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-dl-row">
|
|
|
|
|
<dt class="admin-dl-term">Shop</dt>
|
|
|
|
|
<dd class="admin-dl-value">{@connection.name}</dd>
|
2026-02-12 09:04:51 +00:00
|
|
|
</div>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-dl-row">
|
|
|
|
|
<dt class="admin-dl-term">Products</dt>
|
|
|
|
|
<dd class="admin-dl-value">{@product_count}</dd>
|
2026-02-12 09:04:51 +00:00
|
|
|
</div>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-dl-row">
|
|
|
|
|
<dt class="admin-dl-term">Last synced</dt>
|
|
|
|
|
<dd class="admin-dl-value">
|
2026-02-12 09:04:51 +00:00
|
|
|
<%= if @connection.last_synced_at do %>
|
|
|
|
|
{format_relative_time(@connection.last_synced_at)}
|
|
|
|
|
<% else %>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<span class="admin-text-warning">Never</span>
|
2026-02-12 09:04:51 +00:00
|
|
|
<% end %>
|
|
|
|
|
</dd>
|
|
|
|
|
</div>
|
|
|
|
|
</dl>
|
|
|
|
|
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-cluster admin-section-body">
|
2026-02-12 09:04:51 +00:00
|
|
|
<button
|
|
|
|
|
phx-click="sync"
|
|
|
|
|
phx-value-id={@connection.id}
|
|
|
|
|
disabled={@syncing}
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
class="admin-btn admin-btn-outline admin-btn-sm"
|
2026-02-12 09:04:51 +00:00
|
|
|
>
|
|
|
|
|
<.icon
|
|
|
|
|
name="hero-arrow-path"
|
|
|
|
|
class={if @syncing, do: "size-4 animate-spin", else: "size-4"}
|
|
|
|
|
/>
|
|
|
|
|
{if @syncing, do: "Syncing...", else: "Sync products"}
|
|
|
|
|
</button>
|
|
|
|
|
<.link
|
|
|
|
|
navigate={~p"/admin/providers/#{@connection.id}/edit"}
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
class="admin-btn admin-btn-outline admin-btn-sm"
|
2026-02-12 09:04:51 +00:00
|
|
|
>
|
|
|
|
|
<.icon name="hero-cog-6-tooth" class="size-4" /> Settings
|
|
|
|
|
</.link>
|
|
|
|
|
<button
|
|
|
|
|
phx-click="delete_connection"
|
|
|
|
|
phx-value-id={@connection.id}
|
2026-02-15 10:53:15 +00:00
|
|
|
data-confirm={"Disconnect from #{@provider_label}? Your synced products will remain in your shop."}
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
class="admin-link-danger"
|
2026-02-12 09:04:51 +00:00
|
|
|
>
|
|
|
|
|
Disconnect
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-02-12 08:35:22 +00:00
|
|
|
</div>
|
2026-02-07 17:12:53 +00:00
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp stripe_setup_form(assigns) do
|
|
|
|
|
~H"""
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-section-body">
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<p class="admin-section-desc admin-section-desc-flush">
|
2026-02-07 17:12:53 +00:00
|
|
|
To accept payments, connect your Stripe account by entering your secret key.
|
|
|
|
|
You can find it in your
|
add external link UX: icons, rel attributes, screen reader labels
New external_link component in core_components handles target="_blank",
rel="noopener noreferrer", external-link icon, and sr-only "(opens in
new tab)" text. Migrated admin providers form, settings (Stripe),
order tracking, onboarding setup links to use it. Fixed rel="noopener"
to "noopener noreferrer" on remaining links (email settings, product
show, core_components card radio). Added sr-only text to shop social
link cards and aria-label to page renderer tracking link.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 00:55:09 +00:00
|
|
|
<.external_link href="https://dashboard.stripe.com/apikeys" class="admin-link">
|
2026-02-07 17:12:53 +00:00
|
|
|
Stripe dashboard
|
add external link UX: icons, rel attributes, screen reader labels
New external_link component in core_components handles target="_blank",
rel="noopener noreferrer", external-link icon, and sr-only "(opens in
new tab)" text. Migrated admin providers form, settings (Stripe),
order tracking, onboarding setup links to use it. Fixed rel="noopener"
to "noopener noreferrer" on remaining links (email settings, product
show, core_components card radio). Added sr-only text to shop social
link cards and aria-label to page renderer tracking link.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 00:55:09 +00:00
|
|
|
</.external_link>
|
2026-02-07 17:12:53 +00:00
|
|
|
under Developers → API keys.
|
|
|
|
|
</p>
|
|
|
|
|
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<.form for={@connect_form} phx-submit="connect_stripe" class="admin-card-spaced">
|
2026-02-07 17:12:53 +00:00
|
|
|
<.input
|
|
|
|
|
field={@connect_form[:api_key]}
|
|
|
|
|
type="password"
|
|
|
|
|
label="Secret key"
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
placeholder="sk_test_... or sk_live_..."
|
|
|
|
|
/>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<p class="admin-help-text">
|
2026-02-07 17:12:53 +00:00
|
|
|
Starts with <code>sk_test_</code> (test mode) or <code>sk_live_</code> (live mode).
|
|
|
|
|
This key is encrypted at rest in the database.
|
|
|
|
|
</p>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-section-body">
|
2026-02-07 17:12:53 +00:00
|
|
|
<.button phx-disable-with="Connecting...">
|
|
|
|
|
Connect Stripe
|
|
|
|
|
</.button>
|
|
|
|
|
</div>
|
|
|
|
|
</.form>
|
|
|
|
|
</div>
|
|
|
|
|
"""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
defp stripe_connected_view(assigns) do
|
|
|
|
|
~H"""
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-stack admin-section-body">
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<dl class="admin-dl">
|
|
|
|
|
<div class="admin-dl-row">
|
|
|
|
|
<dt class="admin-dl-term">API key</dt>
|
|
|
|
|
<dd class="admin-dl-value"><code>{@stripe_api_key_hint}</code></dd>
|
2026-02-07 17:12:53 +00:00
|
|
|
</div>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-dl-row">
|
|
|
|
|
<dt class="admin-dl-term">Webhook URL</dt>
|
2026-03-01 19:39:56 +00:00
|
|
|
<dd class="admin-dl-value">
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<code class="admin-code-break">{@stripe_webhook_url}</code>
|
2026-03-01 19:39:56 +00:00
|
|
|
</dd>
|
2026-02-07 17:12:53 +00:00
|
|
|
</div>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-dl-row">
|
|
|
|
|
<dt class="admin-dl-term">Webhook secret</dt>
|
|
|
|
|
<dd class="admin-dl-value">
|
2026-02-07 17:12:53 +00:00
|
|
|
<%= if @stripe_has_signing_secret do %>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<code>{@stripe_signing_secret_hint}</code>
|
2026-02-07 17:12:53 +00:00
|
|
|
<% else %>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<span class="admin-text-warning">Not set</span>
|
2026-02-07 17:12:53 +00:00
|
|
|
<% end %>
|
|
|
|
|
</dd>
|
|
|
|
|
</div>
|
|
|
|
|
</dl>
|
|
|
|
|
|
|
|
|
|
<%= if @stripe_status == :connected_localhost do %>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<div class="admin-info-box admin-info-box-amber">
|
|
|
|
|
<p>
|
2026-02-07 17:12:53 +00:00
|
|
|
Stripe can't reach localhost for webhooks. For local testing, run the Stripe CLI:
|
|
|
|
|
</p>
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
<pre>stripe listen --forward-to localhost:4000/webhooks/stripe</pre>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<p class="admin-help-text admin-text-warning">
|
2026-02-07 17:12:53 +00:00
|
|
|
The CLI will output a signing secret starting with <code>whsec_</code>. Enter it below.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<.form for={@secret_form} phx-submit="save_signing_secret">
|
2026-02-07 17:12:53 +00:00
|
|
|
<.input
|
|
|
|
|
field={@secret_form[:signing_secret]}
|
|
|
|
|
type="password"
|
|
|
|
|
label="Webhook signing secret"
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
placeholder="whsec_..."
|
|
|
|
|
/>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-form-actions-sm">
|
2026-02-07 17:12:53 +00:00
|
|
|
<.button phx-disable-with="Saving...">Save signing secret</.button>
|
|
|
|
|
</div>
|
|
|
|
|
</.form>
|
|
|
|
|
<% else %>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-separator">
|
2026-03-01 19:39:56 +00:00
|
|
|
<button
|
|
|
|
|
phx-click="toggle_stripe_advanced"
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
class="admin-link-subtle admin-row admin-row-sm"
|
2026-03-01 19:39:56 +00:00
|
|
|
>
|
2026-02-07 17:12:53 +00:00
|
|
|
<.icon
|
|
|
|
|
name={if @advanced_open, do: "hero-chevron-down-mini", else: "hero-chevron-right-mini"}
|
|
|
|
|
class="size-4"
|
|
|
|
|
/> Advanced
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<%= if @advanced_open do %>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-form-actions-sm">
|
|
|
|
|
<p class="admin-text-tertiary">
|
2026-02-07 17:12:53 +00:00
|
|
|
Override the webhook signing secret if you need to use a custom endpoint or the Stripe CLI.
|
|
|
|
|
</p>
|
|
|
|
|
<.form for={@secret_form} phx-submit="save_signing_secret">
|
|
|
|
|
<.input
|
|
|
|
|
field={@secret_form[:signing_secret]}
|
|
|
|
|
type="password"
|
|
|
|
|
label="Webhook signing secret"
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
placeholder="whsec_..."
|
|
|
|
|
/>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-form-actions-sm">
|
2026-02-07 17:12:53 +00:00
|
|
|
<.button phx-disable-with="Saving...">Save signing secret</.button>
|
|
|
|
|
</div>
|
|
|
|
|
</.form>
|
|
|
|
|
</div>
|
|
|
|
|
<% end %>
|
|
|
|
|
</div>
|
|
|
|
|
<% end %>
|
|
|
|
|
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
<div class="admin-separator-lg">
|
2026-02-07 17:12:53 +00:00
|
|
|
<button
|
|
|
|
|
phx-click="disconnect_stripe"
|
|
|
|
|
data-confirm="This will remove your Stripe API key and delete the webhook endpoint. Are you sure?"
|
refactor admin CSS: replace utility classes with semantic styles
Replace Tailwind utility soup across admin templates with semantic
CSS classes. Add layout primitives (stack, row, cluster, grid),
extract JS transition helpers into transitions.css, and refactor
core_components, layouts, settings, newsletter, order_show, providers,
and theme editor templates.
Utility occurrences reduced from 290+ to 127 across admin files.
All 1679 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:15:25 +00:00
|
|
|
class="admin-link-danger"
|
2026-02-07 17:12:53 +00:00
|
|
|
>
|
|
|
|
|
Disconnect Stripe
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
"""
|
|
|
|
|
end
|
2026-02-12 09:04:51 +00:00
|
|
|
|
|
|
|
|
defp format_relative_time(datetime) do
|
|
|
|
|
diff = DateTime.diff(DateTime.utc_now(), datetime, :second)
|
|
|
|
|
|
|
|
|
|
cond do
|
|
|
|
|
diff < 60 -> "just now"
|
|
|
|
|
diff < 3600 -> "#{div(diff, 60)} min ago"
|
|
|
|
|
diff < 86400 -> "#{div(diff, 3600)} hours ago"
|
|
|
|
|
true -> "#{div(diff, 86400)} days ago"
|
|
|
|
|
end
|
|
|
|
|
end
|
2026-02-07 17:12:53 +00:00
|
|
|
end
|