defmodule BerrypodWeb.Admin.Setup do use BerrypodWeb, :live_view alias Berrypod.{Products, Settings, Setup} alias Berrypod.Products.ProviderConnection alias Berrypod.Providers alias Berrypod.Stripe.Setup, as: StripeSetup @impl true def mount(_params, _session, socket) do if Settings.site_live?() do {:ok, push_navigate(socket, to: ~p"/admin")} else status = Setup.setup_status() conn = Products.get_provider_connection_by_type("printify") if conn && connected?(socket) do Phoenix.PubSub.subscribe(Berrypod.PubSub, "sync:#{conn.id}") end active_step = determine_active_step(status) {:ok, socket |> assign(:page_title, "Get started") |> assign(:setup, status) |> assign(:active_step, active_step) # Printify state |> assign(:printify_conn, conn) |> assign(:printify_form, to_form(%{"api_key" => ""}, as: :printify)) |> assign(:printify_testing, false) |> assign(:printify_test_result, nil) |> assign(:printify_saving, false) |> assign(:pending_api_key, nil) |> assign(:sync_status, conn && conn.sync_status) # Stripe state |> assign(:stripe_form, to_form(%{"api_key" => ""}, as: :stripe)) |> assign(:stripe_connecting, false) |> assign(:stripe_api_key_hint, Settings.secret_hint("stripe_api_key")) # Celebration |> assign(:just_went_live, false)} end end # -- Step determination -- defp determine_active_step(status) do cond do !status.printify_connected -> :printify !status.products_synced -> :printify !status.stripe_connected -> :stripe !status.site_live -> :go_live true -> :complete end end # -- Events: Printify -- @impl true def handle_event("validate_printify", %{"printify" => params}, socket) do {:noreply, assign(socket, pending_api_key: params["api_key"])} end def handle_event("test_printify", _params, socket) do api_key = socket.assigns.pending_api_key if api_key in [nil, ""] do {:noreply, assign(socket, printify_test_result: {:error, :no_api_key})} else socket = assign(socket, printify_testing: true, printify_test_result: nil) temp_conn = %ProviderConnection{ provider_type: "printify", api_key_encrypted: encrypt_api_key(api_key) } result = Providers.test_connection(temp_conn) {:noreply, assign(socket, printify_testing: false, printify_test_result: result)} end end def handle_event("connect_printify", %{"printify" => %{"api_key" => api_key}}, socket) do if api_key == "" do {:noreply, put_flash(socket, :error, "Please enter your Printify API token")} else socket = assign(socket, printify_saving: true) params = %{"api_key" => api_key, "provider_type" => "printify"} |> maybe_add_shop_config(socket.assigns.printify_test_result) |> maybe_add_name(socket.assigns.printify_test_result) case Products.create_provider_connection(params) do {:ok, connection} -> Products.enqueue_sync(connection) if connected?(socket) do Phoenix.PubSub.subscribe(Berrypod.PubSub, "sync:#{connection.id}") end status = %{socket.assigns.setup | printify_connected: true} {:noreply, socket |> assign(:printify_saving, false) |> assign(:printify_conn, connection) |> assign(:sync_status, "syncing") |> assign(:setup, status) |> put_flash(:info, "Connected to Printify! Syncing products...")} {:error, _changeset} -> {:noreply, socket |> assign(:printify_saving, false) |> put_flash(:error, "Failed to save connection")} end end end def handle_event("retry_sync", _params, socket) do conn = socket.assigns.printify_conn if conn do Products.enqueue_sync(conn) {:noreply, assign(socket, sync_status: "syncing")} else {:noreply, socket} end end # -- Events: Stripe -- def handle_event("connect_stripe", %{"stripe" => %{"api_key" => api_key}}, socket) do if api_key == "" do {:noreply, put_flash(socket, :error, "Please enter your Stripe secret key")} else socket = assign(socket, stripe_connecting: true) case StripeSetup.connect(api_key) do {:ok, _result} -> status = %{socket.assigns.setup | stripe_connected: true, can_go_live: true} {:noreply, socket |> assign(:stripe_connecting, false) |> assign(:setup, status) |> assign(:stripe_api_key_hint, Settings.secret_hint("stripe_api_key")) |> assign(:active_step, :go_live) |> put_flash(:info, "Stripe connected")} {:error, message} -> {:noreply, socket |> assign(:stripe_connecting, false) |> put_flash(:error, "Stripe connection failed: #{message}")} end end end # -- Events: Go live -- def handle_event("go_live", _params, socket) do {:ok, _} = Settings.set_site_live(true) status = %{socket.assigns.setup | site_live: true} {:noreply, socket |> assign(:setup, status) |> assign(:just_went_live, true)} end # -- Events: Step navigation -- def handle_event("toggle_step", %{"step" => step}, socket) do step = String.to_existing_atom(step) new_active = if socket.assigns.active_step == step do determine_active_step(socket.assigns.setup) else step end {:noreply, assign(socket, active_step: new_active)} end # -- PubSub: Sync progress -- @impl true def handle_info({:sync_status, "completed", product_count}, socket) do status = %{ socket.assigns.setup | products_synced: true, product_count: product_count } active_step = if status.stripe_connected, do: :go_live, else: :stripe {:noreply, socket |> assign(:setup, status) |> assign(:sync_status, "completed") |> assign(:active_step, active_step) |> put_flash(:info, "#{product_count} products synced")} end def handle_info({:sync_status, "failed"}, socket) do {:noreply, socket |> assign(:sync_status, "failed") |> put_flash(:error, "Product sync failed — try again")} end def handle_info({:sync_status, status}, socket) do {:noreply, assign(socket, sync_status: status)} end # -- Render -- @impl true def render(assigns) do ~H""" <.header> Get started <%!-- Celebration state --%> <.celebration :if={@just_went_live} /> <%!-- Setup stepper --%> <.setup_stepper :if={!@just_went_live} setup={@setup} active_step={@active_step} printify_conn={@printify_conn} printify_form={@printify_form} printify_testing={@printify_testing} printify_test_result={@printify_test_result} printify_saving={@printify_saving} sync_status={@sync_status} stripe_form={@stripe_form} stripe_connecting={@stripe_connecting} stripe_api_key_hint={@stripe_api_key_hint} /> """ end # ========================================================================== # Setup stepper components # ========================================================================== attr :setup, :map, required: true attr :active_step, :atom, required: true attr :printify_conn, :any, required: true attr :printify_form, :any, required: true attr :printify_testing, :boolean, required: true attr :printify_test_result, :any, required: true attr :printify_saving, :boolean, required: true attr :sync_status, :string, required: true attr :stripe_form, :any, required: true attr :stripe_connecting, :boolean, required: true attr :stripe_api_key_hint, :string, required: true defp setup_stepper(assigns) do ~H"""
{render_slot(@summary)}
<%!-- Expanded content --%>Connect your Printify account to import products. Get an API token from Printify → Account → Connections .
<.form for={@printify_form} phx-change="validate_printify" phx-submit="connect_printify" > <.input field={@printify_form[:api_key]} type="password" label="Printify API token" placeholder="Paste your token here" autocomplete="off" />Product sync failed.
{@setup.product_count} products synced from Printify.
Enter your Stripe secret key to accept payments. Find it in your Stripe dashboard under Developers → API keys.
<.form for={@stripe_form} phx-submit="connect_stripe"> <.input field={@stripe_form[:api_key]} type="password" label="Secret key" autocomplete="off" placeholder="sk_test_... or sk_live_..." />
Starts with sk_test_ or sk_live_. Encrypted at rest.
Your shop is ready. Visitors currently see a "coming soon" page — hit the button to make it live.
Customers can now browse and buy from your shop.