# `PhoenixKitEcommerce.Web.ShopifySync`
[🔗](https://github.com/BeamLabEU/phoenix_kit_ecommerce/blob/0.5.11/lib/phoenix_kit_ecommerce/web/shopify_sync.ex#L1)

Admin LiveView for the Shopify → shop one-way sync.

Fetches products from the Shopify Admin API connection registered in
`PhoenixKitEcommerce.Shopify.Provider`, diffs them against the local
catalog (`PhoenixKitEcommerce.Shopify.Sync.check/2`), and lets an
operator apply changes — nothing is written without an explicit click.
When the Admin API token is rejected, `Sync.check/2` falls back to a
price-only storefront read instead of failing outright; this LiveView
surfaces that with a banner so an operator never mistakes a price-only
report for a complete one, and only ever renders the Prices section in
that mode (see `visible_sections/2`).

Changes are grouped into field sections (Prices, Titles, Descriptions,
Full Descriptions, Tags, Statuses, Vendors — in that order, price first). One
product's change can appear in more than one section if more than one
of its fields differs. Sections are collapsed by default and show a
count; expanding one reveals its rows, 25 at a time (see the module
attribute doc on `@per_page` for why chunking here is a correctness
requirement, not polish). An operator can apply a single field on a
single product, a whole section, or every pending change at once —
always through `PhoenixKitEcommerce.Shopify.Sync`'s existing
`apply_change/2` / `apply_changes/2`, never by writing to a product
directly.

Only updates products that already exist locally (matched by Shopify's
`handle` against the product's slug). A Shopify product with no local
match is skipped — creating new products is the CSV import's job, not
this sync's.

## Applying changes: request → confirm, never a direct write

Every apply affordance (one field on one product, a whole section, the
checked rows in a section, or everything) is two-phase: a `request_*`
event validates the click and stashes what it would do in `@pending`,
then renders `<.confirm_modal>` describing it; only `"confirm_apply"`
(the modal's own confirm button) calls into `Sync.apply_change/2` /
`apply_changes/2`. `"cancel_apply"` — and re-deriving what to write from
live `@changes` at confirm time rather than trusting whatever `@pending`
captured at request time — both exist so a stale or cancelled
confirmation can never turn into a write; see `clear_pending/1` and the
`do_confirm_*` functions. This replaces the page's previous
`data-confirm` (a bare browser `confirm()`, which cannot show the
extreme-price exclusion notice below) with PhoenixKit's own modal.

## Selection is a bulk scope, scoped to one section, one page

A checkbox column (`<.bulk_select_scope>` / `bulk_select_cell`) lets an
operator pick specific rows within one expanded section and apply just
that field to just those products via "Apply selection" — distinct from
"Apply section" (every row) and a single row's own "Apply" button.
Selection is client-side (see `BulkSelectScope`'s JS-hook moduledoc) and
scoped to the section's CURRENT PAGE of `@per_page`; paging re-renders
the row set, which prunes any selection that isn't on the new page — so
selection deliberately does not persist across pages. Like "Apply
section", it excludes extreme price changes (`price_extreme?`) from the
bulk write; see `split_bulk_eligible/2`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
