From 61cb2b7a870642982fe1c3d21438c5e8b8af4dd7 Mon Sep 17 00:00:00 2001 From: jamey Date: Sun, 15 Feb 2026 10:53:15 +0000 Subject: [PATCH] make admin provider UI support both Printify and Printful - Provider form accepts ?type= query param (printify/printful) - Conditional setup instructions per provider (API key steps, login URLs) - Dynamic labels, titles, and config handling (shop_id vs store_id) - Provider index shows dropdown with both provider options - Settings page renamed from @printify to @provider (generic) - Fix Printful shipping rates: add default state codes for US/CA/AU Co-Authored-By: Claude Opus 4.6 --- lib/simpleshop_theme/providers/printful.ex | 15 ++- .../live/admin/providers/form.ex | 67 +++++++---- .../live/admin/providers/form.html.heex | 105 ++++++++++++------ .../live/admin/providers/index.html.heex | 35 ++++-- .../live/admin/settings.ex | 25 +++-- .../live/admin/providers_test.exs | 29 +++-- .../live/admin/settings_test.exs | 2 +- 7 files changed, 192 insertions(+), 86 deletions(-) diff --git a/lib/simpleshop_theme/providers/printful.ex b/lib/simpleshop_theme/providers/printful.ex index 142c516..353fbf4 100644 --- a/lib/simpleshop_theme/providers/printful.ex +++ b/lib/simpleshop_theme/providers/printful.ex @@ -198,8 +198,9 @@ defmodule SimpleshopTheme.Providers.Printful do defp fetch_rate_for_product(catalog_product_id, variant_id, country_code) do items = [%{source: "catalog", catalog_variant_id: variant_id, quantity: 1}] + recipient = build_recipient(country_code) - case Client.calculate_shipping(%{country_code: country_code}, items) do + case Client.calculate_shipping(recipient, items) do {:ok, rates} when is_list(rates) -> standard = Enum.find(rates, &(&1["shipping"] == "STANDARD")) || List.first(rates) @@ -228,6 +229,18 @@ defmodule SimpleshopTheme.Providers.Printful do end end + # Printful requires state_code for US, CA, and AU + @default_state_codes %{"US" => "NY", "CA" => "ON", "AU" => "NSW"} + + defp build_recipient(country_code) do + base = %{country_code: country_code} + + case @default_state_codes[country_code] do + nil -> base + state -> Map.put(base, :state_code, state) + end + end + # Returns {catalog_product_id, first_catalog_variant_id} per product defp extract_per_product_items(products) do products diff --git a/lib/simpleshop_theme_web/live/admin/providers/form.ex b/lib/simpleshop_theme_web/live/admin/providers/form.ex index 626b24e..5b0c33e 100644 --- a/lib/simpleshop_theme_web/live/admin/providers/form.ex +++ b/lib/simpleshop_theme_web/live/admin/providers/form.ex @@ -5,15 +5,20 @@ defmodule SimpleshopThemeWeb.Admin.Providers.Form do alias SimpleshopTheme.Products.ProviderConnection alias SimpleshopTheme.Providers + @supported_types ~w(printify printful) + @impl true def mount(params, _session, socket) do {:ok, apply_action(socket, socket.assigns.live_action, params)} end - defp apply_action(socket, :new, _params) do + defp apply_action(socket, :new, params) do + provider_type = validated_type(params["type"]) + socket - |> assign(:page_title, "Connect to Printify") - |> assign(:connection, %ProviderConnection{provider_type: "printify"}) + |> assign(:page_title, "Connect to #{provider_label(provider_type)}") + |> assign(:provider_type, provider_type) + |> assign(:connection, %ProviderConnection{provider_type: provider_type}) |> assign(:form, to_form(ProviderConnection.changeset(%ProviderConnection{}, %{}))) |> assign(:testing, false) |> assign(:test_result, nil) @@ -24,7 +29,8 @@ defmodule SimpleshopThemeWeb.Admin.Providers.Form do connection = Products.get_provider_connection!(id) socket - |> assign(:page_title, "Printify settings") + |> assign(:page_title, "#{provider_label(connection.provider_type)} settings") + |> assign(:provider_type, connection.provider_type) |> assign(:connection, connection) |> assign(:form, to_form(ProviderConnection.changeset(connection, %{}))) |> assign(:testing, false) @@ -48,14 +54,13 @@ defmodule SimpleshopThemeWeb.Admin.Providers.Form do def handle_event("test_connection", _params, socket) do socket = assign(socket, testing: true, test_result: nil) - # Use pending_api_key from validation, or fall back to existing encrypted key api_key = socket.assigns[:pending_api_key] || ProviderConnection.get_api_key(socket.assigns.connection) if api_key && api_key != "" do temp_conn = %ProviderConnection{ - provider_type: "printify", + provider_type: socket.assigns.provider_type, api_key_encrypted: encrypt_api_key(api_key) } @@ -72,17 +77,19 @@ defmodule SimpleshopThemeWeb.Admin.Providers.Form do end defp save_connection(socket, :new, params) do + provider_type = socket.assigns.provider_type + params = params - |> Map.put("provider_type", "printify") - |> maybe_add_shop_config(socket.assigns.test_result) - |> maybe_add_name(socket.assigns.test_result) + |> Map.put("provider_type", provider_type) + |> maybe_add_config(provider_type, socket.assigns.test_result) + |> maybe_add_name(provider_type, socket.assigns.test_result) case Products.create_provider_connection(params) do {:ok, _connection} -> {:noreply, socket - |> put_flash(:info, "Connected to Printify!") + |> put_flash(:info, "Connected to #{provider_label(provider_type)}!") |> push_navigate(to: ~p"/admin/settings")} {:error, %Ecto.Changeset{} = changeset} -> @@ -103,19 +110,29 @@ defmodule SimpleshopThemeWeb.Admin.Providers.Form do end end - defp maybe_add_shop_config(params, {:ok, %{shop_id: shop_id}}) do + # Printify returns shop_id, Printful returns store_id + defp maybe_add_config(params, "printify", {:ok, %{shop_id: shop_id}}) do config = Map.get(params, "config", %{}) |> Map.put("shop_id", to_string(shop_id)) Map.put(params, "config", config) end - defp maybe_add_shop_config(params, _), do: params - - defp maybe_add_name(params, {:ok, %{shop_name: shop_name}}) when is_binary(shop_name) do - Map.put_new(params, "name", shop_name) + defp maybe_add_config(params, "printful", {:ok, %{store_id: store_id}}) do + config = Map.get(params, "config", %{}) |> Map.put("store_id", to_string(store_id)) + Map.put(params, "config", config) end - defp maybe_add_name(params, _) do - Map.put_new(params, "name", "Printify") + defp maybe_add_config(params, _type, _result), do: params + + defp maybe_add_name(params, "printify", {:ok, %{shop_name: name}}) when is_binary(name) do + Map.put_new(params, "name", name) + end + + defp maybe_add_name(params, "printful", {:ok, %{store_name: name}}) when is_binary(name) do + Map.put_new(params, "name", name) + end + + defp maybe_add_name(params, type, _result) do + Map.put_new(params, "name", provider_label(type)) end defp encrypt_api_key(api_key) do @@ -125,9 +142,21 @@ defmodule SimpleshopThemeWeb.Admin.Providers.Form do end end - defp format_error(:no_api_key), do: "Please enter your connection key" + defp validated_type(type) when type in @supported_types, do: type + defp validated_type(_), do: "printify" + + # Shared helpers used by the template + + defp provider_label("printful"), do: "Printful" + defp provider_label(_), do: "Printify" + + defp connection_name({:ok, %{shop_name: name}}), do: name + defp connection_name({:ok, %{store_name: name}}), do: name + defp connection_name(_), do: nil + + defp format_error(:no_api_key), do: "Please enter your API key" defp format_error(:unauthorized), do: "That key doesn't seem to be valid" - defp format_error(:timeout), do: "Couldn't reach Printify - try again" + defp format_error(:timeout), do: "Couldn't reach the provider - try again" defp format_error({:http_error, _code}), do: "Something went wrong - try again" defp format_error(error) when is_binary(error), do: error defp format_error(_), do: "Connection failed - check your key and try again" diff --git a/lib/simpleshop_theme_web/live/admin/providers/form.html.heex b/lib/simpleshop_theme_web/live/admin/providers/form.html.heex index d163bcc..3dd0c5d 100644 --- a/lib/simpleshop_theme_web/live/admin/providers/form.html.heex +++ b/lib/simpleshop_theme_web/live/admin/providers/form.html.heex @@ -1,54 +1,84 @@ <.header> - {if @live_action == :new, do: "Connect to Printify", else: "Printify settings"} + {if @live_action == :new, + do: "Connect to #{provider_label(@provider_type)}", + else: "#{provider_label(@provider_type)} settings"}
<%= if @live_action == :new do %>

- Printify is a print-on-demand service that prints and ships products for you. + {provider_label(@provider_type)} is a print-on-demand service that prints and ships products for you. Connect your account to automatically import your products into your shop.

-
-

Get your connection key from Printify:

-
    -
  1. - - Log in to Printify - - (or create a free account) -
  2. -
  3. Click Account (top right)
  4. -
  5. Select Connections from the dropdown
  6. -
  7. Find API tokens and click Generate
  8. -
  9. - Enter a name (e.g. "My Shop"), keep all scopes - selected, and click Generate token -
  10. -
  11. Click Copy to clipboard and paste it below
  12. -
-
+ <%= if @provider_type == "printify" do %> +
+

Get your API key from Printify:

+
    +
  1. + + Log in to Printify + + (or create a free account) +
  2. +
  3. Click Account (top right)
  4. +
  5. Select Connections from the dropdown
  6. +
  7. Find API tokens and click Generate
  8. +
  9. + Enter a name (e.g. "My Shop"), keep all scopes + selected, and click Generate token +
  10. +
  11. Click Copy to clipboard and paste it below
  12. +
+
+ <% else %> +
+

Get your API key from Printful:

+
    +
  1. + + Log in to Printful + + (or create a free account) +
  2. +
  3. Go to SettingsAPI access
  4. +
  5. Click Create API key
  6. +
  7. Give it a name and select all scopes
  8. +
  9. Copy the token and paste it below
  10. +
+
+ <% end %> <% end %> <.form for={@form} id="provider-form" phx-change="validate" phx-submit="save"> - + <.input field={@form[:api_key]} type="password" - label="Printify connection key" + label={"#{provider_label(@provider_type)} API key"} placeholder={ if @live_action == :edit, do: "Leave blank to keep current key", @@ -73,9 +103,10 @@
<%= case @test_result do %> - <% {:ok, info} -> %> + <% {:ok, _info} -> %> - <.icon name="hero-check-circle" class="size-4" /> Connected to {info.shop_name} + <.icon name="hero-check-circle" class="size-4" /> + Connected to {connection_name(@test_result) || provider_label(@provider_type)} <% {:error, reason} -> %> @@ -92,7 +123,9 @@
<.button type="submit" disabled={@testing}> - {if @live_action == :new, do: "Connect to Printify", else: "Save changes"} + {if @live_action == :new, + do: "Connect to #{provider_label(@provider_type)}", + else: "Save changes"} <.link navigate={~p"/admin/providers"} class="btn btn-ghost"> Cancel diff --git a/lib/simpleshop_theme_web/live/admin/providers/index.html.heex b/lib/simpleshop_theme_web/live/admin/providers/index.html.heex index 69f16c2..51c3f1a 100644 --- a/lib/simpleshop_theme_web/live/admin/providers/index.html.heex +++ b/lib/simpleshop_theme_web/live/admin/providers/index.html.heex @@ -1,23 +1,38 @@ <.header> Providers <:actions> - <.button navigate={~p"/admin/providers/new"}> - <.icon name="hero-plus" class="size-4 mr-1" /> Connect Printify - +
Disconnect diff --git a/lib/simpleshop_theme_web/live/admin/settings.ex b/lib/simpleshop_theme_web/live/admin/settings.ex index 51beb83..9a044c9 100644 --- a/lib/simpleshop_theme_web/live/admin/settings.ex +++ b/lib/simpleshop_theme_web/live/admin/settings.ex @@ -59,7 +59,7 @@ defmodule SimpleshopThemeWeb.Admin.Settings do nil end - assign(socket, :printify, connection_info) + assign(socket, :provider, connection_info) end # -- Account assigns -- @@ -339,7 +339,7 @@ defmodule SimpleshopThemeWeb.Admin.Settings do

Products

- <%= if @printify do %> + <%= if @provider do %> <.status_pill color="green"> <.icon name="hero-check-circle-mini" class="size-3" /> Connected @@ -348,8 +348,8 @@ defmodule SimpleshopThemeWeb.Admin.Settings do <% end %>
- <%= if @printify do %> - <.printify_connected printify={@printify} /> + <%= if @provider do %> + <.provider_connected provider={@provider} /> <% else %>

@@ -357,10 +357,10 @@ defmodule SimpleshopThemeWeb.Admin.Settings do

<.link - navigate={~p"/admin/providers/new"} + navigate={~p"/admin/providers"} class="inline-flex items-center gap-2 rounded-md bg-base-content px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-base-content/80" > - <.icon name="hero-plus-mini" class="size-4" /> Connect to Printify + <.icon name="hero-plus-mini" class="size-4" /> Connect a provider
@@ -474,23 +474,24 @@ defmodule SimpleshopThemeWeb.Admin.Settings do """ end - attr :printify, :map, required: true + attr :provider, :map, required: true - defp printify_connected(assigns) do - conn = assigns.printify.connection + defp provider_connected(assigns) do + conn = assigns.provider.connection assigns = assigns |> assign(:connection, conn) - |> assign(:product_count, assigns.printify.product_count) + |> assign(:product_count, assigns.provider.product_count) |> assign(:syncing, conn.sync_status == "syncing") + |> assign(:provider_label, String.capitalize(conn.provider_type)) ~H"""
Provider
-
Printify
+
{@provider_label}
Shop
@@ -534,7 +535,7 @@ defmodule SimpleshopThemeWeb.Admin.Settings do