feat: add admin provider setup UI with improved product sync

- Add /admin/providers LiveView for connecting and managing POD providers
- Implement pagination for Printify API (handles all products, not just first page)
- Add parallel processing (5 concurrent) for faster product sync
- Add slug-based fallback matching when provider_product_id changes
- Add error recovery with try/rescue to prevent stuck sync status
- Add checksum-based change detection to skip unchanged products
- Add upsert tests covering race conditions and slug matching
- Add Printify provider tests
- Document Printify integration research (product identity, order risks,
  open source vs managed hosting implications)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-01-31 22:08:34 +00:00
parent bbd748f123
commit 5b736b99fd
17 changed files with 1352 additions and 38 deletions

View File

@@ -469,4 +469,68 @@ defmodule SimpleshopThemeWeb.CoreComponents do
def translate_errors(errors, field) when is_list(errors) do
for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts})
end
@doc """
Renders a modal dialog.
Uses daisyUI's modal component with proper accessibility.
## Examples
<.modal id="confirm-modal">
Are you sure?
<:actions>
<button class="btn">Cancel</button>
<button class="btn btn-primary">Confirm</button>
</:actions>
</.modal>
"""
attr :id, :string, required: true
attr :show, :boolean, default: false
attr :on_cancel, JS, default: %JS{}
slot :inner_block, required: true
slot :actions
def modal(assigns) do
~H"""
<dialog
id={@id}
class="modal"
phx-mounted={@show && show_modal(@id)}
phx-remove={hide_modal(@id)}
>
<div class="modal-box max-w-lg">
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
phx-click={@on_cancel}
aria-label={gettext("close")}
>
<.icon name="hero-x-mark" class="size-5" />
</button>
</form>
{render_slot(@inner_block)}
<div :if={@actions != []} class="modal-action">
{render_slot(@actions)}
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button phx-click={@on_cancel}>close</button>
</form>
</dialog>
"""
end
def show_modal(js \\ %JS{}, id) when is_binary(id) do
js
|> JS.exec("showModal()", to: "##{id}")
end
def hide_modal(js \\ %JS{}, id) when is_binary(id) do
js
|> JS.exec("close()", to: "##{id}")
|> JS.pop_focus()
end
end