diff --git a/extra/lib/plausible_web/live/funnel_settings.ex b/extra/lib/plausible_web/live/funnel_settings.ex index 99ea3189db75..85ed95994f2a 100644 --- a/extra/lib/plausible_web/live/funnel_settings.ex +++ b/extra/lib/plausible_web/live/funnel_settings.ex @@ -50,40 +50,61 @@ defmodule PlausibleWeb.Live.FunnelSettings do
<.flash_messages flash={@flash} /> - <%= if @setup_funnel? do %> - {live_render( - @socket, - PlausibleWeb.Live.FunnelSettings.Form, - id: "funnels-form", - session: %{ - "domain" => @domain, - "funnel_id" => @funnel_id - } - )} - <% end %> -
= Funnel.min_steps()}> - <.live_component - module={PlausibleWeb.Live.FunnelSettings.List} - id="funnels-list" - funnels={@displayed_funnels} - filter_text={@filter_text} - /> -
- -
-

- Ready to dig into user flows? -

-

- Set up a few goals first (e.g. "Signup", "Visit /", or "Scroll 50% on /blog/*") and return here to build your first funnel! -

- <.button_link - class="mb-2" - href={PlausibleWeb.Router.Helpers.site_path(@socket, :settings_goals, @domain)} + <.tile + docs="funnel-analysis" + feature_mod={Plausible.Billing.Feature.Funnels} + feature_toggle?={true} + show_content?={!Plausible.Billing.Feature.Funnels.opted_out?(@site)} + site={@site} + current_user={@current_user} + current_team={@current_team} + > + <:title> + Funnels + + <:subtitle :if={Enum.count(@all_funnels) > 0}> + Compose goals into funnels to track user flows and conversion rates. + + <%= if @setup_funnel? do %> + {live_render( + @socket, + PlausibleWeb.Live.FunnelSettings.Form, + id: "funnels-form", + session: %{ + "domain" => @domain, + "funnel_id" => @funnel_id + } + )} + <% end %> +
= Funnel.min_steps()}> + <.live_component + module={PlausibleWeb.Live.FunnelSettings.List} + id="funnels-list" + funnels={@displayed_funnels} + filter_text={@filter_text} + /> +
+ +
- Set up goals → - -
+

+ Ready to dig into user flows? +

+

+ Set up a few goals like <.highlighted>Signup, <.highlighted>Visit /, or + <.highlighted>Scroll 50% on /blog/* + first, then return here to build your first funnel. +

+ <.button_link + class="mt-4" + href={PlausibleWeb.Router.Helpers.site_path(@socket, :settings_goals, @domain)} + > + Set up goals → + +
+
""" end @@ -147,4 +168,8 @@ defmodule PlausibleWeb.Live.FunnelSettings do def handle_info(:cancel_setup_funnel, socket) do {:noreply, assign(socket, setup_funnel?: false, funnel_id: nil)} end + + def handle_info({:feature_toggled, flash_msg, updated_site}, socket) do + {:noreply, assign(put_flash(socket, :success, flash_msg), site: updated_site)} + end end diff --git a/extra/lib/plausible_web/live/funnel_settings/list.ex b/extra/lib/plausible_web/live/funnel_settings/list.ex index 008756911647..e4c4e642554f 100644 --- a/extra/lib/plausible_web/live/funnel_settings/list.ex +++ b/extra/lib/plausible_web/live/funnel_settings/list.ex @@ -10,13 +10,17 @@ defmodule PlausibleWeb.Live.FunnelSettings.List do use PlausibleWeb, :live_component def render(assigns) do + assigns = assign(assigns, :searching?, String.trim(assigns.filter_text) != "") + ~H"""
- <.filter_bar filter_text={@filter_text} placeholder="Search Funnels"> - <.button id="add-funnel-button" phx-click="add-funnel" mt?={false}> - Add funnel - - + <%= if @searching? or Enum.count(@funnels) > 0 do %> + <.filter_bar filter_text={@filter_text} placeholder="Search Funnels"> + <.button id="add-funnel-button" phx-click="add-funnel" mt?={false}> + Add funnel + + + <% end %> <%= if Enum.count(@funnels) > 0 do %> <.table rows={@funnels}> @@ -42,19 +46,44 @@ defmodule PlausibleWeb.Live.FunnelSettings.List do <% else %> -

- - No funnels found for this site. Please refine or - <.styled_link phx-click="reset-filter-text" id="reset-filter-hint"> - reset your search. - - - - No funnels configured for this site. - -

+ <.no_search_results :if={@searching?} /> + <.empty_state :if={not @searching?} /> <% end %>
""" end + + defp no_search_results(assigns) do + ~H""" +

+ No funnels found for this site. Please refine or + <.styled_link phx-click="reset-filter-text" id="reset-filter-hint"> + reset your search. + +

+ """ + end + + defp empty_state(assigns) do + ~H""" +
+

+ Create your first funnel +

+

+ Compose goals into funnels to track user flows and conversion rates. + <.styled_link href="https://plausible.io/docs/funnel-analysis" target="_blank"> + Learn more + +

+ <.button + id="add-funnel-button" + phx-click="add-funnel" + class="mt-4" + > + Add funnel + +
+ """ + end end diff --git a/lib/plausible_web/components/billing/billing.ex b/lib/plausible_web/components/billing/billing.ex index db45f3472ce5..a55c80d19309 100644 --- a/lib/plausible_web/components/billing/billing.ex +++ b/lib/plausible_web/components/billing/billing.ex @@ -7,7 +7,8 @@ defmodule PlausibleWeb.Components.Billing do require Plausible.Billing.Subscription.Status alias Plausible.Billing.{Subscription, Subscriptions, Plan, Plans, EnterprisePlan} - attr :current_role, :atom, required: true + attr :site, Plausible.Site, required: false, default: nil + attr :current_user, Plausible.Auth.User, required: true attr :current_team, :any, required: true attr :locked?, :boolean, required: true slot :inner_block, required: true @@ -35,7 +36,7 @@ defmodule PlausibleWeb.Components.Billing do class="max-w-sm sm:max-w-md mb-2 text-sm text-gray-600 dark:text-gray-100/60 leading-normal text-center" > To access this feature, - <.upgrade_call_to_action current_role={@current_role} current_team={@current_team} /> + <.upgrade_call_to_action current_user={@current_user} current_team={@current_team} /> @@ -357,8 +358,23 @@ defmodule PlausibleWeb.Components.Billing do defp change_plan_or_upgrade_text(_subscription), do: "Change plan" def upgrade_call_to_action(assigns) do + user = assigns.current_user + site = assigns[:site] team = Plausible.Teams.with_subscription(assigns.current_team) + current_role = + if site do + case Plausible.Teams.Memberships.site_role(site, user) do + {:ok, {_, site_role}} -> site_role + _ -> nil + end + else + if team do + {:ok, team_role} = Plausible.Teams.Memberships.team_role(team, user) + team_role + end + end + upgrade_assistance_required? = case Plans.get_subscription_plan(team && team.subscription) do %Plan{kind: :business} -> true @@ -367,7 +383,7 @@ defmodule PlausibleWeb.Components.Billing do end cond do - not is_nil(assigns.current_role) and assigns.current_role not in [:owner, :billing] -> + not is_nil(current_role) and current_role not in [:owner, :billing] -> ~H"ask your team owner to upgrade their subscription." upgrade_assistance_required? -> diff --git a/lib/plausible_web/components/billing/notice.ex b/lib/plausible_web/components/billing/notice.ex index 090795add3e4..491eaff93058 100644 --- a/lib/plausible_web/components/billing/notice.ex +++ b/lib/plausible_web/components/billing/notice.ex @@ -64,7 +64,7 @@ defmodule PlausibleWeb.Components.Billing.Notice do end attr(:current_team, :any, required: true) - attr(:current_role, :atom, required: true) + attr(:current_user, :atom, required: true) attr(:limit, :integer, required: true) attr(:resource, :string, required: true) attr(:rest, :global) @@ -75,7 +75,7 @@ defmodule PlausibleWeb.Components.Billing.Notice do {account_label(@current_team)} is limited to {pretty_print_resource_limit(@limit, @resource)}. To increase this limit, . """ diff --git a/lib/plausible_web/components/generic.ex b/lib/plausible_web/components/generic.ex index f781b3755aa0..5c3396413381 100644 --- a/lib/plausible_web/components/generic.ex +++ b/lib/plausible_web/components/generic.ex @@ -485,10 +485,11 @@ defmodule PlausibleWeb.Components.Generic do slot :subtitle, required: false attr :feature_mod, :atom, default: nil attr :feature_toggle?, :boolean, default: false - attr :current_role, :atom, default: nil attr :current_team, :any, default: nil - attr :site, :any - attr :conn, :any + attr :current_user, :any, default: nil + attr :site, :any, default: nil + attr :conn, :any, default: nil + attr :show_content?, :boolean, default: true def tile(assigns) do ~H""" @@ -502,20 +503,24 @@ defmodule PlausibleWeb.Components.Generic do
{render_slot(@subtitle)}
- -
-
+
+
<%= if @feature_mod do %>
{render_slot(@inner_block)} @@ -1125,6 +1130,16 @@ defmodule PlausibleWeb.Components.Generic do """ end + slot :inner_block, required: true + + def highlighted(assigns) do + ~H""" + + {render_slot(@inner_block)} + + """ + end + def settings_badge(%{type: :new} = assigns) do ~H""" diff --git a/lib/plausible_web/components/site/feature.ex b/lib/plausible_web/components/site/feature.ex deleted file mode 100644 index 4976f9f93cec..000000000000 --- a/lib/plausible_web/components/site/feature.ex +++ /dev/null @@ -1,44 +0,0 @@ -defmodule PlausibleWeb.Components.Site.Feature do - @moduledoc """ - Phoenix Component for rendering a user-facing feature toggle - capable of flipping booleans in `Plausible.Site` via the `toggle_feature` controller action. - """ - use PlausibleWeb, :view - - attr(:site, Plausible.Site, required: true) - attr(:feature_mod, :atom, required: true, values: Plausible.Billing.Feature.list()) - attr(:conn, Plug.Conn, required: true) - attr(:class, :any, default: nil) - slot(:inner_block) - - def toggle(assigns) do - assigns = - assigns - |> assign(:current_setting, assigns.feature_mod.enabled?(assigns.site)) - |> assign(:disabled?, assigns.feature_mod.check_availability(assigns.site.team) !== :ok) - - ~H""" -
- <.form - action={target(@site, @feature_mod.toggle_field(), @conn, !@current_setting)} - method="put" - for={nil} - class={@class} - > - <.toggle_submit set_to={@current_setting} disabled?={@disabled?}> - Show {String.downcase(@feature_mod.display_name())} in the dashboard - - - -
- {render_slot(@inner_block)} -
-
- """ - end - - def target(site, setting, conn, set_to) when is_boolean(set_to) do - r = conn.request_path - Routes.site_path(conn, :update_feature_visibility, site.domain, setting, r: r, set: set_to) - end -end diff --git a/lib/plausible_web/components/site/toggle_live.ex b/lib/plausible_web/components/site/toggle_live.ex new file mode 100644 index 000000000000..2ede2f90c01b --- /dev/null +++ b/lib/plausible_web/components/site/toggle_live.ex @@ -0,0 +1,90 @@ +defmodule PlausibleWeb.Components.Site.Feature.ToggleLive do + @moduledoc """ + LiveComponent for rendering a user-facing feature toggle in LiveView contexts. + Instead of using form submission, this component messages itself to handle toggles. + """ + use PlausibleWeb, :live_component + + def update(assigns, socket) do + site = Plausible.Repo.preload(assigns.site, :team) + current_setting = assigns.feature_mod.enabled?(site) + disabled? = assigns.feature_mod.check_availability(site.team) !== :ok + + {:ok, + socket + |> assign(assigns) + |> assign(:site, site) + |> assign(:current_setting, current_setting) + |> assign(:disabled?, disabled?)} + end + + attr :site, Plausible.Site, required: true + attr :feature_mod, :atom, required: true + attr :current_user, Plausible.Auth.User, required: true + + def render(assigns) do + ~H""" +
+
+ + + + Show in dashboard + +
+
+ """ + end + + def handle_event("toggle", _params, socket) do + site = socket.assigns.site + feature_mod = socket.assigns.feature_mod + current_user = socket.assigns.current_user + + case feature_mod.toggle(site, current_user) do + {:ok, updated_site} -> + new_setting = Map.fetch!(updated_site, feature_mod.toggle_field()) + + message = + if new_setting do + "#{feature_mod.display_name()} are now visible again on your dashboard" + else + "#{feature_mod.display_name()} are now hidden from your dashboard" + end + + send(self(), {:feature_toggled, message, updated_site}) + + socket = + assign(socket, site: updated_site, current_setting: feature_mod.enabled?(updated_site)) + + {:noreply, socket} + + {:error, _} -> + {:noreply, socket} + end + end +end diff --git a/lib/plausible_web/controllers/site_controller.ex b/lib/plausible_web/controllers/site_controller.ex index 8f9e1f67f2be..c5909bbbc2ea 100644 --- a/lib/plausible_web/controllers/site_controller.ex +++ b/lib/plausible_web/controllers/site_controller.ex @@ -91,42 +91,6 @@ defmodule PlausibleWeb.SiteController do end end - def update_feature_visibility(conn, %{ - "setting" => setting, - "r" => "/" <> _ = redirect_path, - "set" => value - }) - when setting in ~w[conversions_enabled funnels_enabled props_enabled] and - value in ["true", "false"] do - site = conn.assigns[:site] - toggle_field = String.to_existing_atom(setting) - - feature_mod = - Enum.find(Plausible.Billing.Feature.list(), &(&1.toggle_field() == toggle_field)) - - case feature_mod.toggle(site, conn.assigns.current_user, override: value == "true") do - {:ok, updated_site} -> - message = - if Map.fetch!(updated_site, toggle_field) do - "#{feature_mod.display_name()} are now visible again on your dashboard" - else - "#{feature_mod.display_name()} are now hidden from your dashboard" - end - - conn - |> put_flash(:success, message) - |> redirect(to: redirect_path) - - {:error, _} -> - conn - |> put_flash( - :error, - "Something went wrong. Failed to toggle #{feature_mod.display_name()} on your dashboard." - ) - |> redirect(to: redirect_path) - end - end - def settings(conn, %{"domain" => domain}) do redirect(conn, to: Routes.site_path(conn, :settings_general, domain)) end diff --git a/lib/plausible_web/live/goal_settings.ex b/lib/plausible_web/live/goal_settings.ex index 91a8c5340af4..8e8212b3f1db 100644 --- a/lib/plausible_web/live/goal_settings.ex +++ b/lib/plausible_web/live/goal_settings.ex @@ -21,14 +21,6 @@ defmodule PlausibleWeb.Live.GoalSettings do include_consolidated?: true ) end) - |> assign_new(:site_role, fn %{site: site, current_user: current_user} -> - if Plausible.Auth.is_super_admin?(current_user) do - :super_admin - else - {:ok, {_, role}} = Plausible.Teams.Memberships.site_role(site, current_user) - role - end - end) |> assign_new(:all_goals, fn %{site: site} -> Goals.for_site(site, preload_funnels?: true) end) @@ -62,41 +54,63 @@ defmodule PlausibleWeb.Live.GoalSettings do
<.flash_messages flash={@flash} /> - <.live_component :let={modal_unique_id} module={Modal} preload?={false} id="goals-form-modal"> + <.tile + docs="goal-conversions" + feature_mod={Plausible.Billing.Feature.Goals} + feature_toggle?={true} + show_content?={!Plausible.Billing.Feature.Goals.opted_out?(@site)} + site={@site} + current_user={@current_user} + > + <:title> + Goals + + <:subtitle :if={Enum.count(@all_goals) > 0}> +

+ Define actions that you want your users to take, like visiting a certain page, submitting a form, etc. +

+

+ You can also + <.styled_link href={Routes.site_path(@socket, :settings_funnels, @domain)}> + compose goals into funnels. + +

+ + <.live_component :let={modal_unique_id} module={Modal} preload?={false} id="goals-form-modal"> + <.live_component + module={PlausibleWeb.Live.GoalSettings.Form} + id={"goals-form-#{modal_unique_id}"} + context_unique_id={modal_unique_id} + event_name_options={@event_name_options} + domain={@domain} + site={@site} + current_user={@current_user} + site_team={@site_team} + existing_goals={@all_goals} + goal={@form_goal} + on_save_goal={ + fn goal, socket -> + send(self(), {:goal_added, goal}) + Modal.close(socket, "goals-form-modal") + end + } + on_autoconfigure={ + fn socket -> + send(self(), :autoconfigure) + Modal.close(socket, "goals-form-modal") + end + } + /> + <.live_component - module={PlausibleWeb.Live.GoalSettings.Form} - id={"goals-form-#{modal_unique_id}"} - context_unique_id={modal_unique_id} - event_name_options={@event_name_options} + module={PlausibleWeb.Live.GoalSettings.List} + id="goals-list" + goals={@displayed_goals} domain={@domain} + filter_text={@filter_text} site={@site} - current_user={@current_user} - site_role={@site_role} - site_team={@site_team} - existing_goals={@all_goals} - goal={@form_goal} - on_save_goal={ - fn goal, socket -> - send(self(), {:goal_added, goal}) - Modal.close(socket, "goals-form-modal") - end - } - on_autoconfigure={ - fn socket -> - send(self(), :autoconfigure) - Modal.close(socket, "goals-form-modal") - end - } /> - - <.live_component - module={PlausibleWeb.Live.GoalSettings.List} - id="goals-list" - goals={@displayed_goals} - domain={@domain} - filter_text={@filter_text} - site={@site} - /> +
""" end @@ -194,4 +208,8 @@ defmodule PlausibleWeb.Live.GoalSettings do {:noreply, socket} end + + def handle_info({:feature_toggled, flash_msg, updated_site}, socket) do + {:noreply, assign(put_flash(socket, :success, flash_msg), site: updated_site)} + end end diff --git a/lib/plausible_web/live/goal_settings/form.ex b/lib/plausible_web/live/goal_settings/form.ex index 3abcaf53d695..1871bf0cc33a 100644 --- a/lib/plausible_web/live/goal_settings/form.ex +++ b/lib/plausible_web/live/goal_settings/form.ex @@ -35,7 +35,6 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do event_name_options_count: length(assigns.event_name_options), event_name_options: Enum.map(assigns.event_name_options, &{&1, &1}), current_user: assigns.current_user, - site_role: assigns.site_role, site_team: assigns.site_team, domain: assigns.domain, selected_tab: selected_tab, @@ -72,7 +71,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do :if={@selected_tab == "custom_events"} f={f} suffix={@context_unique_id} - site_role={@site_role} + current_user={@current_user} site_team={@site_team} site={@site} goal={@goal} @@ -122,7 +121,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do x-show="!tabSelectionInProgress" f={f} suffix={suffix(@context_unique_id, @tab_sequence_id)} - site_role={@site_role} + current_user={@current_user} site_team={@site_team} site={@site} existing_goals={@existing_goals} @@ -319,7 +318,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do attr(:f, Phoenix.HTML.Form) attr(:site, Plausible.Site) - attr(:site_role, :atom) + attr(:current_user, Plausible.Auth.User) attr(:site_team, Plausible.Teams.Team) attr(:suffix, :string) attr(:existing_goals, :list) @@ -382,7 +381,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do <.revenue_goal_settings f={@f} site={@site} - site_role={@site_role} + current_user={@current_user} site_team={@site_team} has_access_to_revenue_goals?={@has_access_to_revenue_goals?} goal={@goal} @@ -595,7 +594,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
To get access to this feature .
diff --git a/lib/plausible_web/live/goal_settings/list.ex b/lib/plausible_web/live/goal_settings/list.ex index 1153c959944d..f8edabcf6699 100644 --- a/lib/plausible_web/live/goal_settings/list.ex +++ b/lib/plausible_web/live/goal_settings/list.ex @@ -12,21 +12,27 @@ defmodule PlausibleWeb.Live.GoalSettings.List do def render(assigns) do revenue_goals_enabled? = Plausible.Billing.Feature.RevenueGoals.enabled?(assigns.site) - assigns = assign(assigns, revenue_goals_enabled?: revenue_goals_enabled?) + + assigns = + assigns + |> assign(:revenue_goals_enabled?, revenue_goals_enabled?) + |> assign(:searching?, String.trim(assigns.filter_text) != "") ~H"""
- <.filter_bar filter_text={@filter_text} placeholder="Search Goals"> - <.button - id="add-goal-button" - phx-click="add-goal" - mt?={false} - x-data - x-on:click={Modal.JS.preopen("goals-form-modal")} - > - Add goal - - + <%= if @searching? or Enum.count(@goals) > 0 do %> + <.filter_bar filter_text={@filter_text} placeholder="Search Goals"> + <.button + id="add-goal-button" + phx-click="add-goal" + mt?={false} + x-data + x-on:click={Modal.JS.preopen("goals-form-modal")} + > + Add goal + + + <% end %> <%= if Enum.count(@goals) > 0 do %> <.table rows={@goals}> @@ -89,22 +95,49 @@ defmodule PlausibleWeb.Live.GoalSettings.List do <% else %> -

- - No goals found for this site. Please refine or - <.styled_link phx-click="reset-filter-text" id="reset-filter-hint"> - reset your search. - - - - No goals configured for this site. - -

+ <.no_search_results :if={@searching?} /> + <.empty_state :if={not @searching?} /> <% end %>
""" end + defp no_search_results(assigns) do + ~H""" +

+ No goals found for this site. Please refine or + <.styled_link phx-click="reset-filter-text" id="reset-filter-hint"> + reset your search. + +

+ """ + end + + defp empty_state(assigns) do + ~H""" +
+

+ Create your first goal +

+

+ Define actions that you want your users to take, like visiting a certain page, submitting a form, etc. + <.styled_link href="https://plausible.io/docs/goal-conversions" target="_blank"> + Learn more + +

+ <.button + id="add-goal-button" + phx-click="add-goal" + x-data + x-on:click={Modal.JS.preopen("goals-form-modal")} + class="mt-4" + > + Add goal + +
+ """ + end + defp page_scroll_description(goal) do case pageview_description(goal) do "" -> "Scroll > #{goal.scroll_threshold}" diff --git a/lib/plausible_web/live/imports_exports_settings.ex b/lib/plausible_web/live/imports_exports_settings.ex index 1559cd9f133e..945e5b1bf72c 100644 --- a/lib/plausible_web/live/imports_exports_settings.ex +++ b/lib/plausible_web/live/imports_exports_settings.ex @@ -74,97 +74,134 @@ defmodule PlausibleWeb.Live.ImportsExportsSettings do {@import_warning} -
- <.button_link - theme="secondary" - href={Plausible.Google.API.import_authorize_url(@site.id)} - disabled={@import_in_progress? or @at_maximum?} - mt?={false} - > - Import from - Google Analytics import - - <.button_link - disabled={@import_in_progress? or @at_maximum?} - href={"/#{URI.encode_www_form(@site.domain)}/settings/import"} - mt?={false} - > - Import from CSV - -
- -

- There are no imports yet for this site. -

- -
- <.table :if={not Enum.empty?(@site_imports)} rows={@site_imports}> - <:thead> - <.th>Import - <.th hide_on_mobile>Date Range - <.th hide_on_mobile> -
Pageviews
- - <.th invisible>Actions - - - <:tbody :let={entry}> - <.td max_width="max-w-40"> -
-
- - <.spinner - :if={entry.live_status == SiteImport.importing()} - class="block size-5 text-indigo-600 dark:text-green-600" - /> - - -
-
- {Plausible.Imported.SiteImport.label(entry.site_import)} -
-
- - - <.td hide_on_mobile> - {format_date(entry.site_import.start_date)} - {format_date(entry.site_import.end_date)} - - - <.td> -
- {if entry.live_status == SiteImport.completed(), - do: - PlausibleWeb.StatsView.large_number_format( - pageview_count(entry.site_import, @pageview_counts) - )} -
- - <.td actions> - <.delete_button - href={"/#{URI.encode_www_form(@site.domain)}/settings/forget-import/#{entry.site_import.id}"} - method="delete" - data-confirm="Are you sure you want to delete this import?" + <.tile docs="google-analytics-import"> + <:title> + Import data + + <:subtitle :if={not Enum.empty?(@site_imports)}> + Import data from external sources. Up to {Plausible.Imported.max_complete_imports()} imports are allowed at a time. + + <%= if Enum.empty?(@site_imports) do %> +
+

+ Import your first data +

+

+ Import data from external sources. Up to {Plausible.Imported.max_complete_imports()} imports are allowed at a time. +

+
+ <.button_link + theme="secondary" + href={Plausible.Google.API.import_authorize_url(@site.id)} + disabled={@import_in_progress? or @at_maximum?} + mt?={false} + > + Import from + Google Analytics import + + <.button_link + disabled={@import_in_progress? or @at_maximum?} + href={"/#{URI.encode_www_form(@site.domain)}/settings/import"} + mt?={false} + > + Import from CSV + +
+
+ <% else %> +
+ <.button_link + theme="secondary" + href={Plausible.Google.API.import_authorize_url(@site.id)} + disabled={@import_in_progress? or @at_maximum?} + mt?={false} + > + Import from + Google Analytics import - - - -
+ + <.button_link + disabled={@import_in_progress? or @at_maximum?} + href={"/#{URI.encode_www_form(@site.domain)}/settings/import"} + mt?={false} + > + Import from CSV + +
+ +
+ <.table rows={@site_imports}> + <:thead> + <.th>Import + <.th hide_on_mobile>Date Range + <.th hide_on_mobile> +
Pageviews
+ + <.th invisible>Actions + + + <:tbody :let={entry}> + <.td max_width="max-w-40"> +
+
+ + <.spinner + :if={entry.live_status == SiteImport.importing()} + class="block size-5 text-indigo-600 dark:text-green-600" + /> + + +
+
+ {Plausible.Imported.SiteImport.label(entry.site_import)} +
+
+ + + <.td hide_on_mobile> + {format_date(entry.site_import.start_date)} - {format_date(entry.site_import.end_date)} + + + <.td> +
+ {if entry.live_status == SiteImport.completed(), + do: + PlausibleWeb.StatsView.large_number_format( + pageview_count(entry.site_import, @pageview_counts) + )} +
+ + <.td actions> + <.delete_button + href={"/#{URI.encode_www_form(@site.domain)}/settings/forget-import/#{entry.site_import.id}"} + method="delete" + data-confirm="Are you sure you want to delete this import?" + /> + + + +
+ <% end %> + """ end diff --git a/lib/plausible_web/live/plugins/api/settings.ex b/lib/plausible_web/live/plugins/api/settings.ex index 04659818fe3d..5f8af6a3cc97 100644 --- a/lib/plausible_web/live/plugins/api/settings.ex +++ b/lib/plausible_web/live/plugins/api/settings.ex @@ -50,14 +50,29 @@ defmodule PlausibleWeb.Live.Plugins.API.Settings do )} <% end %> -
+ <%= if Enum.empty?(@displayed_tokens) do %> +
+

+ Create your first plugin token +

+

+ Control plugin access by creating tokens for third-party integrations. +

+ <.button + phx-click="create-token" + class="mt-4" + > + New plugin token + +
+ <% else %> <.filter_bar filtering_enabled?={false}> <.button phx-click="create-token" mt?={false}> Create plugin token - <.table :if={not Enum.empty?(@displayed_tokens)} rows={@displayed_tokens}> + <.table rows={@displayed_tokens}> <:thead> <.th>Description <.th hide_on_mobile>Hint @@ -86,7 +101,7 @@ defmodule PlausibleWeb.Live.Plugins.API.Settings do -
+ <% end %>
""" end diff --git a/lib/plausible_web/live/props_settings.ex b/lib/plausible_web/live/props_settings.ex index 1a65a9ed78df..9377d2a45c5e 100644 --- a/lib/plausible_web/live/props_settings.ex +++ b/lib/plausible_web/live/props_settings.ex @@ -42,26 +42,43 @@ defmodule PlausibleWeb.Live.PropsSettings do ~H"""
<.flash_messages flash={@flash} /> - <%= if @add_prop? do %> - {live_render( - @socket, - PlausibleWeb.Live.PropsSettings.Form, - id: "props-form", - session: %{ - "domain" => @domain, - "site_id" => @site_id, - "rendered_by" => self() - } - )} - <% end %> - - <.live_component - module={PlausibleWeb.Live.PropsSettings.List} - id="props-list" - props={@displayed_props} - domain={@domain} - filter_text={@filter_text} - /> + <.tile + docs="custom-props/introduction" + feature_mod={Plausible.Billing.Feature.Props} + feature_toggle?={true} + show_content?={!Plausible.Billing.Feature.Props.opted_out?(@site)} + site={@site} + current_user={@current_user} + current_team={@current_team} + > + <:title> + Custom properties + + <:subtitle :if={Enum.count(@all_props) > 0}> + Attach custom properties when sending a pageview or an event to + create custom metrics. + + <%= if @add_prop? do %> + {live_render( + @socket, + PlausibleWeb.Live.PropsSettings.Form, + id: "props-form", + session: %{ + "domain" => @domain, + "site_id" => @site_id, + "rendered_by" => self() + } + )} + <% end %> + + <.live_component + module={PlausibleWeb.Live.PropsSettings.List} + id="props-list" + props={@displayed_props} + domain={@domain} + filter_text={@filter_text} + /> +
""" end @@ -131,6 +148,10 @@ defmodule PlausibleWeb.Live.PropsSettings do {:noreply, assign(socket, add_prop?: false)} end + def handle_info({:feature_toggled, flash_msg, updated_site}, socket) do + {:noreply, assign(put_flash(socket, :success, flash_msg), site: updated_site)} + end + def handle_info({:props_allowed, props}, socket) when is_list(props) do socket = socket diff --git a/lib/plausible_web/live/props_settings/list.ex b/lib/plausible_web/live/props_settings/list.ex index d146716fc12d..9192de9c5ca5 100644 --- a/lib/plausible_web/live/props_settings/list.ex +++ b/lib/plausible_web/live/props_settings/list.ex @@ -9,14 +9,19 @@ defmodule PlausibleWeb.Live.PropsSettings.List do attr(:filter_text, :string) def render(assigns) do + assigns = assign(assigns, :searching?, String.trim(assigns.filter_text) != "") + ~H"""
- <.filter_bar filter_text={@filter_text} placeholder="Search Properties"> - <.button phx-click="add-prop" mt?={false}> - Add property - - - <%= if is_list(@props) && length(@props) > 0 do %> + <%= if @searching? or Enum.count(@props) > 0 do %> + <.filter_bar filter_text={@filter_text} placeholder="Search Properties"> + <.button phx-click="add-prop" mt?={false}> + Add property + + + <% end %> + + <%= if Enum.count(@props) > 0 do %> <.table id="allowed-props" rows={Enum.with_index(@props)}> <:tbody :let={{prop, index}}> <.td id={"prop-#{index}"}>{prop} @@ -32,22 +37,47 @@ defmodule PlausibleWeb.Live.PropsSettings.List do <% else %> -

- - No properties found for this site. Please refine or - <.styled_link phx-click="reset-filter-text" id="reset-filter-hint"> - reset your search. - - - - No properties configured for this site. - -

+ <.no_search_results :if={@searching?} /> + <.empty_state :if={not @searching?} /> <% end %>
""" end + defp no_search_results(assigns) do + ~H""" +

+ No properties found for this site. Please refine or + <.styled_link phx-click="reset-filter-text" id="reset-filter-hint"> + reset your search. + +

+ """ + end + + defp empty_state(assigns) do + ~H""" +
+

+ Create a custom property +

+

+ Attach custom properties when sending a pageview or an event to create custom metrics. + <.styled_link href="https://plausible.io/docs/custom-props/introduction" target="_blank"> + Learn more + +

+ <.button + id="add-property-button" + phx-click="add-prop" + class="mt-4" + > + Add property + +
+ """ + end + defp delete_confirmation_text(prop) do """ Are you sure you want to remove the following property: diff --git a/lib/plausible_web/live/shared_link_settings.ex b/lib/plausible_web/live/shared_link_settings.ex index 3ebf4ea5c335..110d12bcef77 100644 --- a/lib/plausible_web/live/shared_link_settings.ex +++ b/lib/plausible_web/live/shared_link_settings.ex @@ -41,77 +41,111 @@ defmodule PlausibleWeb.Live.SharedLinkSettings do """ end diff --git a/lib/plausible_web/live/shields/country_rules.ex b/lib/plausible_web/live/shields/country_rules.ex index 4fcb753b74bb..067fafdd04de 100644 --- a/lib/plausible_web/live/shields/country_rules.ex +++ b/lib/plausible_web/live/shields/country_rules.ex @@ -29,76 +29,103 @@ defmodule PlausibleWeb.Live.Shields.CountryRules do ~H"""
<.settings_tiles> - <.tile docs="excluding"> + <.tile docs="excluding#exclude-visits-by-country"> <:title>Country block list - <:subtitle>Reject incoming traffic from specific countries. - <.filter_bar - :if={@country_rules_count < Shields.maximum_country_rules()} - filtering_enabled?={false} - > - <.button - id="add-country-rule" - x-data - x-on:click={Modal.JS.open("country-rule-form-modal")} - mt?={false} + <:subtitle :if={not Enum.empty?(@country_rules)}> + Reject incoming traffic from specific countries. + + + <%= if Enum.empty?(@country_rules) do %> +
+

+ Block a country +

+

+ Reject incoming traffic from specific countries. + <.styled_link + href="https://plausible.io/docs/excluding#exclude-visits-by-country" + target="_blank" + > + Learn more + +

+ <.button + :if={@country_rules_count < Shields.maximum_country_rules()} + id="add-country-rule" + x-data + x-on:click={Modal.JS.open("country-rule-form-modal")} + class="mt-4" + > + Add country + +
+ <% else %> + <.filter_bar + :if={@country_rules_count < Shields.maximum_country_rules()} + filtering_enabled?={false} > - Add country - - - - <.notice - :if={@country_rules_count >= Shields.maximum_country_rules()} - class="mt-4" - title="Maximum number of countries reached" - theme={:gray} - > -

- You've reached the maximum number of countries you can block ({Shields.maximum_country_rules()}). Please remove one before adding another. -

- - -

- No country rules configured for this site. -

- - <.table :if={not Enum.empty?(@country_rules)} rows={@country_rules}> - <:thead> - <.th>Country - <.th hide_on_mobile>Status - <.th invisible>Actions - - <:tbody :let={rule}> - <% country = Location.Country.get_country(rule.country_code) %> - <.td> -
- - {country.flag} {country.name} - -
- - <.td hide_on_mobile> - - Blocked - - - Allowed - - - <.td actions> - <.delete_button - id={"remove-country-rule-#{rule.id}"} - phx-target={@myself} - phx-click="remove-country-rule" - phx-value-rule-id={rule.id} - data-confirm="Are you sure you want to revoke this rule?" - /> - - - + <.button + id="add-country-rule" + x-data + x-on:click={Modal.JS.open("country-rule-form-modal")} + mt?={false} + > + Add country + + + + <.notice + :if={@country_rules_count >= Shields.maximum_country_rules()} + class="mt-4" + title="Maximum number of countries reached" + theme={:gray} + > +

+ You've reached the maximum number of countries you can block ({Shields.maximum_country_rules()}). Please remove one before adding another. +

+ + +
+ <.table rows={@country_rules}> + <:thead> + <.th>Country + <.th hide_on_mobile>Status + <.th invisible>Actions + + <:tbody :let={rule}> + <% country = Location.Country.get_country(rule.country_code) %> + <.td> +
+ + {country.flag} {country.name} + +
+ + <.td hide_on_mobile> + + Blocked + + + Allowed + + + <.td actions> + <.delete_button + id={"remove-country-rule-#{rule.id}"} + phx-target={@myself} + phx-click="remove-country-rule" + phx-value-rule-id={rule.id} + data-confirm="Are you sure you want to revoke this rule?" + /> + + + +
+ <% end %> <.live_component :let={modal_unique_id} module={Modal} id="country-rule-form-modal"> <.form diff --git a/lib/plausible_web/live/shields/hostname_rules.ex b/lib/plausible_web/live/shields/hostname_rules.ex index dbb4565bdf84..3f355e69e420 100644 --- a/lib/plausible_web/live/shields/hostname_rules.ex +++ b/lib/plausible_web/live/shields/hostname_rules.ex @@ -34,85 +34,109 @@ defmodule PlausibleWeb.Live.Shields.HostnameRules do <.settings_tiles> <.tile docs="excluding#exclude-visits-by-hostname"> <:title>Hostnames allow list - <:subtitle>Accept incoming traffic only from familiar hostnames. - <.filter_bar - :if={@hostname_rules_count < Shields.maximum_hostname_rules()} - filtering_enabled?={false} - > - <.button - id="add-hostname-rule" - x-data - x-on:click={Modal.JS.open("hostname-rule-form-modal")} - mt?={false} - > - Add hostname - - + <:subtitle :if={not Enum.empty?(@hostname_rules)}> + Accept incoming traffic only from familiar hostnames. + - <.notice - :if={@hostname_rules_count >= Shields.maximum_hostname_rules()} - class="mt-4" - title="Maximum number of hostnames reached" - theme={:gray} - > -

- You've reached the maximum number of hostnames you can block ({Shields.maximum_hostname_rules()}). Please remove one before adding another. -

- + <%= if Enum.empty?(@hostname_rules) do %> +
+

+ Allow a hostname +

+

+ Accept incoming traffic only from familiar hostnames. Traffic from all hostnames is recorded until you add your first rule. + <.styled_link + href="https://plausible.io/docs/excluding#exclude-visits-by-hostname" + target="_blank" + > + Learn more + +

+ <.button + :if={@hostname_rules_count < Shields.maximum_hostname_rules()} + id="add-hostname-rule" + x-data + x-on:click={Modal.JS.open("hostname-rule-form-modal")} + class="mt-4" + > + Add hostname + +
+ <% else %> + <.filter_bar + :if={@hostname_rules_count < Shields.maximum_hostname_rules()} + filtering_enabled?={false} + > + <.button + id="add-hostname-rule" + x-data + x-on:click={Modal.JS.open("hostname-rule-form-modal")} + mt?={false} + > + Add hostname + + -

- No hostname rules configured for this site. - - Traffic from all hostnames is currently accepted. - -

+ <.notice + :if={@hostname_rules_count >= Shields.maximum_hostname_rules()} + class="mt-4" + title="Maximum number of hostnames reached" + theme={:gray} + > +

+ You've reached the maximum number of hostnames you can block ({Shields.maximum_hostname_rules()}). Please remove one before adding another. +

+ - <.table :if={not Enum.empty?(@hostname_rules)} rows={@hostname_rules}> - <:thead> - <.th>Hostname - <.th hide_on_mobile>Status - <.th invisible>Actions - - <:tbody :let={rule}> - <.td> -
- - {rule.hostname} - -
- - <.td hide_on_mobile> -
- - Blocked - - - Allowed - - - - -
- - <.td actions> - <.delete_button - id={"remove-hostname-rule-#{rule.id}"} - phx-target={@myself} - phx-click="remove-hostname-rule" - phx-value-rule-id={rule.id} - data-confirm="Are you sure you want to revoke this rule?" - /> - - - +
+ <.table rows={@hostname_rules}> + <:thead> + <.th>Hostname + <.th hide_on_mobile>Status + <.th invisible>Actions + + <:tbody :let={rule}> + <.td> +
+ + {rule.hostname} + +
+ + <.td hide_on_mobile> +
+ + Blocked + + + Allowed + + + + +
+ + <.td actions> + <.delete_button + id={"remove-hostname-rule-#{rule.id}"} + phx-target={@myself} + phx-click="remove-hostname-rule" + phx-value-rule-id={rule.id} + data-confirm="Are you sure you want to revoke this rule?" + /> + + + +
+ <% end %> <.live_component :let={modal_unique_id} module={Modal} id="hostname-rule-form-modal"> <.form diff --git a/lib/plausible_web/live/shields/ip_rules.ex b/lib/plausible_web/live/shields/ip_rules.ex index 850562d97661..8e7208b7c6b3 100644 --- a/lib/plausible_web/live/shields/ip_rules.ex +++ b/lib/plausible_web/live/shields/ip_rules.ex @@ -32,88 +32,112 @@ defmodule PlausibleWeb.Live.Shields.IPRules do <.settings_tiles> <.tile docs="excluding"> <:title>IP block list - <:subtitle>Reject incoming traffic from specific IP addresses. - <.filter_bar :if={@ip_rules_count < Shields.maximum_ip_rules()} filtering_enabled?={false}> - <.button - id="add-ip-rule" - x-data - x-on:click={Modal.JS.open("ip-rule-form-modal")} - mt?={false} + <:subtitle :if={not Enum.empty?(@ip_rules)}> + Reject incoming traffic from specific IP addresses. + + + <%= if Enum.empty?(@ip_rules) do %> +
+

+ Block an IP address +

+

+ Reject incoming traffic from specific IP addresses. + <.styled_link href="https://plausible.io/docs/excluding" target="_blank"> + Learn more + +

+ <.button + :if={@ip_rules_count < Shields.maximum_ip_rules()} + id="add-ip-rule" + x-data + x-on:click={Modal.JS.open("ip-rule-form-modal")} + class="mt-4" + > + Add IP address + +
+ <% else %> + <.filter_bar :if={@ip_rules_count < Shields.maximum_ip_rules()} filtering_enabled?={false}> + <.button + id="add-ip-rule" + x-data + x-on:click={Modal.JS.open("ip-rule-form-modal")} + mt?={false} + > + Add IP address + + + + <.notice + :if={@ip_rules_count >= Shields.maximum_ip_rules()} + class="mt-4" + title="Maximum number of addresses reached" + theme={:gray} > - Add IP address - - - - <.notice - :if={@ip_rules_count >= Shields.maximum_ip_rules()} - class="mt-4" - title="Maximum number of addresses reached" - theme={:gray} - > -

- You've reached the maximum number of IP addresses you can block ({Shields.maximum_ip_rules()}). Please remove one before adding another. -

- - -

- No IP rules configured for this site. -

- - <.table :if={not Enum.empty?(@ip_rules)} rows={@ip_rules}> - <:thead> - <.th>IP address - <.th hide_on_mobile>Status - <.th hide_on_mobile>Description - <.th invisible>Actions - - <:tbody :let={rule}> - <.td max_width="max-w-40"> -
- - - YOU - - - {rule.inet} - -
- - <.td hide_on_mobile> - - Blocked - - - Allowed - - - <.td hide_on_mobile truncate> - - {rule.description} - - - -- - - - <.td actions> - <.delete_button - id={"remove-ip-rule-#{rule.id}"} - phx-target={@myself} - phx-click="remove-ip-rule" - phx-value-rule-id={rule.id} - data-confirm="Are you sure you want to revoke this rule?" - /> - - - +

+ You've reached the maximum number of IP addresses you can block ({Shields.maximum_ip_rules()}). Please remove one before adding another. +

+ + +
+ <.table rows={@ip_rules}> + <:thead> + <.th>IP address + <.th hide_on_mobile>Status + <.th hide_on_mobile>Description + <.th invisible>Actions + + <:tbody :let={rule}> + <.td max_width="max-w-40"> +
+ + + YOU + + + {rule.inet} + +
+ + <.td hide_on_mobile> + + Blocked + + + Allowed + + + <.td hide_on_mobile truncate> + + {rule.description} + + + -- + + + <.td actions> + <.delete_button + id={"remove-ip-rule-#{rule.id}"} + phx-target={@myself} + phx-click="remove-ip-rule" + phx-value-rule-id={rule.id} + data-confirm="Are you sure you want to revoke this rule?" + /> + + + +
+ <% end %> <.live_component module={Modal} id="ip-rule-form-modal"> <.form diff --git a/lib/plausible_web/live/shields/page_rules.ex b/lib/plausible_web/live/shields/page_rules.ex index 3047fa0c5ef0..a76af986f679 100644 --- a/lib/plausible_web/live/shields/page_rules.ex +++ b/lib/plausible_web/live/shields/page_rules.ex @@ -34,80 +34,107 @@ defmodule PlausibleWeb.Live.Shields.PageRules do <.settings_tiles> <.tile docs="top-pages#block-traffic-from-specific-pages-or-sections"> <:title>Pages block list - <:subtitle>Reject incoming traffic for specific pages. - <.filter_bar - :if={@page_rules_count < Shields.maximum_page_rules()} - filtering_enabled?={false} - > - <.button - id="add-page-rule" - x-data - x-on:click={Modal.JS.open("page-rule-form-modal")} - mt?={false} - > - Add page - - + <:subtitle :if={not Enum.empty?(@page_rules)}> + Reject incoming traffic for specific pages. + - <.notice - :if={@page_rules_count >= Shields.maximum_page_rules()} - class="mt-4" - title="Maximum number of pages reached" - theme={:gray} - > -

- You've reached the maximum number of pages you can block ({Shields.maximum_page_rules()}). Please remove one before adding another. -

- + <%= if Enum.empty?(@page_rules) do %> +
+

+ Block a page +

+

+ Reject incoming traffic for specific pages. + <.styled_link + href="https://plausible.io/docs/top-pages#block-traffic-from-specific-pages-or-sections" + target="_blank" + > + Learn more + +

+ <.button + :if={@page_rules_count < Shields.maximum_page_rules()} + id="add-page-rule" + x-data + x-on:click={Modal.JS.open("page-rule-form-modal")} + class="mt-4" + > + Add page + +
+ <% else %> + <.filter_bar + :if={@page_rules_count < Shields.maximum_page_rules()} + filtering_enabled?={false} + > + <.button + id="add-page-rule" + x-data + x-on:click={Modal.JS.open("page-rule-form-modal")} + mt?={false} + > + Add page + + -

- No page rules configured for this site. -

+ <.notice + :if={@page_rules_count >= Shields.maximum_page_rules()} + class="mt-4" + title="Maximum number of pages reached" + theme={:gray} + > +

+ You've reached the maximum number of pages you can block ({Shields.maximum_page_rules()}). Please remove one before adding another. +

+ - <.table :if={not Enum.empty?(@page_rules)} rows={@page_rules}> - <:thead> - <.th>Page - <.th hide_on_mobile>Status - <.th invisible>Actions - - <:tbody :let={rule}> - <.td max_width="max-w-40" truncate> - - {rule.page_path} - - - <.td hide_on_mobile> -
- - Blocked - - - Allowed - - - - -
- - <.td actions> - <.delete_button - id={"remove-page-rule-#{rule.id}"} - phx-target={@myself} - phx-click="remove-page-rule" - phx-value-rule-id={rule.id} - data-confirm="Are you sure you want to revoke this rule?" - /> - - - +
+ <.table rows={@page_rules}> + <:thead> + <.th>Page + <.th hide_on_mobile>Status + <.th invisible>Actions + + <:tbody :let={rule}> + <.td max_width="max-w-40" truncate> + + {rule.page_path} + + + <.td hide_on_mobile> +
+ + Blocked + + + Allowed + + + + +
+ + <.td actions> + <.delete_button + id={"remove-page-rule-#{rule.id}"} + phx-target={@myself} + phx-click="remove-page-rule" + phx-value-rule-id={rule.id} + data-confirm="Are you sure you want to revoke this rule?" + /> + + + +
+ <% end %> <.live_component :let={modal_unique_id} module={Modal} id="page-rule-form-modal"> <.form diff --git a/lib/plausible_web/live/sites.ex b/lib/plausible_web/live/sites.ex index 8580d20dcb22..d0c7d93fff4c 100644 --- a/lib/plausible_web/live/sites.ex +++ b/lib/plausible_web/live/sites.ex @@ -51,6 +51,34 @@ defmodule PlausibleWeb.Live.Sites do Teams.Users.owns_sites?(current_user, include_pending?: true, only_team: current_team) && Teams.Billing.check_needs_to_upgrade(current_team) end) + |> then(fn socket -> + %{ + sites: sites, + current_team: current_team, + has_sites?: has_sites?, + filter_text: filter_text + } = socket.assigns + + is_empty_state? = + not (sites.entries != [] and (Teams.setup?(current_team) or has_sites?)) and + filter_text == "" + + empty_state_title = + if Teams.setup?(current_team) do + "Add your first team site" + else + "Add your first personal site" + end + + empty_state_description = + "Collect simple, privacy-friendly stats to better understand your audience." + + assign(socket, + is_empty_state?: is_empty_state?, + empty_state_title: empty_state_title, + empty_state_description: empty_state_description + ) + end) {:noreply, socket} end @@ -62,6 +90,7 @@ defmodule PlausibleWeb.Live.Sites do :invitations_map, Enum.map(assigns.invitations, &{&1.invitation.invitation_id, &1}) |> Enum.into(%{}) ) + |> assign(:searching?, String.trim(assigns.filter_text) != "") ~H""" <.flash_messages flash={@flash} /> @@ -87,11 +116,11 @@ defmodule PlausibleWeb.Live.Sites do -
- <.search_form :if={@has_sites?} filter_text={@filter_text} uri={@uri} /> -

- You don't have any sites yet. -

+
+ <.search_form filter_text={@filter_text} uri={@uri} />
-

- No sites found. Please search for something else. +

+ No sites found. Try a different search term.

- -

- You currently have no personal sites. Are you looking for your team’s sites? - <.styled_link href={Routes.auth_path(@socket, :select_team)}> - Go to your team → - -

+

+ {@empty_state_title} +

+

+ {@empty_state_description} +

+
+ <.button_link + href={"/sites/new?flow=#{PlausibleWeb.Flows.provisioning()}"} + theme="primary" + mt?={false} + > + Add website + + <.button_link + :if={not Teams.setup?(@current_team) and @has_sites?} + href={Routes.auth_path(@socket, :select_team)} + theme="secondary" + mt?={false} + > + Go to team sites + +
+
    <.consolidated_view_card_cta :if={ - @filter_text == "" and + not @searching? and !@consolidated_view and @no_consolidated_view_reason not in [:no_sites, :unavailable] and not @consolidated_view_cta_dismissed? } @@ -156,7 +200,7 @@ defmodule PlausibleWeb.Live.Sites do /> <.consolidated_view_card :if={ - @filter_text == "" and not is_nil(@consolidated_view) and + not @searching? and not is_nil(@consolidated_view) and consolidated_view_ok_to_display?(@current_team) } can_manage_consolidated_view?={@can_manage_consolidated_view?} diff --git a/lib/plausible_web/live/team_management.ex b/lib/plausible_web/live/team_management.ex index 1290c14ce776..77b74561968b 100644 --- a/lib/plausible_web/live/team_management.ex +++ b/lib/plausible_web/live/team_management.ex @@ -43,7 +43,7 @@ defmodule PlausibleWeb.Live.TeamManagement do diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex index 03c869400e1a..a5046072e320 100644 --- a/lib/plausible_web/router.ex +++ b/lib/plausible_web/router.ex @@ -703,10 +703,6 @@ defmodule PlausibleWeb.Router do get "/:domain/settings/properties", SiteController, :settings_props get "/:domain/settings/email-reports", SiteController, :settings_email_reports - put "/:domain/settings/features/visibility/:setting", - SiteController, - :update_feature_visibility - put "/:domain/settings", SiteController, :update_settings get "/:domain/export", StatsController, :csv_export diff --git a/lib/plausible_web/templates/settings/api_keys.html.heex b/lib/plausible_web/templates/settings/api_keys.html.heex index a5540ab9e6f1..04cd79b67c6d 100644 --- a/lib/plausible_web/templates/settings/api_keys.html.heex +++ b/lib/plausible_web/templates/settings/api_keys.html.heex @@ -1,63 +1,74 @@ <.settings_tiles> <.tile docs="stats-api" - current_role={@current_team_role} + current_user={@current_user} current_team={@current_team} feature_mod={Plausible.Billing.Feature.StatsAPI} > <:title> API keys - <:subtitle> + <:subtitle :if={not Enum.empty?(@api_keys)}> Create and manage access. - <.filter_bar filtering_enabled?={false}> - <.button_link mt?={false} href={Routes.settings_path(@conn, :new_api_key)}> - New API Key - - + <%= if Enum.empty?(@api_keys) do %> +
    +

    + Create your first API key +

    +

    + Access your stats through the Plausible API. + <.styled_link href="https://plausible.io/docs/stats-api">Learn more +

    + <.button_link href={Routes.settings_path(@conn, :new_api_key)}> + New API Key + +
    + <% else %> + <.filter_bar filtering_enabled?={false}> + <.button_link mt?={false} href={Routes.settings_path(@conn, :new_api_key)}> + New API Key + + -

    - No API keys configured yet. -

    + <.table rows={@api_keys}> + <:thead> + <.th> + Name + + <.th hide_on_mobile> + Key + + <.th :if={ee?()} hide_on_mobile> + Type + + <.th invisible> + Actions + + - <.table rows={@api_keys}> - <:thead> - <.th> - Name - - <.th hide_on_mobile> - Key - - <.th :if={ee?()} hide_on_mobile> - Type - - <.th invisible> - Actions - - - - <:tbody :let={api_key}> - <.td truncate max_width="max-w-40"> - {api_key.name} - - <.td hide_on_mobile> - {api_key.key_prefix} - {String.duplicate("*", 32 - 6)} - - <.td :if={ee?()}> - Stats API - Sites API - - <.td actions> - <.delete_button - method="delete" - href={Routes.settings_path(@conn, :delete_api_key, api_key.id)} - data-confirm="Are you sure you want to revoke this key? This action cannot be reversed." - /> - - - + <:tbody :let={api_key}> + <.td truncate max_width="max-w-40"> + {api_key.name} + + <.td hide_on_mobile> + {api_key.key_prefix} + {String.duplicate("*", 32 - 6)} + + <.td :if={ee?()}> + Stats API + Sites API + + <.td actions> + <.delete_button + method="delete" + href={Routes.settings_path(@conn, :delete_api_key, api_key.id)} + data-confirm="Are you sure you want to revoke this key? This action cannot be reversed." + /> + + + + <% end %> diff --git a/lib/plausible_web/templates/settings/team_general.html.heex b/lib/plausible_web/templates/settings/team_general.html.heex index 4d77915b796a..e4b4e069aaa6 100644 --- a/lib/plausible_web/templates/settings/team_general.html.heex +++ b/lib/plausible_web/templates/settings/team_general.html.heex @@ -28,7 +28,7 @@ <.tile docs="users-roles#managing-team-member-roles" - current_role={@current_team_role} + current_user={@current_user} current_team={@current_team} feature_mod={Plausible.Billing.Feature.Teams} > diff --git a/lib/plausible_web/templates/site/membership/invite_member_form.html.heex b/lib/plausible_web/templates/site/membership/invite_member_form.html.heex index bbb7587be9b1..0b124747ae6a 100644 --- a/lib/plausible_web/templates/site/membership/invite_member_form.html.heex +++ b/lib/plausible_web/templates/site/membership/invite_member_form.html.heex @@ -12,7 +12,7 @@ <.form :let={f} for={@conn} action={Routes.membership_path(@conn, :invite_member, @site.domain)}> - <.tile - docs="funnel-analysis" - feature_mod={Plausible.Billing.Feature.Funnels} - feature_toggle?={true} - current_role={@site_role} - current_team={@site_team} - site={@site} - conn={@conn} - > - <:title> - Funnels - - <:subtitle> - Compose goals into funnels - - +
    {live_render(@conn, PlausibleWeb.Live.FunnelSettings, session: %{"site_id" => @site.id, "domain" => @site.domain} )} - +
    diff --git a/lib/plausible_web/templates/site/settings_goals.html.heex b/lib/plausible_web/templates/site/settings_goals.html.heex index 309295d0367f..452bcba7aa99 100644 --- a/lib/plausible_web/templates/site/settings_goals.html.heex +++ b/lib/plausible_web/templates/site/settings_goals.html.heex @@ -1,30 +1,7 @@ <.settings_tiles> - <.tile - docs="goal-conversions" - feature_mod={Plausible.Billing.Feature.Goals} - feature_toggle?={true} - site={@site} - conn={@conn} - > - <:title> - Goals - - <:subtitle> -

    - Define actions that you want your users to take, like visiting a certain page, submitting a form, etc. -

    -

    - You can also - <.styled_link href={Routes.site_path(@conn, :settings_funnels, @site.domain)}> - compose goals into funnels. - -

    - - -
    - {live_render(@conn, PlausibleWeb.Live.GoalSettings, - session: %{"site_id" => @site.id, "domain" => @site.domain} - )} -
    - +
    + {live_render(@conn, PlausibleWeb.Live.GoalSettings, + session: %{"site_id" => @site.id, "domain" => @site.domain} + )} +
    diff --git a/lib/plausible_web/templates/site/settings_imports_exports.html.heex b/lib/plausible_web/templates/site/settings_imports_exports.html.heex index a4bd11599341..3b88c05e6227 100644 --- a/lib/plausible_web/templates/site/settings_imports_exports.html.heex +++ b/lib/plausible_web/templates/site/settings_imports_exports.html.heex @@ -1,20 +1,9 @@ -<.settings_tiles docs="google-analytics-import"> - <.tile> - <:title> - Import data - - <:subtitle> - Import existing data from external sources. - Pick one of the options below to start a new import.
    - A maximum of {Plausible.Imported.max_complete_imports()} imports at any time is allowed. - - - {live_render(@conn, PlausibleWeb.Live.ImportsExportsSettings, - session: %{"domain" => @site.domain} - )} - +<.settings_tiles> + {live_render(@conn, PlausibleWeb.Live.ImportsExportsSettings, + session: %{"domain" => @site.domain} + )} - <.tile> + <.tile docs="export-stats"> <:title> Export data diff --git a/lib/plausible_web/templates/site/settings_integrations.html.heex b/lib/plausible_web/templates/site/settings_integrations.html.heex index f987fd542a17..49e5594a131a 100644 --- a/lib/plausible_web/templates/site/settings_integrations.html.heex +++ b/lib/plausible_web/templates/site/settings_integrations.html.heex @@ -156,7 +156,7 @@ <:title> Plugin tokens - <:subtitle> + <:subtitle :if={@has_plugins_tokens?}> Control plugin access. diff --git a/lib/plausible_web/templates/site/settings_people.html.heex b/lib/plausible_web/templates/site/settings_people.html.heex index 9b06476a689d..cdfc2a37dca2 100644 --- a/lib/plausible_web/templates/site/settings_people.html.heex +++ b/lib/plausible_web/templates/site/settings_people.html.heex @@ -8,7 +8,7 @@ docs="users-roles" feature_mod={Plausible.Billing.Feature.Teams} site={@site} - current_role={@site_role} + current_user={@current_user} current_team={@site_team} conn={@conn} > diff --git a/lib/plausible_web/templates/site/settings_props.html.heex b/lib/plausible_web/templates/site/settings_props.html.heex index c80ab7337472..8d63b28c77af 100644 --- a/lib/plausible_web/templates/site/settings_props.html.heex +++ b/lib/plausible_web/templates/site/settings_props.html.heex @@ -1,24 +1,8 @@ <.settings_tiles> - <.tile - docs="custom-props/introduction" - feature_mod={Plausible.Billing.Feature.Props} - feature_toggle?={true} - site={@site} - current_role={@site_role} - current_team={@site_team} - conn={@conn} - > - <:title> - Custom properties - - <:subtitle> - Attach custom properties when sending a pageview or an event to - create custom metrics. - - +
    {live_render(@conn, PlausibleWeb.Live.PropsSettings, id: "props-form", session: %{"site_id" => @site.id, "domain" => @site.domain} )} - +
    diff --git a/lib/plausible_web/templates/site/settings_visibility.html.heex b/lib/plausible_web/templates/site/settings_visibility.html.heex index 05bc17b5961a..cf1cd9c86141 100644 --- a/lib/plausible_web/templates/site/settings_visibility.html.heex +++ b/lib/plausible_web/templates/site/settings_visibility.html.heex @@ -27,33 +27,17 @@ - <.tile - docs="shared-links" - feature_mod={Plausible.Billing.Feature.SharedLinks} - site={@site} - current_role={@site_role} - current_team={@site_team} - conn={@conn} - > - <:title> - Shared links - - <:subtitle> - You can share your stats privately by generating a shared link. The links are impossible to guess and you can add password protection for extra security. - - -
    - {live_render(@conn, PlausibleWeb.Live.SharedLinkSettings, - session: %{"site_id" => @site.id, "domain" => @site.domain} - )} -
    - +
    + {live_render(@conn, PlausibleWeb.Live.SharedLinkSettings, + session: %{"site_id" => @site.id, "domain" => @site.domain} + )} +
    <.tile docs="embed-dashboard" feature_mod={Plausible.Billing.Feature.SharedLinks} site={@site} - current_role={@site_role} + current_user={@current_user} current_team={@site_team} conn={@conn} > diff --git a/test/plausible_web/components/billing/billing_test.exs b/test/plausible_web/components/billing/billing_test.exs index fff310ebb949..dd5154644bde 100644 --- a/test/plausible_web/components/billing/billing_test.exs +++ b/test/plausible_web/components/billing/billing_test.exs @@ -11,7 +11,7 @@ defmodule PlausibleWeb.Components.BillingTest do test "renders a blur overlay if the feature is locked", %{user: user} do html = %{ - current_role: :owner, + current_user: user, current_team: user |> subscribe_to_growth_plan() |> team_of(), locked?: true } @@ -22,10 +22,10 @@ defmodule PlausibleWeb.Components.BillingTest do assert text_of_element(html, "#feature-gate-overlay") =~ "Upgrade to unlock" end - test "renders a blur overlay for a teamless account" do + test "renders a blur overlay for a teamless account", %{user: user} do html = %{ - current_role: nil, + current_user: user, current_team: nil, locked?: true } @@ -39,7 +39,7 @@ defmodule PlausibleWeb.Components.BillingTest do test "does not render a blur overlay if feature access is granted", %{user: user} do html = %{ - current_role: :owner, + current_user: user, current_team: user |> subscribe_to_business_plan() |> team_of(), locked?: false } @@ -52,7 +52,7 @@ defmodule PlausibleWeb.Components.BillingTest do test "renders upgrade cta linking to the upgrade page if user role is :owner", %{user: user} do html = %{ - current_role: :owner, + current_user: user, current_team: user |> subscribe_to_growth_plan() |> team_of(), locked?: true } @@ -62,10 +62,13 @@ defmodule PlausibleWeb.Components.BillingTest do end test "renders upgrade cta linking to the upgrade page if user role is :billing", %{user: user} do + team = user |> subscribe_to_growth_plan() |> team_of() + billing = add_member(team, role: :billing) + html = %{ - current_role: :billing, - current_team: user |> subscribe_to_growth_plan() |> team_of(), + current_user: billing, + current_team: team, locked?: true } |> render_feature_gate() @@ -77,10 +80,13 @@ defmodule PlausibleWeb.Components.BillingTest do %{ user: user } do + team = user |> subscribe_to_growth_plan() |> team_of() + editor = add_member(team, role: :editor) + html = %{ - current_role: :editor, - current_team: user |> subscribe_to_growth_plan() |> team_of(), + current_user: editor, + current_team: team, locked?: true } |> render_feature_gate() diff --git a/test/plausible_web/components/billing/notice_test.exs b/test/plausible_web/components/billing/notice_test.exs index cdf9dc63bae2..a6d13ee9956c 100644 --- a/test/plausible_web/components/billing/notice_test.exs +++ b/test/plausible_web/components/billing/notice_test.exs @@ -5,12 +5,12 @@ defmodule PlausibleWeb.Components.Billing.NoticeTest do alias PlausibleWeb.Components.Billing.Notice test "limit_exceeded/1 when user is on growth displays upgrade link" do - me = new_user() |> subscribe_to_growth_plan() - team = team_of(me) + user = new_user() |> subscribe_to_growth_plan() + team = team_of(user) rendered = render_component(&Notice.limit_exceeded/1, - current_role: :owner, + current_user: user, current_team: team, limit: 10, resource: "users" @@ -22,12 +22,12 @@ defmodule PlausibleWeb.Components.Billing.NoticeTest do end test "limit_exceeded/1 prints resource in singular case when limit is 1" do - me = new_user() |> subscribe_to_growth_plan() + user = new_user() |> subscribe_to_growth_plan() rendered = render_component(&Notice.limit_exceeded/1, - current_role: :owner, - current_team: team_of(me), + current_user: user, + current_team: team_of(user), limit: 1, resource: "users" ) @@ -38,13 +38,14 @@ defmodule PlausibleWeb.Components.Billing.NoticeTest do end test "limit_exceeded/1 when current team role is non-owner" do - me = new_user() |> subscribe_to_growth_plan() - my_team = team_of(me) |> Plausible.Teams.complete_setup() + user = new_user() |> subscribe_to_growth_plan() + team = team_of(user) |> Plausible.Teams.complete_setup() + editor = add_member(team, role: :editor) rendered = render_component(&Notice.limit_exceeded/1, - current_role: :editor, - current_team: my_team, + current_user: editor, + current_team: team, limit: 10, resource: "users" ) @@ -55,12 +56,12 @@ defmodule PlausibleWeb.Components.Billing.NoticeTest do @tag :ee_only test "limit_exceeded/1 when user is on trial displays upgrade link" do - me = new_user(trial_expiry_date: Date.utc_today()) + user = new_user(trial_expiry_date: Date.utc_today()) rendered = render_component(&Notice.limit_exceeded/1, - current_role: :owner, - current_team: team_of(me), + current_user: user, + current_team: team_of(user), limit: 10, resource: "users" ) @@ -72,12 +73,12 @@ defmodule PlausibleWeb.Components.Billing.NoticeTest do @tag :ee_only test "limit_exceeded/1 when user is on an enterprise plan displays support email" do - me = new_user() |> subscribe_to_enterprise_plan() + user = new_user() |> subscribe_to_enterprise_plan() rendered = render_component(&Notice.limit_exceeded/1, - current_role: :owner, - current_team: team_of(me), + current_user: user, + current_team: team_of(user), limit: 10, resource: "users" ) @@ -90,12 +91,12 @@ defmodule PlausibleWeb.Components.Billing.NoticeTest do @tag :ee_only test "limit_exceeded/1 when user is on a business plan displays support email" do - me = new_user() |> subscribe_to_business_plan() - team = team_of(me) + user = new_user() |> subscribe_to_business_plan() + team = team_of(user) rendered = render_component(&Notice.limit_exceeded/1, - current_role: :owner, + current_user: user, current_team: team, limit: 10, resource: "users" diff --git a/test/plausible_web/controllers/site_controller_test.exs b/test/plausible_web/controllers/site_controller_test.exs index aa28867508d1..9ff1561f026f 100644 --- a/test/plausible_web/controllers/site_controller_test.exs +++ b/test/plausible_web/controllers/site_controller_test.exs @@ -57,9 +57,77 @@ defmodule PlausibleWeb.SiteControllerTest do describe "GET /sites" do setup [:create_user, :log_in] - test "shows empty screen if no sites", %{conn: conn} do + test "shows personal sites empty state when there are no sites at all", %{conn: conn} do conn = get(conn, "/sites") - assert html_response(conn, 200) =~ "You don't have any sites yet" + resp = html_response(conn, 200) + + assert resp =~ "Add your first personal site" + assert resp =~ "Collect simple, privacy-friendly stats to better understand your audience." + refute resp =~ "Go to team sites" + end + + test "shows team sites empty state when team is setup and there are no sites at all", %{ + conn: conn, + user: user + } do + {:ok, team} = Teams.get_or_create(user) + team = Teams.complete_setup(team) + conn = set_current_team(conn, team) + + conn = get(conn, "/sites") + resp = html_response(conn, 200) + + assert resp =~ "Add your first team site" + assert resp =~ "Collect simple, privacy-friendly stats to better understand your audience." + refute resp =~ "Go to team sites" + end + + test "shows team sites empty state when team is setup but has no team sites, and user has personal sites", + %{conn: conn, user: user} do + _personal_site = new_site(owner: user) + + other_user = new_user() + {:ok, other_team} = Teams.get_or_create(other_user) + other_team = Teams.complete_setup(other_team) + add_member(other_team, user: user, role: :admin) + conn = set_current_team(conn, other_team) + + conn = get(conn, "/sites") + resp = html_response(conn, 200) + + assert resp =~ "Add your first team site" + assert resp =~ "Collect simple, privacy-friendly stats to better understand your audience." + refute resp =~ "Go to team sites" + end + + test "shows personal sites empty state when there are team sites but no personal sites", %{ + conn: conn, + user: user + } do + {:ok, team} = Teams.get_or_create(user) + team = Teams.complete_setup(team) + _team_site = new_site(team: team) + + conn = get(conn, "/sites") + resp = html_response(conn, 200) + + assert resp =~ "Add your first personal site" + assert resp =~ "Collect simple, privacy-friendly stats to better understand your audience." + assert resp =~ "Go to team sites" + end + + test "shows empty search state when filter returns no results but there are sites", %{ + conn: conn, + user: user + } do + _site = new_site(domain: "example.com", owner: user) + + conn = get(conn, "/sites", filter_text: "nonexistent") + resp = html_response(conn, 200) + + assert resp =~ "No sites found. Try a different search term." + refute resp =~ "Add your first" + refute resp =~ "Go to team sites" end test "lists all of your sites with last 24h visitors (defaulting to 0 on first mount)", %{ @@ -195,7 +263,7 @@ defmodule PlausibleWeb.SiteControllerTest do resp = html_response(conn, 200) refute resp =~ "second.example.com" - assert html_response(conn, 200) =~ "No sites found. Please search for something else." + assert html_response(conn, 200) =~ "No sites found. Try a different search term." refute html_response(conn, 200) =~ "You don't have any sites yet." end @@ -776,7 +844,7 @@ defmodule PlausibleWeb.SiteControllerTest do conn = get(conn, "/#{site.domain}/settings/visibility") resp = html_response(conn, 200) - assert resp =~ "No shared links configured for this site" + assert resp =~ "Create your first shared link" end test "does not render shared links with special names", %{conn: conn, site: site} do @@ -990,7 +1058,7 @@ defmodule PlausibleWeb.SiteControllerTest do assert element_exists?(resp, ~s|a[href^="https://accounts.google.com/o/oauth2/"]|) assert(resp =~ "Import data") - assert resp =~ "There are no imports yet" + assert resp =~ "Import your first data" assert resp =~ "Export data" end @@ -1258,123 +1326,6 @@ defmodule PlausibleWeb.SiteControllerTest do end end - describe "PUT /:domain/settings/features/visibility/:setting" do - def query_conn_with_some_url(context) do - {:ok, Map.put(context, :conn_with_url, get(context.conn, "/some_parent_path"))} - end - - setup [:create_user, :log_in, :query_conn_with_some_url] - - for {title, setting} <- %{ - "Goals" => :conversions_enabled, - "Funnels" => :funnels_enabled, - "Custom Properties" => :props_enabled - } do - test "can toggle #{title} with admin access", %{ - user: user, - conn: conn0, - conn_with_url: conn_with_url - } do - site = new_site() - add_guest(site, user: user, role: :editor) - - conn = - put( - conn0, - PlausibleWeb.Components.Site.Feature.target( - site, - unquote(setting), - conn_with_url, - false - ) - ) - - assert Phoenix.Flash.get(conn.assigns.flash, :success) == - "#{unquote(title)} are now hidden from your dashboard" - - assert redirected_to(conn, 302) =~ "/some_parent_path" - - assert %{unquote(setting) => false} = Plausible.Sites.get_by_domain(site.domain) - - conn = - put( - conn0, - PlausibleWeb.Components.Site.Feature.target( - site, - unquote(setting), - conn_with_url, - true - ) - ) - - assert Phoenix.Flash.get(conn.assigns.flash, :success) == - "#{unquote(title)} are now visible again on your dashboard" - - assert redirected_to(conn, 302) =~ "/some_parent_path" - - assert %{unquote(setting) => true} = Plausible.Sites.get_by_domain(site.domain) - end - end - - for {title, setting} <- %{ - "Goals" => :conversions_enabled, - "Funnels" => :funnels_enabled, - "Properties" => :props_enabled - } do - test "cannot toggle #{title} with viewer access", %{ - user: user, - conn: conn0, - conn_with_url: conn_with_url - } do - site = new_site() - add_guest(site, user: user, role: :viewer) - - conn = - put( - conn0, - PlausibleWeb.Components.Site.Feature.target( - site, - unquote(setting), - conn_with_url, - false - ) - ) - - assert conn.status == 404 - assert conn.halted - end - end - - test "setting feature visibility is idempotent", %{ - user: user, - conn: conn0, - conn_with_url: conn_with_url - } do - site = new_site() - add_guest(site, user: user, role: :editor) - - setting = :funnels_enabled - - conn = - put( - conn0, - PlausibleWeb.Components.Site.Feature.target(site, setting, conn_with_url, false) - ) - - assert %{^setting => false} = Plausible.Sites.get_by_domain(site.domain) - assert redirected_to(conn, 302) =~ "/some_parent_path" - - conn = - put( - conn0, - PlausibleWeb.Components.Site.Feature.target(site, setting, conn_with_url, false) - ) - - assert %{^setting => false} = Plausible.Sites.get_by_domain(site.domain) - assert redirected_to(conn, 302) =~ "/some_parent_path" - end - end - describe "POST /sites/:domain/weekly-report/enable" do setup [:create_user, :log_in, :create_site] diff --git a/test/plausible_web/live/funnel_settings_test.exs b/test/plausible_web/live/funnel_settings_test.exs index f3142b0618e1..baf344472262 100644 --- a/test/plausible_web/live/funnel_settings_test.exs +++ b/test/plausible_web/live/funnel_settings_test.exs @@ -38,7 +38,7 @@ defmodule PlausibleWeb.Live.FunnelSettingsTest do end test "search funnels input is rendered", %{conn: conn, site: site} do - setup_goals(site) + {:ok, _} = setup_funnels(site) conn = get(conn, "/#{site.domain}/settings/funnels") resp = html_response(conn, 200) assert element_exists?(resp, ~s/input[type="text"]#filter-text/) @@ -77,7 +77,7 @@ defmodule PlausibleWeb.Live.FunnelSettingsTest do conn = get(conn, "/#{site.domain}/settings/funnels") doc = conn |> html_response(200) - assert text(doc) =~ "Set up a few goals first" + assert text(doc) =~ "Set up a few goals" add_goals_path = Routes.site_path(conn, :settings_goals, site.domain) assert element_exists?(doc, ~s/a[href="#{add_goals_path}"]/) @@ -90,6 +90,16 @@ defmodule PlausibleWeb.Live.FunnelSettingsTest do describe "FunnelSettings live view" do setup [:create_user, :log_in, :create_site] + test "allows dashboard toggle", %{conn: conn, site: site} do + lv = get_liveview(conn, site) + lv |> element("#feature-funnels-toggle button") |> render_click() + assert render(lv) =~ "Funnels are now hidden from your dashboard" + assert Plausible.Billing.Feature.Funnels.opted_out?(Plausible.Repo.reload!(site)) + lv |> element("#feature-funnels-toggle button") |> render_click() + assert render(lv) =~ "Funnels are now visible again on your dashboard" + refute Plausible.Billing.Feature.Funnels.opted_out?(Plausible.Repo.reload!(site)) + end + test "allows list filtering / search", %{conn: conn, site: site} do {:ok, _} = setup_funnels(site, ["Funnel One", "Search Me"]) {lv, html} = get_liveview(conn, site, with_html?: true) diff --git a/test/plausible_web/live/goal_settings_test.exs b/test/plausible_web/live/goal_settings_test.exs index aa4d6a6fe792..d869c544ff78 100644 --- a/test/plausible_web/live/goal_settings_test.exs +++ b/test/plausible_web/live/goal_settings_test.exs @@ -76,7 +76,7 @@ defmodule PlausibleWeb.Live.GoalSettingsTest do test "if no goals are present, a proper info is displayed", %{conn: conn, site: site} do conn = get(conn, "/#{site.domain}/settings/goals") resp = html_response(conn, 200) - assert resp =~ "No goals configured for this site" + assert resp =~ "Create your first goal" end test "if goals are present, no info about missing goals is displayed", %{ @@ -86,7 +86,7 @@ defmodule PlausibleWeb.Live.GoalSettingsTest do {:ok, _goals} = setup_goals(site) conn = get(conn, "/#{site.domain}/settings/goals") resp = html_response(conn, 200) - refute resp =~ "No goals configured for this site" + refute resp =~ "Create your first goal" end test "add goal button is rendered", %{conn: conn, site: site} do @@ -96,6 +96,7 @@ defmodule PlausibleWeb.Live.GoalSettingsTest do end test "search goals input is rendered", %{conn: conn, site: site} do + {:ok, _goals} = setup_goals(site) conn = get(conn, "/#{site.domain}/settings/goals") resp = html_response(conn, 200) assert element_exists?(resp, ~s/input[type="text"]#filter-text/) @@ -119,7 +120,7 @@ defmodule PlausibleWeb.Live.GoalSettingsTest do assert resp = html_response(conn, 200) assert resp =~ "Define actions that you want your users to take" - assert resp =~ "No goals configured for this site" + assert resp =~ "Create your first goal" assert element_exists?(resp, ~s|a[href="https://plausible.io/docs/goal-conversions"]|) end @@ -151,6 +152,16 @@ defmodule PlausibleWeb.Live.GoalSettingsTest do describe "GoalSettings live view" do setup [:create_user, :log_in, :create_site] + test "allows dashboard toggle", %{conn: conn, site: site} do + lv = get_liveview(conn, site) + lv |> element("#feature-goals-toggle button") |> render_click() + assert render(lv) =~ "Goals are now hidden from your dashboard" + assert Plausible.Billing.Feature.Goals.opted_out?(Plausible.Repo.reload!(site)) + lv |> element("#feature-goals-toggle button") |> render_click() + assert render(lv) =~ "Goals are now visible again on your dashboard" + refute Plausible.Billing.Feature.Goals.opted_out?(Plausible.Repo.reload!(site)) + end + test "allows goal deletion", %{conn: conn, site: site} do {:ok, [g1, g2 | _]} = setup_goals(site) {lv, html} = get_liveview(conn, site, with_html?: true) diff --git a/test/plausible_web/live/props_settings_test.exs b/test/plausible_web/live/props_settings_test.exs index dd1e5df04def..d90fdffe6989 100644 --- a/test/plausible_web/live/props_settings_test.exs +++ b/test/plausible_web/live/props_settings_test.exs @@ -75,7 +75,7 @@ defmodule PlausibleWeb.Live.PropsSettingsTest do test "if no props are allowed, a proper info is displayed", %{conn: conn, site: site} do conn = get(conn, "/#{site.domain}/settings/properties") resp = html_response(conn, 200) - assert resp =~ "No properties configured for this site" + assert resp =~ "Create a custom property" end test "if props are enabled, no info about missing props is displayed", %{ @@ -85,7 +85,7 @@ defmodule PlausibleWeb.Live.PropsSettingsTest do {:ok, site} = Plausible.Props.allow(site, ["amount", "logged_in", "is_customer"]) conn = get(conn, "/#{site.domain}/settings/properties") resp = html_response(conn, 200) - refute resp =~ "No properties configured for this site" + refute resp =~ "Create a custom property" end test "add property button is rendered", %{conn: conn, site: site} do @@ -95,6 +95,7 @@ defmodule PlausibleWeb.Live.PropsSettingsTest do end test "search props input is rendered", %{conn: conn, site: site} do + {:ok, site} = Plausible.Props.allow(site, ["amount", "logged_in", "is_customer"]) conn = get(conn, "/#{site.domain}/settings/properties") resp = html_response(conn, 200) assert element_exists?(resp, ~s/input[type="text"]#filter-text/) @@ -142,13 +143,16 @@ defmodule PlausibleWeb.Live.PropsSettingsTest do } do conn = get(conn, "/#{consolidated_view.domain}/settings/properties") resp = html_response(conn, 200) - assert resp =~ "No properties configured for this site" + assert resp =~ "Create a custom property" end test "add property button and search input are rendered", %{ conn: conn, consolidated_view: consolidated_view } do + {:ok, consolidated_view} = + Plausible.Props.allow(consolidated_view, ["amount", "logged_in", "is_customer"]) + conn = get(conn, "/#{consolidated_view.domain}/settings/properties") resp = html_response(conn, 200) assert element_exists?(resp, ~s/button[phx-click="add-prop"]/) @@ -158,19 +162,19 @@ defmodule PlausibleWeb.Live.PropsSettingsTest do end end - # validating input - # clicking suggestions fills out input - # adding props - # error when reached props limit - # clearserror when fixed input - # removal - # removal shows confirmation - # allow existing props: shows/hides - # after adding all suggestions no allow existing props - describe "PropsSettings live view" do setup [:create_user, :log_in, :create_site] + test "allows dashboard toggle", %{conn: conn, site: site} do + lv = get_liveview(conn, site) + lv |> element("#feature-props-toggle button") |> render_click() + assert render(lv) =~ "Custom Properties are now hidden from your dashboard" + assert Plausible.Billing.Feature.Props.opted_out?(Plausible.Repo.reload!(site)) + lv |> element("#feature-props-toggle button") |> render_click() + assert render(lv) =~ "Custom Properties are now visible again on your dashboard" + refute Plausible.Billing.Feature.Props.opted_out?(Plausible.Repo.reload!(site)) + end + test "allows prop removal", %{conn: conn, site: site} do {:ok, site} = Plausible.Props.allow(site, ["amount", "logged_in"]) {lv, html} = get_liveview(conn, site, with_html?: true) @@ -222,6 +226,7 @@ defmodule PlausibleWeb.Live.PropsSettingsTest do end test "allows resetting filter text via no match link", %{conn: conn, site: site} do + {:ok, site} = Plausible.Props.allow(site, ["amount", "logged_in", "is_customer"]) lv = get_liveview(conn, site) html = type_into_search(lv, "Definitely this is not going to render any matches") diff --git a/test/plausible_web/live/shared_link_settings_test.exs b/test/plausible_web/live/shared_link_settings_test.exs index 8e79d95f7a00..3841b4f75d46 100644 --- a/test/plausible_web/live/shared_link_settings_test.exs +++ b/test/plausible_web/live/shared_link_settings_test.exs @@ -101,7 +101,7 @@ defmodule PlausibleWeb.Live.SharedLinkSettingsTest do lv = get_liveview(conn, session) html = render(lv) - assert html =~ "No shared links configured for this site" + assert html =~ "Create your first shared link" end end diff --git a/test/plausible_web/live/shields/countries_test.exs b/test/plausible_web/live/shields/countries_test.exs index ecd98da8db8c..f0975089320e 100644 --- a/test/plausible_web/live/shields/countries_test.exs +++ b/test/plausible_web/live/shields/countries_test.exs @@ -13,7 +13,7 @@ defmodule PlausibleWeb.Live.Shields.CountriesTest do conn = get(conn, "/#{site.domain}/settings/shields/countries") resp = html_response(conn, 200) - assert resp =~ "No country rules configured for this site" + assert resp =~ "Block a country" assert resp =~ "Country block list" end diff --git a/test/plausible_web/live/shields/hostnames_test.exs b/test/plausible_web/live/shields/hostnames_test.exs index 497162bbaae1..22c150dc8fbf 100644 --- a/test/plausible_web/live/shields/hostnames_test.exs +++ b/test/plausible_web/live/shields/hostnames_test.exs @@ -13,9 +13,9 @@ defmodule PlausibleWeb.Live.Shields.HostnamesTest do conn = get(conn, "/#{site.domain}/settings/shields/hostnames") resp = html_response(conn, 200) - assert resp =~ "No hostname rules configured for this site" + assert resp =~ "Allow a hostname" assert resp =~ "Hostnames allow list" - assert resp =~ "Traffic from all hostnames is currently accepted." + assert resp =~ "Traffic from all hostnames is recorded until you add your first rule" end test "lists hostname rules with remove actions", %{conn: conn, site: site} do diff --git a/test/plausible_web/live/shields/ip_addresses_test.exs b/test/plausible_web/live/shields/ip_addresses_test.exs index f5c960ff7ffa..2e044c51220c 100644 --- a/test/plausible_web/live/shields/ip_addresses_test.exs +++ b/test/plausible_web/live/shields/ip_addresses_test.exs @@ -13,7 +13,7 @@ defmodule PlausibleWeb.Live.Shields.IPAddressesTest do conn = get(conn, "/#{site.domain}/settings/shields/ip_addresses") resp = html_response(conn, 200) - assert resp =~ "No IP rules configured for this site" + assert resp =~ "Block an IP address" assert resp =~ "IP block list" end diff --git a/test/plausible_web/live/shields/pages_test.exs b/test/plausible_web/live/shields/pages_test.exs index a97c774f9eae..acc5d3eb3158 100644 --- a/test/plausible_web/live/shields/pages_test.exs +++ b/test/plausible_web/live/shields/pages_test.exs @@ -13,7 +13,7 @@ defmodule PlausibleWeb.Live.Shields.PagesTest do conn = get(conn, "/#{site.domain}/settings/shields/pages") resp = html_response(conn, 200) - assert resp =~ "No page rules configured for this site" + assert resp =~ "Block a page" assert resp =~ "Pages block list" end diff --git a/test/plausible_web/live/sites_test.exs b/test/plausible_web/live/sites_test.exs index a07dcba03451..afb14d696c57 100644 --- a/test/plausible_web/live/sites_test.exs +++ b/test/plausible_web/live/sites_test.exs @@ -14,10 +14,10 @@ defmodule PlausibleWeb.Live.SitesTest do {:ok, _lv, html} = live(conn, "/sites") text = text(html) + assert text =~ "My personal sites" - assert text =~ "You don't have any sites yet" - refute text =~ "You currently have no personal sites" - refute text =~ "Go to your team" + assert text =~ "Add your first personal site" + refute text =~ "Go to team sites" end test "renders team switcher link, if on personal sites with other teams available", %{ @@ -33,8 +33,8 @@ defmodule PlausibleWeb.Live.SitesTest do assert text =~ "My personal sites" refute text =~ "You don't have any sites yet" - assert text =~ "You currently have no personal sites" - assert text =~ "Go to your team" + assert text =~ "Add your first personal site" + assert text =~ "Go to team sites" end test "renders settings link when current team is set", %{user: user, conn: conn} do