PhoenixKitCRM.Web.Components.MirrorConflictModal (PhoenixKitCRM v0.7.0)

Copy Markdown View Source

The per-field "ask, don't silently overwrite" divergence-resolution modal (owner Q5) — one row per PhoenixKitCRM.Mirror.diff/2 entry, a radio choosing which side wins that field.

Pure presentation function component. The consumer LiveView owns @conflicts (from Mirror.diff/2), @master, and @choices (the in-progress selection — see below), and implements:

mirror_choice_changed   form phx-change; receives
                          %{"choices" => %{"<field>" => "keep_crm" | "keep_user"}}
                          for whichever radios are currently
                          rendered  merge into the server-tracked
                          `@choices` (whitelisted against the
                          current `@conflicts` field set; never
                          `String.to_atom/1` a submitted key).
mirror_resolve          form submit; same payload shape as above.
                          Re-fetch the CRM record and the User
                          fresh (records may have changed while the
                          modal was open) and call `Mirror.diff/2`
                          again before `Mirror.resolve/4`  the
                          whole reason `resolve/4` re-checks
                          divergence internally is so a field that
                          stopped diverging while the modal was
                          open is dropped, not blindly applied.
mirror_cancel_conflict  Cancel clicked / modal dismissed

Renders nothing when @conflicts is empty — there is nothing to ask.

Why @choices exists — controlled, not derived from @master alone

Earlier this modal derived every radio's checked from @master alone (checked={@master == :crm}) with no way for a consumer to represent an in-progress selection. That made the radios UNCONTROLLED: the user's click lived only in the DOM, so ANY parent re-render while the modal was open (a flash, a PubSub event, a phx-change on the underlying form) made LiveView's DOM patch re-apply the server-rendered checked and silently revert the selection back to @master — Apply could then resolve with choices the user never made. @choices (%{field_atom => :crm | :user}, default %{}) makes the modal a normal controlled LiveView form: a field with no entry in @choices still defaults to @master (so a consumer that never wires mirror_choice_changed sees identical behavior to before), but once a choice is recorded server- side it survives any re-render.

Usage

<.mirror_conflict_modal
  show={@show_conflict}
  conflicts={@mirror_conflicts}
  master={:crm}
  choices={@mirror_choices}
/>

Summary

Functions

mirror_conflict_modal(assigns)

Attributes

  • conflicts (:list) - Defaults to [].
  • master (:atom) (required) - Must be one of :crm, or :user.
  • show (:boolean) - Defaults to false.
  • choices (:map) - Defaults to %{}.