Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 20 additions & 25 deletions lib/plausible_web/components/flow_progress.ex
Original file line number Diff line number Diff line change
@@ -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"""
<div
:if={not Enum.empty?(@steps)}
id="flow-progress"
class="flex items-center gap-2"
aria-label="Progress"
>
<div id="flow-progress" class="flex items-center gap-2" aria-label="Progress">
<span
:for={{step, idx} <- Enum.with_index(@steps)}
class={
cond do
idx == @current_step_idx -> "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}
/>
</div>
"""
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
29 changes: 29 additions & 0 deletions lib/plausible_web/components/layouts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,33 @@ defmodule PlausibleWeb.Layouts do
</.app>
"""
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}
>
<div class="flex-1 flex flex-col">
<div class="flex-1">
{render_slot(@inner_block)}
</div>
<div class="pb-20 flex justify-center">
<PlausibleWeb.Components.FlowProgress.render
steps={PlausibleWeb.Flows.onboarding_steps()}
current_step={@current_step}
/>
</div>
</div>
</.app>
"""
end
end
77 changes: 77 additions & 0 deletions lib/plausible_web/components/site/new_site_form.ex
Original file line number Diff line number Diff line change
@@ -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." />
<div class="w-full max-w-md mx-auto mt-10 pb-16 px-4 dark:text-gray-300">
<.form :let={f} class="flex flex-col gap-y-8" for={@changeset} action={@form_submit_url}>
<PlausibleWeb.Components.Billing.Notice.limit_exceeded
:if={@site_limit_exceeded?}
current_user={@current_user}
current_team={@current_team}
limit={@site_limit}
resource="sites"
/>

<.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"
/>

<input type="hidden" name={f[:timezone].name} id="tz-input" value="Etc/UTC" />
<script>
if (typeof Intl !== "undefined") {
var detectedTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
if (detectedTimezone) {
document.getElementById("tz-input").value = detectedTimezone;
}
}
</script>

<div class="flex justify-end items-center gap-x-2">
<.button_link theme="ghost" href={@back_button_href} mt?={false}>
{@back_button_text}
</.button_link>
<.button
disabled={@site_limit_exceeded?}
type="submit"
mt?={false}
class="disabled:cursor-not-allowed"
>
Add site
</.button>
</div>
</.form>
</div>
"""
end

def heading_and_subtitle(assigns) do
~H"""
<div class="w-full max-w-md mx-auto mt-10 sm:mt-16 px-4 flex flex-col gap-y-2 dark:text-gray-300">
<h1 class="text-lg sm:text-xl font-semibold">{@heading}</h1>
<p class="text-base text-gray-500 dark:text-gray-400 text-pretty">
{@subtitle}
</p>
</div>
"""
end
end
16 changes: 0 additions & 16 deletions lib/plausible_web/controllers/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 7 additions & 5 deletions lib/plausible_web/controllers/site_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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
Expand Down
110 changes: 57 additions & 53 deletions lib/plausible_web/flows.ex
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading