2023-09-04 14:44:22 +03:00
defmodule PlausibleWeb.Live.GoalSettings.Form do
@moduledoc """
Live view for the goal creation form
"""
2024-01-15 13:39:30 +03:00
use Phoenix.LiveComponent , global_prefixes : ~w( x- )
use Plausible
2023-09-04 14:44:22 +03:00
import PlausibleWeb.Live.Components.Form
2024-01-15 13:39:30 +03:00
alias PlausibleWeb.Live.Components.ComboBox
2023-09-04 14:44:22 +03:00
alias Plausible.Repo
2024-01-15 13:39:30 +03:00
def update ( assigns , socket ) do
site = Repo . preload ( assigns . site , [ :owner ] )
2023-10-11 15:40:01 +03:00
owner = Plausible.Users . with_subscription ( site . owner )
2024-01-15 13:39:30 +03:00
site = %{ site | owner : owner }
2023-10-11 15:40:01 +03:00
has_access_to_revenue_goals? =
2023-10-11 23:24:16 +03:00
Plausible.Billing.Feature.RevenueGoals . check_availability ( owner ) == :ok
2023-09-04 14:44:22 +03:00
2024-01-15 13:39:30 +03:00
form =
% Plausible.Goal { }
|> Plausible.Goal . changeset ( )
|> to_form ( )
socket =
socket
|> assign (
id : assigns . id ,
2024-06-11 09:28:25 +03:00
context_unique_id : assigns . context_unique_id ,
2024-01-15 13:39:30 +03:00
form : form ,
2024-05-10 12:48:27 +03:00
event_name_options_count : length ( assigns . event_name_options ) ,
2024-06-11 09:28:25 +03:00
event_name_options : Enum . map ( assigns . event_name_options , & { &1 , &1 } ) ,
2024-01-15 13:39:30 +03:00
current_user : assigns . current_user ,
domain : assigns . domain ,
selected_tab : " custom_events " ,
2024-06-11 09:28:25 +03:00
tab_sequence_id : 0 ,
2024-01-15 13:39:30 +03:00
site : site ,
has_access_to_revenue_goals? : has_access_to_revenue_goals? ,
2024-06-11 09:28:25 +03:00
existing_goals : assigns . existing_goals ,
2024-05-10 12:48:27 +03:00
on_save_goal : assigns . on_save_goal ,
on_autoconfigure : assigns . on_autoconfigure
2024-01-15 13:39:30 +03:00
)
{ :ok , socket }
2023-09-04 14:44:22 +03:00
end
def render ( assigns ) do
~H """
2024-01-15 13:39:30 +03:00
< div id = { @id } >
< . form
:let = { f }
x - data = " { tabSelectionInProgress: false } "
for = { @form }
phx - submit = " save-goal "
phx - target = { @myself }
>
< PlausibleWeb.Components.Generic . spinner
class = " spinner block absolute right-9 top-8 "
x - show = " tabSelectionInProgress "
/ >
2023-09-04 14:44:22 +03:00
2024-01-15 13:39:30 +03:00
< h2 class = " text-xl font-black dark:text-gray-100 " > Add Goal for < % = @domain % > < / h2 >
2023-09-04 14:44:22 +03:00
2024-01-15 13:39:30 +03:00
< . tabs selected_tab = { @selected_tab } myself = { @myself } / >
2023-09-04 14:44:22 +03:00
2024-01-15 13:39:30 +03:00
< . custom_event_fields
:if = { @selected_tab == " custom_events " }
x - show = " !tabSelectionInProgress "
f = { f }
2024-06-11 09:28:25 +03:00
suffix = { suffix ( @context_unique_id , @tab_sequence_id ) }
2024-01-15 13:39:30 +03:00
current_user = { @current_user }
site = { @site }
2024-06-11 09:28:25 +03:00
existing_goals = { @existing_goals }
goal_options = { @event_name_options }
2024-01-15 13:39:30 +03:00
has_access_to_revenue_goals? = { @has_access_to_revenue_goals? }
x - init = " tabSelectionInProgress = false "
/ >
< . pageview_fields
:if = { @selected_tab == " pageviews " }
x - show = " !tabSelectionInProgress "
f = { f }
2024-06-11 09:28:25 +03:00
suffix = { suffix ( @context_unique_id , @tab_sequence_id ) }
2024-01-15 13:39:30 +03:00
site = { @site }
x - init = " tabSelectionInProgress = false "
/ >
< div class = " py-4 " x - show = " !tabSelectionInProgress " >
< PlausibleWeb.Components.Generic . button type = " submit " class = " w-full " >
Add Goal →
< / PlausibleWeb.Components.Generic . button >
< / div >
2024-05-10 12:48:27 +03:00
< button
:if = { @selected_tab == " custom_events " && @event_name_options_count > 0 }
2024-06-11 09:28:25 +03:00
x - show = " !tabSelectionInProgress "
2024-05-10 12:48:27 +03:00
class = " mt-2 text-sm hover:underline text-indigo-600 dark:text-indigo-400 text-left "
phx - click = " autoconfigure "
phx - target = { @myself }
>
< span :if = { @event_name_options_count > 1 } >
Already sending custom events? We ' ve found <%= @event_name_options_count %> custom events from the last 6 months that are not yet configured as goals. Click here to add them.
< / span >
< span :if = { @event_name_options_count == 1 } >
Already sending custom events? We ' ve found 1 custom event from the last 6 months that is not yet configured as a goal. Click here to add it.
< / span >
< / button >
2024-01-15 13:39:30 +03:00
< / . form >
2023-09-04 14:44:22 +03:00
< / div >
"""
end
attr ( :f , Phoenix.HTML.Form )
attr ( :site , Plausible.Site )
2024-06-11 09:28:25 +03:00
attr ( :suffix , :string )
2023-09-04 14:44:22 +03:00
2024-01-15 13:39:30 +03:00
attr ( :rest , :global )
2023-09-04 14:44:22 +03:00
def pageview_fields ( assigns ) do
~H """
2024-01-15 13:39:30 +03:00
< div id = " pageviews-form " class = " py-2 " { @rest } >
2024-06-11 09:28:25 +03:00
< . label for = { " page_path_input_ #{ @suffix } " } >
2023-09-05 15:43:01 +03:00
Page Path
2023-09-04 14:44:22 +03:00
< / . label >
< . live_component
2024-06-11 09:28:25 +03:00
id = { " page_path_input_ #{ @suffix } " }
2023-09-04 14:44:22 +03:00
submit_name = " goal[page_path] "
class = { [
" py-2 "
] }
module = { ComboBox }
2024-06-11 09:28:25 +03:00
suggest_fun = { fn input , _options -> suggest_page_paths ( input , @site ) end }
2023-09-04 14:44:22 +03:00
creatable
/ >
< . error :for = { { msg , opts } <- @f [ :page_path ] . errors } >
< % = Enum . reduce ( opts , msg , fn { key , value } , acc ->
String . replace ( acc , " %{ #{ key } } " , fn _ -> to_string ( value ) end )
end ) % >
< / . error >
< / div >
"""
end
attr ( :f , Phoenix.HTML.Form )
2023-10-11 15:40:01 +03:00
attr ( :site , Plausible.Site )
attr ( :current_user , Plausible.Auth.User )
2024-06-11 09:28:25 +03:00
attr ( :suffix , :string )
attr ( :existing_goals , :list )
attr ( :goal_options , :list )
2023-10-11 15:40:01 +03:00
attr ( :has_access_to_revenue_goals? , :boolean )
2023-09-04 14:44:22 +03:00
2024-01-15 13:39:30 +03:00
attr ( :rest , :global )
2023-09-04 14:44:22 +03:00
def custom_event_fields ( assigns ) do
~H """
2024-01-15 13:39:30 +03:00
< div id = " custom-events-form " class = " my-6 " { @rest } >
2023-09-04 14:44:22 +03:00
< div id = " event-fields " >
< div class = " pb-6 text-xs text-gray-700 dark:text-gray-200 text-justify rounded-md " >
2023-09-05 15:43:01 +03:00
Custom Events are not tracked by default - you have to configure them on your site to be sent to Plausible . See examples and learn more in < a
2023-09-04 14:44:22 +03:00
class = " text-indigo-500 hover:underline "
target = " _blank "
rel = " noreferrer "
href = " https://plausible.io/docs/custom-event-goals "
> our docs < / a > .
< / div >
< div >
2024-06-11 09:28:25 +03:00
< . label for = { " event_name_input_ #{ @suffix } " } >
Event Name
< / . label >
< . live_component
id = { " event_name_input_ #{ @suffix } " }
submit_name = " goal[event_name] "
2023-09-04 14:44:22 +03:00
placeholder = " e.g. Signup "
2024-06-11 09:28:25 +03:00
class = { [
" py-2 "
] }
module = { ComboBox }
suggest_fun = { fn input , _options -> suggest_event_names ( input , @site , @existing_goals ) end }
options = { @goal_options }
creatable
2023-09-04 14:44:22 +03:00
/ >
< / div >
< div
2024-04-29 09:05:33 +03:00
:if = { ee? ( ) }
2023-09-04 14:44:22 +03:00
class = " mt-6 space-y-3 "
x - data = {
Jason . encode! ( %{
active : ! ! @f [ :currency ] . value and @f [ :currency ] . value != " " ,
currency : @f [ :currency ] . value
} )
}
>
2023-12-15 19:59:16 +03:00
< PlausibleWeb.Components.Billing.Notice . premium_feature
2023-11-08 16:24:30 +03:00
billable_user = { @site . owner }
current_user = { @current_user }
feature_mod = { Plausible.Billing.Feature.RevenueGoals }
size = { :xs }
class = " rounded-b-md "
/ >
2023-10-11 15:40:01 +03:00
< button
class = { [
" flex items-center w-max mb-3 " ,
2023-11-22 17:34:47 +03:00
if @has_access_to_revenue_goals? do
" cursor-pointer "
else
" cursor-not-allowed "
end
2023-10-11 15:40:01 +03:00
] }
aria - labelledby = " enable-revenue-tracking "
role = " switch "
type = " button "
2023-09-04 14:44:22 +03:00
x - on :click = " active = !active; currency = '' "
2023-10-11 15:40:01 +03:00
x - bind :aria - checked = " active "
disabled = { not @has_access_to_revenue_goals? }
2023-09-04 14:44:22 +03:00
>
2023-10-11 15:40:01 +03:00
< span
2023-09-04 14:44:22 +03:00
class = " relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2 "
x - bind :class = " active ? 'bg-indigo-600' : 'dark:bg-gray-700 bg-gray-200' "
>
< span
aria - hidden = " true "
class = " pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out "
x - bind :class = " active ? 'dark:bg-gray-800 translate-x-5' : 'dark:bg-gray-800 translate-x-0' "
/ >
2023-10-11 15:40:01 +03:00
< / span >
2023-09-04 14:44:22 +03:00
< span
2023-10-11 15:40:01 +03:00
class = { [
" ml-3 font-medium " ,
if ( assigns . has_access_to_revenue_goals? ,
do : " text-gray-900 dark:text-gray-100 " ,
else : " text-gray-500 dark:text-gray-300 "
)
] }
2023-09-04 14:44:22 +03:00
id = " enable-revenue-tracking "
>
2023-09-05 15:43:01 +03:00
Enable Revenue Tracking
2023-09-04 14:44:22 +03:00
< / span >
2023-10-11 15:40:01 +03:00
< / button >
2023-09-04 14:44:22 +03:00
< div x - show = " active " >
< . live_component
2024-06-11 09:28:25 +03:00
id = { " currency_input_ #{ @suffix } " }
2023-09-04 14:44:22 +03:00
submit_name = { @f [ :currency ] . name }
module = { ComboBox }
suggest_fun = {
2024-04-29 09:05:33 +03:00
on_ee do
2023-11-22 17:34:47 +03:00
fn
" " , [ ] ->
Plausible.Goal.Revenue . currency_options ( )
input , options ->
ComboBox.StaticSearch . suggest ( input , options , weight_threshold : 0.8 )
end
2023-09-04 14:44:22 +03:00
end
}
/ >
< / div >
< / div >
2024-06-11 09:28:25 +03:00
< . error :for = { { msg , opts } <- @f [ :event_name ] . errors } >
< % = Enum . reduce ( opts , msg , fn { key , value } , acc ->
String . replace ( acc , " %{ #{ key } } " , fn _ -> to_string ( value ) end )
end ) % >
< / . error >
2023-09-04 14:44:22 +03:00
< / div >
< / div >
"""
end
def tabs ( assigns ) do
~H """
2023-09-05 15:43:01 +03:00
< div class = " mt-6 font-medium dark:text-gray-100 " > Goal Trigger < / div >
2023-09-04 14:44:22 +03:00
< div class = " my-3 w-full flex rounded border border-gray-300 dark:border-gray-500 " >
2024-01-15 13:39:30 +03:00
< . custom_events_tab selected? = { @selected_tab == " custom_events " } myself = { @myself } / >
< . pageviews_tab selected? = { @selected_tab == " pageviews " } myself = { @myself } / >
2023-09-04 14:44:22 +03:00
< / div >
"""
end
defp custom_events_tab ( assigns ) do
~H """
< a
class = { [
" w-1/2 text-center py-2 border-r dark:border-gray-500 " ,
" cursor-pointer " ,
2024-01-15 13:39:30 +03:00
@selected? && " shadow-inner font-bold bg-indigo-600 text-white " ,
! @selected? && " dark:text-gray-100 text-gray-800 "
2023-09-04 14:44:22 +03:00
] }
id = " event-tab "
2024-05-10 12:48:27 +03:00
x - on :click = { ! @selected? && " tabSelectionInProgress = true " }
2023-09-04 14:44:22 +03:00
phx - click = " switch-tab "
2024-01-15 13:39:30 +03:00
phx - value - tab = " custom_events "
phx - target = { @myself }
2023-09-04 14:44:22 +03:00
>
2023-09-05 15:43:01 +03:00
Custom Event
2023-09-04 14:44:22 +03:00
< / a >
"""
end
def pageviews_tab ( assigns ) do
~H """
< a
class = { [
" w-1/2 text-center py-2 cursor-pointer " ,
2024-01-15 13:39:30 +03:00
@selected? && " shadow-inner font-bold bg-indigo-600 text-white " ,
! @selected? && " dark:text-gray-100 text-gray-800 "
2023-09-04 14:44:22 +03:00
] }
id = " pageview-tab "
2024-05-10 12:48:27 +03:00
x - on :click = { ! @selected? && " tabSelectionInProgress = true " }
2023-09-04 14:44:22 +03:00
phx - click = " switch-tab "
2024-01-15 13:39:30 +03:00
phx - value - tab = " pageviews "
phx - target = { @myself }
2023-09-04 14:44:22 +03:00
>
Pageview
< / a >
"""
end
2024-01-09 14:28:31 +03:00
2024-01-15 13:39:30 +03:00
def handle_event ( " switch-tab " , %{ " tab " = > tab } , socket ) do
2024-06-11 09:28:25 +03:00
socket =
socket
|> assign ( :selected_tab , tab )
|> update ( :tab_sequence_id , & ( &1 + 1 ) )
{ :noreply , socket }
2023-09-04 14:44:22 +03:00
end
def handle_event ( " save-goal " , %{ " goal " = > goal } , socket ) do
case Plausible.Goals . create ( socket . assigns . site , goal ) do
{ :ok , goal } ->
2024-01-15 13:39:30 +03:00
socket =
goal
|> Map . put ( :funnels , [ ] )
|> socket . assigns . on_save_goal . ( socket )
2023-09-04 14:44:22 +03:00
{ :noreply , socket }
2023-10-11 15:40:01 +03:00
{ :error , % Ecto.Changeset { } = changeset } ->
2023-09-04 14:44:22 +03:00
{ :noreply , assign ( socket , form : to_form ( changeset ) ) }
end
end
2024-05-10 12:48:27 +03:00
def handle_event ( " autoconfigure " , _params , socket ) do
{ :noreply , socket . assigns . on_autoconfigure . ( socket ) }
end
2024-06-11 09:28:25 +03:00
def suggest_page_paths ( input , site ) do
2024-06-06 11:40:48 +03:00
query = Plausible.Stats.Query . from ( site , %{ " with_imported " = > " true " , " period " = > " all " } )
2023-09-04 14:44:22 +03:00
site
|> Plausible.Stats . filter_suggestions ( query , " page " , input )
|> Enum . map ( fn %{ label : label , value : value } -> { label , value } end )
end
2024-06-11 09:28:25 +03:00
def suggest_event_names ( input , site , existing_goals ) do
existing_names =
existing_goals
|> Enum . reject ( & is_nil ( &1 . event_name ) )
|> Enum . map ( & &1 . event_name )
site
|> Plausible.Stats.GoalSuggestions . suggest_event_names ( input , exclude : existing_names )
|> Enum . map ( fn name -> { name , name } end )
end
defp suffix ( context_unique_id , tab_sequence_id ) do
" #{ context_unique_id } -tabseq #{ tab_sequence_id } "
end
2023-09-04 14:44:22 +03:00
end