defmodule BerrypodWeb.Setup.Onboarding do use BerrypodWeb, :live_view alias Berrypod.{Accounts, Products, Settings, Setup} alias Berrypod.Products.ProviderConnection alias Berrypod.Providers.Provider alias Berrypod.Stripe.Setup, as: StripeSetup # ── Mount ── @impl true def mount(_params, _session, socket) do setup = Setup.setup_status() cond do setup.site_live -> {:ok, push_navigate(socket, to: ~p"/")} setup.setup_complete -> {:ok, push_navigate(socket, to: ~p"/admin")} setup.admin_created and is_nil(get_user(socket)) -> {:ok, push_navigate(socket, to: ~p"/users/log-in")} true -> {:ok, mount_setup(socket, setup)} end end defp get_user(socket) do case socket.assigns do %{current_scope: %{user: user}} when not is_nil(user) -> user _ -> nil end end defp mount_setup(socket, setup) do logged_in? = get_user(socket) != nil provider_conn = Products.get_first_provider_connection() socket |> assign(:page_title, "Set up your shop") |> assign(:setup, setup) |> assign(:logged_in?, logged_in?) # Secret gate |> assign(:require_secret?, Setup.require_setup_secret?()) |> assign(:secret_verified, false) |> assign(:secret_form, to_form(%{"secret" => ""}, as: :secret)) # Account (card 1) |> assign(:account_form, to_form(%{"email" => ""}, as: :account)) # Provider (card 2) |> assign(:providers, Provider.all()) |> assign(:selected_provider, nil) |> assign(:provider_form, to_form(%{"api_key" => ""}, as: :provider)) |> assign(:provider_testing, false) |> assign(:provider_test_result, nil) |> assign(:provider_connecting, false) |> assign(:provider_conn, provider_conn) |> assign(:pending_provider_key, nil) # Stripe (card 3) |> assign(:stripe_form, to_form(%{"api_key" => ""}, as: :stripe)) |> assign(:stripe_connecting, false) end # ── Events: Secret gate ── @impl true def handle_event("verify_secret", %{"secret" => %{"secret" => secret}}, socket) do if Plug.Crypto.secure_compare(secret, Setup.setup_secret()) do {:noreply, assign(socket, secret_verified: true)} else {:noreply, put_flash(socket, :error, "Wrong setup secret")} end end # ── Events: Account ── def handle_event("create_account", %{"account" => %{"email" => email}}, socket) do if email == "" do {:noreply, put_flash(socket, :error, "Please enter your email address")} else case Accounts.register_and_confirm_admin(%{email: email}) do {:ok, user} -> token = Accounts.generate_login_token(user) {:noreply, redirect(socket, to: ~p"/setup/login/#{token}")} {:error, :admin_already_exists} -> {:noreply, socket |> put_flash(:error, "An admin account already exists") |> push_navigate(to: ~p"/setup")} {:error, changeset} -> {:noreply, socket |> assign(:account_form, to_form(changeset, as: :account)) |> put_flash(:error, "Could not create account")} end end end # ── Events: Provider ── def handle_event("select_provider", %{"type" => type}, socket) do {:noreply, socket |> assign(:selected_provider, type) |> assign(:provider_form, to_form(%{"api_key" => ""}, as: :provider)) |> assign(:provider_test_result, nil) |> assign(:pending_provider_key, nil)} end def handle_event("validate_provider", %{"provider" => params}, socket) do {:noreply, assign(socket, pending_provider_key: params["api_key"])} end def handle_event("test_provider", _params, socket) do type = socket.assigns.selected_provider api_key = socket.assigns.pending_provider_key if api_key in [nil, ""] do {:noreply, assign(socket, provider_test_result: {:error, :no_api_key})} else socket = assign(socket, provider_testing: true, provider_test_result: nil) temp_conn = %ProviderConnection{ provider_type: type, api_key_encrypted: encrypt_api_key(api_key) } result = Berrypod.Providers.test_connection(temp_conn) {:noreply, assign(socket, provider_testing: false, provider_test_result: result)} end end def handle_event("connect_provider", %{"provider" => %{"api_key" => api_key}}, socket) do type = socket.assigns.selected_provider if api_key == "" do {:noreply, put_flash(socket, :error, "Please enter your API token")} else socket = assign(socket, provider_connecting: true) params = %{"api_key" => api_key, "provider_type" => type} |> maybe_add_shop_config(socket.assigns.provider_test_result) |> maybe_add_name(socket.assigns.provider_test_result, type) case Products.create_provider_connection(params) do {:ok, connection} -> Products.enqueue_sync(connection) setup = %{ socket.assigns.setup | provider_connected: true, provider_type: type } {:noreply, socket |> assign(:provider_connecting, false) |> assign(:provider_conn, connection) |> assign(:setup, setup) |> put_flash(:info, "Connected! Product sync started in the background.")} {:error, _changeset} -> {:noreply, socket |> assign(:provider_connecting, false) |> put_flash(:error, "Failed to save connection")} end 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} -> setup = %{socket.assigns.setup | stripe_connected: true} setup = if setup.admin_created and setup.provider_connected do %{setup | setup_complete: true} else setup end {:noreply, socket |> assign(:stripe_connecting, false) |> assign(:setup, setup) |> put_flash(:info, "Stripe connected")} {:error, message} -> {:noreply, socket |> assign(:stripe_connecting, false) |> put_flash(:error, "Stripe connection failed: #{message}")} end end end # ── Render ── @impl true def render(assigns) do ~H"""
Enter the setup secret from your server logs to get started.
<% else %>Connect your accounts to get going.
<% end %>
Find the setup secret in your server logs or set the SETUP_SECRET
environment variable.
Enter your email to create the admin account.
<.form for={@account_form} phx-submit="create_account"> <.input field={@account_form[:email]} type="email" label="Email address" autocomplete="email" required phx-mounted={JS.focus()} />Head to the dashboard to sync products, customise your theme, and go live.
<.link navigate={~p"/admin"} class="admin-btn admin-btn-primary"> Go to dashboard{@summary}
<% else %>Choose a print-on-demand provider and connect your API key.
{provider_info.setup_hint}. Open {provider_info.name} →
<.form for={@form} phx-change="validate_provider" phx-submit="connect_provider"> <.input field={@form[:api_key]} type="password" label="API token" placeholder="Paste your token here" autocomplete="off" />Enter your Stripe secret key to accept payments. Open Stripe dashboard →
<.form for={@form} phx-submit="connect_stripe"> <.input field={@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.