extract site_name and site_description from theme settings into standalone settings
site_name and site_description are shop identity, not theme concerns. They now live in the Settings table as first-class settings with their own assigns (@site_name, @site_description) piped through hooks and plugs. The setup wizard writes site_name on account creation, and the theme editor reads/writes via Settings.put_setting. Removed the "configure your shop" checklist item since currency/country aren't built yet. Also adds shop name field to setup wizard step 1. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,6 @@ 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
|
||||
|
||||
@@ -38,25 +37,30 @@ defmodule BerrypodWeb.Setup.Onboarding do
|
||||
logged_in? = get_user(socket) != nil
|
||||
provider_conn = Products.get_first_provider_connection()
|
||||
|
||||
current_step =
|
||||
cond do
|
||||
not logged_in? -> 1
|
||||
not setup.provider_connected -> 2
|
||||
true -> 3
|
||||
end
|
||||
|
||||
socket
|
||||
|> assign(:page_title, "Set up your shop")
|
||||
|> assign(:setup, setup)
|
||||
|> assign(:logged_in?, logged_in?)
|
||||
|> assign(:current_step, current_step)
|
||||
# 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))
|
||||
|> assign(:account_form, to_form(%{"email" => "", "shop_name" => ""}, 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)
|
||||
@@ -75,10 +79,17 @@ defmodule BerrypodWeb.Setup.Onboarding do
|
||||
|
||||
# ── Events: Account ──
|
||||
|
||||
def handle_event("create_account", %{"account" => %{"email" => email}}, socket) do
|
||||
def handle_event("create_account", %{"account" => params}, socket) do
|
||||
email = params["email"]
|
||||
shop_name = String.trim(params["shop_name"] || "")
|
||||
|
||||
if email == "" do
|
||||
{:noreply, put_flash(socket, :error, "Please enter your email address")}
|
||||
else
|
||||
if shop_name != "" do
|
||||
Settings.put_setting("site_name", shop_name, "string")
|
||||
end
|
||||
|
||||
case Accounts.register_and_confirm_admin(%{email: email}) do
|
||||
{:ok, user} ->
|
||||
token = Accounts.generate_login_token(user)
|
||||
@@ -105,33 +116,7 @@ defmodule BerrypodWeb.Setup.Onboarding 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
|
||||
|> assign(:provider_form, to_form(%{"api_key" => ""}, as: :provider))}
|
||||
end
|
||||
|
||||
def handle_event("connect_provider", %{"provider" => %{"api_key" => api_key}}, socket) do
|
||||
@@ -142,22 +127,32 @@ defmodule BerrypodWeb.Setup.Onboarding do
|
||||
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)
|
||||
name =
|
||||
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} ->
|
||||
Products.enqueue_sync(connection)
|
||||
setup = Setup.setup_status()
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:provider_connecting, false)
|
||||
|> assign(:provider_conn, connection)
|
||||
|> assign(:setup, setup)
|
||||
|> put_flash(:info, "Connected! Product sync started in the background.")}
|
||||
if setup.setup_complete do
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "You're in! Here's your launch checklist.")
|
||||
|> push_navigate(to: ~p"/admin")}
|
||||
else
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:provider_connecting, false)
|
||||
|> assign(:provider_conn, connection)
|
||||
|> assign(:setup, setup)
|
||||
|> put_flash(:info, "Connected! Product sync started in the background.")}
|
||||
end
|
||||
|
||||
{:error, _changeset} ->
|
||||
{:noreply,
|
||||
@@ -180,11 +175,18 @@ defmodule BerrypodWeb.Setup.Onboarding do
|
||||
{:ok, _result} ->
|
||||
setup = Setup.setup_status()
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:stripe_connecting, false)
|
||||
|> assign(:setup, setup)
|
||||
|> put_flash(:info, "Stripe connected")}
|
||||
if setup.setup_complete do
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "You're in! Here's your launch checklist.")
|
||||
|> push_navigate(to: ~p"/admin")}
|
||||
else
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:stripe_connecting, false)
|
||||
|> assign(:setup, setup)
|
||||
|> put_flash(:info, "Stripe connected")}
|
||||
end
|
||||
|
||||
{:error, message} ->
|
||||
{:noreply,
|
||||
@@ -243,20 +245,27 @@ defmodule BerrypodWeb.Setup.Onboarding do
|
||||
<%!-- All three setup cards --%>
|
||||
<div class="setup-sections">
|
||||
<.section_card
|
||||
title="Create admin account"
|
||||
title="Set up your account"
|
||||
number={1}
|
||||
done={@logged_in?}
|
||||
summary={account_summary(assigns)}
|
||||
>
|
||||
<p class="setup-hint">Enter your email to create the admin account.</p>
|
||||
<p class="setup-hint">Name your shop and create the admin account.</p>
|
||||
<.form for={@account_form} phx-submit="create_account">
|
||||
<.input
|
||||
field={@account_form[:shop_name]}
|
||||
type="text"
|
||||
label="Shop name"
|
||||
placeholder="e.g. Acme Prints"
|
||||
autocomplete="off"
|
||||
phx-mounted={@current_step == 1 && JS.focus()}
|
||||
/>
|
||||
<.input
|
||||
field={@account_form[:email]}
|
||||
type="email"
|
||||
label="Email address"
|
||||
autocomplete="email"
|
||||
required
|
||||
phx-mounted={JS.focus()}
|
||||
/>
|
||||
<div class="setup-actions">
|
||||
<.button phx-disable-with="Creating account...">Create account</.button>
|
||||
@@ -274,8 +283,6 @@ defmodule BerrypodWeb.Setup.Onboarding do
|
||||
providers={@providers}
|
||||
selected={@selected_provider}
|
||||
form={@provider_form}
|
||||
testing={@provider_testing}
|
||||
test_result={@provider_test_result}
|
||||
connecting={@provider_connecting}
|
||||
/>
|
||||
</.section_card>
|
||||
@@ -289,6 +296,7 @@ defmodule BerrypodWeb.Setup.Onboarding do
|
||||
<.stripe_section
|
||||
form={@stripe_form}
|
||||
connecting={@stripe_connecting}
|
||||
focus={@current_step == 3}
|
||||
/>
|
||||
</.section_card>
|
||||
</div>
|
||||
@@ -346,8 +354,6 @@ defmodule BerrypodWeb.Setup.Onboarding do
|
||||
attr :providers, :list, required: true
|
||||
attr :selected, :string, default: nil
|
||||
attr :form, :any, required: true
|
||||
attr :testing, :boolean, required: true
|
||||
attr :test_result, :any, default: nil
|
||||
attr :connecting, :boolean, required: true
|
||||
|
||||
defp provider_section(assigns) do
|
||||
@@ -376,7 +382,7 @@ defmodule BerrypodWeb.Setup.Onboarding do
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<.form for={@form} phx-change="validate_provider" phx-submit="connect_provider">
|
||||
<.form for={@form} phx-submit="connect_provider">
|
||||
<.input
|
||||
field={@form[:api_key]}
|
||||
type="password"
|
||||
@@ -386,58 +392,21 @@ defmodule BerrypodWeb.Setup.Onboarding do
|
||||
/>
|
||||
|
||||
<div class="setup-actions">
|
||||
<button
|
||||
type="button"
|
||||
phx-click="test_provider"
|
||||
disabled={@testing}
|
||||
class="admin-btn admin-btn-secondary"
|
||||
>
|
||||
<%= if @testing do %>
|
||||
<.icon name="hero-arrow-path" class="size-4 animate-spin" /> Checking...
|
||||
<% else %>
|
||||
<.icon name="hero-signal" class="size-4" /> Check connection
|
||||
<% end %>
|
||||
</button>
|
||||
<.button type="submit" disabled={@connecting or @testing}>
|
||||
<.button type="submit" disabled={@connecting}>
|
||||
{if @connecting, do: "Connecting...", else: "Connect"}
|
||||
</.button>
|
||||
</div>
|
||||
|
||||
<.provider_test_feedback :if={@test_result} result={@test_result} />
|
||||
</.form>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
attr :result, :any, required: true
|
||||
|
||||
defp provider_test_feedback(assigns) do
|
||||
~H"""
|
||||
<div class="setup-test-result">
|
||||
<%= case @result do %>
|
||||
<% {:ok, info} -> %>
|
||||
<span class="setup-test-ok">
|
||||
<.icon name="hero-check-circle" class="size-4" />
|
||||
Connected{if info[:shop_name], do: " to #{info.shop_name}", else: ""}
|
||||
</span>
|
||||
<% {:error, :no_api_key} -> %>
|
||||
<span class="setup-test-error">
|
||||
<.icon name="hero-x-circle" class="size-4" /> Please enter your API token
|
||||
</span>
|
||||
<% {:error, reason} -> %>
|
||||
<span class="setup-test-error">
|
||||
<.icon name="hero-x-circle" class="size-4" /> {format_error(reason)}
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
# ── Stripe section ──
|
||||
|
||||
attr :form, :any, required: true
|
||||
attr :connecting, :boolean, required: true
|
||||
attr :focus, :boolean, default: false
|
||||
|
||||
defp stripe_section(assigns) do
|
||||
~H"""
|
||||
@@ -461,13 +430,14 @@ defmodule BerrypodWeb.Setup.Onboarding do
|
||||
label="Secret key"
|
||||
autocomplete="off"
|
||||
placeholder="sk_test_... or sk_live_..."
|
||||
phx-mounted={@focus && JS.focus()}
|
||||
/>
|
||||
<p class="setup-key-hint">
|
||||
Starts with <code>sk_test_</code> or <code>sk_live_</code>. Encrypted at rest.
|
||||
</p>
|
||||
<div class="setup-actions">
|
||||
<.button phx-disable-with="Connecting...">
|
||||
{if @connecting, do: "Connecting...", else: "Connect Stripe"}
|
||||
{if @connecting, do: "Connecting...", else: "Connect"}
|
||||
</.button>
|
||||
</div>
|
||||
</.form>
|
||||
@@ -501,31 +471,6 @@ defmodule BerrypodWeb.Setup.Onboarding do
|
||||
|
||||
defp stripe_summary(_), do: nil
|
||||
|
||||
defp encrypt_api_key(api_key) do
|
||||
case Berrypod.Vault.encrypt(api_key) do
|
||||
{:ok, encrypted} -> encrypted
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_add_shop_config(params, {: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: name}}, _type) when is_binary(name) do
|
||||
Map.put_new(params, "name", name)
|
||||
end
|
||||
|
||||
defp maybe_add_name(params, _, type) do
|
||||
case Provider.get(type) do
|
||||
nil -> Map.put_new(params, "name", type)
|
||||
info -> Map.put_new(params, "name", info.name)
|
||||
end
|
||||
end
|
||||
|
||||
defp provider_card_options(providers) do
|
||||
Enum.map(providers, fn provider ->
|
||||
option = %{
|
||||
@@ -541,11 +486,4 @@ defmodule BerrypodWeb.Setup.Onboarding do
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp format_error(:unauthorized), do: "That token doesn't seem to be valid"
|
||||
defp format_error(:timeout), do: "Couldn't reach the provider — try again"
|
||||
defp format_error(:provider_not_implemented), do: "This provider isn't supported yet"
|
||||
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 token and try again"
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user