diff --git a/lib/simpleshop_theme/sync/product_sync_worker.ex b/lib/simpleshop_theme/sync/product_sync_worker.ex
index a132911..1fd925a 100644
--- a/lib/simpleshop_theme/sync/product_sync_worker.ex
+++ b/lib/simpleshop_theme/sync/product_sync_worker.ex
@@ -66,6 +66,7 @@ defmodule SimpleshopTheme.Sync.ProductSyncWorker do
defp sync_products(conn) do
Logger.info("Starting product sync for #{conn.provider_type} (#{conn.id})")
Products.update_sync_status(conn, "syncing")
+ broadcast_sync(conn.id, {:sync_status, "syncing"})
try do
do_sync_products(conn)
@@ -73,6 +74,7 @@ defmodule SimpleshopTheme.Sync.ProductSyncWorker do
e ->
Logger.error("Product sync crashed for #{conn.provider_type}: #{Exception.message(e)}")
Products.update_sync_status(conn, "failed")
+ broadcast_sync(conn.id, {:sync_status, "failed"})
{:error, :sync_crashed}
end
end
@@ -95,11 +97,14 @@ defmodule SimpleshopTheme.Sync.ProductSyncWorker do
)
Products.update_sync_status(conn, "completed", DateTime.utc_now())
+ product_count = Products.count_products_for_connection(conn.id)
+ broadcast_sync(conn.id, {:sync_status, "completed", product_count})
:ok
else
{:error, reason} = error ->
Logger.error("Product sync failed for #{conn.provider_type}: #{inspect(reason)}")
Products.update_sync_status(conn, "failed")
+ broadcast_sync(conn.id, {:sync_status, "failed"})
error
end
end
@@ -146,6 +151,10 @@ defmodule SimpleshopTheme.Sync.ProductSyncWorker do
Products.upsert_product(conn, attrs)
end
+ defp broadcast_sync(conn_id, message) do
+ Phoenix.PubSub.broadcast(SimpleshopTheme.PubSub, "sync:#{conn_id}", message)
+ end
+
defp sync_product_associations(product, product_data) do
# Sync images
images =
diff --git a/lib/simpleshop_theme_web/live/admin/dashboard.ex b/lib/simpleshop_theme_web/live/admin/dashboard.ex
index 82a0472..71e4cdc 100644
--- a/lib/simpleshop_theme_web/live/admin/dashboard.ex
+++ b/lib/simpleshop_theme_web/live/admin/dashboard.ex
@@ -1,7 +1,10 @@
defmodule SimpleshopThemeWeb.Admin.Dashboard do
use SimpleshopThemeWeb, :live_view
- alias SimpleshopTheme.{Cart, Orders, Setup}
+ alias SimpleshopTheme.{Cart, Orders, Products, Settings, Setup}
+ alias SimpleshopTheme.Products.ProviderConnection
+ alias SimpleshopTheme.Providers
+ alias SimpleshopTheme.Stripe.Setup, as: StripeSetup
@impl true
def mount(_params, _session, socket) do
@@ -10,15 +13,215 @@ defmodule SimpleshopThemeWeb.Admin.Dashboard do
paid_count = Map.get(status_counts, "paid", 0)
recent_orders = Orders.list_orders(status: "paid") |> Enum.take(5)
+ conn = Products.get_provider_connection_by_type("printify")
+
+ if conn && connected?(socket) do
+ Phoenix.PubSub.subscribe(SimpleshopTheme.PubSub, "sync:#{conn.id}")
+ end
+
+ active_step = determine_active_step(status)
+
{:ok,
socket
|> assign(:page_title, "Dashboard")
|> 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)
+ # Stats
|> assign(:paid_count, paid_count)
|> assign(:revenue, Orders.total_revenue())
|> assign(:recent_orders, recent_orders)}
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(SimpleshopTheme.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"""
@@ -26,8 +229,24 @@ defmodule SimpleshopThemeWeb.Admin.Dashboard do
Dashboard
- <%!-- Setup checklist (when not fully set up) --%>
- <.setup_checklist :if={!@setup.site_live} setup={@setup} />
+ <%!-- Celebration state --%>
+ <.celebration :if={@just_went_live} />
+
+ <%!-- Setup stepper (when not live and not celebrating) --%>
+ <.setup_stepper
+ :if={!@setup.site_live and !@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}
+ />
<%!-- Stats --%>
@@ -55,14 +274,17 @@ defmodule SimpleshopThemeWeb.Admin.Dashboard do
Recent orders
- <.link navigate={~p"/admin/orders"} class="text-sm text-zinc-500 hover:text-zinc-700">
+ <.link
+ navigate={~p"/admin/orders"}
+ class="text-sm text-base-content/60 hover:text-base-content"
+ >
View all →
<%= if @recent_orders == [] do %>
-
- <.icon name="hero-inbox" class="size-10 mx-auto mb-3 text-zinc-300" />
+
+ <.icon name="hero-inbox" class="size-10 mx-auto mb-3 text-base-content/30" />
No orders yet
Orders will appear here once customers check out.
@@ -70,7 +292,7 @@ defmodule SimpleshopThemeWeb.Admin.Dashboard do
-
+
| Order |
Date |
Customer |
@@ -81,12 +303,12 @@ defmodule SimpleshopThemeWeb.Admin.Dashboard do
| {order.order_number} |
- {format_date(order.inserted_at)} |
- {order.customer_email || "—"} |
+ {format_date(order.inserted_at)} |
+ {order.customer_email || "—"} |
{Cart.format_price(order.total)} |
<.fulfilment_pill status={order.fulfilment_status} /> |
@@ -98,58 +320,385 @@ defmodule SimpleshopThemeWeb.Admin.Dashboard do
"""
end
- # -- Function components --
+ # ==========================================================================
+ # 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_checklist(assigns) do
- steps = [
- %{done: assigns.setup.admin_created, label: "Create admin account", href: nil},
- %{
- done: assigns.setup.printify_connected,
- label: "Connect to Printify",
- href: ~p"/admin/providers/new"
- },
- %{done: assigns.setup.products_synced, label: "Sync products", href: nil},
- %{done: assigns.setup.stripe_connected, label: "Connect Stripe", href: ~p"/admin/settings"},
- %{done: assigns.setup.site_live, label: "Go live", href: ~p"/admin/settings"}
- ]
-
- done_count = Enum.count(steps, & &1.done)
- assigns = assign(assigns, steps: steps, done_count: done_count, total: length(steps))
-
+ defp setup_stepper(assigns) do
~H"""
-
-
-
Setup progress
- {@done_count}/{@total}
-
-
-
+
+ <%!-- Step 1: Printify --%>
+ <.setup_step
+ step={:printify}
+ number={1}
+ title="Connect to Printify"
+ active_step={@active_step}
+ done={@setup.printify_connected and @setup.products_synced}
+ last={false}
+ next_done={@setup.stripe_connected}
>
-
-
-
- -
- <%= if step.done do %>
- <.icon name="hero-check-circle" class="size-5 text-green-500 shrink-0" />
- {step.label}
- <% else %>
- <.icon name="hero-circle-stack" class="size-5 text-zinc-300 shrink-0" />
- <%= if step.href do %>
- <.link navigate={step.href} class="text-zinc-700 hover:underline">{step.label}
- <% else %>
- {step.label}
- <% end %>
- <% end %>
-
-
+ <:summary :if={@setup.printify_connected and @setup.products_synced}>
+ Connected · {@setup.product_count} products synced
+
+ <:content>
+ <.printify_step_content
+ setup={@setup}
+ 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}
+ />
+
+
+
+ <%!-- Step 2: Stripe --%>
+ <.setup_step
+ step={:stripe}
+ number={2}
+ title="Connect Stripe"
+ active_step={@active_step}
+ done={@setup.stripe_connected}
+ last={false}
+ next_done={@setup.site_live}
+ >
+ <:summary :if={@setup.stripe_connected}>
+ Connected · {@stripe_api_key_hint}
+
+ <:content>
+ <.stripe_step_content
+ stripe_form={@stripe_form}
+ stripe_connecting={@stripe_connecting}
+ />
+
+
+
+ <%!-- Step 3: Go live --%>
+ <.setup_step
+ step={:go_live}
+ number={3}
+ title="Go live"
+ active_step={@active_step}
+ done={@setup.site_live}
+ last={true}
+ next_done={false}
+ >
+ <:content>
+ <.go_live_step_content setup={@setup} />
+
+
+
"""
end
+ attr :step, :atom, required: true
+ attr :number, :integer, required: true
+ attr :title, :string, required: true
+ attr :active_step, :atom, required: true
+ attr :done, :boolean, required: true
+ attr :last, :boolean, required: true
+ attr :next_done, :boolean, required: true
+
+ slot :summary
+ slot :content, required: true
+
+ defp setup_step(assigns) do
+ is_active = assigns.active_step == assigns.step
+ is_clickable = assigns.done
+
+ assigns =
+ assigns
+ |> assign(:is_active, is_active)
+ |> assign(:is_clickable, is_clickable)
+
+ ~H"""
+
+ <%!-- Connector line --%>
+
+
+ <%!-- Step circle --%>
+ "bg-green-500 text-white"
+ @is_active -> "bg-base-content text-white"
+ true -> "bg-base-200 text-base-content/40"
+ end
+ ]}>
+ <%= if @done do %>
+ <.icon name="hero-check-mini" class="size-5" />
+ <% else %>
+ {@number}
+ <% end %>
+
+
+ <%!-- Step header --%>
+ <%= if @is_clickable do %>
+
+ <% else %>
+
+ {@title}
+
+ <% end %>
+
+ <%!-- Collapsed summary for completed steps --%>
+
+ {render_slot(@summary)}
+
+
+ <%!-- Expanded content --%>
+
+ {render_slot(@content)}
+
+
+ """
+ end
+
+ # -- Printify step content --
+
+ attr :setup, :map, 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
+
+ defp printify_step_content(assigns) do
+ ~H"""
+ <%!-- Not yet connected: show form --%>
+
+
+ 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"
+ />
+
+
+
+ <.button type="submit" disabled={@printify_saving or @printify_testing}>
+ {if @printify_saving, do: "Connecting...", else: "Connect to Printify"}
+
+
+
+ <.printify_test_feedback :if={@printify_test_result} result={@printify_test_result} />
+
+
+
+ <%!-- Connected, syncing --%>
+
+ <.icon name="hero-arrow-path" class="size-4 animate-spin text-base-content/40" />
+ Syncing products from Printify...
+
+
+ <%!-- Connected, sync failed --%>
+
+
Product sync failed.
+
+
+
+ <%!-- Connected, synced (shown when user expands a completed step) --%>
+
+
+ {@setup.product_count} products synced from Printify.
+
+
+ """
+ end
+
+ attr :result, :any, required: true
+
+ defp printify_test_feedback(assigns) do
+ ~H"""
+
+ <%= case @result do %>
+ <% {:ok, info} -> %>
+
+ <.icon name="hero-check-circle" class="size-4" /> Connected to {info.shop_name}
+
+ <% {:error, reason} -> %>
+
+ <.icon name="hero-x-circle" class="size-4" />
+ {format_printify_error(reason)}
+
+ <% end %>
+
+ """
+ end
+
+ # -- Stripe step content --
+
+ attr :stripe_form, :any, required: true
+ attr :stripe_connecting, :boolean, required: true
+
+ defp stripe_step_content(assigns) do
+ ~H"""
+
+
+ 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.
+
+
+ <.button phx-disable-with="Connecting...">
+ {if @stripe_connecting, do: "Connecting...", else: "Connect Stripe"}
+
+
+
+
+ """
+ end
+
+ # -- Go live step content --
+
+ attr :setup, :map, required: true
+
+ defp go_live_step_content(assigns) do
+ ~H"""
+
+
+ Your shop is ready. Visitors currently see a "coming soon" page —
+ hit the button to make it live.
+
+
+
+ """
+ end
+
+ # -- Celebration --
+
+ defp celebration(assigns) do
+ ~H"""
+
+ <.icon name="hero-check-badge" class="size-12 mx-auto text-green-600 mb-3" />
+
Your shop is live!
+
+ Customers can now browse and buy from your shop.
+
+
+ <.link
+ navigate={~p"/"}
+ class="inline-flex items-center justify-center gap-1.5 rounded-md bg-green-600 px-3 py-2 text-sm font-semibold text-white hover:bg-green-500"
+ >
+ <.icon name="hero-arrow-top-right-on-square-mini" class="size-4" /> View your shop
+
+ <.link
+ navigate={~p"/admin/theme"}
+ class="inline-flex items-center justify-center gap-1.5 rounded-md bg-base-100 px-3 py-2 text-sm font-medium text-base-content ring-1 ring-base-300 ring-inset hover:bg-base-200/50"
+ >
+ <.icon name="hero-paint-brush-mini" class="size-4" /> Customise theme
+
+
+
+ """
+ end
+
+ # ==========================================================================
+ # Stats components
+ # ==========================================================================
+
attr :label, :string, required: true
attr :value, :any, required: true
attr :icon, :string, required: true
@@ -159,15 +708,15 @@ defmodule SimpleshopThemeWeb.Admin.Dashboard do
~H"""
<.link
navigate={@href}
- class="rounded-lg border border-zinc-200 p-4 hover:border-zinc-300 transition-colors"
+ class="rounded-lg border border-base-200 p-4 hover:border-base-300 transition-colors"
>
-
- <.icon name={@icon} class="size-5 text-zinc-600" />
+
+ <.icon name={@icon} class="size-5 text-base-content/60" />
{@value}
-
{@label}
+
{@label}
@@ -177,13 +726,13 @@ defmodule SimpleshopThemeWeb.Admin.Dashboard do
defp fulfilment_pill(assigns) do
{color, label} =
case assigns.status do
- "unfulfilled" -> {"bg-zinc-100 text-zinc-600", "unfulfilled"}
+ "unfulfilled" -> {"bg-base-200 text-base-content/60", "unfulfilled"}
"submitted" -> {"bg-blue-50 text-blue-700", "submitted"}
"processing" -> {"bg-amber-50 text-amber-700", "processing"}
"shipped" -> {"bg-purple-50 text-purple-700", "shipped"}
"delivered" -> {"bg-green-50 text-green-700", "delivered"}
"failed" -> {"bg-red-50 text-red-700", "failed"}
- _ -> {"bg-zinc-100 text-zinc-600", assigns.status || "—"}
+ _ -> {"bg-base-200 text-base-content/60", assigns.status || "—"}
end
assigns = assign(assigns, color: color, label: label)
@@ -195,6 +744,10 @@ defmodule SimpleshopThemeWeb.Admin.Dashboard do
"""
end
+ # ==========================================================================
+ # Helpers
+ # ==========================================================================
+
defp format_revenue(amount_pence) when is_integer(amount_pence) do
Cart.format_price(amount_pence)
end
@@ -204,4 +757,31 @@ defmodule SimpleshopThemeWeb.Admin.Dashboard do
defp format_date(datetime) do
Calendar.strftime(datetime, "%d %b %Y")
end
+
+ defp encrypt_api_key(api_key) do
+ case SimpleshopTheme.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: shop_name}}) when is_binary(shop_name) do
+ Map.put_new(params, "name", shop_name)
+ end
+
+ defp maybe_add_name(params, _), do: Map.put_new(params, "name", "Printify")
+
+ defp format_printify_error(:no_api_key), do: "Please enter your API token"
+ defp format_printify_error(:unauthorized), do: "That token doesn't seem to be valid"
+ defp format_printify_error(:timeout), do: "Couldn't reach Printify — try again"
+ defp format_printify_error({:http_error, _code}), do: "Something went wrong — try again"
+ defp format_printify_error(error) when is_binary(error), do: error
+ defp format_printify_error(_), do: "Connection failed — check your token and try again"
end
diff --git a/test/simpleshop_theme_web/live/admin/dashboard_test.exs b/test/simpleshop_theme_web/live/admin/dashboard_test.exs
index 48590d3..1e00d37 100644
--- a/test/simpleshop_theme_web/live/admin/dashboard_test.exs
+++ b/test/simpleshop_theme_web/live/admin/dashboard_test.exs
@@ -4,6 +4,7 @@ defmodule SimpleshopThemeWeb.Admin.DashboardTest do
import Phoenix.LiveViewTest
import SimpleshopTheme.AccountsFixtures
import SimpleshopTheme.OrdersFixtures
+ import SimpleshopTheme.ProductsFixtures
setup do
user = user_fixture()
@@ -18,26 +19,72 @@ defmodule SimpleshopThemeWeb.Admin.DashboardTest do
end
end
- describe "setup checklist" do
+ describe "setup stepper" do
setup %{conn: conn, user: user} do
%{conn: log_in_user(conn, user)}
end
- test "shows setup progress when shop is offline", %{conn: conn} do
+ test "shows stepper with printify form when nothing connected", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/admin")
- assert html =~ "Setup progress"
- assert html =~ "Create admin account"
+ assert html =~ "Setup steps"
assert html =~ "Connect to Printify"
+ assert html =~ "Printify API token"
assert html =~ "Connect Stripe"
assert html =~ "Go live"
end
- test "hides setup checklist when shop is live", %{conn: conn} do
+ test "shows stripe form when printify is done", %{conn: conn} do
+ conn_fixture = provider_connection_fixture(%{provider_type: "printify"})
+ _product = product_fixture(%{provider_connection: conn_fixture})
+
+ {:ok, view, _html} = live(conn, ~p"/admin")
+
+ # Printify step should be completed
+ assert has_element?(view, "li:first-child [class*='bg-green-500']")
+ # Stripe step should be active with form
+ assert has_element?(view, "label", "Secret key")
+ end
+
+ test "shows go live button when all services connected", %{conn: conn} do
+ conn_fixture = provider_connection_fixture(%{provider_type: "printify"})
+ _product = product_fixture(%{provider_connection: conn_fixture})
+ {:ok, _} = SimpleshopTheme.Settings.put_secret("stripe_api_key", "sk_test_123")
+
+ {:ok, view, _html} = live(conn, ~p"/admin")
+
+ assert has_element?(view, "button", "Go live")
+ end
+
+ test "go live shows celebration", %{conn: conn} do
+ conn_fixture = provider_connection_fixture(%{provider_type: "printify"})
+ _product = product_fixture(%{provider_connection: conn_fixture})
+ {:ok, _} = SimpleshopTheme.Settings.put_secret("stripe_api_key", "sk_test_123")
+
+ {:ok, view, _html} = live(conn, ~p"/admin")
+
+ html = view |> element("button", "Go live") |> render_click()
+
+ assert html =~ "Your shop is live!"
+ assert html =~ "View your shop"
+ assert html =~ "Customise theme"
+ end
+
+ test "hides stepper when shop is live", %{conn: conn} do
{:ok, _} = SimpleshopTheme.Settings.set_site_live(true)
{:ok, _view, html} = live(conn, ~p"/admin")
- refute html =~ "Setup progress"
+ refute html =~ "Setup steps"
+ refute html =~ "Printify API token"
+ end
+
+ test "completed steps show summary and are collapsible", %{conn: conn} do
+ conn_fixture = provider_connection_fixture(%{provider_type: "printify"})
+ _product = product_fixture(%{provider_connection: conn_fixture})
+
+ {:ok, _view, html} = live(conn, ~p"/admin")
+
+ assert html =~ "products synced"
end
end