diff --git a/lib/plausible_web/components/flow_progress.ex b/lib/plausible_web/components/flow_progress.ex
index 705a00a2c725..d07d4154078f 100644
--- a/lib/plausible_web/components/flow_progress.ex
+++ b/lib/plausible_web/components/flow_progress.ex
@@ -1,44 +1,39 @@
defmodule PlausibleWeb.Components.FlowProgress do
@moduledoc """
- Dotted progress indicator shown during the registration flow.
- One small dot per step in `PlausibleWeb.Flows.steps/1`, with completed
- and current steps highlighted.
+ Dotted progress indicator shown in the onboarding layout.
+ One small dot per step, with completed and current steps highlighted.
"""
use Phoenix.Component
- attr :flow, :string, required: true, values: PlausibleWeb.Flows.valid_keys()
- attr :current_step, :string, required: true, values: PlausibleWeb.Flows.valid_values()
+ attr :steps, :list, required: true
+ attr :current_step, :string, required: true
def render(assigns) do
- steps = PlausibleWeb.Flows.steps(assigns.flow)
- current_step_idx = Enum.find_index(steps, &(&1 == assigns.current_step))
+ current_step_idx = Enum.find_index(assigns.steps, &(&1 == assigns.current_step))
- assigns =
- assign(assigns,
- steps: steps,
- current_step_idx: current_step_idx
- )
+ assigns = assign(assigns, :current_step_idx, current_step_idx)
~H"""
-
+
"h-2 w-5 rounded-full bg-indigo-600 dark:bg-gray-100"
- idx < @current_step_idx -> "size-2 rounded-full bg-indigo-600 dark:bg-gray-100"
- true -> "size-2 rounded-full bg-gray-300 dark:bg-gray-600"
- end
- }
+ class={dot_class(dot_state(idx, @current_step_idx))}
aria-current={idx == @current_step_idx && "step"}
aria-label={step}
/>
"""
end
+
+ @doc """
+ The classnames for a progress dot in the given state. Exposed so tests can
+ assert on rendered dots without duplicating the Tailwind classnames.
+ """
+ def dot_class(:completed), do: "size-2 rounded-full bg-indigo-600 dark:bg-gray-100"
+ def dot_class(:current), do: "h-2 w-5 rounded-full bg-indigo-600 dark:bg-gray-100"
+ def dot_class(:upcoming), do: "size-2 rounded-full bg-gray-300 dark:bg-gray-600"
+
+ defp dot_state(idx, current_idx) when idx < current_idx, do: :completed
+ defp dot_state(idx, current_idx) when idx == current_idx, do: :current
+ defp dot_state(_idx, _current_idx), do: :upcoming
end
diff --git a/lib/plausible_web/components/layouts.ex b/lib/plausible_web/components/layouts.ex
index 6e3ddca889a0..548373adcff7 100644
--- a/lib/plausible_web/components/layouts.ex
+++ b/lib/plausible_web/components/layouts.ex
@@ -136,4 +136,33 @@ defmodule PlausibleWeb.Layouts do
"""
end
+
+ attr :current_user, :any, default: nil
+ attr :current_step, :string, required: true
+ attr :flash, :map, default: %{}
+ slot :inner_block, required: true
+
+ def onboarding(assigns) do
+ ~H"""
+ <.app
+ footer?={false}
+ global_notices?={false}
+ trial_badge?={false}
+ current_user={@current_user}
+ flash={@flash}
+ >
+
+
+ {render_slot(@inner_block)}
+
+
+
+
+ """
+ end
end
diff --git a/lib/plausible_web/components/site/new_site_form.ex b/lib/plausible_web/components/site/new_site_form.ex
new file mode 100644
index 000000000000..32f02545dba7
--- /dev/null
+++ b/lib/plausible_web/components/site/new_site_form.ex
@@ -0,0 +1,77 @@
+defmodule PlausibleWeb.Components.Site.NewSiteForm do
+ @moduledoc false
+
+ use PlausibleWeb, :component
+
+ attr :changeset, Ecto.Changeset, required: true
+ attr :form_submit_url, :string, required: true
+ attr :site_limit_exceeded?, :boolean, required: true
+ attr :site_limit, :integer, required: true
+ attr :current_user, :any, required: true
+ attr :current_team, :any, required: true
+ attr :back_button_text, :string, required: true
+ attr :back_button_href, :string, required: true
+
+ def new_site_form(assigns) do
+ ~H"""
+ <.heading_and_subtitle heading="Add a website" subtitle="Start measuring traffic on a new site." />
+
+ <.form :let={f} class="flex flex-col gap-y-8" for={@changeset} action={@form_submit_url}>
+
+
+ <.input
+ help_text="Just the naked domain or subdomain without 'www', 'https' etc."
+ type="text"
+ placeholder="example.com"
+ field={f[:domain]}
+ label="Domain"
+ disabled={@site_limit_exceeded?}
+ mt?={false}
+ autofocus="autofocus"
+ />
+
+
+
+
+
+ <.button_link theme="ghost" href={@back_button_href} mt?={false}>
+ {@back_button_text}
+
+ <.button
+ disabled={@site_limit_exceeded?}
+ type="submit"
+ mt?={false}
+ class="disabled:cursor-not-allowed"
+ >
+ Add site
+
+
+
+
+ """
+ end
+
+ def heading_and_subtitle(assigns) do
+ ~H"""
+
+
{@heading}
+
+ {@subtitle}
+
+
+ """
+ end
+end
diff --git a/lib/plausible_web/controllers/helpers.ex b/lib/plausible_web/controllers/helpers.ex
index 8d65e5ca6cd7..f04a08889564 100644
--- a/lib/plausible_web/controllers/helpers.ex
+++ b/lib/plausible_web/controllers/helpers.ex
@@ -23,22 +23,6 @@ defmodule PlausibleWeb.ControllerHelpers do
defp error_layout,
do: Application.get_env(:plausible, PlausibleWeb.Endpoint)[:render_errors][:layout]
- @onboarding_layout_assigns [
- layout: {PlausibleWeb.LayoutView, :onboarding},
- hide_footer?: true,
- disable_global_notices?: true,
- bg_class: "bg-white dark:bg-gray-950"
- ]
-
- def render_onboarding_page(conn, template, extra_assigns \\ []) do
- assigns =
- @onboarding_layout_assigns
- |> Keyword.put(:hide_trial_badge?, conn.params["flow"] == PlausibleWeb.Flows.register())
- |> Keyword.merge(extra_assigns)
-
- render(conn, template, assigns)
- end
-
def debug_metadata(conn) do
%{
request_method: conn.method,
diff --git a/lib/plausible_web/controllers/site_controller.ex b/lib/plausible_web/controllers/site_controller.ex
index 1ee302d5e335..9aac75d9bcee 100644
--- a/lib/plausible_web/controllers/site_controller.ex
+++ b/lib/plausible_web/controllers/site_controller.ex
@@ -72,11 +72,9 @@ defmodule PlausibleWeb.SiteController do
defaults = [
changeset: Plausible.Site.changeset(%Plausible.Site{}),
site_limit_exceeded?: false,
- flow: flow,
form_submit_url: "/sites?flow=#{flow}",
- current_step: "Add site info",
- heading: "Add a website",
- subtitle: "Start measuring traffic on a new site."
+ bg_class: "bg-white dark:bg-gray-950",
+ legacy_layout?: false
]
assigns =
@@ -86,7 +84,11 @@ defmodule PlausibleWeb.SiteController do
Plausible.Teams.Billing.site_limit(conn.assigns.current_team)
end)
- render_onboarding_page(conn, "new.html", assigns)
+ if flow == PlausibleWeb.Flows.register() do
+ render(conn, "onboarding_new_site.html", assigns)
+ else
+ render(conn, "provisioning_new_site.html", assigns)
+ end
end
def settings(conn, %{"domain" => domain}) do
diff --git a/lib/plausible_web/flows.ex b/lib/plausible_web/flows.ex
index 706dc2ac5956..81c427bbdf96 100644
--- a/lib/plausible_web/flows.ex
+++ b/lib/plausible_web/flows.ex
@@ -1,58 +1,62 @@
defmodule PlausibleWeb.Flows do
@moduledoc """
- Static compile-time definitions for user progress flows.
- See `PlausibleWeb.Components.FlowProgress` for rendering capabilities.
+ Named identifiers and documentation for the onboarding/installation flows.
"""
- @flows %{
- review: [
- "Install Plausible"
- ],
- domain_change: [
- "Set up new domain",
- "Install Plausible"
- ],
- register: [
- "Add site info",
- "Install Plausible"
- ],
- invitation: [
- "Register",
- "Activate account"
- ],
- provisioning: [
- "Add site info",
- "Install Plausible"
- ]
- }
-
- @valid_values @flows
- |> Enum.flat_map(fn {_, steps} -> steps end)
- |> Enum.uniq()
-
- @valid_keys @flows
- |> Map.keys()
- |> Enum.map(&to_string/1)
-
- @spec steps(binary() | atom()) :: list(binary())
- def steps(flow) when flow in @valid_keys do
- steps(String.to_existing_atom(flow))
- end
-
- def steps(flow) when is_atom(flow) do
- Map.get(@flows, flow, [])
- end
-
- def steps(_), do: []
-
- @spec valid_values() :: list(binary())
- def valid_values(), do: @valid_values
-
- @spec valid_values() :: list(binary())
- def valid_keys(), do: @valid_keys
-
- for {flow, _} <- @flows do
- @spec unquote(flow)() :: binary()
- def unquote(flow)(), do: unquote(to_string(flow))
- end
+ @doc """
+ The flow of creating a new account from scratch:
+
+ * Register form
+ * Activate account (email confirmation)
+ * ...optional onboarding steps (see `onboarding_steps/0`)
+ * End up either on the dashboard (with verification kicked off automatically)
+ or the /sites page, depending on whether a site was setup or not.
+ """
+ def register, do: "register"
+
+ @doc """
+ The flow of an already logged in user adding another site to their team:
+
+ * Add site info
+ * Installation screen
+ * End up either on the dashboard (with verification kicked off automatically)
+ or the /sites page, depending on whether installation was skipped or not
+ """
+ def provisioning, do: "provisioning"
+
+ @doc """
+ The flow of an existing user reviewing/re-checking their already-installed
+ tracking script (triggered from site settings):
+
+ * Installation screen, pre-filled with the earlier installation method
+ * End up on the dashboard (with verification kicked off automatically)
+ """
+ def review, do: "review"
+
+ @doc """
+ The flow of an existing site domain getting changed (triggered from
+ site settings):
+
+ * Domain name change form
+ * Domain change success info page (possible further actions required)
+ * Back to site settings
+ """
+ def domain_change, do: "domain_change"
+
+ @doc """
+ The flow of accepting a team/site invitation as a new user:
+
+ * Register form
+ * Activate account (email confirmation)
+ * Land on the /sites page
+ """
+ def invitation, do: "invitation"
+
+ @doc """
+ The steps shown in the onboarding flow's progress indicator.
+ """
+ def onboarding_steps, do: [add_site_step(), installation_step()]
+
+ def add_site_step, do: "Add site info"
+ def installation_step, do: "Install Plausible"
end
diff --git a/lib/plausible_web/live/installation.ex b/lib/plausible_web/live/installation.ex
index f4ef6cf634cf..ed7565986ed4 100644
--- a/lib/plausible_web/live/installation.ex
+++ b/lib/plausible_web/live/installation.ex
@@ -6,7 +6,7 @@ defmodule PlausibleWeb.Live.Installation do
use Plausible
use PlausibleWeb, :live_view
- alias PlausibleWeb.Flows
+ alias PlausibleWeb.{Flows, Layouts}
alias Phoenix.LiveView.AsyncResult
alias PlausibleWeb.Live.Installation.Icons
alias PlausibleWeb.Live.Installation.Instructions
@@ -93,7 +93,6 @@ defmodule PlausibleWeb.Live.Installation do
site: site,
flow: flow,
return_to: params["return_to"],
- current_step: "Install Plausible",
heading: heading,
subtitle: subtitle
)}
@@ -117,7 +116,11 @@ defmodule PlausibleWeb.Live.Installation do
assigns = assign(assigns, :submit_button_text, @submit_button_text)
~H"""
-
+ <.onboarding_or_app_layout {assigns}>
+
<.async_result :let={recommended_installation_type} assign={@recommended_installation_type}>
<:loading>
@@ -233,7 +236,7 @@ defmodule PlausibleWeb.Live.Installation do
-
+
"""
end
@@ -323,6 +326,34 @@ defmodule PlausibleWeb.Live.Installation do
end
end
+ defp onboarding_or_app_layout(assigns) do
+ if assigns.flow == Flows.register() do
+ ~H"""
+
- <.form :let={f} class="flex flex-col gap-y-8" for={@changeset} action={@form_submit_url}>
-
-
- <.input
- help_text="Just the naked domain or subdomain without 'www', 'https' etc."
- type="text"
- placeholder="example.com"
- field={f[:domain]}
- label="Domain"
- disabled={@site_limit_exceeded?}
- mt?={false}
- autofocus="autofocus"
- />
-
-
-
-
-
- <.button_link theme="ghost" href={Routes.site_path(@conn, :index)} mt?={false}>
- {if @flow == PlausibleWeb.Flows.provisioning(), do: "Back to sites", else: "Skip"}
-
- <.button
- disabled={@site_limit_exceeded?}
- type="submit"
- mt?={false}
- class="disabled:cursor-not-allowed"
- >
- Add site
-
-
-
-
diff --git a/lib/plausible_web/templates/site/onboarding_new_site.html.heex b/lib/plausible_web/templates/site/onboarding_new_site.html.heex
new file mode 100644
index 000000000000..330d8465dd14
--- /dev/null
+++ b/lib/plausible_web/templates/site/onboarding_new_site.html.heex
@@ -0,0 +1,16 @@
+