diff --git a/assets/css/admin/components.css b/assets/css/admin/components.css index b8297e1..9adbc42 100644 --- a/assets/css/admin/components.css +++ b/assets/css/admin/components.css @@ -740,6 +740,35 @@ padding-left: 2.5rem; } +/* Setup: check inbox phase */ +.setup-check-inbox { + text-align: center; + padding: 1rem 0; +} + +.setup-inbox-icon { + color: var(--color-base-content, #171717); + margin: 0 auto 0.75rem; +} + +.setup-inbox-heading { + font-size: 1.125rem; + font-weight: 600; + margin-bottom: 0.25rem; +} + +.setup-inbox-detail { + font-size: 0.875rem; + color: color-mix(in oklch, var(--color-base-content) 60%, transparent); + margin-bottom: 1rem; +} + +.setup-start-over { + font-size: 0.8125rem; + color: color-mix(in oklch, var(--color-base-content) 60%, transparent); + margin-top: 1rem; +} + .setup-hint { font-size: 0.8125rem; color: color-mix(in oklch, var(--color-base-content) 60%, transparent); diff --git a/lib/berrypod/accounts.ex b/lib/berrypod/accounts.ex index 9cacfef..c3abc08 100644 --- a/lib/berrypod/accounts.ex +++ b/lib/berrypod/accounts.ex @@ -69,6 +69,36 @@ defmodule Berrypod.Accounts do Repo.exists?(User) end + @doc """ + Returns the email of the admin user, or nil if no user exists. + """ + def admin_email do + Repo.one(from u in User, select: u.email, limit: 1) + end + + @doc """ + Returns the unconfirmed admin user, or nil. + + Used by the setup wizard to allow "start over" before the magic link is clicked. + """ + def get_unconfirmed_admin do + Repo.one(from u in User, where: is_nil(u.confirmed_at), limit: 1) + end + + @doc """ + Deletes an unconfirmed user (confirmed_at is nil). + + Returns `{:error, :already_confirmed}` if the user has already confirmed, + preventing accidental deletion of an active admin account. + """ + def delete_unconfirmed_user(%User{confirmed_at: nil} = user) do + Repo.delete(user) + end + + def delete_unconfirmed_user(%User{}) do + {:error, :already_confirmed} + end + ## User registration @doc """ diff --git a/lib/berrypod_web/live/setup/onboarding.ex b/lib/berrypod_web/live/setup/onboarding.ex index 3a0ba4f..93acffe 100644 --- a/lib/berrypod_web/live/setup/onboarding.ex +++ b/lib/berrypod_web/live/setup/onboarding.ex @@ -20,10 +20,13 @@ defmodule BerrypodWeb.Setup.Onboarding do {: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")} + {:ok, mount_check_inbox(socket)} + + setup.admin_created -> + {:ok, mount_configure(socket, setup)} true -> - {:ok, mount_setup(socket, setup)} + {:ok, mount_email_form(socket)} end end @@ -34,15 +37,28 @@ defmodule BerrypodWeb.Setup.Onboarding do end end - defp mount_setup(socket, setup) do + defp mount_email_form(socket) do + socket + |> assign(:page_title, "Set up your shop") + |> assign(:phase, :email_form) + |> assign(:account_form, to_form(%{"email" => ""}, as: :account)) + end + + defp mount_check_inbox(socket) do + socket + |> assign(:page_title, "Set up your shop") + |> assign(:phase, :check_inbox) + |> assign(:admin_email, Accounts.admin_email()) + |> assign(:local_mail?, local_mail_adapter?()) + end + + defp mount_configure(socket, setup) do provider_conn = Products.get_first_provider_connection() socket |> assign(:page_title, "Set up your shop") + |> assign(:phase, :configure) |> assign(:setup, setup) - # Account - |> assign(:account_form, to_form(%{"email" => ""}, as: :account)) - |> assign(:account_submitted, false) # Provider |> assign(:providers, Provider.all()) |> assign(:selected_provider, nil) @@ -72,13 +88,11 @@ defmodule BerrypodWeb.Setup.Onboarding do &url(~p"/users/log-in/#{&1}") ) - setup = %{socket.assigns.setup | admin_created: true} - {:noreply, socket - |> assign(:setup, setup) - |> assign(:account_submitted, true) - |> put_flash(:info, "Check your email for a login link")} + |> assign(:phase, :check_inbox) + |> assign(:admin_email, user.email) + |> assign(:local_mail?, local_mail_adapter?())} {:error, changeset} -> {:noreply, @@ -89,6 +103,26 @@ defmodule BerrypodWeb.Setup.Onboarding do end end + def handle_event("start_over", _params, socket) do + case Accounts.get_unconfirmed_admin() do + %Accounts.User{} = user -> + case Accounts.delete_unconfirmed_user(user) do + {:ok, _} -> + {:noreply, + socket + |> assign(:phase, :email_form) + |> assign(:account_form, to_form(%{"email" => ""}, as: :account)) + |> clear_flash()} + + {:error, :already_confirmed} -> + {:noreply, push_navigate(socket, to: ~p"/setup")} + end + + nil -> + {:noreply, push_navigate(socket, to: ~p"/setup")} + end + end + # ── Events: Provider ── def handle_event("select_provider", %{"type" => type}, socket) do @@ -205,32 +239,84 @@ defmodule BerrypodWeb.Setup.Onboarding do
Three quick steps to get everything connected.
++ Enter your email to create the admin account. +
++ Almost there — check your inbox. +
++ Connect your accounts to get going. +
We'll send a login link to get you started.
+ <.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()} + /> ++ We sent a login link to {@admin_email}. + Click it to continue setting up your shop. +
+ ++ Using local mail adapter. + See sent emails at /dev/mailbox. +
++ Wrong email? + +
+Head to the dashboard to sync products, customise your theme, and go live.
@@ -276,13 +360,12 @@ defmodule BerrypodWeb.Setup.Onboarding do attr :number, :integer, required: true attr :done, :boolean, required: true attr :summary, :string, default: nil - attr :hidden, :boolean, default: false slot :inner_block, required: true defp section_card(assigns) do ~H""" -Check your email
-Click the link we sent to finish creating your account.
-- Using local mail adapter. - See sent emails at /dev/mailbox. -
-Enter your email to create the admin account. We'll send a login link.
- - <.form for={@form} phx-submit="create_account"> - <.input - field={@form[:email]} - type="email" - label="Email address" - autocomplete="email" - required - phx-mounted={JS.focus()} - /> -