Some checks failed
deploy / deploy (push) Has been cancelled
Tasks C, H, I from the plan: - Forgiving API key validation: add Printify UUID format and Printful length validation, validate on blur for fast feedback, helpful error messages with specific guidance - External links UX: verified all external links use <.external_link> component with target="_blank", rel="noopener noreferrer", icon, and screen reader text - Input styling WCAG compliance: increase input border contrast from ~3.3:1 to ~4.5-5:1 across all theme moods (neutral, warm, cool, dark) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
131 lines
4.1 KiB
Elixir
131 lines
4.1 KiB
Elixir
defmodule BerrypodWeb.Admin.Providers.Form do
|
|
use BerrypodWeb, :live_view
|
|
|
|
alias Berrypod.{KeyValidation, Products}
|
|
alias Berrypod.Products.ProviderConnection
|
|
alias Berrypod.Providers.Provider
|
|
|
|
@supported_types Enum.map(Provider.available(), & &1.type)
|
|
|
|
@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
|
|
provider_type = validated_type(params["type"])
|
|
provider = Provider.get(provider_type)
|
|
|
|
socket
|
|
|> assign(:page_title, "Connect to #{provider.name}")
|
|
|> assign(:provider_type, provider_type)
|
|
|> assign(:provider, provider)
|
|
|> assign(:connection, %ProviderConnection{provider_type: provider_type})
|
|
|> assign(:form, to_form(ProviderConnection.changeset(%ProviderConnection{}, %{})))
|
|
|> assign(:pending_api_key, nil)
|
|
end
|
|
|
|
defp apply_action(socket, :edit, %{"id" => id}) do
|
|
connection = Products.get_provider_connection!(id)
|
|
provider = Provider.get(connection.provider_type)
|
|
|
|
socket
|
|
|> assign(:page_title, "#{provider.name} settings")
|
|
|> assign(:provider_type, connection.provider_type)
|
|
|> assign(:provider, provider)
|
|
|> assign(:connection, connection)
|
|
|> assign(:form, to_form(ProviderConnection.changeset(connection, %{})))
|
|
|> assign(:pending_api_key, nil)
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("validate", %{"provider_connection" => params}, socket) do
|
|
api_key = params["api_key"] || ""
|
|
provider_type = socket.assigns.provider_type
|
|
|
|
# Build base changeset
|
|
changeset =
|
|
socket.assigns.connection
|
|
|> ProviderConnection.changeset(params)
|
|
|> Map.put(:action, :validate)
|
|
|
|
# Add key format validation error if key is present
|
|
form =
|
|
if api_key != "" do
|
|
case KeyValidation.validate_provider_key(api_key, provider_type) do
|
|
{:ok, _} ->
|
|
to_form(changeset)
|
|
|
|
{:error, message} ->
|
|
changeset
|
|
|> Ecto.Changeset.add_error(:api_key, message)
|
|
|> to_form()
|
|
end
|
|
else
|
|
to_form(changeset)
|
|
end
|
|
|
|
# Store api_key separately since changeset encrypts it immediately
|
|
{:noreply, assign(socket, form: form, pending_api_key: api_key)}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("save", %{"provider_connection" => params}, socket) do
|
|
save_connection(socket, socket.assigns.live_action, params)
|
|
end
|
|
|
|
defp save_connection(socket, :new, params) do
|
|
api_key = params["api_key"] || socket.assigns[:pending_api_key]
|
|
provider_type = socket.assigns.provider_type
|
|
|
|
case KeyValidation.validate_provider_key(api_key, provider_type) do
|
|
{:error, message} ->
|
|
form = form_with_api_key_error(socket, api_key, message)
|
|
{:noreply, assign(socket, :form, form)}
|
|
|
|
{:ok, api_key} ->
|
|
case Products.connect_provider(api_key, provider_type) do
|
|
{:ok, _connection} ->
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:info, "Connected to #{socket.assigns.provider.name}!")
|
|
|> push_navigate(to: ~p"/admin/settings")}
|
|
|
|
{:error, _reason} ->
|
|
form =
|
|
form_with_api_key_error(
|
|
socket,
|
|
api_key,
|
|
"Could not connect. Check your API key and try again"
|
|
)
|
|
|
|
{:noreply, assign(socket, :form, form)}
|
|
end
|
|
end
|
|
end
|
|
|
|
defp save_connection(socket, :edit, params) do
|
|
case Products.update_provider_connection(socket.assigns.connection, params) do
|
|
{:ok, _connection} ->
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:info, "Settings saved")
|
|
|> push_navigate(to: ~p"/admin/settings")}
|
|
|
|
{:error, %Ecto.Changeset{} = changeset} ->
|
|
{:noreply, assign(socket, form: to_form(changeset))}
|
|
end
|
|
end
|
|
|
|
defp form_with_api_key_error(socket, api_key, message) do
|
|
socket.assigns.connection
|
|
|> ProviderConnection.changeset(%{"api_key" => api_key})
|
|
|> Ecto.Changeset.add_error(:api_key, message)
|
|
|> Map.put(:action, :validate)
|
|
|> to_form()
|
|
end
|
|
|
|
defp validated_type(type) when type in @supported_types, do: type
|
|
defp validated_type(_), do: "printify"
|
|
end
|