Onboarding improvements (#4459)
* Migration: add installation meta * Update site schema with installation meta * Remove VERIFICATION_ENABLED env var * Add context API to create/remove special goals * Add context api to update installation meta * Remove verification enabled check * Update new progress flow definitions * Update generic components * Remove internal /status API * Implement installation live view * Update traffic change notifier link * Update verification, no more modal * Update routes * Remove focus.html - will unify everything under app layout * Fix broken link * Update templates with focus_box mostly * Update controller tests * Update controllers and stop using the focus layout * copy changes * Update verification.ex * Remove dead template * Update settings_general.html.heex * Update copy in tests * Update installation.ex * Remove dangling dot * Fix link * Update installation.ex * Update installation.ex * Better tooltips? * Simpler labels * Revert "Simpler labels" This reverts commit 797560ef82f2067458b03b884be5aecc8fdc72bc. * Add copy to clipboard link and fix snippet's dark mode * Offer installation detection skip only if ws connected * Put COPY link at the bottom with background * Make tooltips link to docs * Fix cherry-pick gone wrong * Hide tooltips on mobile screens * WIP: 404 tracking wizard * Revert "WIP: 404 tracking wizard" This reverts commita9c9c79bbd. * Update lib/plausible_web/live/components/verification.ex Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com> * Update lib/plausible_web/live/installation.ex Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com> * Use current_user from socket.assigns * Update lib/plausible_web/live/installation.ex Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com> * Use current_user from socket.assigns * Use conn.private to steer verification tests * Drop non-sticky tooltip in favour of component parametrization Co-authored-by: Artur Pata <artur.pata@gmail.com> * Reapply "WIP: 404 tracking wizard" This reverts commit3ba81671d7. * Fix installation tests including 404 tracking * Fixup the tooltip component * Format * Update installation.ex * Put flash whenever installation option changes * Use last known installation type on domain change * Extract user flow definition to provide compile-time checks * See if this helps running CE migrations successfully * Use `styled_link` on registration/login views * Don't crash when there's no conn.private carried over * Format * Push "Determining installation type" message a bit lower * Use links and footer lists uniformly This commit introduces a `<.focus_list/>` component for rendering focus box footer links with colored discs. It also equips generic link components with the ability of sending non-GET requests along with CSRF token, so we can apply uniform styling and stop using legacy Phoenix link tags. cc @zoldar @apata * ws 👾 * Render more descriptive flashes on script config change --------- Co-authored-by: Marko Saric <34340819+metmarkosaric@users.noreply.github.com> Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com> Co-authored-by: Artur Pata <artur.pata@gmail.com>
This commit is contained in:
parent
f04c47f881
commit
e3af1a317d
|
|
@ -778,11 +778,6 @@ config :plausible, Plausible.PromEx,
|
|||
grafana: :disabled,
|
||||
metrics_server: :disabled
|
||||
|
||||
config :plausible, Plausible.Verification,
|
||||
enabled?:
|
||||
get_var_from_path_or_env(config_dir, "VERIFICATION_ENABLED", "false")
|
||||
|> String.to_existing_atom()
|
||||
|
||||
config :plausible, Plausible.Verification.Checks.Installation,
|
||||
token: get_var_from_path_or_env(config_dir, "BROWSERLESS_TOKEN", "dummy_token"),
|
||||
endpoint: get_var_from_path_or_env(config_dir, "BROWSERLESS_ENDPOINT", "http://0.0.0.0:3000")
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ defmodule Plausible.DataMigration.SiteImports do
|
|||
sites_with_only_legacy_import =
|
||||
from(s in Site,
|
||||
as: :site,
|
||||
select: %{id: s.id, imported_data: s.imported_data},
|
||||
where:
|
||||
not is_nil(s.imported_data) and fragment("?->>'status'", s.imported_data) == "ok" and
|
||||
not exists(site_import_query)
|
||||
|
|
|
|||
|
|
@ -236,6 +236,57 @@ defmodule Plausible.Goals do
|
|||
)
|
||||
end
|
||||
|
||||
@spec create_outbound_links(Plausible.Site.t()) :: :ok
|
||||
def create_outbound_links(%Plausible.Site{} = site) do
|
||||
create(site, %{"event_name" => "Outbound Link: Click"}, upsert?: true)
|
||||
:ok
|
||||
end
|
||||
|
||||
@spec create_file_downloads(Plausible.Site.t()) :: :ok
|
||||
def create_file_downloads(%Plausible.Site{} = site) do
|
||||
create(site, %{"event_name" => "File Download"}, upsert?: true)
|
||||
:ok
|
||||
end
|
||||
|
||||
@spec create_404(Plausible.Site.t()) :: :ok
|
||||
def create_404(%Plausible.Site{} = site) do
|
||||
create(site, %{"event_name" => "404"}, upsert?: true)
|
||||
:ok
|
||||
end
|
||||
|
||||
@spec delete_outbound_links(Plausible.Site.t()) :: :ok
|
||||
def delete_outbound_links(%Plausible.Site{} = site) do
|
||||
q =
|
||||
from g in Goal,
|
||||
where: g.site_id == ^site.id,
|
||||
where: g.event_name == "Outbound Link: Click"
|
||||
|
||||
Repo.delete_all(q)
|
||||
:ok
|
||||
end
|
||||
|
||||
@spec delete_file_downloads(Plausible.Site.t()) :: :ok
|
||||
def delete_file_downloads(%Plausible.Site{} = site) do
|
||||
q =
|
||||
from g in Goal,
|
||||
where: g.site_id == ^site.id,
|
||||
where: g.event_name == "File Download"
|
||||
|
||||
Repo.delete_all(q)
|
||||
:ok
|
||||
end
|
||||
|
||||
@spec delete_404(Plausible.Site.t()) :: :ok
|
||||
def delete_404(%Plausible.Site{} = site) do
|
||||
q =
|
||||
from g in Goal,
|
||||
where: g.site_id == ^site.id,
|
||||
where: g.event_name == "404"
|
||||
|
||||
Repo.delete_all(q)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp insert_goal(site, params, upsert?) do
|
||||
params = Map.delete(params, "site_id")
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ defmodule Plausible.Site do
|
|||
# NOTE: needed by `SiteImports` data migration script
|
||||
embeds_one :imported_data, Plausible.Site.ImportedData, on_replace: :update
|
||||
|
||||
embeds_one :installation_meta, Plausible.Site.InstallationMeta,
|
||||
on_replace: :update,
|
||||
defaults_to_struct: true
|
||||
|
||||
many_to_many :members, User, join_through: Plausible.Site.Membership
|
||||
has_many :memberships, Plausible.Site.Membership
|
||||
has_many :invitations, Plausible.Auth.Invitation
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
defmodule Plausible.Site.InstallationMeta do
|
||||
@moduledoc """
|
||||
Embedded schema for installation meta-data
|
||||
"""
|
||||
use Ecto.Schema
|
||||
|
||||
@type t() :: %__MODULE__{}
|
||||
|
||||
embedded_schema do
|
||||
field :installation_type, :string, default: "manual"
|
||||
field :script_config, :map, default: %{}
|
||||
end
|
||||
end
|
||||
|
|
@ -327,6 +327,13 @@ defmodule Plausible.Sites do
|
|||
end
|
||||
end
|
||||
|
||||
def update_installation_meta!(site, meta) do
|
||||
site
|
||||
|> Ecto.Changeset.change()
|
||||
|> Ecto.Changeset.put_change(:installation_meta, meta)
|
||||
|> Repo.update!()
|
||||
end
|
||||
|
||||
defp get_for_user_q(user_id, domain, roles) do
|
||||
from(s in Site,
|
||||
join: sm in Site.Membership,
|
||||
|
|
|
|||
|
|
@ -4,10 +4,6 @@ defmodule Plausible.Verification do
|
|||
"""
|
||||
use Plausible
|
||||
|
||||
def enabled?() do
|
||||
:plausible |> Application.fetch_env!(__MODULE__) |> Keyword.fetch!(:enabled?)
|
||||
end
|
||||
|
||||
on_ee do
|
||||
def user_agent() do
|
||||
"Plausible Verification Agent - if abused, contact support@plausible.io"
|
||||
|
|
|
|||
|
|
@ -1,38 +1,16 @@
|
|||
defmodule PlausibleWeb.Components.FlowProgress do
|
||||
@moduledoc """
|
||||
Component for provisioning/registration flows displaying
|
||||
progress status.
|
||||
progress status. See `PlausibleWeb.Flows` for the list of
|
||||
flow definitions.
|
||||
"""
|
||||
use Phoenix.Component
|
||||
|
||||
@flows %{
|
||||
"register" => [
|
||||
"Register",
|
||||
"Activate account",
|
||||
"Add site info",
|
||||
"Install snippet",
|
||||
"Verify snippet"
|
||||
],
|
||||
"invitation" => [
|
||||
"Register",
|
||||
"Activate account"
|
||||
],
|
||||
"provisioning" => [
|
||||
"Add site info",
|
||||
"Install snippet",
|
||||
"Verify snippet"
|
||||
]
|
||||
}
|
||||
|
||||
@values @flows |> Enum.flat_map(fn {_, steps} -> steps end) |> Enum.uniq()
|
||||
|
||||
def flows, do: @flows
|
||||
|
||||
attr :flow, :string, required: true
|
||||
attr :current_step, :string, required: true, values: @values
|
||||
attr :flow, :string, required: true, values: PlausibleWeb.Flows.valid_keys()
|
||||
attr :current_step, :string, required: true, values: PlausibleWeb.Flows.valid_values()
|
||||
|
||||
def render(assigns) do
|
||||
steps = Map.get(flows(), assigns.flow, [])
|
||||
steps = PlausibleWeb.Flows.steps(assigns.flow)
|
||||
current_step_idx = Enum.find_index(steps, &(&1 == assigns.current_step))
|
||||
|
||||
assigns =
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ defmodule PlausibleWeb.Components.Generic do
|
|||
attr :new_tab, :boolean, default: false
|
||||
attr :class, :string, default: ""
|
||||
attr :rest, :global
|
||||
attr :method, :string, default: "get"
|
||||
slot :inner_block
|
||||
|
||||
def styled_link(assigns) do
|
||||
|
|
@ -188,6 +189,7 @@ defmodule PlausibleWeb.Components.Generic do
|
|||
<.unstyled_link
|
||||
new_tab={@new_tab}
|
||||
href={@href}
|
||||
method={@method}
|
||||
class={"text-indigo-600 hover:text-indigo-700 dark:text-indigo-500 dark:hover:text-indigo-600 " <> @class}
|
||||
{@rest}
|
||||
>
|
||||
|
|
@ -263,9 +265,23 @@ defmodule PlausibleWeb.Components.Generic do
|
|||
attr :class, :string, default: ""
|
||||
attr :id, :any, default: nil
|
||||
attr :rest, :global
|
||||
attr :method, :string, default: "get"
|
||||
slot :inner_block
|
||||
|
||||
def unstyled_link(assigns) do
|
||||
extra =
|
||||
if assigns.method == "get" do
|
||||
[]
|
||||
else
|
||||
[
|
||||
"data-csrf": Phoenix.HTML.Tag.csrf_token_value(assigns.href),
|
||||
"data-method": assigns.method,
|
||||
"data-to": assigns.href
|
||||
]
|
||||
end
|
||||
|
||||
assigns = assign(assigns, extra: extra)
|
||||
|
||||
if assigns[:new_tab] do
|
||||
assigns = assign(assigns, :icon_class, icon_class(assigns))
|
||||
|
||||
|
|
@ -279,6 +295,7 @@ defmodule PlausibleWeb.Components.Generic do
|
|||
href={@href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
{@extra}
|
||||
{@rest}
|
||||
>
|
||||
<%= render_slot(@inner_block) %>
|
||||
|
|
@ -287,7 +304,7 @@ defmodule PlausibleWeb.Components.Generic do
|
|||
"""
|
||||
else
|
||||
~H"""
|
||||
<.link class={@class} href={@href} {@rest}>
|
||||
<.link class={@class} href={@href} {@extra} {@rest}>
|
||||
<%= render_slot(@inner_block) %>
|
||||
</.link>
|
||||
"""
|
||||
|
|
@ -320,12 +337,22 @@ defmodule PlausibleWeb.Components.Generic do
|
|||
|
||||
attr :wrapper_class, :any, default: ""
|
||||
attr :class, :any, default: ""
|
||||
attr :icon?, :boolean, default: true
|
||||
attr :sticky?, :boolean, default: true
|
||||
attr :position, :string, default: "bottom-10 margin-x-auto left-10 right-10"
|
||||
slot :inner_block, required: true
|
||||
slot :tooltip_content, required: true
|
||||
|
||||
def tooltip(assigns) do
|
||||
wrapper_data =
|
||||
if assigns[:sticky?], do: "{sticky: false, hovered: false}", else: "{hovered: false}"
|
||||
|
||||
show_inner = if assigns[:sticky?], do: "hovered || sticky", else: "hovered"
|
||||
|
||||
assigns = assign(assigns, wrapper_data: wrapper_data, show_inner: show_inner)
|
||||
|
||||
~H"""
|
||||
<div x-data="{sticky: false, hovered: false}" class={["tooltip-wrapper relative", @wrapper_class]}>
|
||||
<div x-data={@wrapper_data} class={["tooltip-wrapper relative", @wrapper_class]}>
|
||||
<p
|
||||
x-on:click="sticky = true; hovered = true"
|
||||
x-on:click.outside="sticky = false; hovered = false"
|
||||
|
|
@ -334,11 +361,14 @@ defmodule PlausibleWeb.Components.Generic do
|
|||
class={["cursor-pointer flex align-items-center", @class]}
|
||||
>
|
||||
<%= render_slot(@inner_block) %>
|
||||
<Heroicons.information_circle class="w-5 h-5 ml-2" />
|
||||
<Heroicons.information_circle :if={@icon?} class="w-5 h-5 ml-2" />
|
||||
</p>
|
||||
<span
|
||||
x-show="hovered || sticky"
|
||||
class="bg-gray-900 pointer-events-none absolute bottom-10 margin-x-auto left-10 right-10 transition-opacity p-4 rounded text-sm text-white"
|
||||
x-show={@show_inner}
|
||||
class={[
|
||||
"bg-gray-900 pointer-events-none absolute transition-opacity p-4 rounded text-sm text-white",
|
||||
@position
|
||||
]}
|
||||
>
|
||||
<%= render_slot(List.first(@tooltip_content)) %>
|
||||
</span>
|
||||
|
|
@ -369,20 +399,27 @@ defmodule PlausibleWeb.Components.Generic do
|
|||
end
|
||||
end
|
||||
|
||||
slot :item, required: true
|
||||
|
||||
def focus_list(assigns) do
|
||||
~H"""
|
||||
<ol class="list-disc space-y-1 ml-4 mt-1 mb-4">
|
||||
<li :for={item <- @item} class="marker:text-indigo-700 dark:marker:text-indigo-700">
|
||||
<%= render_slot(item) %>
|
||||
</li>
|
||||
</ol>
|
||||
"""
|
||||
end
|
||||
|
||||
slot :title
|
||||
slot :subtitle
|
||||
slot :inner_block, required: true
|
||||
slot :footer
|
||||
|
||||
attr :outer_markup, :boolean, default: true
|
||||
|
||||
def focus_box(assigns) do
|
||||
~H"""
|
||||
<div class={[
|
||||
"bg-white w-full max-w-lg mx-auto dark:bg-gray-800 text-black dark:text-gray-100",
|
||||
@outer_markup && "shadow-md rounded mb-4 mt-8"
|
||||
]}>
|
||||
<div class={[@outer_markup && "p-8"]}>
|
||||
<div class="focus-box bg-white w-full max-w-lg mx-auto dark:bg-gray-800 text-black dark:text-gray-100 shadow-md rounded mb-4 mt-8">
|
||||
<div class="p-8">
|
||||
<h2 :if={@title != []} class="text-xl font-black dark:text-gray-100">
|
||||
<%= render_slot(@title) %>
|
||||
</h2>
|
||||
|
|
@ -403,7 +440,7 @@ defmodule PlausibleWeb.Components.Generic do
|
|||
:if={@footer != []}
|
||||
class="flex flex-col dark:text-gray-200 border-t border-gray-300 dark:border-gray-700"
|
||||
>
|
||||
<div class={[@outer_markup && "p-8"]}>
|
||||
<div class="p-8">
|
||||
<%= render_slot(@footer) %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,9 @@
|
|||
defmodule PlausibleWeb.Api.InternalController do
|
||||
use PlausibleWeb, :controller
|
||||
use Plausible.Repo
|
||||
alias Plausible.Stats.Clickhouse, as: Stats
|
||||
alias Plausible.{Sites, Site, Auth}
|
||||
alias Plausible.{Sites, Auth}
|
||||
alias Plausible.Auth.User
|
||||
|
||||
def domain_status(conn, %{"domain" => domain}) do
|
||||
with %User{id: user_id} <- conn.assigns[:current_user],
|
||||
%Site{} = site <- Sites.get_by_domain(domain),
|
||||
true <- Sites.has_admin_access?(user_id, site) || Auth.is_super_admin?(user_id),
|
||||
true <- Stats.has_pageviews?(site) do
|
||||
json(conn, "READY")
|
||||
else
|
||||
_ ->
|
||||
json(conn, "WAITING")
|
||||
end
|
||||
end
|
||||
|
||||
def sites(conn, _params) do
|
||||
current_user = conn.assigns[:current_user]
|
||||
|
||||
|
|
|
|||
|
|
@ -62,12 +62,11 @@ defmodule PlausibleWeb.AuthController do
|
|||
|
||||
def activate_form(conn, params) do
|
||||
user = conn.assigns.current_user
|
||||
flow = params["flow"] || "register"
|
||||
flow = params["flow"] || PlausibleWeb.Flows.register()
|
||||
|
||||
render(conn, "activate.html",
|
||||
has_email_code?: Plausible.Users.has_email_code?(user),
|
||||
has_any_memberships?: Plausible.Site.Memberships.any?(user),
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
form_submit_url: "/activate?flow=#{flow}"
|
||||
)
|
||||
end
|
||||
|
|
@ -98,7 +97,6 @@ defmodule PlausibleWeb.AuthController do
|
|||
error: "Incorrect activation code",
|
||||
has_email_code?: true,
|
||||
has_any_memberships?: has_any_memberships?,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
form_submit_url: "/activate?flow=#{flow}"
|
||||
)
|
||||
|
||||
|
|
@ -107,7 +105,6 @@ defmodule PlausibleWeb.AuthController do
|
|||
error: "Code is expired, please request another one",
|
||||
has_email_code?: false,
|
||||
has_any_memberships?: has_any_memberships?,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
form_submit_url: "/activate?flow=#{flow}"
|
||||
)
|
||||
end
|
||||
|
|
@ -123,16 +120,11 @@ defmodule PlausibleWeb.AuthController do
|
|||
end
|
||||
|
||||
def password_reset_request_form(conn, _) do
|
||||
render(conn, "password_reset_request_form.html",
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
)
|
||||
render(conn, "password_reset_request_form.html")
|
||||
end
|
||||
|
||||
def password_reset_request(conn, %{"email" => ""}) do
|
||||
render(conn, "password_reset_request_form.html",
|
||||
error: "Please enter an email address",
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
)
|
||||
render(conn, "password_reset_request_form.html", error: "Please enter an email address")
|
||||
end
|
||||
|
||||
def password_reset_request(conn, %{"email" => email} = params) do
|
||||
|
|
@ -149,20 +141,13 @@ defmodule PlausibleWeb.AuthController do
|
|||
"Password reset e-mail sent. In dev environment GET /sent-emails for details."
|
||||
)
|
||||
|
||||
render(conn, "password_reset_request_success.html",
|
||||
email: email,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
)
|
||||
render(conn, "password_reset_request_success.html", email: email)
|
||||
else
|
||||
render(conn, "password_reset_request_success.html",
|
||||
email: email,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
)
|
||||
render(conn, "password_reset_request_success.html", email: email)
|
||||
end
|
||||
else
|
||||
render(conn, "password_reset_request_form.html",
|
||||
error: "Please complete the captcha to reset your password",
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
error: "Please complete the captcha to reset your password"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -172,8 +157,7 @@ defmodule PlausibleWeb.AuthController do
|
|||
{:ok, %{email: email}} ->
|
||||
render(conn, "password_reset_form.html",
|
||||
connect_live_socket: true,
|
||||
email: email,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
email: email
|
||||
)
|
||||
|
||||
{:error, :expired} ->
|
||||
|
|
@ -201,7 +185,7 @@ defmodule PlausibleWeb.AuthController do
|
|||
end
|
||||
|
||||
def login_form(conn, _params) do
|
||||
render(conn, "login_form.html", layout: {PlausibleWeb.LayoutView, "focus.html"})
|
||||
render(conn, "login_form.html")
|
||||
end
|
||||
|
||||
def login(conn, %{"user" => params}) do
|
||||
|
|
@ -221,9 +205,9 @@ defmodule PlausibleWeb.AuthController do
|
|||
|
||||
flow =
|
||||
if params["register_action"] == "register_form" do
|
||||
"register"
|
||||
PlausibleWeb.Flows.register()
|
||||
else
|
||||
"invitation"
|
||||
PlausibleWeb.Flows.invitation()
|
||||
end
|
||||
|
||||
Routes.auth_path(conn, :activate_form, flow: flow)
|
||||
|
|
@ -243,19 +227,13 @@ defmodule PlausibleWeb.AuthController do
|
|||
{:error, :wrong_password} ->
|
||||
maybe_log_failed_login_attempts("wrong password for #{email}")
|
||||
|
||||
render(conn, "login_form.html",
|
||||
error: "Wrong email or password. Please try again.",
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
)
|
||||
render(conn, "login_form.html", error: "Wrong email or password. Please try again.")
|
||||
|
||||
{:error, :user_not_found} ->
|
||||
maybe_log_failed_login_attempts("user not found for #{email}")
|
||||
Plausible.Auth.Password.dummy_calculation()
|
||||
|
||||
render(conn, "login_form.html",
|
||||
error: "Wrong email or password. Please try again.",
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
)
|
||||
render(conn, "login_form.html", error: "Wrong email or password. Please try again.")
|
||||
|
||||
{:error, {:rate_limit, _}} ->
|
||||
maybe_log_failed_login_attempts("too many login attempts for #{email}")
|
||||
|
|
@ -370,8 +348,7 @@ defmodule PlausibleWeb.AuthController do
|
|||
{:ok, user} ->
|
||||
if Auth.TOTP.enabled?(user) do
|
||||
render(conn, "verify_2fa.html",
|
||||
remember_2fa_days: TwoFactor.Session.remember_2fa_days(),
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
remember_2fa_days: TwoFactor.Session.remember_2fa_days()
|
||||
)
|
||||
else
|
||||
redirect_to_login(conn)
|
||||
|
|
@ -398,8 +375,7 @@ defmodule PlausibleWeb.AuthController do
|
|||
conn
|
||||
|> put_flash(:error, "The provided code is invalid. Please try again")
|
||||
|> render("verify_2fa.html",
|
||||
remember_2fa_days: TwoFactor.Session.remember_2fa_days(),
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
remember_2fa_days: TwoFactor.Session.remember_2fa_days()
|
||||
)
|
||||
|
||||
{:error, :not_enabled} ->
|
||||
|
|
@ -412,9 +388,7 @@ defmodule PlausibleWeb.AuthController do
|
|||
case TwoFactor.Session.get_2fa_user(conn) do
|
||||
{:ok, user} ->
|
||||
if Auth.TOTP.enabled?(user) do
|
||||
render(conn, "verify_2fa_recovery_code.html",
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
)
|
||||
render(conn, "verify_2fa_recovery_code.html")
|
||||
else
|
||||
redirect_to_login(conn)
|
||||
end
|
||||
|
|
@ -435,9 +409,7 @@ defmodule PlausibleWeb.AuthController do
|
|||
|
||||
conn
|
||||
|> put_flash(:error, "The provided recovery code is invalid. Please try another one")
|
||||
|> render("verify_2fa_recovery_code.html",
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
)
|
||||
|> render("verify_2fa_recovery_code.html")
|
||||
|
||||
{:error, :not_enabled} ->
|
||||
UserAuth.log_in_user(conn, user)
|
||||
|
|
@ -577,10 +549,7 @@ defmodule PlausibleWeb.AuthController do
|
|||
def new_api_key(conn, _params) do
|
||||
changeset = Auth.ApiKey.changeset(%Auth.ApiKey{})
|
||||
|
||||
render(conn, "new_api_key.html",
|
||||
changeset: changeset,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
)
|
||||
render(conn, "new_api_key.html", changeset: changeset)
|
||||
end
|
||||
|
||||
def create_api_key(conn, %{"api_key" => %{"name" => name, "key" => key}}) do
|
||||
|
|
@ -591,10 +560,7 @@ defmodule PlausibleWeb.AuthController do
|
|||
|> redirect(to: "/settings#api-keys")
|
||||
|
||||
{:error, changeset} ->
|
||||
render(conn, "new_api_key.html",
|
||||
changeset: changeset,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
)
|
||||
render(conn, "new_api_key.html", changeset: changeset)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ defmodule PlausibleWeb.BillingController do
|
|||
else
|
||||
render(conn, "choose_plan.html",
|
||||
skip_plausible_tracking: true,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
connect_live_socket: true
|
||||
)
|
||||
end
|
||||
|
|
@ -48,10 +47,7 @@ defmodule PlausibleWeb.BillingController do
|
|||
redirect(conn, to: Routes.auth_path(conn, :user_settings))
|
||||
|
||||
subscribed_to_latest? ->
|
||||
render(conn, "change_enterprise_plan_contact_us.html",
|
||||
skip_plausible_tracking: true,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
)
|
||||
render(conn, "change_enterprise_plan_contact_us.html", skip_plausible_tracking: true)
|
||||
|
||||
true ->
|
||||
render(conn, "upgrade_to_enterprise_plan.html",
|
||||
|
|
@ -59,14 +55,13 @@ defmodule PlausibleWeb.BillingController do
|
|||
price: price,
|
||||
subscription_resumable: subscription_resumable?,
|
||||
contact_link: "https://plausible.io/contact",
|
||||
skip_plausible_tracking: true,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
skip_plausible_tracking: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def upgrade_success(conn, _params) do
|
||||
render(conn, "upgrade_success.html", layout: {PlausibleWeb.LayoutView, "focus.html"})
|
||||
render(conn, "upgrade_success.html")
|
||||
end
|
||||
|
||||
def change_plan_preview(conn, %{"plan_id" => new_plan_id}) do
|
||||
|
|
@ -78,8 +73,7 @@ defmodule PlausibleWeb.BillingController do
|
|||
back_link: Routes.billing_path(conn, :choose_plan),
|
||||
skip_plausible_tracking: true,
|
||||
subscription: subscription,
|
||||
preview_info: preview_info,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
preview_info: preview_info
|
||||
)
|
||||
|
||||
_ ->
|
||||
|
|
|
|||
|
|
@ -43,8 +43,7 @@ defmodule PlausibleWeb.DebugController do
|
|||
|
||||
conn
|
||||
|> render("clickhouse.html",
|
||||
queries: queries,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
queries: queries
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -46,8 +46,7 @@ defmodule PlausibleWeb.GoogleAnalyticsController do
|
|||
expires_at: expires_at,
|
||||
site: conn.assigns.site,
|
||||
properties: properties,
|
||||
selected_property_error: error,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
selected_property_error: error
|
||||
)
|
||||
|
||||
{:error, :rate_limit_exceeded} ->
|
||||
|
|
@ -182,8 +181,7 @@ defmodule PlausibleWeb.GoogleAnalyticsController do
|
|||
selected_property: property,
|
||||
selected_property_name: property_name,
|
||||
start_date: start_date,
|
||||
end_date: end_date,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
end_date: end_date
|
||||
)
|
||||
|
||||
{:error, :rate_limit_exceeded} ->
|
||||
|
|
|
|||
|
|
@ -10,6 +10,6 @@ defmodule PlausibleWeb.PageController do
|
|||
This controller action is only ever triggered in self-hosted Plausible.
|
||||
"""
|
||||
def index(conn, _params) do
|
||||
render(conn, "index.html", layout: {PlausibleWeb.LayoutView, "focus.html"})
|
||||
render(conn, "index.html")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ defmodule PlausibleWeb.Site.MembershipController do
|
|||
conn,
|
||||
"invite_member_form.html",
|
||||
site: site,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
team_member_limit: limit,
|
||||
is_at_limit: not below_limit?,
|
||||
skip_plausible_tracking: true
|
||||
|
|
@ -64,7 +63,6 @@ defmodule PlausibleWeb.Site.MembershipController do
|
|||
render(conn, "invite_member_form.html",
|
||||
error: "Cannot send invite because #{email} is already a member of #{site.domain}",
|
||||
site: site,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
skip_plausible_tracking: true
|
||||
)
|
||||
|
||||
|
|
@ -73,7 +71,6 @@ defmodule PlausibleWeb.Site.MembershipController do
|
|||
error:
|
||||
"Your account is limited to #{limit} team members. You can upgrade your plan to increase this limit.",
|
||||
site: site,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
skip_plausible_tracking: true,
|
||||
is_at_limit: true,
|
||||
team_member_limit: limit
|
||||
|
|
@ -103,7 +100,6 @@ defmodule PlausibleWeb.Site.MembershipController do
|
|||
conn,
|
||||
"transfer_ownership_form.html",
|
||||
site: site,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
skip_plausible_tracking: true
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,14 +14,13 @@ defmodule PlausibleWeb.SiteController do
|
|||
)
|
||||
|
||||
def new(conn, params) do
|
||||
flow = params["flow"] || "register"
|
||||
flow = params["flow"] || PlausibleWeb.Flows.register()
|
||||
current_user = conn.assigns[:current_user]
|
||||
|
||||
render(conn, "new.html",
|
||||
changeset: Plausible.Site.changeset(%Plausible.Site{}),
|
||||
site_limit: Quota.Limits.site_limit(current_user),
|
||||
site_limit_exceeded?: Quota.ensure_can_add_new_site(current_user) != :ok,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
form_submit_url: "/sites?flow=#{flow}",
|
||||
flow: flow
|
||||
)
|
||||
|
|
@ -41,7 +40,7 @@ defmodule PlausibleWeb.SiteController do
|
|||
|
||||
redirect(conn,
|
||||
external:
|
||||
Routes.site_path(conn, :add_snippet, site.domain,
|
||||
Routes.site_path(conn, :installation, site.domain,
|
||||
site_created: true,
|
||||
flow: flow
|
||||
)
|
||||
|
|
@ -53,7 +52,6 @@ defmodule PlausibleWeb.SiteController do
|
|||
first_site?: first_site?,
|
||||
site_limit: limit,
|
||||
site_limit_exceeded?: true,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
flow: flow,
|
||||
form_submit_url: "/sites?flow=#{flow}"
|
||||
)
|
||||
|
|
@ -64,37 +62,12 @@ defmodule PlausibleWeb.SiteController do
|
|||
first_site?: first_site?,
|
||||
site_limit: Quota.Limits.site_limit(user),
|
||||
site_limit_exceeded?: false,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
flow: flow,
|
||||
form_submit_url: "/sites?flow=#{flow}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def add_snippet(conn, params) do
|
||||
flow = params["flow"] || "register"
|
||||
user = conn.assigns[:current_user]
|
||||
site = conn.assigns[:site]
|
||||
|
||||
is_first_site =
|
||||
!Repo.exists?(
|
||||
from(sm in Plausible.Site.Membership,
|
||||
where:
|
||||
sm.user_id == ^user.id and
|
||||
sm.site_id != ^site.id
|
||||
)
|
||||
)
|
||||
|
||||
conn
|
||||
|> render("snippet.html",
|
||||
site: site,
|
||||
skip_plausible_tracking: true,
|
||||
is_first_site: is_first_site,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
form_submit_url: "/#{URI.encode_www_form(site.domain)}?flow=#{flow}"
|
||||
)
|
||||
end
|
||||
|
||||
def update_feature_visibility(conn, %{
|
||||
"setting" => setting,
|
||||
"r" => "/" <> _ = redirect_path,
|
||||
|
|
@ -585,8 +558,7 @@ defmodule PlausibleWeb.SiteController do
|
|||
|> assign(:skip_plausible_tracking, true)
|
||||
|> render("new_shared_link.html",
|
||||
site: site,
|
||||
changeset: changeset,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
changeset: changeset
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -602,8 +574,7 @@ defmodule PlausibleWeb.SiteController do
|
|||
|> assign(:skip_plausible_tracking, true)
|
||||
|> render("new_shared_link.html",
|
||||
site: site,
|
||||
changeset: changeset,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
changeset: changeset
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -617,8 +588,7 @@ defmodule PlausibleWeb.SiteController do
|
|||
|> assign(:skip_plausible_tracking, true)
|
||||
|> render("edit_shared_link.html",
|
||||
site: site,
|
||||
changeset: changeset,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
changeset: changeset
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -636,8 +606,7 @@ defmodule PlausibleWeb.SiteController do
|
|||
|> assign(:skip_plausible_tracking, true)
|
||||
|> render("edit_shared_link.html",
|
||||
site: site,
|
||||
changeset: changeset,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
changeset: changeset
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -750,7 +719,6 @@ defmodule PlausibleWeb.SiteController do
|
|||
conn
|
||||
|> assign(:skip_plausible_tracking, true)
|
||||
|> render("csv_import.html",
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
connect_live_socket: true
|
||||
)
|
||||
end
|
||||
|
|
@ -760,8 +728,7 @@ defmodule PlausibleWeb.SiteController do
|
|||
|
||||
render(conn, "change_domain.html",
|
||||
skip_plausible_tracking: true,
|
||||
changeset: changeset,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
changeset: changeset
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -771,29 +738,20 @@ defmodule PlausibleWeb.SiteController do
|
|||
conn
|
||||
|> put_flash(:success, "Website domain changed successfully")
|
||||
|> redirect(
|
||||
external: Routes.site_path(conn, :add_snippet_after_domain_change, updated_site.domain)
|
||||
external:
|
||||
Routes.site_path(conn, :installation, updated_site.domain,
|
||||
flow: PlausibleWeb.Flows.domain_change()
|
||||
)
|
||||
)
|
||||
|
||||
{:error, changeset} ->
|
||||
render(conn, "change_domain.html",
|
||||
skip_plausible_tracking: true,
|
||||
changeset: changeset,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
changeset: changeset
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def add_snippet_after_domain_change(conn, _params) do
|
||||
site = conn.assigns[:site]
|
||||
|
||||
conn
|
||||
|> assign(:skip_plausible_tracking, true)
|
||||
|> render("snippet_after_domain_change.html",
|
||||
site: site,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
)
|
||||
end
|
||||
|
||||
defp tolerate_unique_contraint_violation(result, name) do
|
||||
case result do
|
||||
{:ok, _} ->
|
||||
|
|
|
|||
|
|
@ -79,20 +79,7 @@ defmodule PlausibleWeb.StatsController do
|
|||
)
|
||||
|
||||
!stats_start_date && can_see_stats? ->
|
||||
render_opts = [
|
||||
site: site,
|
||||
dogfood_page_path: dogfood_page_path,
|
||||
connect_live_socket: true
|
||||
]
|
||||
|
||||
render_opts =
|
||||
if conn.params["flow"] do
|
||||
Keyword.put(render_opts, :layout, {PlausibleWeb.LayoutView, "focus.html"})
|
||||
else
|
||||
render_opts
|
||||
end
|
||||
|
||||
render(conn, "waiting_first_pageview.html", render_opts)
|
||||
redirect(conn, external: Routes.site_path(conn, :verification, site.domain))
|
||||
|
||||
Sites.locked?(site) ->
|
||||
site = Plausible.Repo.preload(site, :owner)
|
||||
|
|
@ -280,7 +267,6 @@ defmodule PlausibleWeb.StatsController do
|
|||
conn
|
||||
|> render("shared_link_password.html",
|
||||
link: shared_link,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
dogfood_page_path: "/share/:dashboard"
|
||||
)
|
||||
end
|
||||
|
|
@ -325,7 +311,6 @@ defmodule PlausibleWeb.StatsController do
|
|||
|> render("shared_link_password.html",
|
||||
link: shared_link,
|
||||
error: "Incorrect password. Please try again.",
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
||||
dogfood_page_path: "/share/:dashboard"
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ defmodule PlausibleWeb.UnsubscribeController do
|
|||
|> assign(:skip_plausible_tracking, true)
|
||||
|> render("success.html",
|
||||
interval: "weekly",
|
||||
site: website,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
site: site || %{domain: website}
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -40,8 +39,7 @@ defmodule PlausibleWeb.UnsubscribeController do
|
|||
|> assign(:skip_plausible_tracking, true)
|
||||
|> render("success.html",
|
||||
interval: "monthly",
|
||||
site: website,
|
||||
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
||||
site: site || %{domain: website}
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ defmodule PlausibleWeb.Email do
|
|||
})
|
||||
end
|
||||
|
||||
def drop_notification(email, site, current_visitors, dashboard_link, settings_link) do
|
||||
def drop_notification(email, site, current_visitors, dashboard_link, installation_link) do
|
||||
base_email()
|
||||
|> to(email)
|
||||
|> tag("drop-notification")
|
||||
|
|
@ -150,7 +150,7 @@ defmodule PlausibleWeb.Email do
|
|||
site: site,
|
||||
current_visitors: current_visitors,
|
||||
dashboard_link: dashboard_link,
|
||||
settings_link: settings_link
|
||||
installation_link: installation_link
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
defmodule PlausibleWeb.Flows do
|
||||
@moduledoc """
|
||||
Static compile-time definitions for user progress flows.
|
||||
See `PlausibleWeb.Components.FlowProgress` for rendering capabilities.
|
||||
"""
|
||||
|
||||
@flows %{
|
||||
review: [
|
||||
"Install Plausible",
|
||||
"Verify installation"
|
||||
],
|
||||
domain_change: [
|
||||
"Set up new domain",
|
||||
"Install Plausible",
|
||||
"Verify installation"
|
||||
],
|
||||
register: [
|
||||
"Register",
|
||||
"Activate account",
|
||||
"Add site info",
|
||||
"Install Plausible",
|
||||
"Verify installation"
|
||||
],
|
||||
invitation: [
|
||||
"Register",
|
||||
"Activate account"
|
||||
],
|
||||
provisioning: [
|
||||
"Add site info",
|
||||
"Install Plausible",
|
||||
"Verify installation"
|
||||
]
|
||||
}
|
||||
|
||||
@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
|
||||
end
|
||||
|
|
@ -6,10 +6,11 @@ defmodule PlausibleWeb.Live.Components.Verification do
|
|||
use Phoenix.LiveComponent
|
||||
use Plausible
|
||||
|
||||
alias PlausibleWeb.Router.Helpers, as: Routes
|
||||
|
||||
import PlausibleWeb.Components.Generic
|
||||
|
||||
attr :domain, :string, required: true
|
||||
attr :modal?, :boolean, default: false
|
||||
|
||||
attr :message, :string, default: "We're visiting your site to ensure that everything is working"
|
||||
|
||||
|
|
@ -18,20 +19,22 @@ defmodule PlausibleWeb.Live.Components.Verification do
|
|||
attr :interpretation, Plausible.Verification.Diagnostics.Result, default: nil
|
||||
attr :attempts, :integer, default: 0
|
||||
attr :flow, :string, default: ""
|
||||
attr :installation_type, :string, default: nil
|
||||
attr :awaiting_first_pageview?, :boolean, default: false
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div id="progress-indicator">
|
||||
<PlausibleWeb.Components.Generic.focus_box outer_markup={not @modal?}>
|
||||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<div
|
||||
:if={not @finished? or (not @modal? and @success?)}
|
||||
:if={not @finished?}
|
||||
class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100 dark:bg-gray-700"
|
||||
>
|
||||
<div class="block pulsating-circle"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
:if={@finished? and @success? and @modal?}
|
||||
:if={@finished? and @success?}
|
||||
class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100 dark:bg-green-500"
|
||||
id="check-circle"
|
||||
>
|
||||
|
|
@ -49,17 +52,21 @@ defmodule PlausibleWeb.Live.Components.Verification do
|
|||
<div class="mt-6">
|
||||
<h3 class="font-semibold leading-6 text-xl">
|
||||
<span :if={@finished? and @success?}>Success!</span>
|
||||
<span :if={not @finished?}>Verifying your integration</span>
|
||||
<span :if={not @finished?}>Verifying your installation</span>
|
||||
|
||||
<span :if={@finished? and not @success? and @interpretation}>
|
||||
<%= List.first(@interpretation.errors) %>
|
||||
</span>
|
||||
</h3>
|
||||
<p :if={@finished? and @success? and @modal?} id="progress" class="mt-2">
|
||||
Your integration is working and visitors are being counted accurately
|
||||
<p :if={@finished? and @success?} id="progress" class="mt-2">
|
||||
Your installation is working and visitors are being counted accurately
|
||||
</p>
|
||||
<p :if={@finished? and @success? and not @modal?} id="progress" class="mt-2 animate-pulse">
|
||||
Your integration is working. Awaiting your first pageview.
|
||||
<p
|
||||
:if={@finished? and @success? and @awaiting_first_pageview?}
|
||||
id="progress"
|
||||
class="mt-2 animate-pulse"
|
||||
>
|
||||
Awaiting your first pageview
|
||||
</p>
|
||||
<p :if={not @finished?} class="mt-2 animate-pulse" id="progress"><%= @message %></p>
|
||||
|
||||
|
|
@ -77,7 +84,7 @@ defmodule PlausibleWeb.Live.Components.Verification do
|
|||
|
||||
<div :if={@finished?} class="mt-8">
|
||||
<.button_link :if={not @success?} href="#" phx-click="retry" class="w-full">
|
||||
Verify integration again
|
||||
Verify installation again
|
||||
</.button_link>
|
||||
<.button_link
|
||||
:if={@success?}
|
||||
|
|
@ -88,34 +95,32 @@ defmodule PlausibleWeb.Live.Components.Verification do
|
|||
</.button_link>
|
||||
</div>
|
||||
|
||||
<:footer :if={
|
||||
(not @modal? and not @success?) or
|
||||
(@finished? and not @success?)
|
||||
}>
|
||||
<ol class="list-disc space-y-1 ml-4 mt-1 mb-4">
|
||||
<%= if ee?() and @finished? and not @success? and @attempts >= 3 do %>
|
||||
<li>
|
||||
<b>Need further help with your integration?</b>
|
||||
<:footer :if={@finished? and not @success?}>
|
||||
<.focus_list>
|
||||
<:item :if={ee?() and @attempts >= 3}>
|
||||
<b>Need further help with your installation?</b>
|
||||
<.styled_link href="https://plausible.io/contact">
|
||||
Contact us
|
||||
</.styled_link>
|
||||
</li>
|
||||
<% end %>
|
||||
<%= if not @success? and not @modal? do %>
|
||||
<li>
|
||||
Need to see the snippet again?
|
||||
<.styled_link href={"/#{URI.encode_www_form(@domain)}/snippet?flow=#{@flow}"}>
|
||||
</:item>
|
||||
<:item>
|
||||
Need to see installation instructions again?
|
||||
<.styled_link href={
|
||||
Routes.site_path(PlausibleWeb.Endpoint, :installation, @domain,
|
||||
flow: @flow,
|
||||
installation_type: @installation_type
|
||||
)
|
||||
}>
|
||||
Click here
|
||||
</.styled_link>
|
||||
</li>
|
||||
<li>
|
||||
Run verification later and go to Site Settings?
|
||||
</:item>
|
||||
<:item>
|
||||
Run verification later and go to site settings?
|
||||
<.styled_link href={"/#{URI.encode_www_form(@domain)}/settings/general"}>
|
||||
Click here
|
||||
</.styled_link>
|
||||
</li>
|
||||
<% end %>
|
||||
</ol>
|
||||
</:item>
|
||||
</.focus_list>
|
||||
</:footer>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,526 @@
|
|||
defmodule PlausibleWeb.Live.Installation do
|
||||
@moduledoc """
|
||||
User assistance module around Plausible installation instructions/onboarding
|
||||
"""
|
||||
use PlausibleWeb, :live_view
|
||||
use Phoenix.HTML
|
||||
|
||||
alias Plausible.Verification.{Checks, State}
|
||||
import PlausibleWeb.Components.Generic
|
||||
|
||||
@script_extension_params [
|
||||
"outbound-links",
|
||||
"tagged-events",
|
||||
"file-downloads",
|
||||
"hash",
|
||||
"pageview-props",
|
||||
"revenue"
|
||||
]
|
||||
|
||||
@script_config_params ["404" | @script_extension_params]
|
||||
|
||||
@installation_types [
|
||||
"GTM",
|
||||
"manual",
|
||||
"WordPress"
|
||||
]
|
||||
|
||||
@valid_qs_params @script_config_params ++ ["installation_type", "flow"]
|
||||
|
||||
def script_extension_params, do: @script_extension_params
|
||||
|
||||
def mount(
|
||||
%{"website" => domain} = params,
|
||||
_session,
|
||||
socket
|
||||
) do
|
||||
site = Plausible.Sites.get_for_user!(socket.assigns.current_user, domain)
|
||||
flow = params["flow"]
|
||||
meta = site.installation_meta || %Plausible.Site.InstallationMeta{}
|
||||
|
||||
script_config =
|
||||
@script_config_params
|
||||
|> Enum.into(%{}, &{&1, false})
|
||||
|> Map.merge(meta.script_config)
|
||||
|> Map.take(@script_config_params)
|
||||
|
||||
installation_type = get_installation_type(flow, meta, params)
|
||||
|
||||
if connected?(socket) and is_nil(installation_type) do
|
||||
Checks.run("https://#{domain}", domain,
|
||||
checks: [
|
||||
Checks.FetchBody,
|
||||
Checks.ScanBody
|
||||
],
|
||||
report_to: self(),
|
||||
async?: true,
|
||||
slowdown: 0
|
||||
)
|
||||
end
|
||||
|
||||
{:ok,
|
||||
assign(socket,
|
||||
uri_params: Map.take(params, @valid_qs_params),
|
||||
connected?: connected?(socket),
|
||||
site: site,
|
||||
site_created?: params["site_created"] == "true",
|
||||
flow: flow,
|
||||
installation_type: installation_type,
|
||||
initial_installation_type: installation_type,
|
||||
domain: domain,
|
||||
script_config: script_config
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_info({:verification_end, %State{} = state}, socket) do
|
||||
installation_type =
|
||||
case state.diagnostics do
|
||||
%{wordpress_likely?: true} -> "WordPress"
|
||||
%{gtm_likely?: true} -> "GTM"
|
||||
_ -> "manual"
|
||||
end
|
||||
|
||||
{:noreply,
|
||||
assign(socket,
|
||||
initial_installation_type: installation_type,
|
||||
installation_type: installation_type
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_info(_msg, socket) do
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div>
|
||||
<.flash_messages flash={@flash} />
|
||||
<PlausibleWeb.Components.FirstDashboardLaunchBanner.set :if={@site_created?} site={@site} />
|
||||
<PlausibleWeb.Components.FlowProgress.render flow={@flow} current_step="Install Plausible" />
|
||||
|
||||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<:title :if={is_nil(@installation_type)}>
|
||||
<div class="flex w-full mx-auto justify-center">
|
||||
<PlausibleWeb.Components.Generic.spinner class="spinner block text-center h-8 w-8" />
|
||||
</div>
|
||||
</:title>
|
||||
<:title :if={@installation_type == "WordPress"}>
|
||||
Install WordPress plugin
|
||||
</:title>
|
||||
<:title :if={@installation_type == "GTM"}>
|
||||
Install Google Tag Manager
|
||||
</:title>
|
||||
<:title :if={@installation_type == "manual"}>
|
||||
Manual installation
|
||||
</:title>
|
||||
|
||||
<:subtitle :if={is_nil(@installation_type)}>
|
||||
<div class="text-center mt-8">
|
||||
Determining installation type...
|
||||
<.styled_link
|
||||
:if={@connected?}
|
||||
href="#"
|
||||
phx-click="switch-installation-type"
|
||||
phx-value-method="manual"
|
||||
>
|
||||
Skip
|
||||
</.styled_link>
|
||||
</div>
|
||||
</:subtitle>
|
||||
|
||||
<:subtitle :if={@flow == PlausibleWeb.Flows.domain_change()}>
|
||||
<p class="mb-4">
|
||||
Your domain has been changed.
|
||||
<strong>
|
||||
You must update the Plausible Installation on your site within 72 hours to guarantee continuous tracking.
|
||||
</strong>
|
||||
<br />
|
||||
<br /> If you're using the API, please also make sure to update your API credentials.
|
||||
</p>
|
||||
</:subtitle>
|
||||
|
||||
<:subtitle :if={@flow == PlausibleWeb.Flows.review() and not is_nil(@installation_type)}>
|
||||
<p class="mb-4">
|
||||
Review your existing installation. You can skip this step and proceed to verifying your installation.
|
||||
</p>
|
||||
</:subtitle>
|
||||
|
||||
<:subtitle :if={@installation_type == "WordPress"}>
|
||||
We've detected your website is using WordPress. Here's how to integrate Plausible:
|
||||
<.focus_list>
|
||||
<:item>
|
||||
<.styled_link href="https://plausible.io/wordpress-analytics-plugin" new_tab={true}>
|
||||
Install our WordPress plugin
|
||||
</.styled_link>
|
||||
</:item>
|
||||
<:item>
|
||||
After activating our plugin, click the button below to verify your installation
|
||||
</:item>
|
||||
</.focus_list>
|
||||
</:subtitle>
|
||||
<:subtitle :if={@installation_type == "GTM"}>
|
||||
We've detected your website is using Google Tag Manager. Here's how to integrate Plausible:
|
||||
<.focus_list>
|
||||
<:item>
|
||||
<.styled_link href="https://plausible.io/docs/google-tag-manager" new_tab={true}>
|
||||
Read our Tag Manager guide
|
||||
</.styled_link>
|
||||
</:item>
|
||||
<:item>
|
||||
Paste this snippet into GTM's Custom HTML section. Once done, click the button below to verify your installation.
|
||||
</:item>
|
||||
</.focus_list>
|
||||
</:subtitle>
|
||||
|
||||
<:subtitle :if={@installation_type == "manual"}>
|
||||
Paste this snippet into the <code><head></code>
|
||||
section of your site. See our
|
||||
<.styled_link href="https://plausible.io/docs/integration-guides" new_tab={true}>
|
||||
installation guides.
|
||||
</.styled_link>
|
||||
Once done, click the button below to verify your installation.
|
||||
</:subtitle>
|
||||
|
||||
<div :if={@installation_type in ["manual", "GTM"]}>
|
||||
<.snippet_form
|
||||
installation_type={@installation_type}
|
||||
script_config={@script_config}
|
||||
domain={@domain}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<.button_link
|
||||
:if={not is_nil(@installation_type)}
|
||||
href={"/#{URI.encode_www_form(@domain)}/verification?#{URI.encode_query(@uri_params)}"}
|
||||
type="submit"
|
||||
class="w-full mt-8"
|
||||
>
|
||||
<%= if @flow == PlausibleWeb.Flows.domain_change() do %>
|
||||
I understand, I'll update my website
|
||||
<% else %>
|
||||
<%= if @flow == PlausibleWeb.Flows.review() do %>
|
||||
Verify your installation
|
||||
<% else %>
|
||||
Start collecting data
|
||||
<% end %>
|
||||
<% end %>
|
||||
</.button_link>
|
||||
|
||||
<:footer :if={@initial_installation_type == "WordPress" and @installation_type == "manual"}>
|
||||
<.styled_link href={} phx-click="switch-installation-type" phx-value-method="WordPress">
|
||||
Click here
|
||||
</.styled_link>
|
||||
if you prefer WordPress installation method.
|
||||
</:footer>
|
||||
|
||||
<:footer :if={@initial_installation_type == "GTM" and @installation_type == "manual"}>
|
||||
<.styled_link href={} phx-click="switch-installation-type" phx-value-method="GTM">
|
||||
Click here
|
||||
</.styled_link>
|
||||
if you prefer Google Tag Manager installation method.
|
||||
</:footer>
|
||||
|
||||
<:footer :if={not is_nil(@installation_type) and @installation_type != "manual"}>
|
||||
<.styled_link href={} phx-click="switch-installation-type" phx-value-method="manual">
|
||||
Click here
|
||||
</.styled_link>
|
||||
if you prefer manual installation method.
|
||||
</:footer>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_snippet("manual", domain, %{"404" => true} = script_config) do
|
||||
script_config = Map.put(script_config, "404", false)
|
||||
|
||||
"""
|
||||
#{render_snippet("manual", domain, script_config)}
|
||||
#{render_snippet_404()}
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_snippet("manual", domain, script_config) do
|
||||
~s|<script defer data-domain="#{domain}" src="#{tracker_url(script_config)}"></script>|
|
||||
end
|
||||
|
||||
defp render_snippet("GTM", domain, %{"404" => true} = script_config) do
|
||||
script_config = Map.put(script_config, "404", false)
|
||||
|
||||
"""
|
||||
#{render_snippet("GTM", domain, script_config)}
|
||||
#{render_snippet_404("GTM")}
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_snippet("GTM", domain, script_config) do
|
||||
"""
|
||||
<script>
|
||||
var script = document.createElement('script');
|
||||
script.defer = true;
|
||||
script.dataset.domain = "#{domain}";
|
||||
script.dataset.api = "https://plausible.io/api/event";
|
||||
script.src = "#{tracker_url(script_config)}";
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
</script>
|
||||
"""
|
||||
end
|
||||
|
||||
def render_snippet_404() do
|
||||
"<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>"
|
||||
end
|
||||
|
||||
def render_snippet_404("GTM") do
|
||||
render_snippet_404()
|
||||
end
|
||||
|
||||
defp script_extension_control(assigns) do
|
||||
~H"""
|
||||
<div class="mt-2 p-1">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={"check-#{@variant}"}
|
||||
name={@variant}
|
||||
checked={@config[@variant]}
|
||||
class="block h-5 w-5 rounded dark:bg-gray-700 border-gray-300 text-indigo-600 focus:ring-indigo-600 mr-2"
|
||||
/>
|
||||
<label for={"check-#{@variant}"}>
|
||||
<%= @label %>
|
||||
</label>
|
||||
<div class="ml-2">
|
||||
<.tooltip sticky?={false} icon?={false} position="z-50 w-64 hidden sm:block">
|
||||
<:tooltip_content>
|
||||
<%= @tooltip %>
|
||||
</:tooltip_content>
|
||||
<a href={@learn_more} target="_blank" rel="noopener noreferrer">
|
||||
<Heroicons.information_circle class="text-gray-700 dark:text-gray-500 w-5 h-5" />
|
||||
</a>
|
||||
</.tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp snippet_form(assigns) do
|
||||
~H"""
|
||||
<form id="snippet-form" phx-change="update-script-config">
|
||||
<div class="relative">
|
||||
<textarea
|
||||
id="snippet"
|
||||
class="w-full border-1 border-gray-300 rounded-md p-4 text-gray-700 0 dark:border-gray-500 dark:bg-gray-900 dark:text-gray-300"
|
||||
rows="5"
|
||||
readonly
|
||||
><%= render_snippet(@installation_type, @domain, @script_config) %></textarea>
|
||||
|
||||
<a
|
||||
onclick="var input = document.getElementById('snippet'); input.focus(); input.select(); document.execCommand('copy'); event.stopPropagation();"
|
||||
href="javascript:void(0)"
|
||||
class="absolute flex items-center text-xs font-medium text-indigo-600 no-underline hover:underline bottom-2 right-4 p-2 bg-white dark:bg-gray-900"
|
||||
>
|
||||
<Heroicons.document_duplicate class="pr-1 text-indigo-600 dark:text-indigo-500 w-5 h-5" />
|
||||
<span>
|
||||
COPY
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h3 class="text-normal mt-4 font-semibold">Enable optional measurements:</h3>
|
||||
<.script_extension_control
|
||||
config={@script_config}
|
||||
variant="outbound-links"
|
||||
label="Outbound links"
|
||||
tooltip="Automatically track clicks on external links"
|
||||
learn_more="https://plausible.io/docs/outbound-link-click-tracking"
|
||||
/>
|
||||
<.script_extension_control
|
||||
config={@script_config}
|
||||
variant="file-downloads"
|
||||
label="File downloads"
|
||||
tooltip="Automatically track file downloads"
|
||||
learn_more="https://plausible.io/docs/file-downloads-tracking"
|
||||
/>
|
||||
<.script_extension_control
|
||||
config={@script_config}
|
||||
variant="404"
|
||||
label="404 error pages"
|
||||
tooltip="Find 404 error pages on your site. Additional action required."
|
||||
learn_more="https://plausible.io/docs/error-pages-tracking-404"
|
||||
/>
|
||||
<.script_extension_control
|
||||
config={@script_config}
|
||||
variant="hash"
|
||||
label="Hashed page paths"
|
||||
tooltip="Automatically track page paths that use a # in the URL"
|
||||
learn_more="https://plausible.io/docs/hash-based-routing"
|
||||
/>
|
||||
<.script_extension_control
|
||||
config={@script_config}
|
||||
variant="tagged-events"
|
||||
label="Custom events"
|
||||
tooltip="Tag site elements like buttons, links and forms to track user activity. Additional action required."
|
||||
learn_more="https://plausible.io/docs/custom-event-goals"
|
||||
/>
|
||||
<.script_extension_control
|
||||
config={@script_config}
|
||||
variant="pageview-props"
|
||||
label="Custom properties"
|
||||
tooltip="Attach custom properties (also known as custom dimensions) to pageviews or custom events to create custom metrics. Additional action required."
|
||||
learn_more="https://plausible.io/docs/custom-props/introduction"
|
||||
/>
|
||||
<.script_extension_control
|
||||
config={@script_config}
|
||||
variant="revenue"
|
||||
label="Ecommerce revenue"
|
||||
tooltip="Assign monetary values to purchases and track revenue attribution. Additional action required."
|
||||
learn_more="https://plausible.io/docs/ecommerce-revenue-tracking"
|
||||
/>
|
||||
</form>
|
||||
"""
|
||||
end
|
||||
|
||||
def handle_event("switch-installation-type", %{"method" => method}, socket)
|
||||
when method in @installation_types do
|
||||
socket = update_uri_params(socket, %{"installation_type" => method})
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("update-script-config", params, socket) do
|
||||
new_params =
|
||||
Enum.into(@script_config_params, %{}, &{&1, params[&1] == "on"})
|
||||
|
||||
flash = snippet_change_flash(socket.assigns.script_config, new_params)
|
||||
|
||||
socket = update_uri_params(socket, new_params)
|
||||
{:noreply, put_live_flash(socket, :success, flash)}
|
||||
end
|
||||
|
||||
def handle_params(params, _uri, socket) do
|
||||
socket = do_handle_params(socket, params)
|
||||
persist_installation_meta(socket)
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
defp do_handle_params(socket, params) when is_map(params) do
|
||||
Enum.reduce(params, socket, ¶m_reducer/2)
|
||||
end
|
||||
|
||||
defp param_reducer({"installation_type", installation_type}, socket)
|
||||
when installation_type in @installation_types do
|
||||
assign(socket,
|
||||
installation_type: installation_type,
|
||||
uri_params: Map.put(socket.assigns.uri_params, "installation_type", installation_type)
|
||||
)
|
||||
end
|
||||
|
||||
defp param_reducer({k, v}, socket)
|
||||
when k in @script_config_params do
|
||||
update_script_config(socket, k, v == "true")
|
||||
end
|
||||
|
||||
defp param_reducer(_, socket) do
|
||||
socket
|
||||
end
|
||||
|
||||
defp update_script_config(socket, "outbound-links" = key, true) do
|
||||
Plausible.Goals.create_outbound_links(socket.assigns.site)
|
||||
update_script_config(socket, %{key => true})
|
||||
end
|
||||
|
||||
defp update_script_config(socket, "outbound-links" = key, false) do
|
||||
Plausible.Goals.delete_outbound_links(socket.assigns.site)
|
||||
update_script_config(socket, %{key => false})
|
||||
end
|
||||
|
||||
defp update_script_config(socket, "file-downloads" = key, true) do
|
||||
Plausible.Goals.create_file_downloads(socket.assigns.site)
|
||||
update_script_config(socket, %{key => true})
|
||||
end
|
||||
|
||||
defp update_script_config(socket, "file-downloads" = key, false) do
|
||||
Plausible.Goals.delete_file_downloads(socket.assigns.site)
|
||||
update_script_config(socket, %{key => false})
|
||||
end
|
||||
|
||||
defp update_script_config(socket, "404" = key, true) do
|
||||
Plausible.Goals.create_404(socket.assigns.site)
|
||||
update_script_config(socket, %{key => true})
|
||||
end
|
||||
|
||||
defp update_script_config(socket, "404" = key, false) do
|
||||
Plausible.Goals.delete_404(socket.assigns.site)
|
||||
update_script_config(socket, %{key => false})
|
||||
end
|
||||
|
||||
defp update_script_config(socket, key, value) do
|
||||
update_script_config(socket, %{key => value})
|
||||
end
|
||||
|
||||
defp update_script_config(socket, kv) when is_map(kv) do
|
||||
new_script_config = Map.merge(socket.assigns.script_config, kv)
|
||||
assign(socket, script_config: new_script_config)
|
||||
end
|
||||
|
||||
defp update_uri_params(socket, params) when is_map(params) do
|
||||
uri_params = Map.merge(socket.assigns.uri_params, params)
|
||||
|
||||
socket
|
||||
|> assign(uri_params: uri_params)
|
||||
|> push_patch(
|
||||
to:
|
||||
Routes.site_path(
|
||||
socket,
|
||||
:installation,
|
||||
socket.assigns.domain,
|
||||
uri_params
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
@domain_change PlausibleWeb.Flows.domain_change()
|
||||
defp get_installation_type(@domain_change, meta, params) do
|
||||
meta.installation_type || get_installation_type(nil, nil, params)
|
||||
end
|
||||
|
||||
defp get_installation_type(_site, _meta, params) do
|
||||
Enum.find(@installation_types, &(&1 == params["installation_type"]))
|
||||
end
|
||||
|
||||
defp tracker_url(script_config) do
|
||||
extensions = Enum.filter(script_config, fn {_, value} -> value end)
|
||||
|
||||
tracker =
|
||||
["script" | Enum.map(extensions, fn {key, _} -> key end)]
|
||||
|> Enum.join(".")
|
||||
|
||||
"#{PlausibleWeb.Endpoint.url()}/js/#{tracker}.js"
|
||||
end
|
||||
|
||||
defp persist_installation_meta(socket) do
|
||||
Plausible.Sites.update_installation_meta!(
|
||||
socket.assigns.site,
|
||||
%{
|
||||
installation_type: socket.assigns.installation_type,
|
||||
script_config: socket.assigns.script_config
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
defp snippet_change_flash(old_config, new_config) do
|
||||
change =
|
||||
Enum.find(new_config, fn {key, value} ->
|
||||
if old_config[key] != new_config[key] do
|
||||
{key, value}
|
||||
end
|
||||
end)
|
||||
|
||||
case change do
|
||||
{k, false} when k in ["outbound-links", "file-downloads", "404"] ->
|
||||
"Snippet updated and goal deleted. Please insert the newest snippet into your site"
|
||||
|
||||
{_, _} ->
|
||||
"Snippet updated. Please insert the newest snippet into your site"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -6,6 +6,7 @@ defmodule PlausibleWeb.Live.RegisterForm do
|
|||
use PlausibleWeb, :live_view
|
||||
use Phoenix.HTML
|
||||
import PlausibleWeb.Live.Components.Form
|
||||
import PlausibleWeb.Components.Generic
|
||||
|
||||
alias Plausible.Auth
|
||||
alias Plausible.Repo
|
||||
|
|
@ -78,12 +79,12 @@ defmodule PlausibleWeb.Live.RegisterForm do
|
|||
|
||||
<PlausibleWeb.Components.FlowProgress.render
|
||||
:if={@live_action == :register_form}
|
||||
flow="register"
|
||||
flow={PlausibleWeb.Flows.register()}
|
||||
current_step="Register"
|
||||
/>
|
||||
<PlausibleWeb.Components.FlowProgress.render
|
||||
:if={@live_action == :register_from_invitation_form}
|
||||
flow="invitation"
|
||||
flow={PlausibleWeb.Flows.invitation()}
|
||||
current_step="Register"
|
||||
/>
|
||||
|
||||
|
|
@ -188,10 +189,10 @@ defmodule PlausibleWeb.Live.RegisterForm do
|
|||
</PlausibleWeb.Components.Generic.button>
|
||||
|
||||
<p class="text-center text-gray-600 dark:text-gray-500 mt-4">
|
||||
Already have an account? <%= link("Log in",
|
||||
to: "/login",
|
||||
class: "underline text-gray-800 dark:text-gray-50"
|
||||
) %>
|
||||
Already have an account?
|
||||
<.styled_link href="/login">
|
||||
Log in
|
||||
</.styled_link>
|
||||
</p>
|
||||
</.form>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ defmodule PlausibleWeb.Live.Sites do
|
|||
You don't have any sites yet.
|
||||
</p>
|
||||
<div class="mt-4 flex sm:ml-4 sm:mt-0">
|
||||
<a href="/sites/new?flow=provisioning" class="button">
|
||||
<a href="/sites/new?flow=#{PlausibleWeb.Flows.provisioning()}" class="button">
|
||||
+ Add Website
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,75 +8,59 @@ defmodule PlausibleWeb.Live.Verification do
|
|||
use Phoenix.HTML
|
||||
|
||||
alias Plausible.Verification.{Checks, State}
|
||||
alias PlausibleWeb.Live.Components.Modal
|
||||
|
||||
@component PlausibleWeb.Live.Components.Verification
|
||||
@slowdown_for_frequent_checking :timer.seconds(5)
|
||||
|
||||
def mount(
|
||||
:not_mounted_at_router,
|
||||
%{"domain" => domain} = session,
|
||||
%{"website" => domain} = params,
|
||||
_session,
|
||||
socket
|
||||
) do
|
||||
site =
|
||||
Plausible.Sites.get_for_user!(socket.assigns.current_user, domain, [
|
||||
:owner,
|
||||
:admin,
|
||||
:super_admin,
|
||||
:viewer
|
||||
])
|
||||
|
||||
private = Map.get(socket.private.connect_info, :private, %{})
|
||||
|
||||
socket =
|
||||
assign(socket,
|
||||
site: site,
|
||||
domain: domain,
|
||||
modal?: !!session["modal?"],
|
||||
has_pageviews?: has_pageviews?(site),
|
||||
component: @component,
|
||||
report_to: session["report_to"] || self(),
|
||||
delay: session["slowdown"] || 500,
|
||||
slowdown: session["slowdown"] || 500,
|
||||
flow: session["flow"] || "",
|
||||
installation_type: params["installation_type"],
|
||||
report_to: self(),
|
||||
delay: private[:delay] || 500,
|
||||
slowdown: private[:slowdown] || 500,
|
||||
flow: params["flow"] || "",
|
||||
checks_pid: nil,
|
||||
attempts: 0
|
||||
)
|
||||
|
||||
if connected?(socket) and !session["modal?"] do
|
||||
if connected?(socket) do
|
||||
launch_delayed(socket)
|
||||
end
|
||||
|
||||
socket =
|
||||
if connected?(socket) and !!session["modal?"] and !!session["open_modal?"] do
|
||||
launch_delayed(socket)
|
||||
Modal.open(socket, "verification-modal")
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div :if={@modal?} phx-click-away="reset">
|
||||
<.live_component module={Modal} id="verification-modal">
|
||||
<.live_component
|
||||
module={@component}
|
||||
domain={@domain}
|
||||
id="verification-within-modal"
|
||||
modal?={@modal?}
|
||||
attempts={@attempts}
|
||||
/>
|
||||
</.live_component>
|
||||
|
||||
<PlausibleWeb.Components.Generic.button
|
||||
id="launch-verification-button"
|
||||
x-data
|
||||
x-on:click={Modal.JS.open("verification-modal")}
|
||||
phx-click="launch-verification"
|
||||
class="mt-6"
|
||||
>
|
||||
Verify your integration
|
||||
</PlausibleWeb.Components.Generic.button>
|
||||
</div>
|
||||
<PlausibleWeb.Components.FlowProgress.render flow={@flow} current_step="Verify installation" />
|
||||
|
||||
<.live_component
|
||||
:if={!@modal?}
|
||||
module={@component}
|
||||
installation_type={@installation_type}
|
||||
domain={@domain}
|
||||
id="verification-standalone"
|
||||
attempts={@attempts}
|
||||
flow={@flow}
|
||||
awaiting_first_pageview?={not @has_pageviews?}
|
||||
/>
|
||||
"""
|
||||
end
|
||||
|
|
@ -127,6 +111,10 @@ defmodule PlausibleWeb.Live.Verification do
|
|||
def handle_info({:verification_end, %State{} = state}, socket) do
|
||||
interpretation = Checks.interpret_diagnostics(state)
|
||||
|
||||
if not socket.assigns.has_pageviews? do
|
||||
Process.send_after(self(), :check_pageviews, socket.assigns.delay * 2)
|
||||
end
|
||||
|
||||
update_component(socket,
|
||||
finished?: true,
|
||||
success?: interpretation.ok?,
|
||||
|
|
@ -136,6 +124,20 @@ defmodule PlausibleWeb.Live.Verification do
|
|||
{:noreply, assign(socket, checks_pid: nil)}
|
||||
end
|
||||
|
||||
def handle_info(:check_pageviews, socket) do
|
||||
socket =
|
||||
if has_pageviews?(socket.assigns.site) do
|
||||
redirect(socket,
|
||||
external: Routes.stats_url(PlausibleWeb.Endpoint, :stats, socket.assigns.domain, [])
|
||||
)
|
||||
else
|
||||
Process.send_after(self(), :check_pageviews, socket.assigns.delay * 2)
|
||||
socket
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
defp reset_component(socket) do
|
||||
update_component(socket,
|
||||
message: "We're visiting your site to ensure that everything is working",
|
||||
|
|
@ -147,20 +149,18 @@ defmodule PlausibleWeb.Live.Verification do
|
|||
socket
|
||||
end
|
||||
|
||||
defp update_component(socket, updates) do
|
||||
defp update_component(_socket, updates) do
|
||||
send_update(
|
||||
@component,
|
||||
Keyword.merge(updates,
|
||||
id:
|
||||
if(socket.assigns.modal?,
|
||||
do: "verification-within-modal",
|
||||
else: "verification-standalone"
|
||||
)
|
||||
)
|
||||
Keyword.merge(updates, id: "verification-standalone")
|
||||
)
|
||||
end
|
||||
|
||||
defp launch_delayed(socket) do
|
||||
Process.send_after(self(), {:start, socket.assigns.report_to}, socket.assigns.delay)
|
||||
end
|
||||
|
||||
defp has_pageviews?(site) do
|
||||
Plausible.Stats.Clickhouse.has_pageviews?(site)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@ defmodule PlausibleWeb.Router do
|
|||
plug :protect_from_forgery
|
||||
end
|
||||
|
||||
pipeline :focus_layout do
|
||||
plug :put_root_layout, html: {PlausibleWeb.LayoutView, :focus}
|
||||
end
|
||||
|
||||
pipeline :app_layout do
|
||||
plug :put_root_layout, html: {PlausibleWeb.LayoutView, :app}
|
||||
end
|
||||
|
|
@ -227,7 +223,6 @@ defmodule PlausibleWeb.Router do
|
|||
post "/paddle/webhook", Api.PaddleController, :webhook
|
||||
get "/paddle/currency", Api.PaddleController, :currency
|
||||
|
||||
get "/:domain/status", Api.InternalController, :domain_status
|
||||
put "/:domain/disable-feature", Api.InternalController, :disable_feature
|
||||
|
||||
get "/sites", Api.InternalController, :sites
|
||||
|
|
@ -237,7 +232,7 @@ defmodule PlausibleWeb.Router do
|
|||
pipe_through [:browser, :csrf]
|
||||
|
||||
scope alias: Live, assigns: %{connect_live_socket: true} do
|
||||
pipe_through [PlausibleWeb.RequireLoggedOutPlug, :focus_layout]
|
||||
pipe_through [PlausibleWeb.RequireLoggedOutPlug, :app_layout]
|
||||
|
||||
scope assigns: %{disable_registration_for: [:invite_only, true]} do
|
||||
pipe_through PlausibleWeb.Plugs.MaybeDisableRegistration
|
||||
|
|
@ -325,7 +320,6 @@ defmodule PlausibleWeb.Router do
|
|||
post "/sites", SiteController, :create_site
|
||||
get "/sites/:website/change-domain", SiteController, :change_domain
|
||||
put "/sites/:website/change-domain", SiteController, :change_domain_submit
|
||||
get "/:website/change-domain-snippet", SiteController, :add_snippet_after_domain_change
|
||||
post "/sites/:website/make-public", SiteController, :make_public
|
||||
post "/sites/:website/make-private", SiteController, :make_private
|
||||
post "/sites/:website/weekly-report/enable", SiteController, :enable_weekly_report
|
||||
|
|
@ -391,7 +385,13 @@ defmodule PlausibleWeb.Router do
|
|||
get "/sites/:website/weekly-report/unsubscribe", UnsubscribeController, :weekly_report
|
||||
get "/sites/:website/monthly-report/unsubscribe", UnsubscribeController, :monthly_report
|
||||
|
||||
get "/:website/snippet", SiteController, :add_snippet
|
||||
scope alias: Live, assigns: %{connect_live_socket: true} do
|
||||
pipe_through [:app_layout, PlausibleWeb.RequireAccountPlug]
|
||||
|
||||
live "/:website/installation", Installation, :installation, as: :site
|
||||
live "/:website/verification", Verification, :verification, as: :site
|
||||
end
|
||||
|
||||
get "/:website/settings", SiteController, :settings
|
||||
get "/:website/settings/general", SiteController, :settings_general
|
||||
get "/:website/settings/people", SiteController, :settings_people
|
||||
|
|
|
|||
|
|
@ -60,54 +60,50 @@
|
|||
<:footer :if={@has_email_code?}>
|
||||
<b>Didn't receive an email?</b>
|
||||
|
||||
<ol class="list-disc space-y-1 ml-4 mt-1 mb-4">
|
||||
<li>Check your spam folder</li>
|
||||
<li>
|
||||
<%= link("Send a new code",
|
||||
class: "underline text-indigo-600",
|
||||
to: "/activate/request-code",
|
||||
method: :post
|
||||
) %> to <%= @conn.assigns[:current_user].email %>
|
||||
</li>
|
||||
|
||||
<%= if ee?() do %>
|
||||
<li>
|
||||
<a class="underline text-indigo-600" href="https://plausible.io/contact">
|
||||
<.focus_list>
|
||||
<:item>
|
||||
Check your spam folder
|
||||
</:item>
|
||||
<:item>
|
||||
<.styled_link href="/activate/request-code" method="post">
|
||||
Send a new code
|
||||
</.styled_link>
|
||||
to <%= @conn.assigns[:current_user].email %>
|
||||
</:item>
|
||||
<:item :if={ee?()}>
|
||||
<.styled_link href="https://plausible.io/contact" new_tab={true}>
|
||||
Contact us
|
||||
</a>
|
||||
</.styled_link>
|
||||
if the problem persists
|
||||
</li>
|
||||
<% else %>
|
||||
<li>
|
||||
Ask on our <%= link("community-supported forum",
|
||||
to: "https://github.com/plausible/analytics/discussions",
|
||||
class: "text-indigo-600 underline"
|
||||
) %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ol>
|
||||
</:item>
|
||||
<:item :if={ce?()}>
|
||||
Ask on our
|
||||
<.styled_link href="https://github.com/plausible/analytics/discussions" new_tab={true}>
|
||||
community-supported forum
|
||||
</.styled_link>
|
||||
</:item>
|
||||
</.focus_list>
|
||||
|
||||
<b>Entered the wrong email address?</b>
|
||||
|
||||
<ol class="list-disc space-y-1 ml-4 mt-1">
|
||||
<%= if @has_any_memberships? do %>
|
||||
<li>
|
||||
<%= link("Change email back to",
|
||||
class: "underline text-indigo-600",
|
||||
to: "/settings/email/cancel",
|
||||
method: "post"
|
||||
) %> to <%= @conn.assigns[:current_user].previous_email %>
|
||||
</li>
|
||||
<% else %>
|
||||
<li>
|
||||
<%= link("Delete this account",
|
||||
class: "underline text-indigo-600",
|
||||
to: "/me?redirect=/register",
|
||||
method: "delete",
|
||||
data: [confirm: "Deleting your account cannot be reversed. Are you sure?"]
|
||||
) %> and start over
|
||||
</li>
|
||||
<% end %>
|
||||
</ol>
|
||||
<.focus_list>
|
||||
<:item :if={@has_any_memberships?}>
|
||||
<.styled_link method="post" href="/settings/email/cancel">
|
||||
Change email back to
|
||||
</.styled_link>
|
||||
<%= @conn.assigns[:current_user].previous_email %>
|
||||
</:item>
|
||||
|
||||
<:item :if={not @has_any_memberships?}>
|
||||
<.styled_link
|
||||
method="delete"
|
||||
href="/me?redirect=/register"
|
||||
data-confim="Deleting your account cannot be reversed. Are you sure?"
|
||||
>
|
||||
Delete this account
|
||||
</.styled_link>
|
||||
and start over
|
||||
</:item>
|
||||
</.focus_list>
|
||||
</:footer>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
|
|||
|
|
@ -8,15 +8,14 @@
|
|||
</:subtitle>
|
||||
|
||||
<:footer>
|
||||
<p>
|
||||
<.focus_list>
|
||||
<:item>
|
||||
Changed your mind?
|
||||
<a
|
||||
href={Routes.auth_path(@conn, :user_settings) <> "#setup-2fa"}
|
||||
class="underline text-indigo-600"
|
||||
>
|
||||
<.styled_link href={Routes.auth_path(@conn, :user_settings) <> "#setup-2fa"}>
|
||||
Go back to Settings
|
||||
</a>
|
||||
</p>
|
||||
</.styled_link>
|
||||
</:item>
|
||||
</.focus_list>
|
||||
</:footer>
|
||||
|
||||
<div class="flex flex-col sm:flex-row items-center sm:items-start">
|
||||
|
|
@ -27,27 +26,17 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-8 sm:ml-4">
|
||||
<ol>
|
||||
<li class="flex items-start">
|
||||
<div class="flex-shrink-0 h-5 w-5 relative flex items-center justify-center">
|
||||
<div class="h-2 w-2 bg-gray-300 dark:bg-gray-700 rounded-full"></div>
|
||||
</div>
|
||||
<.focus_list>
|
||||
<:item>
|
||||
Open the authenticator application
|
||||
</li>
|
||||
<li class="mt-1 flex items-start">
|
||||
<div class="flex-shrink-0 h-5 w-5 relative flex items-center justify-center">
|
||||
<div class="h-2 w-2 bg-gray-300 dark:bg-gray-700 rounded-full"></div>
|
||||
</div>
|
||||
</:item>
|
||||
<:item>
|
||||
Tap Scan a QR Code
|
||||
</li>
|
||||
<li class="mt-1 flex items-start">
|
||||
<div class="flex-shrink-0 h-5 w-5 relative flex items-center justify-center">
|
||||
<div class="h-2 w-2 bg-gray-300 dark:bg-gray-700 rounded-full"></div>
|
||||
</div>
|
||||
</:item>
|
||||
<:item>
|
||||
Scan this code with your phone camera or paste the code manually
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
</:item>
|
||||
</.focus_list>
|
||||
<div class="sm:ml-2">
|
||||
<PlausibleWeb.Live.Components.Form.input_with_clipboard
|
||||
id="secret"
|
||||
|
|
|
|||
|
|
@ -37,22 +37,23 @@
|
|||
<% end %>
|
||||
|
||||
<:footer>
|
||||
<ol class="list-disc space-y-1 ml-4 mt-1 mb-4">
|
||||
<%= if Keyword.fetch!(Application.get_env(:plausible, :selfhost),:disable_registration) == false do %>
|
||||
<li>
|
||||
Don't have an account? <%= link("Register",
|
||||
to: "/register",
|
||||
class: "text-gray-800 dark:text-gray-50 underline"
|
||||
) %> instead.
|
||||
</li>
|
||||
<.focus_list>
|
||||
<:item>
|
||||
<%= if Keyword.fetch!(Application.get_env(:plausible, :selfhost), :disable_registration) == false do %>
|
||||
Don't have an account
|
||||
<.styled_link href="/register">
|
||||
Register
|
||||
</.styled_link>
|
||||
instead.
|
||||
<% end %>
|
||||
<li>
|
||||
</:item>
|
||||
<:item>
|
||||
Forgot password?
|
||||
<a href="/password/request-reset" class="underline text-gray-800 dark:text-gray-50">
|
||||
<.styled_link href="/password/reset-request">
|
||||
Click here
|
||||
</a>
|
||||
</.styled_link>
|
||||
to reset it.
|
||||
</li>
|
||||
</ol>
|
||||
</:item>
|
||||
</.focus_list>
|
||||
</:footer>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<%= form_for @changeset, "/settings/api-keys", [class: "w-full max-w-md mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 py-6 mt-8"], fn f -> %>
|
||||
<h1 class="text-xl font-black dark:text-gray-100">Create new API key</h1>
|
||||
<div class="my-4">
|
||||
|
|
@ -20,3 +21,4 @@
|
|||
</div>
|
||||
<%= submit "Continue", class: "button mt-4 w-full" %>
|
||||
<% end %>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<:title>Create new API key</:title>
|
||||
|
||||
<%= form_for @changeset, "/settings/api-keys", fn f -> %>
|
||||
<div class="my-4">
|
||||
<%= label(f, :name, class: "block font-medium text-gray-700 dark:text-gray-300") %>
|
||||
<div class="mt-1">
|
||||
<%= text_input(f, :name,
|
||||
class:
|
||||
"dark:bg-gray-900 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full border-gray-300 dark:border-gray-500 dark:text-gray-300 dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500 rounded-md",
|
||||
placeholder: "Development"
|
||||
) %>
|
||||
</div>
|
||||
<%= error_tag(f, :name) %>
|
||||
</div>
|
||||
<div class="my-4">
|
||||
<%= label(f, :key, class: "block font-medium text-gray-700 dark:text-gray-300") %>
|
||||
<div class="relative mt-1">
|
||||
<%= text_input(f, :key,
|
||||
id: "key-input",
|
||||
class:
|
||||
"dark:text-gray-300 shadow-sm bg-gray-50 dark:bg-gray-850 focus:ring-indigo-500 focus:border-indigo-500 block w-full border-gray-300 dark:border-gray-500 rounded-md pr-16",
|
||||
readonly: "readonly"
|
||||
) %>
|
||||
<a
|
||||
onclick="var textarea = document.getElementById('key-input'); textarea.focus(); textarea.select(); document.execCommand('copy');"
|
||||
href="javascript:void(0)"
|
||||
class="absolute flex items-center text-xs font-medium text-indigo-600 no-underline hover:underline"
|
||||
style="top: 12px; right: 12px;"
|
||||
>
|
||||
<svg
|
||||
class="pr-1 text-indigo-600 dark:text-indigo-500"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>COPY
|
||||
</a>
|
||||
<%= error_tag(f, :key) %>
|
||||
<p class="mt-2 text-gray-500 dark:text-gray-200">
|
||||
Make sure to store the key in a secure place. Once created, we will not be able to show it again.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<%= submit("Continue", class: "button mt-4 w-full") %>
|
||||
<% end %>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
@ -8,20 +8,20 @@
|
|||
</:subtitle>
|
||||
|
||||
<:footer>
|
||||
<ol class="list-disc space-y-1 ml-4 mt-1 mb-4">
|
||||
<li>
|
||||
Can't access your authenticator application?
|
||||
<.focus_list>
|
||||
<:item>
|
||||
Can't access your authenticator app?
|
||||
<.styled_link href={Routes.auth_path(@conn, :verify_2fa_recovery_code_form)}>
|
||||
Use recovery code
|
||||
</.styled_link>
|
||||
</li>
|
||||
<li :if={ee?()}>
|
||||
</:item>
|
||||
<:item :if={ee?()}>
|
||||
Lost your recovery codes?
|
||||
<.styled_link href="https://plausible.io/contact">
|
||||
Contact us
|
||||
</.styled_link>
|
||||
</li>
|
||||
</ol>
|
||||
</:item>
|
||||
</.focus_list>
|
||||
</:footer>
|
||||
|
||||
<%= form_for @conn.params, Routes.auth_path(@conn, :verify_2fa), [
|
||||
|
|
|
|||
|
|
@ -1,16 +1,30 @@
|
|||
<div class="w-full max-w-3xl mt-4 mx-auto flex">
|
||||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<:title>
|
||||
Enter Recovery Code
|
||||
</:title>
|
||||
|
||||
<:subtitle>
|
||||
Can't access your authenticator application? Enter a recovery code instead.
|
||||
</:subtitle>
|
||||
|
||||
<:footer>
|
||||
Authenticator application working again?
|
||||
<a href={Routes.auth_path(@conn, :verify_2fa)} class="underline text-indigo-600">
|
||||
Enter code
|
||||
</a>
|
||||
<%= if ee?() do %>
|
||||
<br /> Lost your recovery codes?
|
||||
<a href="https://plausible.io/contact" class="underline text-indigo-600">
|
||||
Contact us
|
||||
</a>
|
||||
<% end %>
|
||||
</:footer>
|
||||
|
||||
<%= form_for @conn.params,
|
||||
Routes.auth_path(@conn, :verify_2fa_recovery_code),
|
||||
[
|
||||
class: "w-full max-w-lg mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 py-6 mb-4 mt-8",
|
||||
onsubmit: "document.getElementById('use-code-button').disabled = true"
|
||||
], fn f -> %>
|
||||
<h2 class="text-xl font-black dark:text-gray-100">
|
||||
Enter Recovery Code
|
||||
</h2>
|
||||
|
||||
<div class="text-sm mt-2 text-gray-500 dark:text-gray-200 leading-tight">
|
||||
Can't access your authenticator application? Enter a recovery code instead.
|
||||
<div class="mt-6">
|
||||
<div>
|
||||
<%= text_input(f, :recovery_code,
|
||||
|
|
@ -27,7 +41,7 @@
|
|||
<.button
|
||||
id="use-code-button"
|
||||
type="submit"
|
||||
class="w-full mt-2 [&>span.label-enabled]:block [&>span.label-disabled]:hidden [&[disabled]>span.label-enabled]:hidden [&[disabled]>span.label-disabled]:block"
|
||||
class="w-full mt-4 [&>span.label-enabled]:block [&>span.label-disabled]:hidden [&[disabled]>span.label-enabled]:hidden [&[disabled]>span.label-disabled]:block"
|
||||
>
|
||||
<span class="label-enabled pointer-events-none">
|
||||
Use Code
|
||||
|
|
@ -39,21 +53,5 @@
|
|||
</span>
|
||||
</.button>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-row justify-between items-center">
|
||||
<p class="text-sm">
|
||||
Authenticator application working again?
|
||||
<a href={Routes.auth_path(@conn, :verify_2fa)} class="underline text-indigo-600">
|
||||
Enter verification code
|
||||
</a>
|
||||
<%= if ee?() do %>
|
||||
<br /> Lost your recovery codes?
|
||||
<a href="https://plausible.io/contact" class="underline text-indigo-600">
|
||||
Contact us
|
||||
</a>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
|
|||
|
|
@ -8,24 +8,20 @@
|
|||
</:subtitle>
|
||||
|
||||
<:footer>
|
||||
<p>
|
||||
<.focus_list>
|
||||
<:item>
|
||||
Changed your mind?
|
||||
<a
|
||||
href={Routes.auth_path(@conn, :user_settings) <> "#setup-2fa"}
|
||||
class="underline text-indigo-600"
|
||||
>
|
||||
<.styled_link href={Routes.auth_path(@conn, :user_settings) <> "#setup-2fa"}>
|
||||
Go back to Settings
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%= form_for @conn.params, Routes.auth_path(@conn, :initiate_2fa_setup), [id: "start-over-form"], fn _f -> %>
|
||||
</.styled_link>
|
||||
</:item>
|
||||
<:item>
|
||||
Having trouble?
|
||||
<button class="underline text-indigo-600">
|
||||
<.styled_link method="post" href={Routes.auth_path(@conn, :initiate_2fa_setup)}>
|
||||
Start over
|
||||
</button>
|
||||
<% end %>
|
||||
</p>
|
||||
</.styled_link>
|
||||
</:item>
|
||||
</.focus_list>
|
||||
</:footer>
|
||||
|
||||
<%= form_for @conn.params, Routes.auth_path(@conn, :verify_2fa_setup), [
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
<div class="mx-auto mt-6 text-center">
|
||||
<h1 class="text-3xl font-black dark:text-gray-100">Confirm new subscription plan</h1>
|
||||
</div>
|
||||
|
||||
<div class="w-full max-w-lg px-4 mt-4 mx-auto">
|
||||
<div class="flex-1 bg-white dark:bg-gray-800 shadow-md rounded px-8 py-4 mb-4 mt-8">
|
||||
<div class="text-lg font-bold dark:text-gray-100">Due now</div>
|
||||
<div class="block text-gray-500 dark:text-gray-200 text-sm">
|
||||
Your card will be charged a pro-rated amount for the current billing period
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mt-4">
|
||||
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||
<div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200 dark:border-t dark:border-l dark:border-r dark:shadow-none">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
|
||||
Amount
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
|
||||
Date
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white dark:bg-gray-800">
|
||||
<tr class="border-b border-gray-200">
|
||||
<td class="px-6 py-4 text-sm leading-5 font-bold dark:text-gray-100"><%= present_currency(@preview_info["immediate_payment"]["currency"]) %><%= @preview_info["immediate_payment"]["amount"] %></td>
|
||||
<td class="px-6 py-4 text-sm leading-5 dark:text-gray-100"><%= present_date(@preview_info["immediate_payment"]["date"]) %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-6"></div>
|
||||
|
||||
<div class="py-4 dark:text-gray-100 text-lg font-bold">Next payment</div>
|
||||
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||
<div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200 dark:border-t dark:border-l dark:border-r dark:shadow-none">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
|
||||
Amount
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
|
||||
Date
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white dark:bg-gray-800">
|
||||
<tr class="border-b border-gray-200">
|
||||
<td class="px-6 py-4 text-sm leading-5 font-bold dark:text-gray-100"><%= present_currency(@preview_info["immediate_payment"]["currency"]) %><%= @preview_info["next_payment"]["amount"] %></td>
|
||||
<td class="px-6 py-4 text-sm leading-5 dark:text-gray-100"><%= present_date(@preview_info["next_payment"]["date"]) %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between mt-10">
|
||||
<span class="flex rounded-md shadow-sm">
|
||||
<a href="<%= @back_link %>" type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:text-gray-500 dark:hover:text-gray-200 focus:outline-none focus:border-blue-300 focus:ring active:text-gray-800 dark:active:text-gray-200 active:bg-gray-50 transition ease-in-out duration-150">
|
||||
Back
|
||||
</a>
|
||||
</span>
|
||||
<span class="flex space-betwee rounded-md shadow-sm">
|
||||
<%= button("Confirm plan change", to: Routes.billing_path(@conn, :change_plan, @preview_info["plan_id"]), method: :post, class: "inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:ring active:bg-indigo-700 transition ease-in-out duration-150") %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-8 dark:text-gray-100">
|
||||
Questions? <%= link("Contact us", to: "https://plausible.io/contact", class: "text-indigo-500") %>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<:title>
|
||||
Confirm new subscription plan
|
||||
</:title>
|
||||
|
||||
<div class="text-lg font-bold dark:text-gray-100">Due now</div>
|
||||
<div class="block text-gray-500 dark:text-gray-200 text-sm">
|
||||
Your card will be charged a pro-rated amount for the current billing period
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col mt-4">
|
||||
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||
<div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200 dark:border-t dark:border-l dark:border-r dark:shadow-none">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
|
||||
Amount
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
|
||||
Date
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white dark:bg-gray-800">
|
||||
<tr class="border-b border-gray-200">
|
||||
<td class="px-6 py-4 text-sm leading-5 font-bold dark:text-gray-100">
|
||||
<%= present_currency(@preview_info["immediate_payment"]["currency"]) %><%= @preview_info[
|
||||
"immediate_payment"
|
||||
]["amount"] %>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm leading-5 dark:text-gray-100">
|
||||
<%= present_date(@preview_info["immediate_payment"]["date"]) %>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-6"></div>
|
||||
|
||||
<div class="py-4 dark:text-gray-100 text-lg font-bold">Next payment</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||
<div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200 dark:border-t dark:border-l dark:border-r dark:shadow-none">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
|
||||
Amount
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
|
||||
Date
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white dark:bg-gray-800">
|
||||
<tr class="border-b border-gray-200">
|
||||
<td class="px-6 py-4 text-sm leading-5 font-bold dark:text-gray-100">
|
||||
<%= present_currency(@preview_info["immediate_payment"]["currency"]) %><%= @preview_info[
|
||||
"next_payment"
|
||||
]["amount"] %>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm leading-5 dark:text-gray-100">
|
||||
<%= present_date(@preview_info["next_payment"]["date"]) %>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between mt-10">
|
||||
<span class="flex rounded-md shadow-sm">
|
||||
<a
|
||||
href={@back_link}
|
||||
type="button"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:text-gray-500 dark:hover:text-gray-200 focus:outline-none focus:border-blue-300 focus:ring active:text-gray-800 dark:active:text-gray-200 active:bg-gray-50 transition ease-in-out duration-150"
|
||||
>
|
||||
Back
|
||||
</a>
|
||||
</span>
|
||||
<span class="flex space-betwee rounded-md shadow-sm">
|
||||
<%= button("Confirm plan change",
|
||||
to: Routes.billing_path(@conn, :change_plan, @preview_info["plan_id"]),
|
||||
method: :post,
|
||||
class:
|
||||
"inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:ring active:bg-indigo-700 transition ease-in-out duration-150"
|
||||
) %>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-8 dark:text-gray-100">
|
||||
Questions? <%= link("Contact us",
|
||||
to: "https://plausible.io/contact",
|
||||
class: "text-indigo-500"
|
||||
) %>
|
||||
</div>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
<div class="w-full max-w-lg px-4 mt-4 mx-auto">
|
||||
<div class="flex-1 bg-white dark:bg-gray-800 shadow-md rounded px-8 py-4 mb-4 mt-8">
|
||||
<div class="w-full pt-2 text-xl font-bold dark:text-gray-100">
|
||||
Your account is being upgraded...
|
||||
</div>
|
||||
|
||||
<p class="text-gray-500 dark:text-gray-200 text-sm py-4">
|
||||
Thank you for upgrading your subscription! We're still working on
|
||||
upgrading your account, and you'll be automatically redirected in a few
|
||||
seconds.
|
||||
</p>
|
||||
|
||||
<p hidden id="timeout-notice" class="text-gray-500 dark:text-gray-200 text-sm">
|
||||
Your subscription is taking longer than usual to upgrade. If you're not
|
||||
redirected soon, please contact <a class="text-indigo-500" href="mailto:hello@plausible.io">hello@plausible.io</a>.
|
||||
</p>
|
||||
|
||||
<div class="loading my-12 mx-auto"><div></div></div>
|
||||
|
||||
<script>
|
||||
const PING_SUBSCRIPTION_API = "<%= Routes.billing_path(@conn, :ping_subscription) %>"
|
||||
const REDIRECT_TO = "<%= Routes.auth_path(@conn, :user_settings) %>"
|
||||
const PING_EVERY_MS = 2000
|
||||
const TIMEOUT_AFTER_MS = 15000
|
||||
|
||||
const ping = async function(fun) {
|
||||
let result = {}
|
||||
|
||||
while (!result.is_subscribed) {
|
||||
await wait();
|
||||
const response = await fetch(PING_SUBSCRIPTION_API)
|
||||
result = await response.json()
|
||||
}
|
||||
|
||||
window.location = REDIRECT_TO
|
||||
}
|
||||
|
||||
const wait = function() {
|
||||
return new Promise(resolve => { setTimeout(resolve, PING_EVERY_MS) })
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById("timeout-notice").removeAttribute("hidden")
|
||||
}, TIMEOUT_AFTER_MS)
|
||||
|
||||
// Pings pingSubscriptionUrl every 2 seconds until an active subscription
|
||||
// is created from Paddle webhooks.
|
||||
ping()
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<:title>
|
||||
Your account is being upgraded...
|
||||
</:title>
|
||||
|
||||
<:subtitle>
|
||||
Thank you for upgrading your subscription! We're still working on
|
||||
upgrading your account, and you'll be automatically redirected in a few
|
||||
seconds.
|
||||
</:subtitle>
|
||||
|
||||
<p hidden id="timeout-notice" class="text-gray-500 dark:text-gray-200 text-sm">
|
||||
Your subscription is taking longer than usual to upgrade. If you're not
|
||||
redirected soon, please contact <a class="text-indigo-500" href="mailto:hello@plausible.io">hello@plausible.io</a>.
|
||||
</p>
|
||||
|
||||
<div class="loading my-12 mx-auto">
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const PING_SUBSCRIPTION_API = "<%= Routes.billing_path(@conn, :ping_subscription) %>"
|
||||
const REDIRECT_TO = "<%= Routes.auth_path(@conn, :user_settings) %>"
|
||||
const PING_EVERY_MS = 2000
|
||||
const TIMEOUT_AFTER_MS = 15000
|
||||
|
||||
const ping = async function(fun) {
|
||||
let result = {}
|
||||
|
||||
while (!result.is_subscribed) {
|
||||
await wait();
|
||||
const response = await fetch(PING_SUBSCRIPTION_API)
|
||||
result = await response.json()
|
||||
}
|
||||
|
||||
window.location = REDIRECT_TO
|
||||
}
|
||||
|
||||
const wait = function() {
|
||||
return new Promise(resolve => { setTimeout(resolve, PING_EVERY_MS) })
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById("timeout-notice").removeAttribute("hidden")
|
||||
}, TIMEOUT_AFTER_MS)
|
||||
|
||||
// Pings pingSubscriptionUrl every 2 seconds until an active subscription
|
||||
// is created from Paddle webhooks.
|
||||
ping()
|
||||
</script>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<section class="grid grid-cols-1 gap-y-3 divide-y">
|
||||
<%= for log <- @queries do %>
|
||||
<details class="group py-1">
|
||||
<summary class="flex cursor-pointer flex-row items-center justify-between py-1 font-semibold text-gray-600 dark:text-gray-200 pt-4">
|
||||
<summary class="flex cursor-pointer flex-row items-center justify-between py-1 font-semibold text-gray-800 dark:text-gray-200 pt-4">
|
||||
<%= log["request_method"] %> <%= controller_name(log["phoenix_controller"]) %>.<%= log[
|
||||
"phoenix_action"
|
||||
] %> (<%= log[:query_duration_ms] %>ms)
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</summary>
|
||||
<table class="table table-striped table-auto text-gray-400 dark:text-gray-200">
|
||||
<table class="table table-striped table-auto text-gray-800 dark:text-gray-200">
|
||||
<tbody>
|
||||
<%= for {key, value} <- log do %>
|
||||
<tr class="table-row">
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@ We've recorded <%= @current_visitors %> visitors on <%= link(@site.domain, to: "
|
|||
<br /><br />
|
||||
View dashboard: <%= link(@dashboard_link, to: @dashboard_link) %>
|
||||
<br /><br />
|
||||
Something looks off? Please use our <%= link("integration testing tool", to: @settings_link) %> to verify that Plausible has been integrated correctly.
|
||||
Something looks off? Please <%= link("review your installation", to: @installation_link) %> to verify that Plausible has been integrated correctly.
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
You signed up for a free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool.
|
||||
<br /><br />
|
||||
<% end %>
|
||||
To finish your setup for <%= @site.domain %>, you need to install <%= link("this lightweight line of JavaScript code", to: "#{plausible_url()}/#{URI.encode_www_form(@site.domain)}/snippet") %> into your site to start collecting visitor statistics.
|
||||
To finish your setup for <%= @site.domain %>, review <%= link("your installation", to: "#{plausible_url()}/#{URI.encode_www_form(@site.domain)}/installation") %> and start collecting visitor statistics.
|
||||
<br /><br />
|
||||
This Plausible script is 45 times smaller than Google Analytics script so you’ll have a fast loading site while getting all the important traffic insights on one single page.
|
||||
<br /><br />
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
<%= form_for @conn, Routes.google_analytics_path(@conn, :property, @site.domain), [onsubmit: "continueButton.disabled = true; return true;", class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
|
||||
<h2 class="text-xl font-black dark:text-gray-100">Import from Google Analytics</h2>
|
||||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<:title>
|
||||
Import from Google Analytics
|
||||
</:title>
|
||||
|
||||
<:subtitle>
|
||||
Choose the property in your Google Analytics account that will be imported to the <%= @site.domain %> dashboard.
|
||||
</:subtitle>
|
||||
<%= form_for @conn, Routes.google_analytics_path(@conn, :property, @site.domain), [onsubmit: "continueButton.disabled = true; return true;"], fn f -> %>
|
||||
<%= hidden_input(f, :access_token, value: @access_token) %>
|
||||
<%= hidden_input(f, :refresh_token, value: @refresh_token) %>
|
||||
<%= hidden_input(f, :expires_at, value: @expires_at) %>
|
||||
|
||||
<div class="mt-6 text-sm text-gray-500 dark:text-gray-200">
|
||||
Choose the property in your Google Analytics account that will be imported to the <%= @site.domain %> dashboard.
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<%= styled_label(f, :property, "Google Analytics property") %>
|
||||
<%= styled_select(f, :property, @properties,
|
||||
|
|
@ -19,7 +21,7 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-col-reverse sm:flex-row justify-between items-center">
|
||||
<p class="text-sm mt-4 sm:mt-0 dark:text-gray-100">
|
||||
<p class="mt-4 sm:mt-0 dark:text-gray-100">
|
||||
<a
|
||||
href={Routes.site_path(@conn, :settings_imports_exports, @site.domain)}
|
||||
class="underline text-indigo-600"
|
||||
|
|
@ -30,7 +32,7 @@
|
|||
|
||||
<%= submit(name: "continueButton", class: "button sm:w-auto w-full [&>span.label-enabled]:block [&>span.label-disabled]:hidden [&[disabled]>span.label-enabled]:hidden [&[disabled]>span.label-disabled]:block") do %>
|
||||
<span class="label-enabled pointer-events-none">
|
||||
Continue ->
|
||||
Continue
|
||||
</span>
|
||||
|
||||
<span class="label-disabled">
|
||||
|
|
@ -39,4 +41,5 @@
|
|||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="A lightweight, non-intrusive alternative to Google Analytics."
|
||||
/>
|
||||
<meta name="robots" content={@conn.private.robots} />
|
||||
<%= if assigns[:connect_live_socket] do %>
|
||||
<meta name="csrf-token" content={Plug.CSRFProtection.get_csrf_token()} />
|
||||
<meta name="websocket-url" content={websocket_url()} />
|
||||
<% end %>
|
||||
<PlausibleWeb.Components.Layout.favicon conn={@conn} />
|
||||
<title><%= assigns[:title] || "Plausible · Web analytics" %></title>
|
||||
<link rel="stylesheet" href={Routes.static_path(@conn, "/css/app.css")} />
|
||||
<%= render("_tracking.html", assigns) %>
|
||||
</head>
|
||||
<body class="flex flex-col h-full bg-gray-100 dark:bg-gray-900">
|
||||
<nav class="relative z-20 py-8">
|
||||
<div class="container print:max-w-full">
|
||||
<nav class="relative flex items-center justify-between sm:h-10 md:justify-center">
|
||||
<div class="flex items-center flex-1 md:absolute md:inset-y-0 md:left-0">
|
||||
<a href={home_dest(@conn)}>
|
||||
<%= img_tag(
|
||||
PlausibleWeb.Router.Helpers.static_path(
|
||||
@conn,
|
||||
logo_path("logo_dark.svg")
|
||||
),
|
||||
class: "w-44 -mt-2 hidden dark:inline",
|
||||
alt: "Plausible logo",
|
||||
loading: "lazy"
|
||||
) %>
|
||||
<%= img_tag(
|
||||
PlausibleWeb.Router.Helpers.static_path(
|
||||
@conn,
|
||||
logo_path("logo_light.svg")
|
||||
),
|
||||
class: "w-44 -mt-2 inline dark:hidden",
|
||||
alt: "Plausible logo",
|
||||
loading: "lazy"
|
||||
) %>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<%= if assigns[:flash] do %>
|
||||
<%= render("_flash.html", assigns) %>
|
||||
<% end %>
|
||||
<%= @inner_content %>
|
||||
|
||||
<%= if ee?() do %>
|
||||
<p class="text-center text-gray-500 text-xs py-8">
|
||||
© <%= DateTime.utc_now().year %> Plausible Analytics. All rights reserved.
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<PlausibleWeb.Components.Layout.theme_script current_user={assigns[:current_user]} />
|
||||
|
||||
<script type="text/javascript" src={Routes.static_path(@conn, "/js/app.js")}>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -14,27 +14,27 @@
|
|||
</p>
|
||||
|
||||
<:footer>
|
||||
<ol class="list-disc space-y-1 ml-4 mt-1 mb-4">
|
||||
<li>
|
||||
<.focus_list>
|
||||
<:item>
|
||||
<.styled_link href={Routes.auth_path(@conn, :login)}>
|
||||
Login
|
||||
</.styled_link>
|
||||
</li>
|
||||
<li>
|
||||
</:item>
|
||||
<:item>
|
||||
<.styled_link href={Routes.auth_path(@conn, :register_form)}>
|
||||
Register
|
||||
</.styled_link>
|
||||
</li>
|
||||
<li>
|
||||
</:item>
|
||||
<:item>
|
||||
<.styled_link href="https://plausible.io/docs">
|
||||
Guides & Docs
|
||||
</.styled_link>
|
||||
</li>
|
||||
<li>
|
||||
</:item>
|
||||
<:item>
|
||||
<.styled_link href="https://twitter.com/plausiblehq">
|
||||
Follow on Twitter
|
||||
</.styled_link>
|
||||
</li>
|
||||
</ol>
|
||||
</:item>
|
||||
</.focus_list>
|
||||
</:footer>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
|
|||
|
|
@ -1,44 +1,50 @@
|
|||
<div class="w-full max-w-3xl mt-4 mx-auto flex">
|
||||
<%= form_for @changeset, Routes.site_path(@conn, :change_domain_submit, @site.domain), [class: "max-w-lg w-full mx-auto bg-white dark:bg-gray-800 shadow-lg rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
|
||||
<h2 class="text-xl font-black dark:text-gray-100">Change your website domain</h2>
|
||||
<PlausibleWeb.Components.FlowProgress.render
|
||||
flow={PlausibleWeb.Flows.domain_change()}
|
||||
current_step="Set up new domain"
|
||||
/>
|
||||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<:title>Change your website domain</:title>
|
||||
|
||||
<:subtitle>
|
||||
Once you change your domain, <b>you must update Plausible Installation on your site within 72 hours to guarantee continuous tracking</b>.
|
||||
<br /><br />If you're using the API, please also make sure to update your API credentials. Visit our
|
||||
<.styled_link new_tab href="https://plausible.io/docs/change-domain-name/">
|
||||
documentation
|
||||
</.styled_link>
|
||||
for details.
|
||||
</:subtitle>
|
||||
|
||||
<:footer>
|
||||
<.focus_list>
|
||||
<:item>
|
||||
Changed your mind? Go back to
|
||||
<.styled_link href={Routes.site_path(@conn, :settings_general, @site.domain)}>
|
||||
Site Settings
|
||||
</.styled_link>
|
||||
</:item>
|
||||
</.focus_list>
|
||||
</:footer>
|
||||
|
||||
<%= form_for @changeset, Routes.site_path(@conn, :change_domain_submit, @site.domain, flow: PlausibleWeb.Flows.domain_change()), [], fn f -> %>
|
||||
<h2 class="text-xl font-black dark:text-gray-100"></h2>
|
||||
|
||||
<div class="my-6">
|
||||
<%= label(f, :domain, class: "block text-sm font-medium text-gray-700 dark:text-gray-300") %>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-xs mt-1">
|
||||
Just the naked domain or subdomain without 'www'
|
||||
<%= label(f, :domain, class: "block font-medium dark:text-gray-300") %>
|
||||
<p class="text-gray-500 dark:text-gray-400 mt-1 text-sm">
|
||||
Just the naked domain or subdomain without 'www', 'https' etc.
|
||||
</p>
|
||||
<div class="mt-2 flex rounded-md shadow-sm">
|
||||
<span class="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 dark:border-gray-500 bg-gray-50 dark:bg-gray-850 text-gray-500 dark:text-gray-400 sm:text-sm">
|
||||
https://
|
||||
</span>
|
||||
<%= text_input(f, :domain,
|
||||
class:
|
||||
"focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 flex-1 block w-full px-3 py-2 rounded-none rounded-r-md sm:text-sm border-gray-300 dark:border-gray-500 dark:bg-gray-900 dark:text-gray-300",
|
||||
"focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 flex-1 block w-full px-3 py-2 rounded-none rounded-r-md border-gray-300 dark:border-gray-500 dark:bg-gray-900 dark:text-gray-300",
|
||||
placeholder: "example.com"
|
||||
) %>
|
||||
</div>
|
||||
<%= error_tag(f, :domain) %>
|
||||
</div>
|
||||
|
||||
<p class="text-sm sm:text-sm text-gray-700 dark:text-gray-300">
|
||||
<span class="font-bold dark:text-gray-100">Once you change your domain, you must update the JavaScript snippet on your site within 72 hours to guarantee continuous tracking</span>. If you're using the API, please also make sure to update your API credentials.
|
||||
</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 mt-4">
|
||||
Visit our
|
||||
<.styled_link new_tab href="https://plausible.io/docs/change-domain-name/">
|
||||
documentation
|
||||
</.styled_link>
|
||||
for details.
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.button type="submit" class="mt-4 w-full">
|
||||
Change Domain and add new Snippet →
|
||||
Change Domain and add new Snippet
|
||||
</PlausibleWeb.Components.Generic.button>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<.styled_link href={Routes.site_path(@conn, :settings_general, @site.domain)}>
|
||||
Back to Site Settings
|
||||
</.styled_link>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
<%= form_for @conn, Routes.membership_path(@conn, :invite_member, @site.domain), [class: "max-w-lg w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
|
||||
<h2 class="text-xl font-black dark:text-gray-100 mb-4">Invite member to <%= @site.domain %></h2>
|
||||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<:title>
|
||||
Invite member to <%= @site.domain %>
|
||||
</:title>
|
||||
|
||||
<:subtitle>
|
||||
Enter the email address and role of the person you want to invite. We will contact them over email to offer them access to <%= @site.domain %> analytics.<br /><br />
|
||||
The invitation will expire in 48 hours
|
||||
</:subtitle>
|
||||
|
||||
<%= form_for @conn, Routes.membership_path(@conn, :invite_member, @site.domain), fn f -> %>
|
||||
<PlausibleWeb.Components.Billing.Notice.limit_exceeded
|
||||
:if={Map.get(assigns, :is_at_limit, false)}
|
||||
current_user={@current_user}
|
||||
|
|
@ -9,17 +17,9 @@
|
|||
resource="team members"
|
||||
/>
|
||||
|
||||
<p class="mt-4 max-w-2xl text-sm text-gray-500 dark:text-gray-200">
|
||||
Enter the email address and role of the person you want to invite. We will contact them over email to offer them access to <%= @site.domain %> analytics.
|
||||
</p>
|
||||
|
||||
<p class="mt-4 max-w-2xl text-sm text-gray-500 dark:text-gray-200">
|
||||
The invitation will expire in 48 hours
|
||||
</p>
|
||||
|
||||
<div class="my-6">
|
||||
<%= label(f, :email, "Email address",
|
||||
class: "block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
class: "block font-medium text-gray-700 dark:text-gray-300"
|
||||
) %>
|
||||
<div class="mt-1 relative rounded-md shadow-sm">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
<%= email_input(f, :email,
|
||||
class:
|
||||
"focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-900 dark:text-gray-300 block w-full rounded-md pl-10 sm:text-sm border-gray-300 dark:border-gray-500",
|
||||
"focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-900 dark:text-gray-300 block w-full rounded-md pl-10 border-gray-300 dark:border-gray-500",
|
||||
placeholder: "john.doe@example.com",
|
||||
required: "true"
|
||||
) %>
|
||||
|
|
@ -43,12 +43,12 @@
|
|||
<%= error_tag(f, :email) %>
|
||||
|
||||
<%= if @conn.assigns[:error] do %>
|
||||
<div class="text-red-500 text-xs mb-4"><%= @conn.assigns[:error] %></div>
|
||||
<div class="text-red-500 mb-4"><%= @conn.assigns[:error] %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<fieldset x-data="{selectedOption: null}">
|
||||
<%= label(f, :role, class: "block text-sm font-medium text-gray-700 dark:text-gray-300") %>
|
||||
<%= label(f, :role, class: "block font-medium text-gray-700 dark:text-gray-300") %>
|
||||
<div class="mt-1 bg-white rounded-md -space-y-px dark:bg-gray-800">
|
||||
<label
|
||||
class="border-gray-200 dark:border-gray-500 rounded-tl-md rounded-tr-md relative border p-4 flex cursor-pointer"
|
||||
|
|
@ -62,13 +62,13 @@
|
|||
) %>
|
||||
<div class="ml-3 flex flex-col">
|
||||
<span
|
||||
class="text-gray-900 dark:text-gray-100 block text-sm font-medium"
|
||||
class="text-gray-900 dark:text-gray-100 block font-medium"
|
||||
x-class="{'text-indigo-900 dark:text-white': selectedOption === 'admin', 'text-gray-900 dark:text-gray-100': selectedOption !== 'admin'}"
|
||||
>
|
||||
Admin
|
||||
</span>
|
||||
<span
|
||||
class="text-gray-500 dark:text-gray-200 block text-sm"
|
||||
class="text-gray-500 dark:text-gray-200 block"
|
||||
x-class="{'text-indigo-700 dark:text-gray-100': selectedOption === 'admin', 'text-gray-500 dark:text-gray-200': selectedOption !== 'admin'}"
|
||||
>
|
||||
Can view stats, change site settings and invite other members
|
||||
|
|
@ -88,13 +88,13 @@
|
|||
) %>
|
||||
<div class="ml-3 flex flex-col">
|
||||
<span
|
||||
class="text-gray-900 dark:text-gray-100 block text-sm font-medium"
|
||||
class="text-gray-900 dark:text-gray-100 block font-medium"
|
||||
x-class="{'text-indigo-900 dark:text-white': selectedOption === 'viewer', 'text-gray-900 dark:text-gray-100': selectedOption !== 'viewer'}"
|
||||
>
|
||||
Viewer
|
||||
</span>
|
||||
<span
|
||||
class="text-gray-500 dark:text-gray-200 block text-sm"
|
||||
class="text-gray-500 dark:text-gray-200 block"
|
||||
x-class="{'text-indigo-700 dark:text-gray-100': selectedOption === 'viewer', 'text-gray-500 dark:text-gray-200': selectedOption !== 'viewer'}"
|
||||
>
|
||||
Can view stats but cannot access settings or invite members
|
||||
|
|
@ -106,16 +106,8 @@
|
|||
|
||||
<div class="mt-6">
|
||||
<%= submit(class: "button w-full") do %>
|
||||
<svg
|
||||
class="w-5 h-5 mr-1"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z">
|
||||
</path>
|
||||
</svg>
|
||||
<span>Invite</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
<%= form_for @conn, Routes.membership_path(@conn, :transfer_ownership, @site.domain), [class: "max-w-lg w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
|
||||
<h2 class="text-xl font-black dark:text-gray-100">Transfer ownership of <%= @site.domain %></h2>
|
||||
|
||||
<p class="mt-4 max-w-2xl text-sm text-gray-500">
|
||||
Enter the email address of the new owner. We will contact them over email to
|
||||
offer them the ownership of <%= @site.domain %>. If they don't respond in 48
|
||||
hours, the request will expire automatically.
|
||||
</p>
|
||||
<p class="mt-4 max-w-2xl text-sm text-gray-500">
|
||||
Do note that a subscription plan is not transferred alongside the site. If
|
||||
they accept the transfer request, the new owner will need to have an active
|
||||
subscription. Your access will be downgraded to <b>admin</b> and any other
|
||||
member roles will stay the same.
|
||||
</p>
|
||||
|
||||
<%= if @conn.assigns[:error] do %>
|
||||
<div class="text-red-500 text-xs italic mt-4"><%= @conn.assigns[:error] %></div>
|
||||
<% end %>
|
||||
|
||||
<div class="my-6">
|
||||
<%= label f, :email, "Email address", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||
<div class="mt-1 relative rounded-md shadow-sm">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" /><path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" /></svg>
|
||||
</div>
|
||||
<%= email_input(f, :email, class: "focus:ring-indigo-500 focus:border-indigo-500 block w-full rounded-md pl-10 sm:text-sm border-gray-300", placeholder: "john.doe@example.com", required: "true") %>
|
||||
</div>
|
||||
<%= error_tag f, :email %>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<%= submit("Request transfer", class: "button w-full") %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<:title>
|
||||
Transfer ownership of <%= @site.domain %>
|
||||
</:title>
|
||||
<:subtitle>
|
||||
Enter the email address of the new owner. We will contact them over email to
|
||||
offer them the ownership of <%= @site.domain %>. If they don't respond in 48
|
||||
hours, the request will expire automatically. <br /><br />
|
||||
Do note that a subscription plan is not transferred alongside the site. If
|
||||
they accept the transfer request, the new owner will need to have an active
|
||||
subscription. Your access will be downgraded to <b>admin</b>
|
||||
and any other
|
||||
member roles will stay the same.
|
||||
</:subtitle>
|
||||
<%= form_for @conn, Routes.membership_path(@conn, :transfer_ownership, @site.domain), fn f -> %>
|
||||
<%= if @conn.assigns[:error] do %>
|
||||
<div class="text-red-500 text-xs italic mt-4"><%= @conn.assigns[:error] %></div>
|
||||
<% end %>
|
||||
|
||||
<div class="my-6">
|
||||
<%= label(f, :email, "Email address",
|
||||
class: "block font-medium text-gray-700 dark:text-gray-300"
|
||||
) %>
|
||||
<div class="mt-1 relative rounded-md shadow-sm">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" /><path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
|
||||
</svg>
|
||||
</div>
|
||||
<%= email_input(f, :email,
|
||||
class:
|
||||
"focus:ring-indigo-500 focus:border-indigo-500 block w-full rounded-md pl-10 border-gray-300",
|
||||
placeholder: "john.doe@example.com",
|
||||
required: "true"
|
||||
) %>
|
||||
</div>
|
||||
<%= error_tag(f, :email) %>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<%= submit("Request transfer", class: "button w-full") %>
|
||||
</div>
|
||||
<% end %>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
@ -46,12 +46,9 @@
|
|||
<div class="my-6">
|
||||
<%= label(f, :domain, class: "block font-medium dark:text-gray-300") %>
|
||||
<p class="text-gray-500 dark:text-gray-400 mt-1 text-sm">
|
||||
Just the naked domain or subdomain without 'www'
|
||||
Just the naked domain or subdomain without 'www', 'https' etc.
|
||||
</p>
|
||||
<div class="mt-2 flex rounded-md shadow-sm">
|
||||
<span class="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 dark:border-gray-500 bg-gray-50 dark:bg-gray-850 dark:text-gray-400">
|
||||
https://
|
||||
</span>
|
||||
<%= text_input(f, :domain,
|
||||
class:
|
||||
"focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 flex-1 block w-full px-3 py-2 rounded-none rounded-r-md border-gray-300 dark:border-gray-500 dark:bg-gray-900 dark:text-gray-300",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<header class="relative">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Site Domain</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
Moving your Site to a different Domain? We got you!
|
||||
Moving your site to a different domain? We got you!
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="change-domain-name" />
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
Site Timezone
|
||||
</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
Update your reporting Timezone.
|
||||
Update your reporting timezone.
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="general" />
|
||||
|
|
@ -62,63 +62,28 @@
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= form_for @conn, "/", [class: "shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6"], fn f -> %>
|
||||
<div class="shadow sm:rounded-md sm:overflow-hidden">
|
||||
<div class="bg-white dark:bg-gray-800 py-6 px-4 space-y-6 sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
|
||||
<a id="snippet">JavaScript Snippet</a>
|
||||
<a id="snippet">Site Installation</a>
|
||||
</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
Include this Snippet in the <code><head></code> of your Website.
|
||||
Control what data is collected and verify your installation.
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="plausible-script" />
|
||||
</header>
|
||||
|
||||
<div class="my-4">
|
||||
<div class="relative">
|
||||
<code>
|
||||
<%= textarea(f, :domain,
|
||||
id: "snippet_code",
|
||||
class:
|
||||
"transition overflow-hidden bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 pr-6 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white focus:border-gray-300 dark:focus:border-gray-500 text-xs mt-2 resize-none",
|
||||
value: render_snippet(@site),
|
||||
rows: 2
|
||||
) %>
|
||||
</code>
|
||||
<a
|
||||
onclick="var textarea = document.getElementById('snippet_code'); textarea.focus(); textarea.select(); document.execCommand('copy');"
|
||||
href="javascript:void(0)"
|
||||
class="no-underline text-indigo-500 text-sm hover:underline"
|
||||
>
|
||||
<svg
|
||||
class="absolute text-indigo-500"
|
||||
style="top: 24px; right: 12px;"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div :if={Plausible.Verification.enabled?()}>
|
||||
<%= live_render(@conn, PlausibleWeb.Live.Verification,
|
||||
session: %{
|
||||
"open_modal?" => !!@conn.params["launch_verification"],
|
||||
"site_id" => @site.id,
|
||||
"domain" => @site.domain,
|
||||
"modal?" => true,
|
||||
"slowdown" => @conn.private[:verification_slowdown]
|
||||
<PlausibleWeb.Components.Generic.button_link
|
||||
class="mt-4"
|
||||
href={
|
||||
Routes.site_path(@conn, :installation, @site.domain, flow: PlausibleWeb.Flows.review())
|
||||
}
|
||||
) %>
|
||||
>
|
||||
Review Installation
|
||||
</PlausibleWeb.Components.Generic.button_link>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
<PlausibleWeb.Components.FirstDashboardLaunchBanner.set
|
||||
:if={@conn.params["site_created"] == "true"}
|
||||
site={@site}
|
||||
/>
|
||||
|
||||
<PlausibleWeb.Components.FlowProgress.render
|
||||
flow={@conn.params["flow"]}
|
||||
current_step="Install snippet"
|
||||
/>
|
||||
|
||||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<:title>
|
||||
Add JavaScript snippet
|
||||
</:title>
|
||||
<:subtitle>
|
||||
<p :if={Plausible.Verification.enabled?()} class="dark:text-gray-100">
|
||||
Include this snippet in the <code><head></code>
|
||||
section of your website.<br />To verify your integration, click the button below to confirm that everything is working correctly.
|
||||
</p>
|
||||
<p :if={not Plausible.Verification.enabled?()} class="dark:text-gray-100">
|
||||
Paste this snippet in the <code><head></code> of your website.
|
||||
</p>
|
||||
</:subtitle>
|
||||
|
||||
<:footer>
|
||||
<ol class="list-disc space-y-1 ml-4 mt-1 mb-4">
|
||||
<li>
|
||||
On WordPress? We have
|
||||
<.styled_link new_tab href="https://plausible.io/wordpress-analytics-plugin">
|
||||
a plugin
|
||||
</.styled_link>
|
||||
</li>
|
||||
<li>
|
||||
See more
|
||||
<.styled_link new_tab href="https://plausible.io/docs/integration-guides">
|
||||
integration guides
|
||||
</.styled_link>
|
||||
</li>
|
||||
</ol>
|
||||
</:footer>
|
||||
|
||||
<%= form_for @conn, @form_submit_url, [], fn f -> %>
|
||||
<div>
|
||||
<div class="relative">
|
||||
<%= textarea(f, :domain,
|
||||
id: "snippet_code",
|
||||
class:
|
||||
"transition overflow-hidden bg-gray-100 dark:bg-gray-900 appearance-none border border-transparent rounded w-full p-2 pr-6 text-gray-700 dark:text-gray-300 leading-normal appearance-none focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-400 dark:focus:border-gray-500 font-mono mt-4 resize-none text-xs",
|
||||
value: render_snippet(@site),
|
||||
rows: 3,
|
||||
readonly: "readonly"
|
||||
) %>
|
||||
<a
|
||||
onclick="var textarea = document.getElementById('snippet_code'); textarea.focus(); textarea.select(); document.execCommand('copy');"
|
||||
href="javascript:void(0)"
|
||||
class="no-underline text-indigo-500 hover:underline"
|
||||
>
|
||||
<svg
|
||||
class="absolute text-indigo-500"
|
||||
style="top: 24px; right: 12px;"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% button_label =
|
||||
if Plausible.Verification.enabled?() do
|
||||
"Verify your integration to start collecting data"
|
||||
else
|
||||
"Start collecting data"
|
||||
end %>
|
||||
<%= link(button_label,
|
||||
class: "button mt-4 w-full",
|
||||
to: @form_submit_url
|
||||
) %>
|
||||
<% end %>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
||||
<div class="w-full max-w-3xl mt-4 mx-auto flex"></div>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
<div class="w-full max-w-3xl mt-4 mx-auto flex">
|
||||
<%= form_for @conn, "/", [class: "max-w-lg w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
|
||||
<h2 class="text-xl font-bold dark:text-gray-100">Change JavaScript snippet</h2>
|
||||
<div class="my-4">
|
||||
<p class="dark:text-gray-100">Replace your snippet in the <code><head></code> of your website.</p>
|
||||
|
||||
<div class="relative">
|
||||
<%= textarea f, :domain, id: "snippet_code", class: "transition overflow-hidden bg-gray-100 dark:bg-gray-900 appearance-none border border-transparent rounded w-full p-2 pr-6 text-gray-700 dark:text-gray-300 leading-normal appearance-none focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-400 dark:focus:border-gray-500 text-xs mt-4 resize-none", value: render_snippet(@site), rows: 3, readonly: "readonly" %>
|
||||
<a onclick="var textarea = document.getElementById('snippet_code'); textarea.focus(); textarea.select(); document.execCommand('copy');" href="javascript:void(0)" class="no-underline text-indigo-500 text-sm hover:underline">
|
||||
<svg class="absolute text-indigo-500" style="top: 24px; right: 12px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-sm sm:text-sm text-gray-700 dark:text-gray-300">
|
||||
<span class="font-bold dark:text-gray-100">Your domain has been changed. You must update the JavaScript snippet on your site within 72 hours to guarantee continuous tracking</span>. If you're using the API, please also make sure to update your API credentials.</p>
|
||||
<p class="text-sm sm:text-sm text-gray-700 dark:text-gray-300 mt-4">
|
||||
Visit our <a target="_blank" href="https://plausible.io/docs/change-domain-name/" class="text-indigo-500">documentation</a> for details.
|
||||
</p>
|
||||
|
||||
<%= link("I understand, I'll change my snippet →", class: "button mt-4 w-full", to: "/#{URI.encode_www_form(@site.domain)}") %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<%= form_for @conn, "/share/#{@link.slug}/authenticate", [class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
|
||||
<h2 class="text-xl font-black dark:text-gray-100">Enter password</h2>
|
||||
<div class="my-4 dark:text-gray-100">
|
||||
This link is password-protected. Please enter the password to continue to the dashboard.
|
||||
</div>
|
||||
|
||||
<div class="my-6">
|
||||
<%= label f, :password, "Password", class: "block text-sm font-bold dark:text-gray-100" %>
|
||||
<%= password_input f, :password, class: "transition mt-3 bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500" %>
|
||||
|
||||
<%= if @conn.assigns[:error] do %>
|
||||
<div class="text-red-500 text-xs italic mt-4"><%= @conn.assigns[:error] %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= submit "Continue", class: "button mt-4 w-full" %>
|
||||
<% end %>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<:title>Enter password</:title>
|
||||
<:subtitle>
|
||||
This link is password-protected. Please enter the password to continue to the dashboard.
|
||||
</:subtitle>
|
||||
<%= form_for @conn, "/share/#{@link.slug}/authenticate", fn f -> %>
|
||||
<div class="my-6">
|
||||
<%= label(f, :password, "Password", class: "block dark:text-gray-100") %>
|
||||
<%= password_input(f, :password,
|
||||
class:
|
||||
"transition mt-3 bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500"
|
||||
) %>
|
||||
|
||||
<%= if @conn.assigns[:error] do %>
|
||||
<div class="text-red-500 text-xs italic mt-4"><%= @conn.assigns[:error] %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= submit("Continue", class: "button mt-4 w-full") %>
|
||||
<% end %>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
<script>
|
||||
function updateStatus() {
|
||||
fetch("/api/<%= URI.encode_www_form(@site.domain) %>/status")
|
||||
.then(function(res) { return res.json() })
|
||||
.then(function(status) {
|
||||
if (status === "READY") {
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setInterval(updateStatus, 5000)
|
||||
</script>
|
||||
|
||||
<PlausibleWeb.Components.FlowProgress.render
|
||||
flow={@conn.params["flow"]}
|
||||
current_step="Verify snippet"
|
||||
/>
|
||||
|
||||
<%= if @site.locked do %>
|
||||
<div
|
||||
class="w-full px-4 py-4 text-sm font-bold text-center text-yellow-800 bg-yellow-100 rounded transition"
|
||||
style="top: 91px"
|
||||
role="alert"
|
||||
>
|
||||
<p>This dashboard is actually locked. You are viewing it with super-admin access</p>
|
||||
</div>
|
||||
<% end %>
|
||||
<div
|
||||
:if={not Plausible.Verification.enabled?()}
|
||||
class="bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-16 relative text-center"
|
||||
>
|
||||
<h2 class="text-xl font-bold dark:text-gray-100">Waiting for first pageview</h2>
|
||||
<h2 class="text-xl font-bold dark:text-gray-100">on <%= @site.domain %></h2>
|
||||
<div class="my-44">
|
||||
<div class="block pulsating-circle top-1/2 left-1/2"></div>
|
||||
<p class="text-gray-600 dark:text-gray-400 text-xs absolute left-0 bottom-0 mb-6 w-full text-center leading-normal">
|
||||
Need to see the snippet again?
|
||||
<.styled_link href={"/#{URI.encode_www_form(@site.domain)}/snippet?flow=#{@conn.params[~s|flow|]}"}>
|
||||
Click here
|
||||
</.styled_link>
|
||||
|
||||
<br /> Not working?
|
||||
<.styled_link
|
||||
new_tab
|
||||
href="https://plausible.io/docs/troubleshoot-integration#check-for-the-plausible-snippet-in-your-source-code"
|
||||
>
|
||||
Troubleshoot the integration
|
||||
</.styled_link>
|
||||
|
||||
<br />
|
||||
<span :if={ee?()}>
|
||||
Check the
|
||||
<.styled_link href={Routes.site_path(@conn, :settings_general, @site.domain)}>
|
||||
site settings
|
||||
</.styled_link>
|
||||
to invite team members, <br /> import historical stats and more.
|
||||
</span>
|
||||
<span :if={ce?()}>
|
||||
Still not working? Ask on our
|
||||
<.styled_link new_tab href="https://github.com/plausible/analytics/discussions">
|
||||
community-supported forum
|
||||
</.styled_link>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= if Plausible.Verification.enabled?(),
|
||||
do:
|
||||
live_render(@conn, PlausibleWeb.Live.Verification,
|
||||
session: %{
|
||||
"site_id" => @site.id,
|
||||
"domain" => @site.domain,
|
||||
"slowdown" => @conn.private[:verification_slowdown],
|
||||
"flow" => @conn.params["flow"]
|
||||
}
|
||||
) %>
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
<div class="w-full max-w-md mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 py-6 mt-8"]>
|
||||
<h2 class="dark:text-gray-100">Unsubscribe successful</h2>
|
||||
<p class="mt-4 dark:text-gray-100">You will no longer receive a <%= @interval %> analytics report for <%= @site %></p>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<PlausibleWeb.Components.Generic.focus_box>
|
||||
<:title>
|
||||
Unsubscribe successful
|
||||
</:title>
|
||||
|
||||
<:subtitle>
|
||||
You will no longer receive a <%= @interval %> analytics report for <%= @site.domain %>
|
||||
</:subtitle>
|
||||
</PlausibleWeb.Components.Generic.focus_box>
|
||||
|
|
@ -99,13 +99,13 @@ defmodule Plausible.Workers.TrafficChangeNotifier do
|
|||
defp send_drop_notification(recipient, site, current_visitors) do
|
||||
site = Repo.preload(site, :members)
|
||||
|
||||
{dashboard_link, verification_link} =
|
||||
{dashboard_link, installation_link} =
|
||||
if Enum.any?(site.members, &(&1.email == recipient)) do
|
||||
{
|
||||
Routes.stats_url(PlausibleWeb.Endpoint, :stats, site.domain, []),
|
||||
Routes.site_url(PlausibleWeb.Endpoint, :settings_general, site.domain,
|
||||
launch_verification: true
|
||||
) <> "#snippet"
|
||||
Routes.site_url(PlausibleWeb.Endpoint, :installation, site.domain,
|
||||
flow: PlausibleWeb.Flows.review()
|
||||
)
|
||||
}
|
||||
else
|
||||
{nil, nil}
|
||||
|
|
@ -117,7 +117,7 @@ defmodule Plausible.Workers.TrafficChangeNotifier do
|
|||
site,
|
||||
current_visitors,
|
||||
dashboard_link,
|
||||
verification_link
|
||||
installation_link
|
||||
)
|
||||
|
||||
Plausible.Mailer.send(template)
|
||||
|
|
|
|||
|
|
@ -544,7 +544,7 @@ defmodule Plausible.Verification.ChecksTest do
|
|||
</html>
|
||||
"""
|
||||
|
||||
test "disallowd via content-security-policy and GTM should make CSP a priority" do
|
||||
test "disallowed via content-security-policy and GTM should make CSP a priority" do
|
||||
stub_fetch_body(fn conn ->
|
||||
conn
|
||||
|> put_resp_header("content-security-policy", "default-src 'self' foo.local")
|
||||
|
|
|
|||
|
|
@ -27,18 +27,18 @@ defmodule PlausibleWeb.Components.FlowProgressTest do
|
|||
test "register" do
|
||||
rendered =
|
||||
render_component(&FlowProgress.render/1,
|
||||
flow: "register",
|
||||
flow: PlausibleWeb.Flows.register(),
|
||||
current_step: "Register"
|
||||
)
|
||||
|
||||
assert text_of_element(rendered, "#flow-progress") ==
|
||||
"1 Register 2 Activate account 3 Add site info 4 Install snippet 5 Verify snippet"
|
||||
"1 Register 2 Activate account 3 Add site info 4 Install Plausible 5 Verify installation"
|
||||
end
|
||||
|
||||
test "invitation" do
|
||||
rendered =
|
||||
render_component(&FlowProgress.render/1,
|
||||
flow: "invitation",
|
||||
flow: PlausibleWeb.Flows.invitation(),
|
||||
current_step: "Register"
|
||||
)
|
||||
|
||||
|
|
@ -49,11 +49,33 @@ defmodule PlausibleWeb.Components.FlowProgressTest do
|
|||
test "provisioning" do
|
||||
rendered =
|
||||
render_component(&FlowProgress.render/1,
|
||||
flow: "provisioning",
|
||||
flow: PlausibleWeb.Flows.provisioning(),
|
||||
current_step: "Add site info"
|
||||
)
|
||||
|
||||
assert text_of_element(rendered, "#flow-progress") ==
|
||||
"1 Add site info 2 Install snippet 3 Verify snippet"
|
||||
"1 Add site info 2 Install Plausible 3 Verify installation"
|
||||
end
|
||||
|
||||
test "review" do
|
||||
rendered =
|
||||
render_component(&FlowProgress.render/1,
|
||||
flow: PlausibleWeb.Flows.review(),
|
||||
current_step: "Install Plausible"
|
||||
)
|
||||
|
||||
assert text_of_element(rendered, "#flow-progress") ==
|
||||
"1 Install Plausible 2 Verify installation"
|
||||
end
|
||||
|
||||
test "domain_change" do
|
||||
rendered =
|
||||
render_component(&FlowProgress.render/1,
|
||||
flow: PlausibleWeb.Flows.domain_change(),
|
||||
current_step: "Set up new domain"
|
||||
)
|
||||
|
||||
assert text_of_element(rendered, "#flow-progress") ==
|
||||
"1 Set up new domain 2 Install Plausible 3 Verify installation"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,41 +2,6 @@ defmodule PlausibleWeb.Api.InternalControllerTest do
|
|||
use PlausibleWeb.ConnCase, async: true
|
||||
use Plausible.Repo
|
||||
|
||||
describe "GET /api/:domain/status" do
|
||||
setup [:create_user, :log_in]
|
||||
|
||||
test "is WAITING when site has no pageviews", %{conn: conn, user: user} do
|
||||
site = insert(:site, members: [user])
|
||||
conn = get(conn, "/api/#{site.domain}/status")
|
||||
|
||||
assert json_response(conn, 200) == "WAITING"
|
||||
end
|
||||
|
||||
test "is READY when site has at least 1 pageview", %{conn: conn, user: user} do
|
||||
site = insert(:site, members: [user])
|
||||
Plausible.TestUtils.create_pageviews([%{site: site}])
|
||||
|
||||
conn = get(conn, "/api/#{site.domain}/status")
|
||||
|
||||
assert json_response(conn, 200) == "READY"
|
||||
end
|
||||
|
||||
test "is WAITING when unauthenticated", %{user: user} do
|
||||
site = insert(:site, members: [user])
|
||||
Plausible.TestUtils.create_pageviews([%{site: site}])
|
||||
|
||||
conn = get(build_conn(), "/api/#{site.domain}/status")
|
||||
|
||||
assert json_response(conn, 200) == "WAITING"
|
||||
end
|
||||
|
||||
test "is WAITING when non-existing site", %{conn: conn} do
|
||||
conn = get(conn, "/api/example.com/status")
|
||||
|
||||
assert json_response(conn, 200) == "WAITING"
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/sites" do
|
||||
setup [:create_user, :log_in]
|
||||
|
||||
|
|
|
|||
|
|
@ -1504,8 +1504,10 @@ defmodule PlausibleWeb.AuthControllerTest do
|
|||
|
||||
assert element_exists?(html, "input[name=code]")
|
||||
|
||||
assert text_of_attr(html, "form#start-over-form", "action") ==
|
||||
Routes.auth_path(conn, :initiate_2fa_setup)
|
||||
assert element_exists?(
|
||||
html,
|
||||
~s|a[data-method="post"][data-to="#{Routes.auth_path(conn, :initiate_2fa_setup)}"|
|
||||
)
|
||||
end
|
||||
|
||||
test "redirects back to settings if 2FA not initiated", %{conn: conn} do
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
})
|
||||
|
||||
assert redirected_to(conn) ==
|
||||
"/#{URI.encode_www_form("éxample.com")}/snippet?site_created=true&flow="
|
||||
"/#{URI.encode_www_form("éxample.com")}/installation?site_created=true&flow="
|
||||
|
||||
assert site = Repo.get_by(Plausible.Site, domain: "éxample.com")
|
||||
assert site.timezone == "Europe/London"
|
||||
|
|
@ -341,7 +341,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
}
|
||||
})
|
||||
|
||||
assert redirected_to(conn) == "/example.com/snippet?site_created=true&flow="
|
||||
assert redirected_to(conn) == "/example.com/installation?site_created=true&flow="
|
||||
assert Repo.get_by(Plausible.Site, domain: "example.com")
|
||||
end
|
||||
|
||||
|
|
@ -361,7 +361,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
}
|
||||
})
|
||||
|
||||
assert redirected_to(conn) == "/example.com/snippet?site_created=true&flow="
|
||||
assert redirected_to(conn) == "/example.com/installation?site_created=true&flow="
|
||||
assert Plausible.Billing.Quota.Usage.site_usage(user) == 3
|
||||
end
|
||||
|
||||
|
|
@ -375,7 +375,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
}
|
||||
})
|
||||
|
||||
assert redirected_to(conn) == "/example.com/snippet?site_created=true&flow="
|
||||
assert redirected_to(conn) == "/example.com/installation?site_created=true&flow="
|
||||
assert Repo.get_by(Plausible.Site, domain: "example.com")
|
||||
end
|
||||
end
|
||||
|
|
@ -457,17 +457,20 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
})
|
||||
|
||||
assert redirected_to(conn) ==
|
||||
"/example.com/snippet?site_created=true&flow="
|
||||
"/example.com/installation?site_created=true&flow="
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /:website/snippet" do
|
||||
describe "GET /:website/installation" do
|
||||
setup [:create_user, :log_in, :create_site]
|
||||
|
||||
test "shows snippet", %{conn: conn, site: site} do
|
||||
conn = get(conn, "/#{site.domain}/snippet")
|
||||
test "static render - spinner determining installation type", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
conn = get(conn, "/#{site.domain}/installation")
|
||||
|
||||
assert html_response(conn, 200) =~ "Add JavaScript snippet"
|
||||
assert html_response(conn, 200) =~ "Determining installation type"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -482,7 +485,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
|
||||
assert resp =~ "Site Timezone"
|
||||
assert resp =~ "Site Domain"
|
||||
assert resp =~ "JavaScript Snippet"
|
||||
assert resp =~ "Site Installation"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1596,8 +1599,8 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
resp = html_response(conn, 200)
|
||||
assert resp =~ Routes.site_path(conn, :change_domain_submit, site.domain)
|
||||
|
||||
assert resp =~
|
||||
"Once you change your domain, you must update the JavaScript snippet on your site within 72 hours"
|
||||
assert text(resp) =~
|
||||
"Once you change your domain, you must update Plausible Installation on your site within 72 hours"
|
||||
end
|
||||
|
||||
test "domain change form submission when no change is made", %{conn: conn, site: site} do
|
||||
|
|
@ -1645,7 +1648,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
assert is_nil(site.domain_changed_from)
|
||||
end
|
||||
|
||||
test "domain change successful form submission redirects to snippet change info", %{
|
||||
test "domain change successful form submission redirects to installation", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
|
|
@ -1658,31 +1661,14 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
})
|
||||
|
||||
assert redirected_to(conn) ==
|
||||
Routes.site_path(conn, :add_snippet_after_domain_change, new_domain)
|
||||
Routes.site_path(conn, :installation, new_domain,
|
||||
flow: PlausibleWeb.Flows.domain_change()
|
||||
)
|
||||
|
||||
site = Repo.reload!(site)
|
||||
assert site.domain == new_domain
|
||||
assert site.domain_changed_from == original_domain
|
||||
end
|
||||
|
||||
test "snippet info after domain change", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
put(conn, Routes.site_path(conn, :change_domain_submit, site.domain), %{
|
||||
"site" => %{"domain" => "foo.example.com"}
|
||||
})
|
||||
|
||||
resp =
|
||||
conn
|
||||
|> get(Routes.site_path(conn, :add_snippet_after_domain_change, "foo.example.com"))
|
||||
|> html_response(200)
|
||||
|> Floki.parse_document!()
|
||||
|> Floki.text()
|
||||
|
||||
assert resp =~
|
||||
"Your domain has been changed. You must update the JavaScript snippet on your site within 72 hours"
|
||||
end
|
||||
end
|
||||
|
||||
describe "reset stats" do
|
||||
|
|
|
|||
|
|
@ -53,11 +53,24 @@ defmodule PlausibleWeb.StatsControllerTest do
|
|||
assert text_of_element(resp, "title") == "Plausible Analytics: Live Demo"
|
||||
end
|
||||
|
||||
test "public site - shows waiting for first pageview", %{conn: conn} do
|
||||
test "public site - redirect to /login when no stats because verification requires it", %{
|
||||
conn: conn
|
||||
} do
|
||||
insert(:site, domain: "some-other-public-site.io", public: true)
|
||||
|
||||
conn = get(conn, "/some-other-public-site.io")
|
||||
assert html_response(conn, 200) =~ "Need to see the snippet again?"
|
||||
conn = get(conn, conn |> get("/some-other-public-site.io") |> redirected_to())
|
||||
assert redirected_to(conn) == "/login"
|
||||
end
|
||||
|
||||
test "public site - no stats with skip_to_dashboard", %{
|
||||
conn: conn
|
||||
} do
|
||||
insert(:site, domain: "some-other-public-site.io", public: true)
|
||||
|
||||
conn = get(conn, "/some-other-public-site.io?skip_to_dashboard=true")
|
||||
resp = html_response(conn, 200)
|
||||
|
||||
assert text_of_attr(resp, @react_container, "data-logged-in") == "false"
|
||||
end
|
||||
|
||||
test "can not view stats of a private website", %{conn: conn} do
|
||||
|
|
@ -81,7 +94,7 @@ defmodule PlausibleWeb.StatsControllerTest do
|
|||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
resp = conn |> get("/" <> site.domain) |> html_response(200)
|
||||
resp = conn |> get(conn |> get("/" <> site.domain) |> redirected_to()) |> html_response(200)
|
||||
refute text_of_attr(resp, @react_container, "data-logged-in") == "true"
|
||||
|
||||
resp = conn |> get("/" <> site.domain <> "?skip_to_dashboard=true") |> html_response(200)
|
||||
|
|
@ -101,7 +114,7 @@ defmodule PlausibleWeb.StatsControllerTest do
|
|||
end
|
||||
|
||||
test "does not show CRM link to the site", %{conn: conn, site: site} do
|
||||
conn = get(conn, "/" <> site.domain)
|
||||
conn = get(conn, conn |> get("/" <> site.domain) |> redirected_to())
|
||||
refute html_response(conn, 200) =~ "/crm/sites/site/#{site.id}"
|
||||
end
|
||||
end
|
||||
|
|
@ -118,11 +131,11 @@ defmodule PlausibleWeb.StatsControllerTest do
|
|||
assert html_response(conn, 200) =~ "stats-react-container"
|
||||
end
|
||||
|
||||
test "can view a private dashboard without stats", %{conn: conn} do
|
||||
test "can enter verification when site is without stats", %{conn: conn} do
|
||||
site = insert(:site)
|
||||
|
||||
conn = get(conn, "/" <> site.domain)
|
||||
assert html_response(conn, 200) =~ "Need to see the snippet again?"
|
||||
conn = get(conn, conn |> get("/" <> site.domain) |> redirected_to())
|
||||
assert html_response(conn, 200) =~ "Verifying your installation"
|
||||
end
|
||||
|
||||
test "can view a private locked dashboard with stats", %{conn: conn} do
|
||||
|
|
@ -138,13 +151,12 @@ defmodule PlausibleWeb.StatsControllerTest do
|
|||
assert Enum.all?(attrs, fn {k, v} -> is_binary(k) and is_binary(v) end)
|
||||
end
|
||||
|
||||
test "can view a private locked dashboard without stats", %{conn: conn} do
|
||||
test "can view private locked verification without stats", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
site = insert(:site, locked: true, members: [user])
|
||||
|
||||
conn = get(conn, "/" <> site.domain)
|
||||
assert html_response(conn, 200) =~ "Need to see the snippet again?"
|
||||
assert html_response(conn, 200) =~ "This dashboard is actually locked"
|
||||
conn = get(conn, conn |> get("/#{site.domain}") |> redirected_to())
|
||||
assert html_response(conn, 200) =~ "Verifying your installation"
|
||||
end
|
||||
|
||||
test "can view a locked public dashboard", %{conn: conn} do
|
||||
|
|
@ -160,7 +172,7 @@ defmodule PlausibleWeb.StatsControllerTest do
|
|||
|
||||
test "shows CRM link to the site", %{conn: conn} do
|
||||
site = insert(:site)
|
||||
conn = get(conn, "/" <> site.domain)
|
||||
conn = get(conn, conn |> get("/" <> site.domain) |> redirected_to())
|
||||
assert html_response(conn, 200) =~ "/crm/sites/site/#{site.id}"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do
|
|||
assert text_of_element(html, @progress) ==
|
||||
"We're visiting your site to ensure that everything is working"
|
||||
|
||||
assert element_exists?(html, ~s|a[href="/example.com/snippet?flow="]|)
|
||||
assert element_exists?(html, ~s|a[href="/example.com/settings/general"]|)
|
||||
assert element_exists?(html, @pulsating_circle)
|
||||
refute class_of_element(html, @pulsating_circle) =~ "hidden"
|
||||
refute element_exists?(html, @recommendations)
|
||||
|
|
@ -55,11 +53,10 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do
|
|||
]
|
||||
end
|
||||
|
||||
test "hides pulsating circle when finished in a modal, shows check circle" do
|
||||
test "hides pulsating circle when finished, shows check circle" do
|
||||
html =
|
||||
render_component(@component,
|
||||
domain: "example.com",
|
||||
modal?: true,
|
||||
success?: true,
|
||||
finished?: true
|
||||
)
|
||||
|
|
@ -77,11 +74,29 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do
|
|||
@tag :ee_only
|
||||
test "renders contact link on >3 attempts" do
|
||||
html = render_component(@component, domain: "example.com", attempts: 2, finished?: true)
|
||||
refute html =~ "Need further help with your integration?"
|
||||
refute html =~ "Need further help with your installation?"
|
||||
refute element_exists?(html, ~s|a[href="https://plausible.io/contact"]|)
|
||||
|
||||
html = render_component(@component, domain: "example.com", attempts: 3, finished?: true)
|
||||
assert html =~ "Need further help with your integration?"
|
||||
assert html =~ "Need further help with your installation?"
|
||||
assert element_exists?(html, ~s|a[href="https://plausible.io/contact"]|)
|
||||
end
|
||||
|
||||
test "offers escape paths: settings and installation instructions on failure" do
|
||||
html =
|
||||
render_component(@component,
|
||||
domain: "example.com",
|
||||
success?: false,
|
||||
finished?: true,
|
||||
installation_type: "WordPress",
|
||||
flow: PlausibleWeb.Flows.review()
|
||||
)
|
||||
|
||||
assert element_exists?(html, ~s|a[href="/example.com/settings/general"]|)
|
||||
|
||||
assert element_exists?(
|
||||
html,
|
||||
~s|a[href="/example.com/installation?flow=review&installation_type=WordPress"]|
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,348 @@
|
|||
defmodule PlausibleWeb.Live.InstallationTest do
|
||||
use PlausibleWeb.ConnCase, async: true
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
import Plausible.Test.Support.HTML
|
||||
|
||||
setup [:create_user, :log_in, :create_site]
|
||||
|
||||
describe "GET /:domain/installation" do
|
||||
test "static verification screen renders", %{conn: conn, site: site} do
|
||||
resp = get(conn, "/#{site.domain}/installation") |> html_response(200)
|
||||
|
||||
assert resp =~ "Determining installation type"
|
||||
refute resp =~ "Review your existing installation."
|
||||
end
|
||||
|
||||
test "static verification screen renders for flow=review", %{conn: conn, site: site} do
|
||||
resp =
|
||||
conn
|
||||
|> get("/#{site.domain}/installation?flow=review&installation_type=manual")
|
||||
|> html_response(200)
|
||||
|
||||
assert resp =~ "Review your existing installation."
|
||||
assert resp =~ "Verify your installation"
|
||||
|
||||
assert resp =~
|
||||
Routes.site_path(PlausibleWeb.Endpoint, :verification, site.domain,
|
||||
flow: PlausibleWeb.Flows.review()
|
||||
)
|
||||
end
|
||||
|
||||
test "static verification screen renders for flow=domain_change", %{conn: conn, site: site} do
|
||||
resp =
|
||||
conn
|
||||
|> get("/#{site.domain}/installation?flow=#{PlausibleWeb.Flows.domain_change()}")
|
||||
|> html_response(200)
|
||||
|
||||
assert resp =~ "Your domain has been changed"
|
||||
assert resp =~ "I understand, I'll update my website"
|
||||
assert resp =~ "Manual installation"
|
||||
refute resp =~ "Review your existing installation."
|
||||
|
||||
assert resp =~
|
||||
Routes.site_path(PlausibleWeb.Endpoint, :verification, site.domain,
|
||||
flow: PlausibleWeb.Flows.domain_change()
|
||||
)
|
||||
end
|
||||
|
||||
test "static verification screen renders for flow=domain_change using original installation type",
|
||||
%{conn: conn, site: site} do
|
||||
site = Plausible.Sites.update_installation_meta!(site, %{installation_type: "WordPress"})
|
||||
|
||||
resp =
|
||||
conn
|
||||
|> get("/#{site.domain}/installation?flow=#{PlausibleWeb.Flows.domain_change()}")
|
||||
|> html_response(200)
|
||||
|
||||
assert resp =~ "Your domain has been changed"
|
||||
assert resp =~ "I understand, I'll update my website"
|
||||
assert resp =~ "WordPress plugin"
|
||||
refute resp =~ "Manuial installation"
|
||||
refute resp =~ "Review your existing installation."
|
||||
|
||||
assert resp =~
|
||||
Routes.site_path(PlausibleWeb.Endpoint, :verification, site.domain,
|
||||
flow: PlausibleWeb.Flows.domain_change()
|
||||
)
|
||||
end
|
||||
|
||||
test "renders pre-determined installation type: WordPress", %{conn: conn, site: site} do
|
||||
resp =
|
||||
conn
|
||||
|> get("/#{site.domain}/installation?installation_type=WordPress")
|
||||
|> html_response(200)
|
||||
|
||||
assert resp =~ "Install WordPress plugin"
|
||||
assert resp =~ "Start collecting data"
|
||||
refute resp =~ "Review your existing installation."
|
||||
|
||||
assert resp =~
|
||||
Routes.site_path(PlausibleWeb.Endpoint, :verification, site.domain,
|
||||
installation_type: "WordPress"
|
||||
)
|
||||
end
|
||||
|
||||
test "renders pre-determined installation type: GTM", %{conn: conn, site: site} do
|
||||
resp =
|
||||
conn |> get("/#{site.domain}/installation?installation_type=GTM") |> html_response(200)
|
||||
|
||||
assert resp =~ "Install Google Tag Manager"
|
||||
assert resp =~ "Start collecting data"
|
||||
refute resp =~ "Review your existing installation."
|
||||
|
||||
assert resp =~
|
||||
Routes.site_path(PlausibleWeb.Endpoint, :verification, site.domain,
|
||||
installation_type: "GTM"
|
||||
)
|
||||
end
|
||||
|
||||
test "renders pre-determined installation type: manual", %{conn: conn, site: site} do
|
||||
resp =
|
||||
conn |> get("/#{site.domain}/installation?installation_type=manual") |> html_response(200)
|
||||
|
||||
assert resp =~ "Manual installation"
|
||||
assert resp =~ "Start collecting data"
|
||||
refute resp =~ "Review your existing installation."
|
||||
|
||||
assert resp =~
|
||||
Routes.site_path(PlausibleWeb.Endpoint, :verification, site.domain,
|
||||
installation_type: "manual"
|
||||
)
|
||||
end
|
||||
|
||||
test "ignores unknown installation types", %{conn: conn, site: site} do
|
||||
resp =
|
||||
conn |> get("/#{site.domain}/installation?installation_type=UM_NO") |> html_response(200)
|
||||
|
||||
assert resp =~ "Determining installation type"
|
||||
end
|
||||
end
|
||||
|
||||
describe "LiveView" do
|
||||
test "mounts and detects installation type", %{conn: conn, site: site} do
|
||||
stub_fetch_body(200, "wp-content")
|
||||
|
||||
{lv, _} = get_lv(conn, site)
|
||||
|
||||
assert eventually(fn ->
|
||||
html = render(lv)
|
||||
|
||||
{
|
||||
text(html) =~ "Install WordPress",
|
||||
html
|
||||
}
|
||||
end)
|
||||
|
||||
_ = render(lv)
|
||||
end
|
||||
|
||||
@tag :slow
|
||||
test "mounts and does not detect installation type, if it's provided", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
stub_fetch_body(200, "wp-content")
|
||||
|
||||
{lv, _} = get_lv(conn, site, "?installation_type=GTM")
|
||||
|
||||
refute eventually(fn ->
|
||||
html = render(lv)
|
||||
|
||||
{
|
||||
text(html) =~ "Install WordPress",
|
||||
html
|
||||
}
|
||||
end)
|
||||
|
||||
_ = render(lv)
|
||||
end
|
||||
|
||||
test "allows manual snippet customization", %{conn: conn, site: site} do
|
||||
{lv, html} = get_lv(conn, site, "?installation_type=manual")
|
||||
|
||||
assert text_of_element(html, "textarea#snippet") ==
|
||||
"&lt;script defer data-domain=&quot;#{site.domain}&quot; src=&quot;http://localhost:8000/js/script.js&quot;&gt;&lt;/script&gt;"
|
||||
|
||||
for param <- PlausibleWeb.Live.Installation.script_extension_params() do
|
||||
lv
|
||||
|> element(~s|form#snippet-form|)
|
||||
|> render_change(%{
|
||||
param => "on"
|
||||
})
|
||||
|
||||
html = lv |> render()
|
||||
assert text_of_element(html, "textarea#snippet") =~ "/js/script.#{param}.js"
|
||||
|
||||
lv
|
||||
|> element(~s|form#snippet-form|)
|
||||
|> render_change(%{})
|
||||
|
||||
html = lv |> render()
|
||||
assert text_of_element(html, "textarea#snippet") =~ "/js/script.js"
|
||||
|
||||
assert html =~ "Snippet updated"
|
||||
end
|
||||
end
|
||||
|
||||
test "allows GTM snippet customization", %{conn: conn, site: site} do
|
||||
{lv, html} = get_lv(conn, site, "?installation_type=GTM")
|
||||
|
||||
assert text_of_element(html, "textarea#snippet") =~ "script.defer = true"
|
||||
|
||||
for param <- PlausibleWeb.Live.Installation.script_extension_params() do
|
||||
lv
|
||||
|> element(~s|form#snippet-form|)
|
||||
|> render_change(%{
|
||||
param => "on"
|
||||
})
|
||||
|
||||
html = lv |> render()
|
||||
assert text_of_element(html, "textarea#snippet") =~ "/js/script.#{param}.js"
|
||||
|
||||
lv
|
||||
|> element(~s|form#snippet-form|)
|
||||
|> render_change(%{})
|
||||
|
||||
html = lv |> render()
|
||||
assert text_of_element(html, "textarea#snippet") =~ "/js/script.js"
|
||||
|
||||
assert html =~ "Snippet updated"
|
||||
end
|
||||
end
|
||||
|
||||
test "allows manual snippet customization with 404 links", %{conn: conn, site: site} do
|
||||
{lv, _html} = get_lv(conn, site, "?installation_type=manual")
|
||||
|
||||
lv
|
||||
|> element(~s|form#snippet-form|)
|
||||
|> render_change(%{
|
||||
"404" => "on"
|
||||
})
|
||||
|
||||
html = lv |> render()
|
||||
|
||||
assert text_of_element(html, "textarea#snippet") =~
|
||||
"function() { (window.plausible.q = window.plausible.q || []).push(arguments) }&lt;/script&gt;"
|
||||
|
||||
lv
|
||||
|> element(~s|form#snippet-form|)
|
||||
|> render_change(%{})
|
||||
|
||||
html = lv |> render()
|
||||
|
||||
refute text_of_element(html, "textarea#snippet") =~
|
||||
"function() { (window.plausible.q = window.plausible.q || []).push(arguments) }&lt;/script&gt;"
|
||||
end
|
||||
|
||||
test "turning on file-downloads, outbound-links and 404 creates special goals", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
{lv, _html} = get_lv(conn, site, "?installation_type=manual")
|
||||
|
||||
assert Plausible.Goals.for_site(site) == []
|
||||
|
||||
lv
|
||||
|> element(~s|form#snippet-form|)
|
||||
|> render_change(%{
|
||||
"file-downloads" => "on",
|
||||
"outbound-links" => "on",
|
||||
"404" => "on"
|
||||
})
|
||||
|
||||
lv |> render()
|
||||
|
||||
assert [clicks, downloads, error_404] = Plausible.Goals.for_site(site)
|
||||
assert clicks.event_name == "Outbound Link: Click"
|
||||
assert downloads.event_name == "File Download"
|
||||
assert error_404.event_name == "404"
|
||||
end
|
||||
|
||||
test "turning off file-downloads, outbound-links and 404 deletes special goals", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
{lv, _html} = get_lv(conn, site, "?installation_type=manual")
|
||||
|
||||
assert Plausible.Goals.for_site(site) == []
|
||||
|
||||
lv
|
||||
|> element(~s|form#snippet-form|)
|
||||
|> render_change(%{
|
||||
"file-downloads" => "on",
|
||||
"outbound-links" => "on",
|
||||
"404" => "on"
|
||||
})
|
||||
|
||||
assert [_, _, _] = Plausible.Goals.for_site(site)
|
||||
|
||||
lv
|
||||
|> element(~s|form#snippet-form|)
|
||||
|> render_change(%{
|
||||
"file-downloads" => "on",
|
||||
"outbound-links" => "on"
|
||||
})
|
||||
|
||||
assert render(lv) =~ "Snippet updated and goal deleted"
|
||||
|
||||
lv
|
||||
|> element(~s|form#snippet-form|)
|
||||
|> render_change(%{
|
||||
"file-downloads" => "on"
|
||||
})
|
||||
|
||||
assert render(lv) =~ "Snippet updated and goal deleted"
|
||||
|
||||
lv
|
||||
|> element(~s|form#snippet-form|)
|
||||
|> render_change(%{})
|
||||
|
||||
assert render(lv) =~ "Snippet updated and goal deleted"
|
||||
|
||||
assert [] = Plausible.Goals.for_site(site)
|
||||
end
|
||||
|
||||
test "turning off remaining checkboxes doesn't render goal deleted flash", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
{lv, _html} = get_lv(conn, site, "?installation_type=manual")
|
||||
|
||||
lv
|
||||
|> element(~s|form#snippet-form|)
|
||||
|> render_change(%{
|
||||
"tagged-events" => "on",
|
||||
"hash" => "on",
|
||||
"pageview-props" => "on",
|
||||
"revenue" => "on"
|
||||
})
|
||||
|
||||
assert render(lv) =~ "Snippet updated. Please insert the newest snippet into your site"
|
||||
|
||||
lv
|
||||
|> element(~s|form#snippet-form|)
|
||||
|> render_change(%{})
|
||||
|
||||
assert render(lv) =~ "Snippet updated. Please insert the newest snippet into your site"
|
||||
end
|
||||
end
|
||||
|
||||
defp stub_fetch_body(f) when is_function(f, 1) do
|
||||
Req.Test.stub(Plausible.Verification.Checks.FetchBody, f)
|
||||
end
|
||||
|
||||
defp stub_fetch_body(status, body) do
|
||||
stub_fetch_body(fn conn ->
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_resp(status, body)
|
||||
end)
|
||||
end
|
||||
|
||||
defp get_lv(conn, site, qs \\ nil) do
|
||||
{:ok, lv, html} = live(conn, "/#{site.domain}/installation#{qs}")
|
||||
|
||||
{lv, html}
|
||||
end
|
||||
end
|
||||
|
|
@ -9,7 +9,7 @@ defmodule PlausibleWeb.Live.PluginsAPISettingsTest do
|
|||
setup [:create_user, :log_in, :create_site]
|
||||
|
||||
test "does not display the Plugins API section by default", %{conn: conn, site: site} do
|
||||
conn = get(conn, "/#{site.domain}/integrations")
|
||||
conn = get(conn, "/#{site.domain}/settings/integrations")
|
||||
resp = html_response(conn, 200)
|
||||
|
||||
refute resp =~ "Plugin Tokens"
|
||||
|
|
|
|||
|
|
@ -6,46 +6,33 @@ defmodule PlausibleWeb.Live.VerificationTest do
|
|||
|
||||
setup [:create_user, :log_in, :create_site]
|
||||
|
||||
@verify_button ~s|button#launch-verification-button[phx-click="launch-verification"]|
|
||||
@verification_modal ~s|div#verification-modal|
|
||||
# @verify_button ~s|button#launch-verification-button[phx-click="launch-verification"]|
|
||||
@retry_button ~s|a[phx-click="retry"]|
|
||||
@go_to_dashboard_button ~s|a[href$="?skip_to_dashboard=true"]|
|
||||
# @go_to_dashboard_button ~s|a[href$="?skip_to_dashboard=true"]|
|
||||
@progress ~s|#progress-indicator p#progress|
|
||||
@heading ~s|#progress-indicator h3|
|
||||
|
||||
describe "GET /:domain" do
|
||||
test "static verification screen renders", %{conn: conn, site: site} do
|
||||
resp = conn |> no_slowdown() |> get("/#{site.domain}") |> html_response(200)
|
||||
resp =
|
||||
get(conn, conn |> no_slowdown() |> get("/#{site.domain}") |> redirected_to)
|
||||
|> html_response(200)
|
||||
|
||||
assert text_of_element(resp, @progress) =~
|
||||
"We're visiting your site to ensure that everything is working"
|
||||
|
||||
assert resp =~ "Verifying your integration"
|
||||
assert resp =~ "Need to see the snippet again?"
|
||||
assert resp =~ "Run verification later and go to Site Settings?"
|
||||
refute resp =~ "modal"
|
||||
refute element_exists?(resp, @verification_modal)
|
||||
assert resp =~ "Verifying your installation"
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /settings/general" do
|
||||
test "verification elements render under the snippet", %{conn: conn, site: site} do
|
||||
resp =
|
||||
conn |> no_slowdown() |> get("/#{site.domain}/settings/general") |> html_response(200)
|
||||
|
||||
assert element_exists?(resp, @verify_button)
|
||||
assert element_exists?(resp, @verification_modal)
|
||||
end
|
||||
end
|
||||
|
||||
describe "LiveView: standalone" do
|
||||
describe "LiveView" do
|
||||
test "LiveView mounts", %{conn: conn, site: site} do
|
||||
stub_fetch_body(200, "")
|
||||
stub_installation()
|
||||
|
||||
{_, html} = get_lv_standalone(conn, site)
|
||||
{_, html} = get_lv(conn, site)
|
||||
|
||||
assert html =~ "Verifying your integration"
|
||||
assert html =~ "Verifying your installation"
|
||||
|
||||
assert text_of_element(html, @progress) =~
|
||||
"We're visiting your site to ensure that everything is working"
|
||||
|
|
@ -55,7 +42,7 @@ defmodule PlausibleWeb.Live.VerificationTest do
|
|||
stub_fetch_body(200, source(site.domain))
|
||||
stub_installation()
|
||||
|
||||
{:ok, lv} = kick_off_live_verification_standalone(conn, site)
|
||||
{:ok, lv} = kick_off_live_verification(conn, site)
|
||||
|
||||
assert eventually(fn ->
|
||||
html = render(lv)
|
||||
|
|
@ -69,14 +56,61 @@ defmodule PlausibleWeb.Live.VerificationTest do
|
|||
|
||||
html = render(lv)
|
||||
assert html =~ "Success!"
|
||||
assert html =~ "Your integration is working"
|
||||
assert html =~ "Awaiting your first pageview"
|
||||
end
|
||||
|
||||
test "won't await first pageview if site has pageviews", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview)
|
||||
])
|
||||
|
||||
stub_fetch_body(200, source(site.domain))
|
||||
stub_installation()
|
||||
|
||||
{:ok, lv} = kick_off_live_verification(conn, site)
|
||||
|
||||
assert eventually(fn ->
|
||||
html = render(lv)
|
||||
|
||||
{
|
||||
text(html) =~ "Success",
|
||||
html
|
||||
}
|
||||
end)
|
||||
|
||||
html = render(lv)
|
||||
|
||||
refute text_of_element(html, @progress) =~ "Awaiting your first pageview"
|
||||
refute_redirected(lv, "http://localhost:8000/#{URI.encode_www_form(site.domain)}")
|
||||
end
|
||||
|
||||
test "will redirect when first pageview arrives", %{conn: conn, site: site} do
|
||||
stub_fetch_body(200, source(site.domain))
|
||||
stub_installation()
|
||||
|
||||
{:ok, lv} = kick_off_live_verification(conn, site)
|
||||
|
||||
assert eventually(fn ->
|
||||
html = render(lv)
|
||||
|
||||
{
|
||||
text(html) =~ "Awaiting",
|
||||
html
|
||||
}
|
||||
end)
|
||||
|
||||
populate_stats(site, [
|
||||
build(:pageview)
|
||||
])
|
||||
|
||||
assert_redirect(lv, "http://localhost:8000/#{URI.encode_www_form(site.domain)}/")
|
||||
end
|
||||
|
||||
test "eventually fails to verify installation", %{conn: conn, site: site} do
|
||||
stub_fetch_body(200, "")
|
||||
stub_installation(200, plausible_installed(false))
|
||||
|
||||
{:ok, lv} = kick_off_live_verification_standalone(conn, site)
|
||||
{:ok, lv} = kick_off_live_verification(conn, site)
|
||||
|
||||
assert html =
|
||||
eventually(fn ->
|
||||
|
|
@ -90,133 +124,39 @@ defmodule PlausibleWeb.Live.VerificationTest do
|
|||
}
|
||||
end)
|
||||
|
||||
refute element_exists?(html, @verification_modal)
|
||||
assert element_exists?(html, @retry_button)
|
||||
|
||||
assert html =~ "Please insert the snippet into your site"
|
||||
end
|
||||
end
|
||||
|
||||
describe "LiveView: modal" do
|
||||
test "LiveView mounts", %{conn: conn, site: site} do
|
||||
stub_fetch_body(200, "")
|
||||
stub_installation()
|
||||
defp get_lv(conn, site) do
|
||||
{:ok, lv, html} = conn |> no_slowdown() |> live("/#{site.domain}/verification")
|
||||
|
||||
{_, html} = get_lv_modal(conn, site)
|
||||
|
||||
text = text(html)
|
||||
|
||||
refute text =~ "Need to see the snippet again?"
|
||||
refute text =~ "Run verification later and go to Site Settings?"
|
||||
assert element_exists?(html, @verification_modal)
|
||||
end
|
||||
|
||||
test "Clicking the Verify modal launches verification", %{conn: conn, site: site} do
|
||||
stub_fetch_body(200, source(site.domain))
|
||||
stub_installation()
|
||||
|
||||
{lv, html} = get_lv_modal(conn, site)
|
||||
|
||||
assert element_exists?(html, @verification_modal)
|
||||
assert element_exists?(html, @verify_button)
|
||||
assert text_of_attr(html, @verify_button, "x-on:click") =~ "open-modal"
|
||||
|
||||
assert text_of_element(html, @progress) =~
|
||||
"We're visiting your site to ensure that everything is working"
|
||||
|
||||
lv |> element(@verify_button) |> render_click()
|
||||
|
||||
assert html =
|
||||
eventually(fn ->
|
||||
html = render(lv)
|
||||
|
||||
{
|
||||
html =~ "Success!",
|
||||
html
|
||||
}
|
||||
end)
|
||||
|
||||
refute html =~ "Awaiting your first pageview"
|
||||
assert element_exists?(html, @go_to_dashboard_button)
|
||||
end
|
||||
|
||||
test "query launch_verification=true launches the modal", %{conn: conn, site: site} do
|
||||
stub_fetch_body(200, source(site.domain))
|
||||
stub_installation()
|
||||
|
||||
{lv, _html} = get_lv_modal(conn, site, "?launch_verification=true")
|
||||
|
||||
assert html =
|
||||
eventually(fn ->
|
||||
html = render(lv)
|
||||
|
||||
{
|
||||
html =~ "Success!",
|
||||
html
|
||||
}
|
||||
end)
|
||||
|
||||
refute html =~ "Awaiting your first pageview"
|
||||
assert element_exists?(html, @go_to_dashboard_button)
|
||||
end
|
||||
|
||||
test "failed verification can be retried", %{conn: conn, site: site} do
|
||||
stub_fetch_body(200, "")
|
||||
stub_installation(200, plausible_installed(false))
|
||||
|
||||
{lv, _html} = get_lv_modal(conn, site)
|
||||
|
||||
lv |> element(@verify_button) |> render_click()
|
||||
|
||||
assert html =
|
||||
eventually(fn ->
|
||||
html = render(lv)
|
||||
|
||||
{text_of_element(html, @heading) =~
|
||||
"We couldn't find the Plausible snippet", html}
|
||||
end)
|
||||
|
||||
assert element_exists?(html, @retry_button)
|
||||
|
||||
stub_fetch_body(200, source(site.domain))
|
||||
stub_installation()
|
||||
|
||||
lv |> element(@retry_button) |> render_click()
|
||||
|
||||
assert eventually(fn ->
|
||||
html = render(lv)
|
||||
{html =~ "Success!", html}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_lv_standalone(conn, site) do
|
||||
conn = conn |> no_slowdown() |> assign(:live_module, PlausibleWeb.Live.Verification)
|
||||
{:ok, lv, html} = live(conn, "/#{site.domain}")
|
||||
{lv, html}
|
||||
end
|
||||
|
||||
defp get_lv_modal(conn, site, query_string \\ "") do
|
||||
conn = conn |> no_slowdown() |> assign(:live_module, PlausibleWeb.Live.Verification)
|
||||
{:ok, lv, html} = live(no_slowdown(conn), "/#{site.domain}/settings/general#{query_string}")
|
||||
{lv, html}
|
||||
end
|
||||
|
||||
defp kick_off_live_verification_standalone(conn, site) do
|
||||
{:ok, lv, _} =
|
||||
live_isolated(conn, PlausibleWeb.Live.Verification,
|
||||
session: %{
|
||||
"domain" => site.domain,
|
||||
"delay" => 0,
|
||||
"slowdown" => 0
|
||||
}
|
||||
)
|
||||
defp kick_off_live_verification(conn, site) do
|
||||
{:ok, lv, _html} = conn |> no_slowdown() |> no_delay() |> live("/#{site.domain}/verification")
|
||||
|
||||
# {:ok, lv, _} =
|
||||
# live_isolated(conn, PlausibleWeb.Live.Verification,
|
||||
# session: %{
|
||||
# "domain" => site.domain,
|
||||
# "delay" => 0,
|
||||
# "slowdown" => 0
|
||||
# }
|
||||
# )
|
||||
#
|
||||
{:ok, lv}
|
||||
end
|
||||
|
||||
defp no_slowdown(conn) do
|
||||
Plug.Conn.put_private(conn, :verification_slowdown, 0)
|
||||
Plug.Conn.put_private(conn, :slowdown, 0)
|
||||
end
|
||||
|
||||
defp no_delay(conn) do
|
||||
Plug.Conn.put_private(conn, :delay, 0)
|
||||
end
|
||||
|
||||
defp stub_fetch_body(f) when is_function(f, 1) do
|
||||
|
|
|
|||
|
|
@ -139,7 +139,9 @@ defmodule Plausible.Workers.TrafficChangeNotifierTest do
|
|||
|
||||
TrafficChangeNotifier.perform(nil, clickhouse_stub)
|
||||
|
||||
assert_email_delivered_with(html_body: ~r|http://localhost:8000/example.com/settings|)
|
||||
assert_email_delivered_with(
|
||||
html_body: ~r|http://localhost:8000/example.com/installation\?flow=review|
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue