Shared helper functions for Shop public LiveViews.
Centralizes utility functions that were duplicated across shop_catalog, catalog_category, catalog_product, cart_page, checkout_page, and checkout_complete.
Summary
Functions
Find the best enabled language that has a slug for this entity.
Build a localized URL path, adding language prefix for non-default languages. Delegates to Routes.path which handles default vs non-default consistently.
Get the first image URL for a product.
Format a price value with currency. Returns "-" for nil price.
Extract current user from socket assigns scope.
Determine language from URL params.
Get signed URL for a Storage image file.
Whether the storefront drops an all-zero fractional part ("40" rather than "40.00").
Convert a key string to human-readable format.
Assigns :admin_edit_url/:admin_edit_label on socket for an admin
visitor, via core's PhoenixKitWeb.AdminEditHelper.assign_admin_edit/3.
The billing identity an order was placed with.
Parse an integer from a LiveView event payload, falling back to default.
Parse page param with validation. Returns 1 for invalid/missing values.
Format address for a billing profile struct or an order's snapshot map.
Format display name for a billing profile.
Contact email from a billing profile STRUCT or an order's snapshot MAP.
Point this module's Gettext backend at language, falling back to the base
language when the catalogue has no dialect.
Sets the content locale from a socket, falling back to the shop's CONFIGURED default rather than a hardcoded "en".
Re-marks @currency as changed so every price expression that reads it
re-evaluates on the next render — the ONE way a mounted storefront picks
up a currency-table change (§4.2.1 п.5).
Whether the storefront's category navigation is switched on
(shop_sidebar_show_categories, default true). Read by every public page
that renders a category list, so the catalog aside, the catalog grid and
the product page's category panel cannot disagree about the default.
Whether a product's tags may be shown on a page rendered in language.
Functions
Find the best enabled language that has a slug for this entity.
Prefers the default language, then checks other enabled languages. Returns nil if no valid language found.
Build a localized URL path, adding language prefix for non-default languages. Delegates to Routes.path which handles default vs non-default consistently.
Get the first image URL for a product.
Handles Storage-based images (new format with featured_image_uuid or image_uuids) and legacy URL-based images (Shopify imports). Returns nil if no image is available.
Format a price value with currency. Returns "-" for nil price.
Accepts the amount as a Decimal, number, or numeric string (order line
items persist their amounts as strings). The currency may be a
Currency struct, a bare code string (Shop.currency_for_code/1
falls back to the code when the record's currency no longer resolves —
showing "12.50 XYZ" is honest, borrowing today's default symbol is not),
or nil (no default currency configured at all — legacy $).
Extract current user from socket assigns scope.
Determine language from URL params.
Uses locale param if present, otherwise falls back to Translations.default_language/0. Used by catalog and category pages (non-product pages).
Get signed URL for a Storage image file.
Returns nil if file or variant not found (unlike product detail page which returns a placeholder). Falls back to original variant if requested variant is not available.
Whether the storefront drops an all-zero fractional part ("40" rather than "40.00").
Off by default, because dropping the decimals is wrong for most shops. It exists for shops whose prices are round by nature — services quoted in whole units, where "40.00 EUR" reads as unnecessarily precise and, as one operator put it, faintly alarming.
Storefront only. Invoices, receipts and credit notes keep two decimals: they are accounting documents, and this setting must never reach them.
Convert a key string to human-readable format.
Example: "material_type" -> "Material Type"
Assigns :admin_edit_url/:admin_edit_label on socket for an admin
visitor, via core's PhoenixKitWeb.AdminEditHelper.assign_admin_edit/3.
Guarded with Code.ensure_loaded?/1 + function_exported?/3 rather than
calling the helper directly: ecommerce pins phoenix_kit with a ~>
requirement, not an exact version, so a host running an older core that
predates the helper must not crash storefront pages. Returns socket
unchanged when the helper isn't available or the visitor isn't an admin.
The billing identity an order was placed with.
Prefers the order's immutable billing_snapshot over the live billing
profile: the profile is editable, so reading it made a historical order
claim an address it was never billed to (and deleting the profile made
the true one reappear). The live profile is a fallback only for orders
placed before snapshots existed.
Parse an integer from a LiveView event payload, falling back to default.
String.to_integer/1 raises on anything non-numeric, and a raise inside
handle_event/3 takes the whole LiveView down — so any hand-crafted or
merely stale phx-value-* produced a crashed socket rather than an
ignored event. That is reachable unauthenticated on the storefront
(quantity fields) and by any admin elsewhere.
Returns default for nil, blank, partially-numeric ("3abc") and
non-binary input. Callers that need a floor should still apply one —
this only guarantees you get an integer back.
Parse page param with validation. Returns 1 for invalid/missing values.
Format address for a billing profile struct or an order's snapshot map.
Format display name for a billing profile.
Contact email from a billing profile STRUCT or an order's snapshot MAP.
The snapshot is a plain map, so profile.email raises on it — the crash
a rendered confirmation page hit after order pages started preferring
the snapshot. Same shape problem as profile_display_name/1.
Point this module's Gettext backend at language, falling back to the base
language when the catalogue has no dialect.
Without this the storefront renders English in every locale, however complete
the catalogues are. The content language here is a DIALECT (resolve_dialect/1
returns "ru-RU", "et-EE", "en-US"), and that is also what core puts into the
process locale — but this module ships priv/gettext/{en,ru,et}, plain codes
with no region. Gettext does not fall back from "ru-RU" to "ru" on its own, so
every lookup missed and returned its msgid, which is the English source string.
Core's own catalogue has the same plain-code shape, so this is not specific to
the shop; it is why a fully translated module can still render entirely in
English. Verified on a dev box: put_locale("ru") translates,
put_locale("ru-RU") does not.
Called from mount/3, which runs once per process for both the dead render and
the connected mount, so the whole lifecycle of that LiveView is covered.
Sets the content locale from a socket, falling back to the shop's CONFIGURED default rather than a hardcoded "en".
:current_locale is supplied by core's live_session on_mount; a host that
mounts these LiveViews outside it gets nil, and a hardcoded English fallback
would force English on a shop whose default language is Russian.
@spec refresh_display_currency(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
Re-marks @currency as changed so every price expression that reads it
re-evaluates on the next render — the ONE way a mounted storefront picks
up a currency-table change (§4.2.1 п.5).
The rate is deliberately not in assigns (§12.4), so nothing in the
socket knows it moved; Phoenix.Component.assign/3 skips an equal value
and HEEx re-evaluates an expression only when one of ITS assigns
changed. Passing through a sentinel value marks the key changed while
the code itself stays what the request resolved. Base numbers
(@products, @calculated_price) are unchanged and are not re-read.
@spec sidebar_categories_enabled?() :: boolean()
Whether the storefront's category navigation is switched on
(shop_sidebar_show_categories, default true). Read by every public page
that renders a category list, so the catalog aside, the catalog grid and
the product page's category panel cannot disagree about the default.
Whether a product's tags may be shown on a page rendered in language.
Tags arrive from Shopify as one untranslated list on
data["ecommerce"]["tags"] — there is no per-language variant of them.
Rendering that list on a translated page puts the only untranslated text
on the card, so tags stay on the default-language storefront and are
hidden elsewhere until translated tags exist.