share provider connection logic between setup wizard and providers form

Extract Products.connect_provider/2 that tests the connection, fetches
shop_id, creates the record, and enqueues sync. Both the setup wizard
and the providers form now use this shared function instead of
duplicating the flow. Also makes the products empty state context-aware
(distinguishes "no provider" from "provider connected but no products").

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey 2026-03-03 15:19:17 +00:00
parent 5b41f3fedf
commit 0853b6f528
6 changed files with 92 additions and 50 deletions

View File

@ -64,6 +64,59 @@ defmodule Berrypod.Products do
|> Repo.insert() |> Repo.insert()
end end
@doc """
Tests an API key, creates the connection with config (shop_id etc),
and enqueues a product sync. Used by both setup wizard and providers form.
Returns `{:ok, connection}` or `{:error, reason}`.
"""
def connect_provider(api_key, provider_type) do
alias Berrypod.Providers
encrypted =
case Berrypod.Vault.encrypt(api_key) do
{:ok, enc} -> enc
_ -> nil
end
temp_conn = %ProviderConnection{
provider_type: provider_type,
api_key_encrypted: encrypted
}
with {:ok, test_result} <- Providers.test_connection(temp_conn) do
name = provider_display_name(provider_type, test_result)
config = provider_config(provider_type, test_result)
params =
%{"api_key" => api_key, "provider_type" => provider_type, "name" => name}
|> then(fn p -> if config != %{}, do: Map.put(p, "config", config), else: p end)
case create_provider_connection(params) do
{:ok, connection} ->
enqueue_sync(connection)
{:ok, connection}
{:error, _} = error ->
error
end
end
end
defp provider_display_name("printify", %{shop_name: name}) when is_binary(name), do: name
defp provider_display_name("printful", %{store_name: name}) when is_binary(name), do: name
defp provider_display_name(type, _) do
case Berrypod.Providers.Provider.get(type) do
nil -> type
info -> info.name
end
end
defp provider_config("printify", %{shop_id: id}), do: %{"shop_id" => to_string(id)}
defp provider_config("printful", %{store_id: id}), do: %{"store_id" => to_string(id)}
defp provider_config(_, _), do: %{}
@doc """ @doc """
Updates a provider connection. Updates a provider connection.
""" """

View File

@ -184,12 +184,19 @@ defmodule BerrypodWeb.Admin.Products do
<div :if={@product_count == 0} class="admin-empty-state"> <div :if={@product_count == 0} class="admin-empty-state">
<.icon name="hero-cube" class="admin-empty-state-icon" /> <.icon name="hero-cube" class="admin-empty-state-icon" />
<p class="admin-empty-state-title">No products yet</p> <p class="admin-empty-state-title">No products yet</p>
<p class="admin-empty-state-text"> <p :if={@connections == []} class="admin-empty-state-text">
<.link navigate={~p"/admin/providers"} class="admin-link"> <.link navigate={~p"/admin/providers"} class="admin-link">
Connect a provider Connect a provider
</.link> </.link>
to sync your products. to sync your products.
</p> </p>
<p :if={@connections != []} class="admin-empty-state-text">
Head to the
<.link navigate={~p"/admin/providers"} class="admin-link">
providers page
</.link>
to sync your products.
</p>
</div> </div>
""" """
end end

View File

@ -82,23 +82,18 @@ defmodule BerrypodWeb.Admin.Providers.Form do
end end
defp save_connection(socket, :new, params) do defp save_connection(socket, :new, params) do
api_key = params["api_key"] || socket.assigns[:pending_api_key]
provider_type = socket.assigns.provider_type provider_type = socket.assigns.provider_type
params = case Products.connect_provider(api_key, provider_type) do
params
|> 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} -> {:ok, _connection} ->
{:noreply, {:noreply,
socket socket
|> put_flash(:info, "Connected to #{socket.assigns.provider.name}!") |> put_flash(:info, "Connected to #{socket.assigns.provider.name}!")
|> push_navigate(to: ~p"/admin/settings")} |> push_navigate(to: ~p"/admin/settings")}
{:error, %Ecto.Changeset{} = changeset} -> {:error, _reason} ->
{:noreply, assign(socket, form: to_form(changeset))} {:noreply, put_flash(socket, :error, "Could not connect — check your API key")}
end end
end end
@ -115,32 +110,6 @@ defmodule BerrypodWeb.Admin.Providers.Form do
end end
end end
# 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_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_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
provider = Provider.get(type)
Map.put_new(params, "name", (provider && provider.name) || type)
end
defp encrypt_api_key(api_key) do defp encrypt_api_key(api_key) do
case Berrypod.Vault.encrypt(api_key) do case Berrypod.Vault.encrypt(api_key) do
{:ok, encrypted} -> encrypted {:ok, encrypted} -> encrypted

View File

@ -127,17 +127,8 @@ defmodule BerrypodWeb.Setup.Onboarding do
else else
socket = assign(socket, provider_connecting: true) socket = assign(socket, provider_connecting: true)
name = case Products.connect_provider(api_key, type) do
case Provider.get(type) do
nil -> type
info -> info.name
end
params = %{"api_key" => api_key, "provider_type" => type, "name" => name}
case Products.create_provider_connection(params) do
{:ok, connection} -> {:ok, connection} ->
Products.enqueue_sync(connection)
setup = Setup.setup_status() setup = Setup.setup_status()
if setup.setup_complete do if setup.setup_complete do
@ -154,11 +145,17 @@ defmodule BerrypodWeb.Setup.Onboarding do
|> put_flash(:info, "Connected! Product sync started in the background.")} |> put_flash(:info, "Connected! Product sync started in the background.")}
end end
{:error, _changeset} -> {:error, :no_api_key} ->
{:noreply, {:noreply,
socket socket
|> assign(:provider_connecting, false) |> assign(:provider_connecting, false)
|> put_flash(:error, "Failed to save connection")} |> put_flash(:error, "Please enter your API token")}
{:error, _reason} ->
{:noreply,
socket
|> assign(:provider_connecting, false)
|> put_flash(:error, "Could not connect — check your API key and try again")}
end end
end end
end end
@ -448,7 +445,13 @@ defmodule BerrypodWeb.Setup.Onboarding do
# ── Helpers ── # ── Helpers ──
defp account_summary(%{current_scope: %{user: user}}) when not is_nil(user) do defp account_summary(%{current_scope: %{user: user}}) when not is_nil(user) do
user.email site_name = Settings.site_name()
if site_name != "Store Name" do
"#{site_name} · #{user.email}"
else
user.email
end
end end
defp account_summary(_), do: "Account created" defp account_summary(_), do: "Account created"

View File

@ -125,7 +125,7 @@ defmodule BerrypodWeb.Admin.ProductsTest do
{:ok, _view, html} = live(conn, ~p"/admin/products") {:ok, _view, html} = live(conn, ~p"/admin/products")
assert html =~ "No products yet" assert html =~ "No products yet"
assert html =~ "Connect a provider" assert html =~ "providers page"
end end
end end

View File

@ -136,6 +136,12 @@ defmodule BerrypodWeb.Admin.ProvidersTest do
describe "form - new" do describe "form - new" do
setup %{conn: conn, user: user} do setup %{conn: conn, user: user} do
Application.put_env(:berrypod, :provider_modules, %{
"printify" => MockProvider
})
on_exit(fn -> Application.delete_env(:berrypod, :provider_modules) end)
%{conn: log_in_user(conn, user)} %{conn: log_in_user(conn, user)}
end end
@ -170,6 +176,10 @@ defmodule BerrypodWeb.Admin.ProvidersTest do
end end
test "saves new connection", %{conn: conn} do test "saves new connection", %{conn: conn} do
expect(MockProvider, :test_connection, fn _conn ->
{:ok, %{shop_name: "My Printify Shop", shop_id: 12345}}
end)
{:ok, view, _html} = live(conn, ~p"/admin/providers/new") {:ok, view, _html} = live(conn, ~p"/admin/providers/new")
{:ok, _view, html} = {:ok, _view, html} =