Upgrade deps, add storybook (#4947)

* Add `<.dropdown_item>`

* Make the ellipsis menu functional again

* Upgrade deps so that storybook can be added

* Add storybook and dropdown story

* Remove lingering warnings/errors

* Add color mode to storybook

* Use new liveview used_input? function

* Alpine improvements

* Add select input to storybook

* Bring back `render_form` for CRM

* Configure eslint so it can see deps

* Remove LiveViewTest patch

* Fix test for phoenix liveview 1.0

* Build assets in prod

* Fix tests

* Attempt to fix lint error

* Add explicit text color to input

* mix format

* Format after merge master

* Add moduledocs

* Only run storybook in production

* Update storybook dependency

* Mix format
This commit is contained in:
Uku Taht 2025-01-13 14:31:18 +02:00 committed by GitHub
parent 32d1783604
commit 6c30f62d5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
148 changed files with 683 additions and 587 deletions

View File

@ -2,5 +2,10 @@
plugins: [Phoenix.LiveView.HTMLFormatter], plugins: [Phoenix.LiveView.HTMLFormatter],
import_deps: [:ecto, :ecto_sql, :phoenix], import_deps: [:ecto, :ecto_sql, :phoenix],
subdirectories: ["priv/*/migrations"], subdirectories: ["priv/*/migrations"],
inputs: ["*.{heex,ex,exs}", "{config,lib,test,extra}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"] inputs: [
"*.{heex,ex,exs}",
"{config,lib,test,extra}/**/*.{heex,ex,exs}",
"priv/*/seeds.exs",
"storybook/**/*.exs"
]
] ]

View File

@ -41,6 +41,7 @@ COPY tracker ./tracker
COPY priv ./priv COPY priv ./priv
COPY lib ./lib COPY lib ./lib
COPY extra ./extra COPY extra ./extra
COPY storybook ./storybook
RUN npm run deploy --prefix ./tracker && \ RUN npm run deploy --prefix ./tracker && \
mix assets.deploy && \ mix assets.deploy && \

View File

@ -50,6 +50,9 @@
"import/resolver": { "import/resolver": {
"typescript": { "typescript": {
"alwaysTryTypes": true // always try to resolve types under `<root>@types` directory even it doesn't contain any source code, like `@types/unist` "alwaysTryTypes": true // always try to resolve types under `<root>@types` directory even it doesn't contain any source code, like `@types/unist`
},
"node": {
"moduleDirectory": ["node_modules", "../deps"]
} }
}, },
"react": { "react": {

14
assets/css/storybook.css Normal file
View File

@ -0,0 +1,14 @@
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
/*
* Put your component styling within the Tailwind utilities layer.
* See the https://hexdocs.pm/phoenix_storybook/sandboxing.html guide for more info.
*/
@layer utilities {
* {
font-family: system-ui;
}
}

View File

@ -1,7 +1,11 @@
// eslint-disable-next-line import/no-unresolved
import "phoenix_html" import "phoenix_html"
import Alpine from 'alpinejs' // eslint-disable-next-line import/no-unresolved
import { Socket } from "phoenix" import { Socket } from "phoenix"
// eslint-disable-next-line import/no-unresolved
import { LiveSocket } from "phoenix_live_view" import { LiveSocket } from "phoenix_live_view"
// eslint-disable-next-line import/no-unresolved
import Alpine from 'alpinejs'
let csrfToken = document.querySelector("meta[name='csrf-token']") let csrfToken = document.querySelector("meta[name='csrf-token']")
let websocketUrl = document.querySelector("meta[name='websocket-url']") let websocketUrl = document.querySelector("meta[name='websocket-url']")

39
assets/js/storybook.js Normal file
View File

@ -0,0 +1,39 @@
import Alpine from 'alpinejs'
import dropdown from "./liveview/dropdown"
// If your components require any hooks or custom uploaders, or if your pages
// require connect parameters, uncomment the following lines and declare them as
// such:
//
// import * as Hooks from "./hooks";
// import * as Params from "./params";
// import * as Uploaders from "./uploaders";
// (function () {
// window.storybook = { Hooks, Params, Uploaders };
// })();
window.Alpine = Alpine
document.addEventListener('DOMContentLoaded', () => {
window.Alpine.start();
});
document.addEventListener('alpine:init', () => {
window.Alpine.data('dropdown', dropdown)
});
(function () {
window.storybook = {
LiveSocketOptions: {
dom: {
onBeforeElUpdated(from, to) {
if (from._x_dataStack) {
window.Alpine.clone(from, to)
}
}
}
}
};
})();

View File

@ -26,9 +26,6 @@
"d3": "^7.9.0", "d3": "^7.9.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"iframe-resizer": "^4.3.2", "iframe-resizer": "^4.3.2",
"phoenix": "^1.7.2",
"phoenix_html": "^3.3.1",
"phoenix_live_view": "^0.18.18",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-flatpickr": "3.10.5", "react-flatpickr": "3.10.5",
@ -9308,17 +9305,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/phoenix": {
"version": "1.7.2",
"license": "MIT"
},
"node_modules/phoenix_html": {
"version": "3.3.1"
},
"node_modules/phoenix_live_view": {
"version": "0.18.18",
"license": "MIT"
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",

View File

@ -30,9 +30,6 @@
"d3": "^7.9.0", "d3": "^7.9.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"iframe-resizer": "^4.3.2", "iframe-resizer": "^4.3.2",
"phoenix": "^1.7.2",
"phoenix_html": "^3.3.1",
"phoenix_live_view": "^0.18.18",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-flatpickr": "3.10.5", "react-flatpickr": "3.10.5",

View File

@ -14,6 +14,7 @@ module.exports = {
"max-w-screen-xl" "max-w-screen-xl"
], ],
darkMode: 'class', darkMode: 'class',
important: '.plausible',
theme: { theme: {
container: { container: {
center: true, center: true,
@ -51,7 +52,6 @@ module.exports = {
plugins: [ plugins: [
require('@tailwindcss/forms'), require('@tailwindcss/forms'),
require('@tailwindcss/aspect-ratio'), require('@tailwindcss/aspect-ratio'),
plugin(({ addVariant }) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])),
plugin(({ addVariant }) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])), plugin(({ addVariant }) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])),
plugin(({ addVariant }) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])), plugin(({ addVariant }) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])),
plugin(({ addVariant }) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])), plugin(({ addVariant }) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])),

View File

@ -20,7 +20,7 @@ config :esbuild,
version: "0.17.11", version: "0.17.11",
default: [ default: [
args: args:
~w(js/app.js js/dashboard.tsx js/embed.host.js js/embed.content.js --bundle --target=es2017 --loader:.js=jsx --outdir=../priv/static/js --define:BUILD_EXTRA=true), ~w(js/app.js js/storybook.js js/dashboard.tsx js/embed.host.js js/embed.content.js --bundle --target=es2017 --loader:.js=jsx --outdir=../priv/static/js --define:BUILD_EXTRA=true),
cd: Path.expand("../assets", __DIR__), cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
] ]
@ -34,6 +34,14 @@ config :tailwind,
--output=../priv/static/css/app.css --output=../priv/static/css/app.css
), ),
cd: Path.expand("../assets", __DIR__) cd: Path.expand("../assets", __DIR__)
],
storybook: [
args: ~w(
--config=tailwind.config.js
--input=css/storybook.css
--output=../priv/static/css/storybook.css
),
cd: Path.expand("../assets", __DIR__)
] ]
config :ua_inspector, config :ua_inspector,

View File

@ -8,6 +8,7 @@ config :plausible, PlausibleWeb.Endpoint,
watchers: [ watchers: [
esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}, esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}, tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]},
storybook_tailwind: {Tailwind, :install_and_run, [:storybook, ~w(--watch)]},
npm: ["--prefix", "assets", "run", "typecheck", "--", "--watch", "--preserveWatchOutput"], npm: ["--prefix", "assets", "run", "typecheck", "--", "--watch", "--preserveWatchOutput"],
npm: [ npm: [
"run", "run",
@ -21,7 +22,8 @@ config :plausible, PlausibleWeb.Endpoint,
], ],
patterns: [ patterns: [
~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$}, ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
~r"lib/plausible_web/(controllers|live|components|templates|views|plugs)/.*(ex|heex)$" ~r"lib/plausible_web/(controllers|live|components|templates|views|plugs)/.*(ex|heex)$",
~r"storybook/.*(exs)$"
] ]
] ]

View File

@ -969,3 +969,5 @@ unless s3_disabled? do
exports_bucket: s3_env_value.("S3_EXPORTS_BUCKET"), exports_bucket: s3_env_value.("S3_EXPORTS_BUCKET"),
imports_bucket: s3_env_value.("S3_IMPORTS_BUCKET") imports_bucket: s3_env_value.("S3_IMPORTS_BUCKET")
end end
config :phoenix_storybook, enabled: env !== "prod"

View File

@ -48,7 +48,7 @@ defmodule PlausibleWeb.Live.FunnelSettings do
<.flash_messages flash={@flash} /> <.flash_messages flash={@flash} />
<%= if @setup_funnel? do %> <%= if @setup_funnel? do %>
<%= live_render( {live_render(
@socket, @socket,
PlausibleWeb.Live.FunnelSettings.Form, PlausibleWeb.Live.FunnelSettings.Form,
id: "funnels-form", id: "funnels-form",
@ -56,7 +56,7 @@ defmodule PlausibleWeb.Live.FunnelSettings do
"domain" => @domain, "domain" => @domain,
"funnel_id" => @funnel_id "funnel_id" => @funnel_id
} }
) %> )}
<% end %> <% end %>
<div :if={@goal_count >= Funnel.min_steps()}> <div :if={@goal_count >= Funnel.min_steps()}>
<.live_component <.live_component

View File

@ -64,7 +64,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
class="bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8" class="bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"
> >
<.title class="mb-6"> <.title class="mb-6">
<%= if @funnel, do: "Edit", else: "Add" %> Funnel {if @funnel, do: "Edit", else: "Add"} Funnel
</.title> </.title>
<.input <.input
@ -136,7 +136,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
length(@step_ids) > map_size(@selections_made) length(@step_ids) > map_size(@selections_made)
} }
> >
<span><%= if @funnel, do: "Update", else: "Add" %> Funnel</span> <span>{if @funnel, do: "Update", else: "Add"} Funnel</span>
</.button> </.button>
</div> </div>
</.form> </.form>

View File

@ -22,11 +22,11 @@ defmodule PlausibleWeb.Live.FunnelSettings.List do
<.table rows={@funnels}> <.table rows={@funnels}>
<:tbody :let={funnel}> <:tbody :let={funnel}>
<.td truncate> <.td truncate>
<span class="font-medium"><%= funnel.name %></span> <span class="font-medium">{funnel.name}</span>
</.td> </.td>
<.td hide_on_mobile> <.td hide_on_mobile>
<span class="text-gray-500 dark:text-gray-400"> <span class="text-gray-500 dark:text-gray-400">
<%= funnel.steps_count %>-step funnel {funnel.steps_count}-step funnel
</span> </span>
</.td> </.td>
<.td actions> <.td actions>

View File

@ -17,7 +17,7 @@ defmodule PlausibleWeb.HelpScoutView do
<%= if @conn.assigns[:error] do %> <%= if @conn.assigns[:error] do %>
<p> <p>
Failed to get details: <%= @error %> Failed to get details: {@error}
</p> </p>
<% else %> <% else %>
<div class="status"> <div class="status">
@ -25,7 +25,7 @@ defmodule PlausibleWeb.HelpScoutView do
Status Status
</p> </p>
<p class="value"> <p class="value">
<a href={@status_link} target="_blank"><%= @status_label %></a> <a href={@status_link} target="_blank">{@status_label}</a>
</p> </p>
</div> </div>
@ -34,13 +34,13 @@ defmodule PlausibleWeb.HelpScoutView do
Plan Plan
</p> </p>
<p class="value"> <p class="value">
<a href={@plan_link} target="_blank"><%= @plan_label %></a> <a href={@plan_link} target="_blank">{@plan_label}</a>
</p> </p>
</div> </div>
<div class="sites"> <div class="sites">
<p class="label"> <p class="label">
Owner of <b><a href={@sites_link} target="_blank"><%= @sites_count %> sites</a></b> Owner of <b><a href={@sites_link} target="_blank">{@sites_count} sites</a></b>
</p> </p>
<p class="value"></p> <p class="value"></p>
</div> </div>
@ -51,7 +51,7 @@ defmodule PlausibleWeb.HelpScoutView do
</p> </p>
<div class="value"> <div class="value">
<%= Phoenix.HTML.Format.text_to_html(@notes, escape: true) %> {PhoenixHTMLHelpers.Format.text_to_html(@notes, escape: true)}
</div> </div>
</div> </div>
<% end %> <% end %>
@ -64,7 +64,7 @@ defmodule PlausibleWeb.HelpScoutView do
<.layout> <.layout>
<%= if @conn.assigns[:error] do %> <%= if @conn.assigns[:error] do %>
<p> <p>
Failed to run search: <%= @error %> Failed to run search: {@error}
</p> </p>
<% else %> <% else %>
<div class="search"> <div class="search">
@ -82,7 +82,7 @@ defmodule PlausibleWeb.HelpScoutView do
onclick={"loadContent('/helpscout/show?#{URI.encode_query(email: user.email, conversation_id: @conversation_id, customer_id: @customer_id)}')"} onclick={"loadContent('/helpscout/show?#{URI.encode_query(email: user.email, conversation_id: @conversation_id, customer_id: @customer_id)}')"}
href="#" href="#"
> >
<%= user.email %> (<%= user.sites_count %> sites) {user.email} ({user.sites_count} sites)
</a> </a>
</li> </li>
</ul> </ul>
@ -160,7 +160,7 @@ defmodule PlausibleWeb.HelpScoutView do
<body> <body>
<div id="content"> <div id="content">
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</div> </div>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -52,15 +52,8 @@ defmodule Plausible.Billing.Ecto.FeatureList do
for mod <- Plausible.Billing.Feature.list(), not mod.free?() do for mod <- Plausible.Billing.Feature.list(), not mod.free?() do
[ [
{:safe, ~s(<label style="padding-right: 15px;">)}, {:safe, ~s(<label style="padding-right: 15px;">)},
Phoenix.HTML.Tag.tag( {:safe,
:input, ~s(<input type="checkbox" name="#{form.name}[#{field}][]" "#{form.name}_#{field}_#{mod.name()}" value="#{mod.name()}" style="margin-right: 3px;" #{if mod in features, do: "checked", else: ""}>)},
name: Phoenix.HTML.Form.input_name(form, field) <> "[]",
id: Phoenix.HTML.Form.input_id(form, field, mod.name()),
type: "checkbox",
value: mod.name(),
style: "margin-right: 3px;",
checked: mod in features
),
mod.display_name(), mod.display_name(),
{:safe, ~s(</label>)} {:safe, ~s(</label>)}
] ]
@ -68,7 +61,7 @@ defmodule Plausible.Billing.Ecto.FeatureList do
[ [
{:safe, ~s(<div class="form-group">)}, {:safe, ~s(<div class="form-group">)},
Phoenix.HTML.Form.label(form, field), {:safe, ~s(<label for="#{form.name}_#{field}">#{Phoenix.Naming.humanize(field)}</label>)},
{:safe, ~s(<div class="form-control">)}, {:safe, ~s(<div class="form-control">)},
checkboxes, checkboxes,
{:safe, ~s(</div>)}, {:safe, ~s(</div>)},

View File

@ -25,14 +25,9 @@ defmodule Plausible.Billing.Ecto.Limit do
[ [
{:safe, ~s(<div class="form-group">)}, {:safe, ~s(<div class="form-group">)},
Phoenix.HTML.Form.label(form, field), {:safe, ~s(<label for="#{form.name}_#{field}">#{Phoenix.Naming.humanize(field)}</label>)},
Phoenix.HTML.Form.number_input(form, field, {:safe,
class: "form-control", ~s(<input id="#{form.name}_#{field}" name="#{form.name}[#{field}]" class="form-control" value="#{value}" min="-1" type="number" />)},
name: "#{form.name}[#{field}]",
id: "#{form.name}_#{field}",
value: value,
min: -1
),
{:safe, ~s(<p class="help_text">Use -1 for unlimited.</p>)}, {:safe, ~s(<p class="help_text">Use -1 for unlimited.</p>)},
{:safe, ~s(</div>)} {:safe, ~s(</div>)}
] ]

View File

@ -60,7 +60,6 @@ defmodule PlausibleWeb do
use Phoenix.Component use Phoenix.Component
import PlausibleWeb.ErrorHelpers
import PlausibleWeb.Components.Generic import PlausibleWeb.Components.Generic
import PlausibleWeb.Live.Components.Form import PlausibleWeb.Live.Components.Form
alias PlausibleWeb.Router.Helpers, as: Routes alias PlausibleWeb.Router.Helpers, as: Routes

View File

@ -114,12 +114,12 @@ defmodule PlausibleWeb.Components.Billing do
class="text-sm dark:text-gray-100" class="text-sm dark:text-gray-100"
x-bind:class={"tab === '#{@tab}' ? 'text-indigo-600 dark:text-indigo-500 font-semibold' : 'font-medium'"} x-bind:class={"tab === '#{@tab}' ? 'text-indigo-600 dark:text-indigo-500 font-semibold' : 'font-medium'"}
> >
<%= @name %> {@name}
</span> </span>
<span class="flex text-xs text-gray-500 dark:text-gray-400"> <span class="flex text-xs text-gray-500 dark:text-gray-400">
<%= if @disabled, {if @disabled,
do: "Not available", do: "Not available",
else: PlausibleWeb.TextHelpers.format_date_range(@date_range) %> else: PlausibleWeb.TextHelpers.format_date_range(@date_range)}
</span> </span>
</div> </div>
</button> </button>
@ -152,7 +152,7 @@ defmodule PlausibleWeb.Components.Billing do
~H""" ~H"""
<table class="min-w-full text-gray-900 dark:text-gray-100" {@rest}> <table class="min-w-full text-gray-900 dark:text-gray-100" {@rest}>
<tbody class="divide-y divide-gray-200 dark:divide-gray-600"> <tbody class="divide-y divide-gray-200 dark:divide-gray-600">
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</tbody> </tbody>
</table> </table>
""" """
@ -168,11 +168,11 @@ defmodule PlausibleWeb.Components.Billing do
~H""" ~H"""
<tr {@rest}> <tr {@rest}>
<td class={["text-sm py-4 pr-1 sm:whitespace-nowrap text-left", @pad && "pl-6"]}> <td class={["text-sm py-4 pr-1 sm:whitespace-nowrap text-left", @pad && "pl-6"]}>
<%= @title %> {@title}
</td> </td>
<td class="text-sm py-4 sm:whitespace-nowrap text-right"> <td class="text-sm py-4 sm:whitespace-nowrap text-right">
<%= Cldr.Number.to_string!(@usage) %> {Cldr.Number.to_string!(@usage)}
<%= if is_number(@limit), do: "/ #{Cldr.Number.to_string!(@limit)}" %> {if is_number(@limit), do: "/ #{Cldr.Number.to_string!(@limit)}"}
</td> </td>
</tr> </tr>
""" """
@ -186,7 +186,7 @@ defmodule PlausibleWeb.Components.Billing do
> >
<h4 class="font-black dark:text-gray-100">Monthly quota</h4> <h4 class="font-black dark:text-gray-100">Monthly quota</h4>
<div class="py-2 text-xl font-medium dark:text-gray-100"> <div class="py-2 text-xl font-medium dark:text-gray-100">
<%= PlausibleWeb.AuthView.subscription_quota(@subscription, format: :long) %> {PlausibleWeb.AuthView.subscription_quota(@subscription, format: :long)}
</div> </div>
<.styled_link <.styled_link
:if={ :if={
@ -196,7 +196,7 @@ defmodule PlausibleWeb.Components.Billing do
id="#upgrade-or-change-plan-link" id="#upgrade-or-change-plan-link"
href={Routes.billing_path(PlausibleWeb.Endpoint, :choose_plan)} href={Routes.billing_path(PlausibleWeb.Endpoint, :choose_plan)}
> >
<%= change_plan_or_upgrade_text(@subscription) %> {change_plan_or_upgrade_text(@subscription)}
</.styled_link> </.styled_link>
</div> </div>
""" """
@ -206,13 +206,13 @@ defmodule PlausibleWeb.Components.Billing do
~H""" ~H"""
<ul class="w-full py-4"> <ul class="w-full py-4">
<li> <li>
Up to <b><%= present_limit(@plan, :monthly_pageview_limit) %></b> monthly pageviews Up to <b>{present_limit(@plan, :monthly_pageview_limit)}</b> monthly pageviews
</li> </li>
<li> <li>
Up to <b><%= present_limit(@plan, :site_limit) %></b> sites Up to <b>{present_limit(@plan, :site_limit)}</b> sites
</li> </li>
<li> <li>
Up to <b><%= present_limit(@plan, :hourly_api_request_limit) %></b> hourly api requests Up to <b>{present_limit(@plan, :hourly_api_request_limit)}</b> hourly api requests
</li> </li>
</ul> </ul>
""" """
@ -258,7 +258,7 @@ defmodule PlausibleWeb.Components.Billing do
@checkout_disabled && "pointer-events-none bg-gray-400 dark:bg-gray-600" @checkout_disabled && "pointer-events-none bg-gray-400 dark:bg-gray-600"
]} ]}
> >
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</button> </button>
""" """
end end

View File

@ -76,7 +76,7 @@ defmodule PlausibleWeb.Components.Billing.Notice do
title="Notice" title="Notice"
{@rest} {@rest}
> >
<%= account_label(@current_user, @billable_user) %> does not have access to <%= @feature_mod.display_name() %>. To get access to this feature, {account_label(@current_user, @billable_user)} does not have access to {@feature_mod.display_name()}. To get access to this feature,
<.upgrade_call_to_action <.upgrade_call_to_action
current_team={@current_team} current_team={@current_team}
current_user={@current_user} current_user={@current_user}
@ -96,7 +96,7 @@ defmodule PlausibleWeb.Components.Billing.Notice do
def limit_exceeded(assigns) do def limit_exceeded(assigns) do
~H""" ~H"""
<.notice {@rest} title="Notice"> <.notice {@rest} title="Notice">
<%= account_label(@current_user, @billable_user) %> is limited to <%= @limit %> <%= @resource %>. To increase this limit, {account_label(@current_user, @billable_user)} is limited to {@limit} {@resource}. To increase this limit,
<.upgrade_call_to_action <.upgrade_call_to_action
current_team={@current_team} current_team={@current_team}
current_user={@current_user} current_user={@current_user}
@ -239,7 +239,7 @@ defmodule PlausibleWeb.Components.Billing.Notice do
~H""" ~H"""
<aside class={@class}> <aside class={@class}>
<.notice title="Pending ownership transfers" class="shadow-md dark:shadow-none mt-4"> <.notice title="Pending ownership transfers" class="shadow-md dark:shadow-none mt-4">
<%= @message %> To exclude pending sites from your usage, please go to {@message} To exclude pending sites from your usage, please go to
<.link href="https://plausible.io/sites" class="whitespace-nowrap font-semibold"> <.link href="https://plausible.io/sites" class="whitespace-nowrap font-semibold">
plausible.io/sites plausible.io/sites
</.link> </.link>

View File

@ -19,7 +19,7 @@ defmodule PlausibleWeb.Components.Billing.PageviewSlider do
<output class="lg:w-1/4 lg:order-1 font-medium text-lg text-gray-600 dark:text-gray-200"> <output class="lg:w-1/4 lg:order-1 font-medium text-lg text-gray-600 dark:text-gray-200">
<span :if={@volume != :enterprise}>Up to</span> <span :if={@volume != :enterprise}>Up to</span>
<strong id="slider-value" class="text-gray-900 dark:text-gray-100"> <strong id="slider-value" class="text-gray-900 dark:text-gray-100">
<%= format_volume(@volume, @available_volumes) %> {format_volume(@volume, @available_volumes)}
</strong> </strong>
monthly pageviews monthly pageviews
</output> </output>
@ -39,7 +39,7 @@ defmodule PlausibleWeb.Components.Billing.PageviewSlider do
<form class="max-w-md lg:max-w-none w-full lg:w-1/2 lg:order-2"> <form class="max-w-md lg:max-w-none w-full lg:w-1/2 lg:order-2">
<div class="flex items-baseline space-x-2"> <div class="flex items-baseline space-x-2">
<span class="text-xs font-medium text-gray-600 dark:text-gray-200"> <span class="text-xs font-medium text-gray-600 dark:text-gray-200">
<%= List.first(@slider_labels) %> {List.first(@slider_labels)}
</span> </span>
<div class="flex-1 relative"> <div class="flex-1 relative">
<input <input
@ -64,7 +64,7 @@ defmodule PlausibleWeb.Components.Billing.PageviewSlider do
/> />
</div> </div>
<span class="text-xs font-medium text-gray-600 dark:text-gray-200"> <span class="text-xs font-medium text-gray-600 dark:text-gray-200">
<%= List.last(@slider_labels) %> {List.last(@slider_labels)}
</span> </span>
</div> </div>
</form> </form>

View File

@ -28,7 +28,7 @@ defmodule PlausibleWeb.Components.Billing.PlanBenefits do
<ul role="list" class={["mt-8 space-y-3 text-sm leading-6 xl:mt-10", @class]}> <ul role="list" class={["mt-8 space-y-3 text-sm leading-6 xl:mt-10", @class]}>
<li :for={benefit <- @benefits} class="flex gap-x-3"> <li :for={benefit <- @benefits} class="flex gap-x-3">
<Heroicons.check class="h-6 w-5 text-indigo-600 dark:text-green-600" /> <Heroicons.check class="h-6 w-5 text-indigo-600 dark:text-green-600" />
<%= if is_binary(benefit), do: benefit, else: benefit.(assigns) %> {if is_binary(benefit), do: benefit, else: benefit.(assigns)}
</li> </li>
</ul> </ul>
""" """

View File

@ -32,7 +32,7 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
!@highlight && "text-gray-900 dark:text-gray-100", !@highlight && "text-gray-900 dark:text-gray-100",
@highlight && "text-indigo-600 dark:text-indigo-300" @highlight && "text-indigo-600 dark:text-indigo-300"
]}> ]}>
<%= String.capitalize(to_string(@kind)) %> {String.capitalize(to_string(@kind))}
</h3> </h3>
<.pill :if={@highlight} text={@highlight} /> <.pill :if={@highlight} text={@highlight} />
</div> </div>
@ -98,7 +98,7 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
id="highlight-pill" id="highlight-pill"
class="rounded-full bg-indigo-600/10 px-2.5 py-1 text-xs font-semibold leading-5 text-indigo-600 dark:text-indigo-300 dark:ring-1 dark:ring-indigo-300/50" class="rounded-full bg-indigo-600/10 px-2.5 py-1 text-xs font-semibold leading-5 text-indigo-600 dark:text-indigo-300 dark:ring-1 dark:ring-indigo-300/50"
> >
<%= @text %> {@text}
</p> </p>
</div> </div>
""" """
@ -142,7 +142,7 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
id={"#{@kind}-price-tag-amount"} id={"#{@kind}-price-tag-amount"}
class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100" class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100"
> >
<%= @plan_to_render.monthly_cost |> Plausible.Billing.format_price() %> {@plan_to_render.monthly_cost |> Plausible.Billing.format_price()}
</span> </span>
<span <span
id={"#{@kind}-price-tag-interval"} id={"#{@kind}-price-tag-interval"}
@ -156,13 +156,13 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
defp price_tag(%{selected_interval: :yearly} = assigns) do defp price_tag(%{selected_interval: :yearly} = assigns) do
~H""" ~H"""
<span class="text-2xl font-bold w-max tracking-tight line-through text-gray-500 dark:text-gray-600 mr-1"> <span class="text-2xl font-bold w-max tracking-tight line-through text-gray-500 dark:text-gray-600 mr-1">
<%= @plan_to_render.monthly_cost |> Money.mult!(12) |> Plausible.Billing.format_price() %> {@plan_to_render.monthly_cost |> Money.mult!(12) |> Plausible.Billing.format_price()}
</span> </span>
<span <span
id={"#{@kind}-price-tag-amount"} id={"#{@kind}-price-tag-amount"}
class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100" class="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100"
> >
<%= @plan_to_render.yearly_cost |> Plausible.Billing.format_price() %> {@plan_to_render.yearly_cost |> Plausible.Billing.format_price()}
</span> </span>
<span id={"#{@kind}-price-tag-interval"} class="text-sm font-semibold leading-6 text-gray-600"> <span id={"#{@kind}-price-tag-interval"} class="text-sm font-semibold leading-6 text-gray-600">
/year /year
@ -234,13 +234,13 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
<% end %> <% end %>
<.tooltip :if={@exceeded_plan_limits != [] && @disabled_message}> <.tooltip :if={@exceeded_plan_limits != [] && @disabled_message}>
<div class="pt-2 text-sm w-full flex items-center text-red-700 dark:text-red-500 justify-center"> <div class="pt-2 text-sm w-full flex items-center text-red-700 dark:text-red-500 justify-center">
<%= @disabled_message %> {@disabled_message}
<Heroicons.information_circle class="hidden sm:block w-5 h-5 sm:ml-2" /> <Heroicons.information_circle class="hidden sm:block w-5 h-5 sm:ml-2" />
</div> </div>
<:tooltip_content> <:tooltip_content>
Your usage exceeds the following limit(s):<br /><br /> Your usage exceeds the following limit(s):<br /><br />
<p :for={limit <- @exceeded_plan_limits}> <p :for={limit <- @exceeded_plan_limits}>
<%= Phoenix.Naming.humanize(limit) %><br /> {Phoenix.Naming.humanize(limit)}<br />
</p> </p>
</:tooltip_content> </:tooltip_content>
</.tooltip> </.tooltip>
@ -248,7 +248,7 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
:if={@disabled_message && @exceeded_plan_limits == []} :if={@disabled_message && @exceeded_plan_limits == []}
class="pt-2 text-sm w-full text-red-700 dark:text-red-500 text-center" class="pt-2 text-sm w-full text-red-700 dark:text-red-500 text-center"
> >
<%= @disabled_message %> {@disabled_message}
</div> </div>
""" """
end end
@ -337,7 +337,7 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
@checkout_disabled && "pointer-events-none bg-gray-400 dark:bg-gray-600" @checkout_disabled && "pointer-events-none bg-gray-400 dark:bg-gray-600"
]} ]}
> >
<%= @change_plan_link_text %> {@change_plan_link_text}
</button> </button>
""" """
end end

View File

@ -34,25 +34,25 @@ defmodule PlausibleWeb.Components.FlowProgress do
:if={idx == @current_step_idx} :if={idx == @current_step_idx}
class="w-5 h-5 bg-indigo-600 text-white rounded-full flex items-center justify-center font-semibold" class="w-5 h-5 bg-indigo-600 text-white rounded-full flex items-center justify-center font-semibold"
> >
<%= idx + 1 %> {idx + 1}
</div> </div>
<div <div
:if={idx > @current_step_idx} :if={idx > @current_step_idx}
class="w-5 h-5 bg-gray-300 text-white dark:bg-gray-800 rounded-full flex items-center justify-center" class="w-5 h-5 bg-gray-300 text-white dark:bg-gray-800 rounded-full flex items-center justify-center"
> >
<%= idx + 1 %> {idx + 1}
</div> </div>
<span :if={idx < @current_step_idx} class="ml-2 text-gray-500"> <span :if={idx < @current_step_idx} class="ml-2 text-gray-500">
<%= step %> {step}
</span> </span>
<span <span
:if={idx == @current_step_idx} :if={idx == @current_step_idx}
class="ml-2 font-semibold text-black dark:text-gray-300" class="ml-2 font-semibold text-black dark:text-gray-300"
> >
<%= step %> {step}
</span> </span>
<span :if={idx > @current_step_idx} class="ml-2 text-gray-500"> <span :if={idx > @current_step_idx} class="ml-2 text-gray-500">
<%= step %> {step}
</span> </span>
</div> </div>
<div :if={idx + 1 != length(@steps)} class="flex-1 h-px bg-gray-300 mx-4 dark:bg-gray-800 "> <div :if={idx + 1 != length(@steps)} class="flex-1 h-px bg-gray-300 mx-4 dark:bg-gray-800 ">

View File

@ -63,7 +63,7 @@ defmodule PlausibleWeb.Components.Generic do
]} ]}
{@rest} {@rest}
> >
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</button> </button>
""" """
end end
@ -84,7 +84,7 @@ defmodule PlausibleWeb.Components.Generic do
[] []
else else
[ [
"data-csrf": Phoenix.HTML.Tag.csrf_token_value(assigns.href), "data-csrf": Phoenix.Controller.get_csrf_token(),
"data-method": assigns.method, "data-method": assigns.method,
"data-to": assigns.href "data-to": assigns.href
] ]
@ -126,7 +126,7 @@ defmodule PlausibleWeb.Components.Generic do
{@extra} {@extra}
{@rest} {@rest}
> >
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</.link> </.link>
""" """
end end
@ -178,11 +178,11 @@ defmodule PlausibleWeb.Components.Generic do
</div> </div>
<div class={["w-full", @title && "ml-3"]}> <div class={["w-full", @title && "ml-3"]}>
<h3 :if={@title} class={"font-medium #{@theme.title_text} mb-2"}> <h3 :if={@title} class={"font-medium #{@theme.title_text} mb-2"}>
<%= @title %> {@title}
</h3> </h3>
<div class={"#{@theme.body_text}"}> <div class={"#{@theme.body_text}"}>
<p> <p>
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</p> </p>
</div> </div>
</div> </div>
@ -216,7 +216,7 @@ defmodule PlausibleWeb.Components.Generic do
class={"text-indigo-600 hover:text-indigo-700 dark:text-indigo-500 dark:hover:text-indigo-600 " <> @class} class={"text-indigo-600 hover:text-indigo-700 dark:text-indigo-500 dark:hover:text-indigo-600 " <> @class}
{@rest} {@rest}
> >
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</.unstyled_link> </.unstyled_link>
""" """
end end
@ -241,10 +241,11 @@ defmodule PlausibleWeb.Components.Generic do
class="relative inline-block text-left" class="relative inline-block text-left"
> >
<button x-ref="button" x-on:click="toggle()" type="button" class={List.first(@button).class}> <button x-ref="button" x-on:click="toggle()" type="button" class={List.first(@button).class}>
<%= render_slot(List.first(@button)) %> {render_slot(List.first(@button))}
</button> </button>
<div <div
x-show="open" x-show="open"
x-cloak
x-transition:enter="transition ease-out duration-100" x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="opacity-0 scale-95" x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100" x-transition:enter-end="opacity-100 scale-100"
@ -258,7 +259,7 @@ defmodule PlausibleWeb.Components.Generic do
@menu_class @menu_class
]} ]}
> >
<%= render_slot(List.first(@menu)) %> {render_slot(List.first(@menu))}
</div> </div>
</div> </div>
""" """
@ -293,7 +294,7 @@ defmodule PlausibleWeb.Components.Generic do
data-ui-state={@state} data-ui-state={@state}
{@rest} {@rest}
> >
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</.unstyled_link> </.unstyled_link>
""" """
else else
@ -301,7 +302,7 @@ defmodule PlausibleWeb.Components.Generic do
~H""" ~H"""
<div data-ui-state={@state} class={@class}> <div data-ui-state={@state} class={@class}>
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</div> </div>
""" """
end end
@ -327,7 +328,7 @@ defmodule PlausibleWeb.Components.Generic do
[] []
else else
[ [
"data-csrf": Phoenix.HTML.Tag.csrf_token_value(assigns.href), "data-csrf": Phoenix.Controller.get_csrf_token(),
"data-method": assigns.method, "data-method": assigns.method,
"data-to": assigns.href "data-to": assigns.href
] ]
@ -350,13 +351,13 @@ defmodule PlausibleWeb.Components.Generic do
{@extra} {@extra}
{@rest} {@rest}
> >
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
<Heroicons.arrow_top_right_on_square class={["opacity-60", @icon_class]} /> <Heroicons.arrow_top_right_on_square class={["opacity-60", @icon_class]} />
</.link> </.link>
""" """
else else
~H""" ~H"""
<.link class={@class} href={@href} {@extra} {@rest}><%= render_slot(@inner_block) %></.link> <.link class={@class} href={@href} {@extra} {@rest}>{render_slot(@inner_block)}</.link>
""" """
end end
end end
@ -388,7 +389,7 @@ defmodule PlausibleWeb.Components.Generic do
def settings_tiles(assigns) do def settings_tiles(assigns) do
~H""" ~H"""
<div class="text-gray-900 leading-5 dark:text-gray-100"> <div class="text-gray-900 leading-5 dark:text-gray-100">
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</div> </div>
""" """
end end
@ -406,12 +407,12 @@ defmodule PlausibleWeb.Components.Generic do
<div class="shadow bg-white dark:bg-gray-800 rounded-md mb-6"> <div class="shadow bg-white dark:bg-gray-800 rounded-md mb-6">
<header class="relative py-4 px-6"> <header class="relative py-4 px-6">
<.title> <.title>
<%= render_slot(@title) %> {render_slot(@title)}
<.docs_info :if={@docs} slug={@docs} /> <.docs_info :if={@docs} slug={@docs} />
</.title> </.title>
<div class="text-sm mt-px text-gray-500 dark:text-gray-400 leading-5"> <div class="text-sm mt-px text-gray-500 dark:text-gray-400 leading-5">
<%= render_slot(@subtitle) %> {render_slot(@subtitle)}
</div> </div>
<%= if @feature_mod do %> <%= if @feature_mod do %>
<PlausibleWeb.Components.Site.Feature.toggle <PlausibleWeb.Components.Site.Feature.toggle
@ -424,7 +425,7 @@ defmodule PlausibleWeb.Components.Generic do
</header> </header>
<div class="pb-4 px-6"> <div class="pb-4 px-6">
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</div> </div>
</div> </div>
""" """
@ -455,7 +456,7 @@ defmodule PlausibleWeb.Components.Generic do
x-transition:leave-start="opacity-100" x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0" x-transition:leave-end="opacity-0"
> >
<%= render_slot(List.first(@tooltip_content)) %> {render_slot(List.first(@tooltip_content))}
</div> </div>
<div <div
x-on:click="sticky = true; hovered = true" x-on:click="sticky = true; hovered = true"
@ -463,7 +464,7 @@ defmodule PlausibleWeb.Components.Generic do
x-on:mouseover="hovered = true" x-on:mouseover="hovered = true"
x-on:mouseout="hovered = false" x-on:mouseout="hovered = false"
> >
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</div> </div>
</div> </div>
""" """
@ -500,7 +501,7 @@ defmodule PlausibleWeb.Components.Generic do
~H""" ~H"""
<ol class="list-disc space-y-1 ml-4 text-sm"> <ol class="list-disc space-y-1 ml-4 text-sm">
<li :for={item <- @item} class="marker:text-indigo-700 dark:marker:text-indigo-700"> <li :for={item <- @item} class="marker:text-indigo-700 dark:marker:text-indigo-700">
<%= render_slot(item) %> {render_slot(item)}
</li> </li>
</ol> </ol>
""" """
@ -520,20 +521,20 @@ defmodule PlausibleWeb.Components.Generic do
> >
<div class="p-8"> <div class="p-8">
<.title :if={@title != []}> <.title :if={@title != []}>
<%= render_slot(@title) %> {render_slot(@title)}
</.title> </.title>
<div></div> <div></div>
<div :if={@subtitle != []} class="text-sm mt-4 leading-6"> <div :if={@subtitle != []} class="text-sm mt-4 leading-6">
<%= render_slot(@subtitle) %> {render_slot(@subtitle)}
</div> </div>
<div :if={@title != []} class="mt-8"> <div :if={@title != []} class="mt-8">
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</div> </div>
<div :if={@title == []}> <div :if={@title == []}>
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</div> </div>
</div> </div>
<div <div
@ -541,7 +542,7 @@ defmodule PlausibleWeb.Components.Generic do
class="flex flex-col dark:text-gray-200 border-t border-gray-300 dark:border-gray-700" class="flex flex-col dark:text-gray-200 border-t border-gray-300 dark:border-gray-700"
> >
<div class="p-8"> <div class="p-8">
<%= render_slot(@footer) %> {render_slot(@footer)}
</div> </div>
</div> </div>
</div> </div>
@ -561,14 +562,14 @@ defmodule PlausibleWeb.Components.Generic do
<table :if={not Enum.empty?(@rows)} class={@width} {@rest}> <table :if={not Enum.empty?(@rows)} class={@width} {@rest}>
<thead :if={@thead != []}> <thead :if={@thead != []}>
<tr class="border-b border-gray-200 dark:border-gray-700"> <tr class="border-b border-gray-200 dark:border-gray-700">
<%= render_slot(@thead) %> {render_slot(@thead)}
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700"> <tbody class="divide-y divide-gray-200 dark:divide-gray-700">
<tr :for={item <- @rows} {if @row_attrs, do: @row_attrs.(item), else: %{}}> <tr :for={item <- @rows} {if @row_attrs, do: @row_attrs.(item), else: %{}}>
<%= render_slot(@tbody, item) %> {render_slot(@tbody, item)}
</tr> </tr>
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</tbody> </tbody>
</table> </table>
""" """
@ -605,10 +606,10 @@ defmodule PlausibleWeb.Components.Generic do
{@rest} {@rest}
> >
<div :if={@actions} class="flex gap-2"> <div :if={@actions} class="flex gap-2">
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</div> </div>
<div :if={!@actions}> <div :if={!@actions}>
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</div> </div>
</td> </td>
""" """
@ -630,7 +631,7 @@ defmodule PlausibleWeb.Components.Generic do
~H""" ~H"""
<th scope="col" class={[@hide_on_mobile && "hidden md:table-cell", @class]}> <th scope="col" class={[@hide_on_mobile && "hidden md:table-cell", @class]}>
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</th> </th>
""" """
end end
@ -667,7 +668,7 @@ defmodule PlausibleWeb.Components.Generic do
else: "text-gray-900 dark:text-gray-100" else: "text-gray-900 dark:text-gray-100"
) )
]}> ]}>
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</span> </span>
</div> </div>
""" """
@ -743,7 +744,7 @@ defmodule PlausibleWeb.Components.Generic do
</form> </form>
</div> </div>
</div> </div>
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</div> </div>
""" """
end end
@ -754,7 +755,7 @@ defmodule PlausibleWeb.Components.Generic do
def h2(assigns) do def h2(assigns) do
~H""" ~H"""
<h2 class={[@class || "font-semibold leading-6 text-gray-900 dark:text-gray-100"]}> <h2 class={[@class || "font-semibold leading-6 text-gray-900 dark:text-gray-100"]}>
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</h2> </h2>
""" """
end end
@ -765,7 +766,7 @@ defmodule PlausibleWeb.Components.Generic do
def title(assigns) do def title(assigns) do
~H""" ~H"""
<.h2 class={["text-lg font-medium text-gray-900 dark:text-gray-100 leading-7", @class]}> <.h2 class={["text-lg font-medium text-gray-900 dark:text-gray-100 leading-7", @class]}>
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</.h2> </.h2>
""" """
end end

View File

@ -26,12 +26,12 @@ defmodule PlausibleWeb.Components.Site.Feature do
class={@class} class={@class}
> >
<.toggle_submit set_to={@current_setting} disabled?={@disabled?}> <.toggle_submit set_to={@current_setting} disabled?={@disabled?}>
Show <%= @feature_mod.display_name() %> in the Dashboard Show {@feature_mod.display_name()} in the Dashboard
</.toggle_submit> </.toggle_submit>
</.form> </.form>
<div :if={@current_setting}> <div :if={@current_setting}>
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</div> </div>
</div> </div>
""" """

View File

@ -16,7 +16,7 @@ defmodule PlausibleWeb.Components.TwoFactor do
assigns = assign(assigns, :code, qr_code) assigns = assign(assigns, :code, qr_code)
~H""" ~H"""
<%= Phoenix.HTML.raw(@code) %> {Phoenix.HTML.raw(@code)}
""" """
end end
@ -38,25 +38,29 @@ defmodule PlausibleWeb.Components.TwoFactor do
end end
assigns = assign(assigns, :input_class, input_class) assigns = assign(assigns, :input_class, input_class)
assigns = assign(assigns, :field, assigns[:form][assigns[:field]])
~H""" ~H"""
<div class={[@class, "flex items-center"]}> <div class={[@class, "flex items-center"]}>
<%= Phoenix.HTML.Form.text_input(@form, @field, <input
autocomplete: "off", type="text"
class: @input_class, name={@field.name}
oninput: value={@field.value}
autocomplete="off"
class={@input_class}
oninput={
if @show_button? do if @show_button? do
"this.value=this.value.replace(/[^0-9]/g, ''); if (this.value.length >= 6) document.getElementById('#{@id}').focus()" "this.value=this.value.replace(/[^0-9]/g, ''); if (this.value.length >= 6) document.getElementById('#{@id}').focus()"
else else
"this.value=this.value.replace(/[^0-9]/g, '');" "this.value=this.value.replace(/[^0-9]/g, '');"
end, end
onclick: "this.select();", }
oninvalid: @show_button? && "document.getElementById('#{@id}').disabled = false", onclick="this.select();"
maxlength: "6", oninvalid={@show_button? && "document.getElementById('#{@id}').disabled = false"}
placeholder: "••••••", maxlength="6"
value: "", placeholder="••••••"
required: "required" required="required"
) %> />
<.button <.button
:if={@show_button?} :if={@show_button?}
type="submit" type="submit"
@ -142,19 +146,19 @@ defmodule PlausibleWeb.Components.TwoFactor do
</div> </div>
<div class="sm:flex sm:items-start"> <div class="sm:flex sm:items-start">
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-green-100 sm:mx-0 sm:h-10 sm:w-10"> <div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-green-100 sm:mx-0 sm:h-10 sm:w-10">
<%= render_slot(@icon) %> {render_slot(@icon)}
</div> </div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left text-gray-900 dark:text-gray-100"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left text-gray-900 dark:text-gray-100">
<h3 class="text-lg leading-6 font-medium" id="modal-title"> <h3 class="text-lg leading-6 font-medium" id="modal-title">
<%= @title %> {@title}
</h3> </h3>
<%= render_slot(@inner_block, f) %> {render_slot(@inner_block, f)}
</div> </div>
</div> </div>
</div> </div>
<div class="bg-gray-50 dark:bg-gray-850 px-4 py-3 sm:px-9 sm:flex sm:flex-row-reverse"> <div class="bg-gray-50 dark:bg-gray-850 px-4 py-3 sm:px-9 sm:flex sm:flex-row-reverse">
<%= render_slot(@buttons) %> {render_slot(@buttons)}
<.button <.button
type="button" type="button"
x-on:click={"#{@state_param} = false"} x-on:click={"#{@state_param} = false"}

View File

@ -57,6 +57,7 @@ defmodule PlausibleWeb.AuthController do
flow = params["flow"] || PlausibleWeb.Flows.register() flow = params["flow"] || PlausibleWeb.Flows.register()
render(conn, "activate.html", render(conn, "activate.html",
error: nil,
has_email_code?: Plausible.Users.has_email_code?(user), has_email_code?: Plausible.Users.has_email_code?(user),
has_any_memberships?: Plausible.Teams.Users.has_sites?(user), has_any_memberships?: Plausible.Teams.Users.has_sites?(user),
form_submit_url: "/activate?flow=#{flow}" form_submit_url: "/activate?flow=#{flow}"

View File

@ -3,7 +3,6 @@ defmodule PlausibleWeb.Live.ChoosePlan do
LiveView for upgrading to a plan, or changing an existing plan. LiveView for upgrading to a plan, or changing an existing plan.
""" """
use PlausibleWeb, :live_view use PlausibleWeb, :live_view
use Phoenix.HTML
require Plausible.Billing.Subscription.Status require Plausible.Billing.Subscription.Status
@ -107,9 +106,9 @@ defmodule PlausibleWeb.Live.ChoosePlan do
<Notice.upgrade_ineligible :if={not Quota.eligible_for_upgrade?(@usage)} /> <Notice.upgrade_ineligible :if={not Quota.eligible_for_upgrade?(@usage)} />
<div class="mx-auto max-w-4xl text-center"> <div class="mx-auto max-w-4xl text-center">
<p class="text-4xl font-bold tracking-tight lg:text-5xl"> <p class="text-4xl font-bold tracking-tight lg:text-5xl">
<%= if @owned_plan, {if @owned_plan,
do: "Change subscription plan", do: "Change subscription plan",
else: "Upgrade your account" %> else: "Upgrade your account"}
</p> </p>
</div> </div>
<div class="mt-12 flex flex-col gap-8 lg:flex-row items-center lg:items-baseline"> <div class="mt-12 flex flex-col gap-8 lg:flex-row items-center lg:items-baseline">

View File

@ -253,9 +253,9 @@ defmodule PlausibleWeb.Live.Components.ComboBox do
class="block truncate py-2 px-3" class="block truncate py-2 px-3"
> >
<%= if @creatable do %> <%= if @creatable do %>
Create "<%= @display_value %>" Create "{@display_value}"
<% else %> <% else %>
<%= @display_value %> {@display_value}
<% end %> <% end %>
</a> </a>
</li> </li>

View File

@ -18,7 +18,7 @@ defmodule PlausibleWeb.Live.Components.Form do
<.input name="my-input" errors={["oh no!"]} /> <.input name="my-input" errors={["oh no!"]} />
""" """
@default_input_class "text-sm dark:bg-gray-900 block pl-3.5 py-2.5 border-gray-300 dark:border-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 rounded-md" @default_input_class "text-sm text-gray-900 dark:text-white dark:bg-gray-900 block pl-3.5 py-2.5 border-gray-300 dark:border-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 rounded-md"
attr(:id, :any, default: nil) attr(:id, :any, default: nil)
attr(:name, :any) attr(:name, :any)
@ -55,6 +55,8 @@ defmodule PlausibleWeb.Live.Components.Form do
slot(:inner_block) slot(:inner_block)
def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
errors = if Phoenix.Component.used_input?(field), do: field.errors, else: []
assigns assigns
|> assign( |> assign(
field: nil, field: nil,
@ -63,7 +65,7 @@ defmodule PlausibleWeb.Live.Components.Form do
mt?: assigns.mt?, mt?: assigns.mt?,
width: assigns.width width: assigns.width
) )
|> assign(:errors, Enum.map(field.errors, &translate_error(&1))) |> assign(:errors, Enum.map(errors, &translate_error(&1)))
|> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end) |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
|> assign_new(:value, fn -> field.value end) |> assign_new(:value, fn -> field.value end)
|> input() |> input()
@ -71,27 +73,27 @@ defmodule PlausibleWeb.Live.Components.Form do
def input(%{type: "select"} = assigns) do def input(%{type: "select"} = assigns) do
~H""" ~H"""
<div phx-feedback-for={@name} class={@mt? && "mt-2"}> <div class={@mt? && "mt-2"}>
<.label for={@id} class="mb-2"><%= @label %></.label> <.label for={@id} class="mb-2">{@label}</.label>
<p :if={@help_text} class="text-gray-500 dark:text-gray-400 mb-2 text-sm"> <p :if={@help_text} class="text-gray-500 dark:text-gray-400 mb-2 text-sm">
<%= @help_text %> {@help_text}
</p> </p>
<select id={@id} name={@name} multiple={@multiple} class={[@class, @width]} {@rest}> <select id={@id} name={@name} multiple={@multiple} class={[@class, @width]} {@rest}>
<option :if={@prompt} value=""><%= @prompt %></option> <option :if={@prompt} value="">{@prompt}</option>
<%= Phoenix.HTML.Form.options_for_select(@options, @value) %> {Phoenix.HTML.Form.options_for_select(@options, @value)}
</select> </select>
<.error :for={msg <- @errors}><%= msg %></.error> <.error :for={msg <- @errors}>{msg}</.error>
</div> </div>
""" """
end end
def input(%{type: "checkbox"} = assigns) do def input(%{type: "checkbox"} = assigns) do
~H""" ~H"""
<div <div class={[
phx-feedback-for={@name} "flex flex-inline items-center sm:justify-start justify-center gap-x-2",
class={["flex flex-inline items-center sm:justify-start justify-center gap-x-2", @mt? && "mt-2"]} @mt? && "mt-2"
> ]}>
<input <input
type="checkbox" type="checkbox"
value={@value || "true"} value={@value || "true"}
@ -99,7 +101,7 @@ defmodule PlausibleWeb.Live.Components.Form do
name={@name} name={@name}
class="block h-5 w-5 rounded dark:bg-gray-700 border-gray-300 text-indigo-600 focus:ring-indigo-600" class="block h-5 w-5 rounded dark:bg-gray-700 border-gray-300 text-indigo-600 focus:ring-indigo-600"
/> />
<.label for={@id}><%= @label %></.label> <.label for={@id}>{@label}</.label>
</div> </div>
""" """
end end
@ -116,12 +118,12 @@ defmodule PlausibleWeb.Live.Components.Form do
assigns = assign(assigns, :errors, errors) assigns = assign(assigns, :errors, errors)
~H""" ~H"""
<div phx-feedback-for={@name} class={@mt? && "mt-2"}> <div class={@mt? && "mt-2"}>
<.label :if={@label != nil and @label != ""} for={@id} class="mb-2"> <.label :if={@label != nil and @label != ""} for={@id} class="mb-2">
<%= @label %> {@label}
</.label> </.label>
<p :if={@help_text} class="text-gray-500 dark:text-gray-400 mb-2 text-sm"> <p :if={@help_text} class="text-gray-500 dark:text-gray-400 mb-2 text-sm">
<%= @help_text %> {@help_text}
</p> </p>
<input <input
type={@type} type={@type}
@ -131,9 +133,9 @@ defmodule PlausibleWeb.Live.Components.Form do
class={[@class, @width, assigns[:rest][:disabled] && "text-gray-500 dark:text-gray-400"]} class={[@class, @width, assigns[:rest][:disabled] && "text-gray-500 dark:text-gray-400"]}
{@rest} {@rest}
/> />
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
<.error :for={msg <- @errors}> <.error :for={msg <- @errors}>
<%= msg %> {msg}
</.error> </.error>
</div> </div>
""" """
@ -153,7 +155,7 @@ defmodule PlausibleWeb.Live.Components.Form do
<div> <div>
<div :if={@label}> <div :if={@label}>
<.label for={@id} class="mb-2"> <.label for={@id} class="mb-2">
<%= @label %> {@label}
</.label> </.label>
</div> </div>
<div class="relative"> <div class="relative">
@ -218,10 +220,14 @@ defmodule PlausibleWeb.Live.Components.Form do
|> assign(:too_weak?, too_weak?) |> assign(:too_weak?, too_weak?)
|> assign(:field, %{field | errors: errors}) |> assign(:field, %{field | errors: errors})
|> assign(:strength, strength) |> assign(:strength, strength)
|> assign(
:show_meter?,
Phoenix.Component.used_input?(field) && (too_weak? || strength.score > 0)
)
~H""" ~H"""
<.input field={@field} type="password" autocomplete="new-password" label={@label} id={@id} {@rest}> <.input field={@field} type="password" autocomplete="new-password" label={@label} id={@id} {@rest}>
<.strength_meter :if={@too_weak? or @strength.score > 0} {@strength} /> <.strength_meter :if={@show_meter?} {@strength} />
</.input> </.input>
""" """
end end
@ -256,7 +262,7 @@ defmodule PlausibleWeb.Live.Components.Form do
assigns = assign(assigns, :class, final_class) assigns = assign(assigns, :class, final_class)
~H""" ~H"""
<p class={@class}>Min <%= @minimum %> characters</p> <p class={@class}>Min {@minimum} characters</p>
""" """
end end
@ -311,11 +317,11 @@ defmodule PlausibleWeb.Live.Components.Form do
> >
</div> </div>
</div> </div>
<p :if={@score <= 2} class="text-sm text-red-500 phx-no-feedback:hidden"> <p :if={@score <= 2} class="text-sm text-red-500">
Password is too weak Password is too weak
</p> </p>
<p :if={@feedback} class="text-xs text-gray-500"> <p :if={@feedback} class="text-xs text-gray-500">
<%= @feedback %> {@feedback}
</p> </p>
""" """
end end
@ -330,7 +336,7 @@ defmodule PlausibleWeb.Live.Components.Form do
def label(assigns) do def label(assigns) do
~H""" ~H"""
<label for={@for} class={["text-sm block font-medium dark:text-gray-100", @class]}> <label for={@for} class={["text-sm block font-medium dark:text-gray-100", @class]}>
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</label> </label>
""" """
end end
@ -342,8 +348,8 @@ defmodule PlausibleWeb.Live.Components.Form do
def error(assigns) do def error(assigns) do
~H""" ~H"""
<p class="flex gap-3 text-sm leading-6 text-red-500 phx-no-feedback:hidden"> <p class="flex gap-3 text-sm leading-6 text-red-500">
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</p> </p>
""" """
end end

View File

@ -260,7 +260,7 @@ defmodule PlausibleWeb.Live.Components.Modal do
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-on:click.outside="closeModal()" x-on:click.outside="closeModal()"
> >
<%= render_slot(@inner_block, modal_unique_id(@modal_sequence_id)) %> {render_slot(@inner_block, modal_unique_id(@modal_sequence_id))}
</Phoenix.Component.focus_wrap> </Phoenix.Component.focus_wrap>
<div x-show="modalOpen" class="modal-loading hidden w-full self-center"> <div x-show="modalOpen" class="modal-loading hidden w-full self-center">
<div class="text-center"> <div class="text-center">

View File

@ -12,7 +12,7 @@ defmodule PlausibleWeb.Live.Components.Pagination do
> >
<div class="hidden sm:block"> <div class="hidden sm:block">
<p class="text-sm text-gray-700 dark:text-gray-300"> <p class="text-sm text-gray-700 dark:text-gray-300">
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</p> </p>
</div> </div>
<div class="flex-1 flex justify-between sm:justify-end"> <div class="flex-1 flex justify-between sm:justify-end">

View File

@ -59,7 +59,7 @@ defmodule PlausibleWeb.Live.Components.Verification do
<span :if={not @finished?}>Verifying your installation</span> <span :if={not @finished?}>Verifying your installation</span>
<span :if={@finished? and not @success? and @interpretation}> <span :if={@finished? and not @success? and @interpretation}>
<%= List.first(@interpretation.errors) %> {List.first(@interpretation.errors)}
</span> </span>
</.title> </.title>
<p :if={@finished? and @success?} id="progress" class="text-sm mt-4"> <p :if={@finished? and @success?} id="progress" class="text-sm mt-4">
@ -67,19 +67,19 @@ defmodule PlausibleWeb.Live.Components.Verification do
</p> </p>
<p <p
:if={@finished? and @success? and @awaiting_first_pageview?} :if={@finished? and @success? and @awaiting_first_pageview?}
id="progress" id="awaiting"
class="text-sm mt-4 animate-pulse" class="text-sm mt-4 animate-pulse"
> >
Awaiting your first pageview Awaiting your first pageview
</p> </p>
<p :if={not @finished?} class="text-sm mt-4 animate-pulse" id="progress"><%= @message %></p> <p :if={not @finished?} class="text-sm mt-4 animate-pulse" id="progress">{@message}</p>
<p <p
:if={@finished? and not @success? and @interpretation} :if={@finished? and not @success? and @interpretation}
class="mt-4 text-sm text-ellipsis overflow-hidden" class="mt-4 text-sm text-ellipsis overflow-hidden"
id="recommendation" id="recommendation"
> >
<span><%= List.first(@interpretation.recommendations).text %>.&nbsp;</span> <span>{List.first(@interpretation.recommendations).text}.&nbsp;</span>
<.styled_link href={List.first(@interpretation.recommendations).url} new_tab={true}> <.styled_link href={List.first(@interpretation.recommendations).url} new_tab={true}>
Learn more Learn more
</.styled_link> </.styled_link>
@ -145,7 +145,7 @@ defmodule PlausibleWeb.Live.Components.Verification do
<.focus_list> <.focus_list>
<:item :for={{diag, value} <- Map.from_struct(@verification_state.diagnostics)}> <:item :for={{diag, value} <- Map.from_struct(@verification_state.diagnostics)}>
<span class="text-sm"> <span class="text-sm">
<%= Phoenix.Naming.humanize(diag) %>: <span class="font-mono"><%= value %></span> {Phoenix.Naming.humanize(diag)}: <span class="font-mono">{value}</span>
</span> </span>
</:item> </:item>
</.focus_list> </.focus_list>

View File

@ -173,7 +173,7 @@ defmodule PlausibleWeb.Live.CSVExport do
<:tbody :let={export}> <:tbody :let={export}>
<.td> <.td>
<.styled_link href={@href}> <.styled_link href={@href}>
<%= export.name %> {export.name}
</.styled_link> </.styled_link>
</.td> </.td>
<.td actions> <.td actions>
@ -188,14 +188,14 @@ defmodule PlausibleWeb.Live.CSVExport do
<p :if={@export.expires_at} class="text-sm"> <p :if={@export.expires_at} class="text-sm">
Note: this file will expire Note: this file will expire
<.hint message={@export.expires_at}> <.hint message={@export.expires_at}>
<%= Timex.Format.DateTime.Formatters.Relative.format!(@export.expires_at, "{relative}") %>. {Timex.Format.DateTime.Formatters.Relative.format!(@export.expires_at, "{relative}")}.
</.hint> </.hint>
</p> </p>
<p :if={@storage == "local"} class="text-sm"> <p :if={@storage == "local"} class="text-sm">
Located at Located at
<.hint message={@export.path}><%= format_path(@export.path) %></.hint> <.hint message={@export.path}>{format_path(@export.path)}</.hint>
(<%= format_bytes(@export.size) %>) ({format_bytes(@export.size)})
</p> </p>
""" """
end end
@ -203,7 +203,7 @@ defmodule PlausibleWeb.Live.CSVExport do
defp hint(assigns) do defp hint(assigns) do
~H""" ~H"""
<span title={@message} class="underline cursor-help underline-offset-2 decoration-dashed"> <span title={@message} class="underline cursor-help underline-offset-2 decoration-dashed">
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</span> </span>
""" """
end end

View File

@ -87,7 +87,7 @@ defmodule PlausibleWeb.Live.CSVImport do
original={@original_date_range} original={@original_date_range}
/> />
<p :for={error <- upload_errors(@uploads.import)} class="text-red-400"> <p :for={error <- upload_errors(@uploads.import)} class="text-red-400">
<%= error_to_string(error) %> {error_to_string(error)}
</p> </p>
</form> </form>
</div> </div>
@ -151,8 +151,8 @@ defmodule PlausibleWeb.Live.CSVImport do
defp dates(assigns) do defp dates(assigns) do
~H""" ~H"""
<span class="whitespace-nowrap"> <span class="whitespace-nowrap">
<span class="font-medium"><%= @range.first %></span> <span class="font-medium">{@range.first}</span>
to <span class="font-medium"><%= @range.last %></span> to <span class="font-medium">{@range.last}</span>
</span> </span>
""" """
end end
@ -182,15 +182,15 @@ defmodule PlausibleWeb.Live.CSVImport do
if(@status == :error, do: "text-red-600 dark:text-red-700") if(@status == :error, do: "text-red-600 dark:text-red-700")
]}> ]}>
<%= if @upload do %> <%= if @upload do %>
<%= @upload.client_name %> {@upload.client_name}
<% else %> <% else %>
<%= @table %>_YYYYMMDD_YYYYMMDD.csv {@table}_YYYYMMDD_YYYYMMDD.csv
<% end %> <% end %>
</span> </span>
</div> </div>
<p :for={error <- @errors} class="ml-6 text-sm text-red-600 dark:text-red-700"> <p :for={error <- @errors} class="ml-6 text-sm text-red-600 dark:text-red-700">
<%= error_to_string(error) %> {error_to_string(error)}
</p> </p>
</li> </li>
""" """

View File

@ -55,10 +55,10 @@ defmodule PlausibleWeb.Live.Flash do
<.icon_success /> <.icon_success />
</:icon> </:icon>
<:title> <:title>
<%= Flash.get(@flash, :success_title) || "Success!" %> {Flash.get(@flash, :success_title) || "Success!"}
</:title> </:title>
<:message> <:message>
<%= Flash.get(@flash, :success) %> {Flash.get(@flash, :success)}
</:message> </:message>
</.flash> </.flash>
<.flash :if={Flash.get(@flash, :error)} key="error"> <.flash :if={Flash.get(@flash, :error)} key="error">
@ -66,10 +66,10 @@ defmodule PlausibleWeb.Live.Flash do
<.icon_error /> <.icon_error />
</:icon> </:icon>
<:title> <:title>
<%= Flash.get(@flash, :error_title) || "Error!" %> {Flash.get(@flash, :error_title) || "Error!"}
</:title> </:title>
<:message> <:message>
<%= Flash.get(@flash, :error) %> {Flash.get(@flash, :error)}
</:message> </:message>
</.flash> </.flash>
<.flash <.flash
@ -96,7 +96,7 @@ defmodule PlausibleWeb.Live.Flash do
end end
slot(:icon, required: true) slot(:icon, required: true)
slot(:title, require: true) slot(:title, required: true)
slot(:message, required: true) slot(:message, required: true)
attr(:key, :string, default: nil) attr(:key, :string, default: nil)
attr(:on_close, :any, default: "lv:clear-flash") attr(:on_close, :any, default: "lv:clear-flash")
@ -115,13 +115,13 @@ defmodule PlausibleWeb.Live.Flash do
<div class="rounded-lg ring-1 ring-black ring-opacity-5 overflow-hidden"> <div class="rounded-lg ring-1 ring-black ring-opacity-5 overflow-hidden">
<div class="p-4"> <div class="p-4">
<div class="flex items-start"> <div class="flex items-start">
<%= render_slot(@icon) %> {render_slot(@icon)}
<div class="ml-3 w-0 flex-1 pt-0.5"> <div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100"> <p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100">
<%= render_slot(@title) %> {render_slot(@title)}
</p> </p>
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200"> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
<%= render_slot(@message) %> {render_slot(@message)}
</p> </p>
</div> </div>
<div class="ml-4 flex-shrink-0 flex"> <div class="ml-4 flex-shrink-0 flex">

View File

@ -56,8 +56,8 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
def render(assigns) do def render(assigns) do
~H""" ~H"""
<div id={@id}> <div id={@id}>
<%= if @goal, do: edit_form(assigns) %> {if @goal, do: edit_form(assigns)}
<%= if is_nil(@goal), do: create_form(assigns) %> {if is_nil(@goal), do: create_form(assigns)}
</div> </div>
""" """
end end
@ -65,7 +65,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
def edit_form(assigns) do def edit_form(assigns) do
~H""" ~H"""
<.form :let={f} for={@form} phx-submit="save-goal" phx-target={@myself}> <.form :let={f} for={@form} phx-submit="save-goal" phx-target={@myself}>
<.title>Edit Goal for <%= @domain %></.title> <.title>Edit Goal for {@domain}</.title>
<.custom_event_fields <.custom_event_fields
:if={@selected_tab == "custom_events"} :if={@selected_tab == "custom_events"}
@ -105,7 +105,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
> >
<.spinner class="spinner block absolute right-9 top-8" x-show="tabSelectionInProgress" /> <.spinner class="spinner block absolute right-9 top-8" x-show="tabSelectionInProgress" />
<.title>Add Goal for <%= @domain %></.title> <.title>Add Goal for {@domain}</.title>
<.tabs selected_tab={@selected_tab} myself={@myself} /> <.tabs selected_tab={@selected_tab} myself={@myself} />
@ -145,7 +145,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
phx-target={@myself} phx-target={@myself}
> >
<span :if={@event_name_options_count > 1}> <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. 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>
<span :if={@event_name_options_count == 1}> <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. 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.
@ -182,7 +182,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
/> />
<.error :for={msg <- Enum.map(@f[:page_path].errors, &translate_error/1)}> <.error :for={msg <- Enum.map(@f[:page_path].errors, &translate_error/1)}>
<%= msg %> {msg}
</.error> </.error>
<.input <.input
@ -241,7 +241,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
/> />
<.error :for={msg <- Enum.map(@f[:event_name].errors, &translate_error/1)}> <.error :for={msg <- Enum.map(@f[:event_name].errors, &translate_error/1)}>
<%= msg %> {msg}
</.error> </.error>
</div> </div>

View File

@ -36,7 +36,7 @@ defmodule PlausibleWeb.Live.GoalSettings.List do
<div class="truncate block"> <div class="truncate block">
<%= if not @revenue_goals_enabled? && goal.currency do %> <%= if not @revenue_goals_enabled? && goal.currency do %>
<div class="truncate"> <div class="truncate">
<%= goal %> {goal}
<br /> <br />
<span class="text-red-600"> <span class="text-red-600">
Unlock Revenue Goals by upgrading to a business plan Unlock Revenue Goals by upgrading to a business plan
@ -44,7 +44,7 @@ defmodule PlausibleWeb.Live.GoalSettings.List do
</div> </div>
<% else %> <% else %>
<.goal_description goal={goal} /> <.goal_description goal={goal} />
<span><%= goal %></span> <span>{goal}</span>
<% end %> <% end %>
</div> </div>
</div> </div>
@ -115,11 +115,11 @@ defmodule PlausibleWeb.Live.GoalSettings.List do
def goal_description(assigns) do def goal_description(assigns) do
~H""" ~H"""
<span :if={@goal.page_path} class="block truncate text-gray-400 dark:text-gray-600"> <span :if={@goal.page_path} class="block truncate text-gray-400 dark:text-gray-600">
<%= pageview_description(@goal) %> {pageview_description(@goal)}
</span> </span>
<span :if={@goal.event_name} class="block truncate text-gray-400 dark:text-gray-600"> <span :if={@goal.event_name} class="block truncate text-gray-400 dark:text-gray-600">
<%= custom_event_description(@goal) %> {custom_event_description(@goal)}
</span> </span>
""" """
end end

View File

@ -68,7 +68,7 @@ defmodule PlausibleWeb.Live.ImportsExportsSettings do
~H""" ~H"""
<.notice :if={@import_warning} theme={:gray}> <.notice :if={@import_warning} theme={:gray}>
<%= @import_warning %> {@import_warning}
</.notice> </.notice>
<div class="mt-4 flex justify-end gap-x-4"> <div class="mt-4 flex justify-end gap-x-4">
@ -132,24 +132,22 @@ defmodule PlausibleWeb.Live.ImportsExportsSettings do
class="max-w-sm" class="max-w-sm"
title={"#{Plausible.Imported.SiteImport.label(entry.site_import)} created at #{format_date(entry.site_import.inserted_at)}"} title={"#{Plausible.Imported.SiteImport.label(entry.site_import)} created at #{format_date(entry.site_import.inserted_at)}"}
> >
<%= Plausible.Imported.SiteImport.label(entry.site_import) %> {Plausible.Imported.SiteImport.label(entry.site_import)}
</div> </div>
</div> </div>
</.td> </.td>
<.td hide_on_mobile> <.td hide_on_mobile>
<%= format_date(entry.site_import.start_date) %> - <%= format_date( {format_date(entry.site_import.start_date)} - {format_date(entry.site_import.end_date)}
entry.site_import.end_date
) %>
</.td> </.td>
<.td> <.td>
<div class="text-right"> <div class="text-right">
<%= if entry.live_status == SiteImport.completed(), {if entry.live_status == SiteImport.completed(),
do: do:
PlausibleWeb.StatsView.large_number_format( PlausibleWeb.StatsView.large_number_format(
pageview_count(entry.site_import, @pageview_counts) pageview_count(entry.site_import, @pageview_counts)
) %> )}
</div> </div>
</.td> </.td>
<.td actions> <.td actions>

View File

@ -293,12 +293,12 @@ defmodule PlausibleWeb.Live.Installation do
class="block h-5 w-5 rounded dark:bg-gray-700 border-gray-300 text-indigo-600 focus:ring-indigo-600 mr-2" class="block h-5 w-5 rounded dark:bg-gray-700 border-gray-300 text-indigo-600 focus:ring-indigo-600 mr-2"
/> />
<label for={"check-#{@variant}"}> <label for={"check-#{@variant}"}>
<%= @label %> {@label}
</label> </label>
<div class="ml-2 collapse md:visible"> <div class="ml-2 collapse md:visible">
<.tooltip sticky?={false}> <.tooltip sticky?={false}>
<:tooltip_content> <:tooltip_content>
<%= @tooltip %> {@tooltip}
<br /><br />Click to learn more. <br /><br />Click to learn more.
</:tooltip_content> </:tooltip_content>
<a href={@learn_more} target="_blank" rel="noopener noreferrer"> <a href={@learn_more} target="_blank" rel="noopener noreferrer">

View File

@ -35,7 +35,7 @@ defmodule PlausibleWeb.Live.Plugins.API.Settings do
<.flash_messages flash={@flash} /> <.flash_messages flash={@flash} />
<%= if @add_token? do %> <%= if @add_token? do %>
<%= live_render( {live_render(
@socket, @socket,
PlausibleWeb.Live.Plugins.API.TokenForm, PlausibleWeb.Live.Plugins.API.TokenForm,
id: "token-form", id: "token-form",
@ -44,7 +44,7 @@ defmodule PlausibleWeb.Live.Plugins.API.Settings do
"token_description" => @token_description, "token_description" => @token_description,
"rendered_by" => self() "rendered_by" => self()
} }
) %> )}
<% end %> <% end %>
<div> <div>
@ -64,14 +64,14 @@ defmodule PlausibleWeb.Live.Plugins.API.Settings do
<:tbody :let={token}> <:tbody :let={token}>
<.td> <.td>
<span class="token-description"> <span class="token-description">
<%= token.description %> {token.description}
</span> </span>
</.td> </.td>
<.td hide_on_mobile> <.td hide_on_mobile>
**********<%= token.hint %> **********{token.hint}
</.td> </.td>
<.td hide_on_mobile> <.td hide_on_mobile>
<%= Plausible.Plugins.API.Token.last_used_humanize(token) %> {Plausible.Plugins.API.Token.last_used_humanize(token)}
</.td> </.td>
<.td actions> <.td actions>
<.delete_button <.delete_button

View File

@ -58,7 +58,7 @@ defmodule PlausibleWeb.Live.Plugins.API.TokenForm do
phx-click-away="cancel-add-token" phx-click-away="cancel-add-token"
> >
<.title> <.title>
Add Plugin Token for <%= @domain %> Add Plugin Token for {@domain}
</.title> </.title>
<div class="mt-4"> <div class="mt-4">

View File

@ -39,7 +39,7 @@ defmodule PlausibleWeb.Live.PropsSettings do
<section id="props-settings-main"> <section id="props-settings-main">
<.flash_messages flash={@flash} /> <.flash_messages flash={@flash} />
<%= if @add_prop? do %> <%= if @add_prop? do %>
<%= live_render( {live_render(
@socket, @socket,
PlausibleWeb.Live.PropsSettings.Form, PlausibleWeb.Live.PropsSettings.Form,
id: "props-form", id: "props-form",
@ -48,7 +48,7 @@ defmodule PlausibleWeb.Live.PropsSettings do
"site_id" => @site_id, "site_id" => @site_id,
"rendered_by" => self() "rendered_by" => self()
} }
) %> )}
<% end %> <% end %>
<.live_component <.live_component

View File

@ -53,7 +53,7 @@ defmodule PlausibleWeb.Live.PropsSettings.Form do
phx-submit="allow-prop" phx-submit="allow-prop"
phx-click-away="cancel-allow-prop" phx-click-away="cancel-allow-prop"
> >
<.title>Add Property for <%= @domain %></.title> <.title>Add Property for {@domain}</.title>
<div class="mt-6"> <div class="mt-6">
<.label for="prop_input"> <.label for="prop_input">
@ -89,9 +89,9 @@ defmodule PlausibleWeb.Live.PropsSettings.Form do
/> />
<.error :for={{msg, opts} <- f[:allowed_event_props].errors}> <.error :for={{msg, opts} <- f[:allowed_event_props].errors}>
<%= Enum.reduce(opts, msg, fn {key, value}, acc -> {Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end) String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end)
end) %> end)}
</.error> </.error>
</div> </div>
@ -105,7 +105,7 @@ defmodule PlausibleWeb.Live.PropsSettings.Form do
class="mt-4 text-sm hover:underline text-indigo-600 dark:text-indigo-400 text-left" class="mt-4 text-sm hover:underline text-indigo-600 dark:text-indigo-400 text-left"
phx-click="allow-existing-props" phx-click="allow-existing-props"
> >
Already sending custom properties? Click to add <%= @prop_key_options_count %> existing properties we found. Already sending custom properties? Click to add {@prop_key_options_count} existing properties we found.
</button> </button>
</.form> </.form>
</div> </div>

View File

@ -19,7 +19,7 @@ defmodule PlausibleWeb.Live.PropsSettings.List do
<%= if is_list(@props) && length(@props) > 0 do %> <%= if is_list(@props) && length(@props) > 0 do %>
<.table id="allowed-props" rows={Enum.with_index(@props)}> <.table id="allowed-props" rows={Enum.with_index(@props)}>
<:tbody :let={{prop, index}}> <:tbody :let={{prop, index}}>
<.td id={"prop-#{index}"}><span class="font-medium"><%= prop %></span></.td> <.td id={"prop-#{index}"}><span class="font-medium">{prop}</span></.td>
<.td actions> <.td actions>
<.delete_button <.delete_button
id={"disallow-prop-#{prop}"} id={"disallow-prop-#{prop}"}

View File

@ -42,7 +42,7 @@ defmodule PlausibleWeb.Live.RegisterForm do
def render(%{invitation_expired: true} = assigns) do def render(%{invitation_expired: true} = assigns) do
~H""" ~H"""
<div class="mx-auto mt-6 text-center dark:text-gray-300"> <div class="mx-auto mt-6 text-center dark:text-gray-300">
<h1 class="text-3xl font-black"><%= Plausible.product_name() %></h1> <h1 class="text-3xl font-black">{Plausible.product_name()}</h1>
<div class="text-xl font-medium">Lightweight and privacy-friendly web analytics</div> <div class="text-xl font-medium">Lightweight and privacy-friendly web analytics</div>
</div> </div>
@ -63,7 +63,7 @@ defmodule PlausibleWeb.Live.RegisterForm do
<div class="mx-auto text-center dark:text-gray-300"> <div class="mx-auto text-center dark:text-gray-300">
<h1 class="text-3xl font-black"> <h1 class="text-3xl font-black">
<%= if ce?() or @live_action == :register_from_invitation_form do %> <%= if ce?() or @live_action == :register_from_invitation_form do %>
Register your <%= Plausible.product_name() %> account Register your {Plausible.product_name()} account
<% else %> <% else %>
Register your 30-day free trial Register your 30-day free trial
<% end %> <% end %>
@ -155,7 +155,7 @@ defmodule PlausibleWeb.Live.RegisterForm do
</div> </div>
<%= if @captcha_error do %> <%= if @captcha_error do %>
<div class="text-red-500 text-xs italic mt-3" x-data x-init="hcaptcha.reset()"> <div class="text-red-500 text-xs italic mt-3" x-data x-init="hcaptcha.reset()">
<%= @captcha_error %> {@captcha_error}
</div> </div>
<% end %> <% end %>
<script <script
@ -176,7 +176,7 @@ defmodule PlausibleWeb.Live.RegisterForm do
"Start my free trial" "Start my free trial"
end %> end %>
<.button id="register" disabled={@disable_submit} type="submit" class="mt-4 w-full"> <.button id="register" disabled={@disable_submit} type="submit" class="mt-4 w-full">
<%= submit_text %> {submit_text}
</.button> </.button>
<p class="text-center text-gray-600 dark:text-gray-500 mt-4"> <p class="text-center text-gray-600 dark:text-gray-500 mt-4">

View File

@ -53,7 +53,7 @@ defmodule PlausibleWeb.Live.Shields.CountryRules do
theme={:gray} theme={:gray}
> >
<p> <p>
You've reached the maximum number of countries you can block (<%= Shields.maximum_country_rules() %>). Please remove one before adding another. You've reached the maximum number of countries you can block ({Shields.maximum_country_rules()}). Please remove one before adding another.
</p> </p>
</.notice> </.notice>
@ -76,7 +76,7 @@ defmodule PlausibleWeb.Live.Shields.CountryRules do
class="mr-4 cursor-help" class="mr-4 cursor-help"
title={"Added at #{format_added_at(rule.inserted_at, @site.timezone)} by #{rule.added_by}"} title={"Added at #{format_added_at(rule.inserted_at, @site.timezone)} by #{rule.added_by}"}
> >
<%= country.flag %> <%= country.name %> {country.flag} {country.name}
</span> </span>
</div> </div>
</.td> </.td>

View File

@ -9,8 +9,6 @@ defmodule PlausibleWeb.Live.Shields.HostnameRules do
alias Plausible.Shields alias Plausible.Shields
alias Plausible.Shield alias Plausible.Shield
import PlausibleWeb.ErrorHelpers
def update(assigns, socket) do def update(assigns, socket) do
socket = socket =
socket socket
@ -58,7 +56,7 @@ defmodule PlausibleWeb.Live.Shields.HostnameRules do
theme={:gray} theme={:gray}
> >
<p> <p>
You've reached the maximum number of hostnames you can block (<%= Shields.maximum_hostname_rules() %>). Please remove one before adding another. You've reached the maximum number of hostnames you can block ({Shields.maximum_hostname_rules()}). Please remove one before adding another.
</p> </p>
</.notice> </.notice>
@ -83,7 +81,7 @@ defmodule PlausibleWeb.Live.Shields.HostnameRules do
class="mr-4 cursor-help text-ellipsis truncate max-w-xs" class="mr-4 cursor-help text-ellipsis truncate max-w-xs"
title={"Added at #{format_added_at(rule.inserted_at, @site.timezone)} by #{rule.added_by}"} title={"Added at #{format_added_at(rule.inserted_at, @site.timezone)} by #{rule.added_by}"}
> >
<%= rule.hostname %> {rule.hostname}
</span> </span>
</div> </div>
</.td> </.td>
@ -136,12 +134,11 @@ defmodule PlausibleWeb.Live.Shields.HostnameRules do
id={"#{f[:hostname].id}-#{modal_unique_id}"} id={"#{f[:hostname].id}-#{modal_unique_id}"}
creatable creatable
/> />
<.error :for={msg <- f[:hostname].errors}>{translate_error(msg)}</.error>
<%= error_tag(f, :hostname) %>
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400"> <p class="mt-4 text-sm text-gray-500 dark:text-gray-400">
You can use a wildcard (<code>*</code>) to match multiple hostnames. For example, You can use a wildcard (<code>*</code>) to match multiple hostnames. For example,
<code>*<%= @site.domain %></code> <code>*{@site.domain}</code>
will only record traffic on your main domain and all of its subdomains.<br /><br /> will only record traffic on your main domain and all of its subdomains.<br /><br />
<%= if @hostname_rules_count >= 1 do %> <%= if @hostname_rules_count >= 1 do %>

View File

@ -51,7 +51,7 @@ defmodule PlausibleWeb.Live.Shields.IPRules do
theme={:gray} theme={:gray}
> >
<p> <p>
You've reached the maximum number of IP addresses you can block (<%= Shields.maximum_ip_rules() %>). Please remove one before adding another. You've reached the maximum number of IP addresses you can block ({Shields.maximum_ip_rules()}). Please remove one before adding another.
</p> </p>
</.notice> </.notice>
@ -83,7 +83,7 @@ defmodule PlausibleWeb.Live.Shields.IPRules do
class="cursor-help" class="cursor-help"
title={"Added at #{format_added_at(rule.inserted_at, @site.timezone)} by #{rule.added_by}"} title={"Added at #{format_added_at(rule.inserted_at, @site.timezone)} by #{rule.added_by}"}
> >
<%= rule.inet %> {rule.inet}
</span> </span>
</div> </div>
</.td> </.td>
@ -97,7 +97,7 @@ defmodule PlausibleWeb.Live.Shields.IPRules do
</.td> </.td>
<.td hide_on_mobile truncate> <.td hide_on_mobile truncate>
<span :if={rule.description} title={rule.description}> <span :if={rule.description} title={rule.description}>
<%= rule.description %> {rule.description}
</span> </span>
<span :if={!rule.description} class="text-gray-400 dark:text-gray-600"> <span :if={!rule.description} class="text-gray-400 dark:text-gray-600">
-- --

View File

@ -9,8 +9,6 @@ defmodule PlausibleWeb.Live.Shields.PageRules do
alias Plausible.Shields alias Plausible.Shields
alias Plausible.Shield alias Plausible.Shield
import PlausibleWeb.ErrorHelpers
def update(assigns, socket) do def update(assigns, socket) do
socket = socket =
socket socket
@ -58,7 +56,7 @@ defmodule PlausibleWeb.Live.Shields.PageRules do
theme={:gray} theme={:gray}
> >
<p> <p>
You've reached the maximum number of pages you can block (<%= Shields.maximum_page_rules() %>). Please remove one before adding another. You've reached the maximum number of pages you can block ({Shields.maximum_page_rules()}). Please remove one before adding another.
</p> </p>
</.notice> </.notice>
@ -79,7 +77,7 @@ defmodule PlausibleWeb.Live.Shields.PageRules do
class="mr-4 cursor-help text-ellipsis truncate max-w-xs" class="mr-4 cursor-help text-ellipsis truncate max-w-xs"
title={"Added at #{format_added_at(rule.inserted_at, @site.timezone)} by #{rule.added_by}"} title={"Added at #{format_added_at(rule.inserted_at, @site.timezone)} by #{rule.added_by}"}
> >
<%= rule.page_path %> {rule.page_path}
</span> </span>
</.td> </.td>
<.td hide_on_mobile> <.td hide_on_mobile>
@ -132,7 +130,7 @@ defmodule PlausibleWeb.Live.Shields.PageRules do
creatable creatable
/> />
<%= error_tag(f, :page_path) %> <.error :for={msg <- f[:page_path].errors}>{translate_error(msg)}</.error>
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400"> <p class="mt-4 text-sm text-gray-500 dark:text-gray-400">
You can use a wildcard (<code>*</code>) to match multiple pages. For example, You can use a wildcard (<code>*</code>) to match multiple pages. For example,

View File

@ -100,7 +100,7 @@ defmodule PlausibleWeb.Live.Sites do
page_number={@sites.page_number} page_number={@sites.page_number}
total_pages={@sites.total_pages} total_pages={@sites.total_pages}
> >
Total of <span class="font-medium"><%= @sites.total_entries %></span> sites Total of <span class="font-medium">{@sites.total_entries}</span> sites
</.pagination> </.pagination>
<.invitation_modal :if={Enum.any?(@sites.entries, &(&1.entry_type == "invitation"))} /> <.invitation_modal :if={Enum.any?(@sites.entries, &(&1.entry_type == "invitation"))} />
</div> </div>
@ -166,7 +166,7 @@ defmodule PlausibleWeb.Live.Sites do
/> />
<div class="flex-1 truncate -mt-px"> <div class="flex-1 truncate -mt-px">
<h3 class="text-gray-900 font-medium text-lg truncate dark:text-gray-100"> <h3 class="text-gray-900 font-medium text-lg truncate dark:text-gray-100">
<%= @site.domain %> {@site.domain}
</h3> </h3>
</div> </div>
@ -212,7 +212,7 @@ defmodule PlausibleWeb.Live.Sites do
class="text-gray-900 font-medium text-lg truncate dark:text-gray-100" class="text-gray-900 font-medium text-lg truncate dark:text-gray-100"
style="width: calc(100% - 4rem)" style="width: calc(100% - 4rem)"
> >
<%= @site.domain %> {@site.domain}
</h3> </h3>
</div> </div>
</div> </div>
@ -308,7 +308,7 @@ defmodule PlausibleWeb.Live.Sites do
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<p> <p>
<span class="text-gray-800 dark:text-gray-200"> <span class="text-gray-800 dark:text-gray-200">
<b><%= PlausibleWeb.StatsView.large_number_format(@hourly_stats.visitors) %></b> <b>{PlausibleWeb.StatsView.large_number_format(@hourly_stats.visitors)}</b>
visitor<span :if={@hourly_stats.visitors != 1}>s</span> in last 24h visitor<span :if={@hourly_stats.visitors != 1}>s</span> in last 24h
</span> </span>
</p> </p>
@ -357,7 +357,7 @@ defmodule PlausibleWeb.Live.Sites do
</path> </path>
</svg> </svg>
<%= abs(@change) %>% {abs(@change)}%
</p> </p>
""" """
end end

View File

@ -2,6 +2,7 @@ defmodule PlausibleWeb.Router do
use PlausibleWeb, :router use PlausibleWeb, :router
use Plausible use Plausible
import Phoenix.LiveView.Router import Phoenix.LiveView.Router
import PhoenixStorybook.Router
pipeline :browser do pipeline :browser do
plug :accepts, ["html"] plug :accepts, ["html"]
@ -77,6 +78,15 @@ defmodule PlausibleWeb.Router do
forward "/sent-emails", Bamboo.SentEmailViewerPlug forward "/sent-emails", Bamboo.SentEmailViewerPlug
end end
scope "/" do
storybook_assets()
end
scope "/", PlausibleWeb do
pipe_through :browser
live_storybook("/storybook", backend_module: PlausibleWeb.Storybook)
end
on_ee do on_ee do
use Kaffy.Routes, use Kaffy.Routes,
scope: "/crm", scope: "/crm",

View File

@ -0,0 +1,13 @@
defmodule PlausibleWeb.Storybook do
@moduledoc false
use PhoenixStorybook,
otp_app: :plausible_web,
title: "Plausible Storybook",
content_path: Path.expand("../../storybook", __DIR__),
# assets path are remote path, not local file-system paths
css_path: "/css/storybook.css",
js_path: "/js/storybook.js",
sandbox_class: "plausible",
color_mode: true
end

View File

@ -18,13 +18,13 @@
<:subtitle :if={@has_email_code?}> <:subtitle :if={@has_email_code?}>
<p class="truncate"> <p class="truncate">
Please enter the 4-digit code we sent to <b><%= @conn.assigns[:current_user].email %></b> Please enter the 4-digit code we sent to <b>{@conn.assigns[:current_user].email}</b>
</p> </p>
</:subtitle> </:subtitle>
<:subtitle :if={!@has_email_code?}> <:subtitle :if={!@has_email_code?}>
<p class="truncate"> <p class="truncate">
A 4-digit activation code will be sent to <b><%= @conn.assigns[:current_user].email %></b> A 4-digit activation code will be sent to <b>{@conn.assigns[:current_user].email}</b>
</p> </p>
</:subtitle> </:subtitle>
@ -46,7 +46,7 @@
</.form> </.form>
</div> </div>
<%= error_tag(assigns, :error) %> <.error>{@error}</.error>
<div :if={!@has_email_code?}> <div :if={!@has_email_code?}>
<.button_link method="post" class="w-full" href="/activate/request-code"> <.button_link method="post" class="w-full" href="/activate/request-code">
@ -65,7 +65,7 @@
<.styled_link href="/activate/request-code" method="post"> <.styled_link href="/activate/request-code" method="post">
Send a new code Send a new code
</.styled_link> </.styled_link>
to <%= @conn.assigns[:current_user].email %> to {@conn.assigns[:current_user].email}
</:item> </:item>
<:item :if={ee?()}> <:item :if={ee?()}>
<.styled_link href="https://plausible.io/contact" new_tab={true}> <.styled_link href="https://plausible.io/contact" new_tab={true}>
@ -88,7 +88,7 @@
<.styled_link method="post" href={Routes.settings_path(@conn, :cancel_update_email)}> <.styled_link method="post" href={Routes.settings_path(@conn, :cancel_update_email)}>
Change email back to Change email back to
</.styled_link> </.styled_link>
<%= @conn.assigns[:current_user].previous_email %> {@conn.assigns[:current_user].previous_email}
</:item> </:item>
<:item :if={not @has_any_memberships?}> <:item :if={not @has_any_memberships?}>

View File

@ -16,7 +16,7 @@
class="font-mono border-2 border-dotted border-gray-200 dark:border-gray-700 rounded-md text-gray-600 dark:text-gray-200 text-lg bg-gray-100 dark:bg-gray-900 p-2 mt-6 flex flex-wrap" class="font-mono border-2 border-dotted border-gray-200 dark:border-gray-700 rounded-md text-gray-600 dark:text-gray-200 text-lg bg-gray-100 dark:bg-gray-900 p-2 mt-6 flex flex-wrap"
> >
<%= for code <- @recovery_codes do %> <%= for code <- @recovery_codes do %>
<div class="basis-1/2 text-center"><%= code %></div> <div class="basis-1/2 text-center">{code}</div>
<% end %> <% end %>
</div> </div>

View File

@ -1,11 +1,11 @@
<.focus_box> <.focus_box>
<:title> <:title>
<%= Phoenix.Flash.get(@flash, :login_title) || "Enter your account credentials" %> {Phoenix.Flash.get(@flash, :login_title) || "Enter your account credentials"}
</:title> </:title>
<:subtitle> <:subtitle>
<%= if Phoenix.Flash.get(@flash, :login_instructions) do %> <%= if Phoenix.Flash.get(@flash, :login_instructions) do %>
<p class="text-gray-500 mt-1 mb-2"> <p class="text-gray-500 mt-1 mb-2">
<%= Phoenix.Flash.get(@flash, :login_instructions) %> {Phoenix.Flash.get(@flash, :login_instructions)}
</p> </p>
<% end %> <% end %>
</:subtitle> </:subtitle>
@ -30,7 +30,7 @@
</div> </div>
<%= if @conn.assigns[:error] do %> <%= if @conn.assigns[:error] do %>
<div class="text-red-500 mt-4"><%= @conn.assigns[:error] %></div> <div class="text-red-500 mt-4">{@conn.assigns[:error]}</div>
<% end %> <% end %>
<.button class="w-full" type="submit">Log in</.button> <.button class="w-full" type="submit">Log in</.button>

View File

@ -1,4 +1,4 @@
<%= live_render(@conn, PlausibleWeb.Live.ResetPasswordForm, {live_render(@conn, PlausibleWeb.Live.ResetPasswordForm,
container: {:div, class: "contents"}, container: {:div, class: "contents"},
session: %{"email" => @email} session: %{"email" => @email}
) %> )}

View File

@ -12,14 +12,14 @@
<.input type="email" field={f[:email]} placeholder="user@example.com" /> <.input type="email" field={f[:email]} placeholder="user@example.com" />
</div> </div>
<%= if @conn.assigns[:error] do %> <%= if @conn.assigns[:error] do %>
<div class="text-red-500 my-2"><%= @conn.assigns[:error] %></div> <div class="text-red-500 my-2">{@conn.assigns[:error]}</div>
<% end %> <% end %>
<%= if PlausibleWeb.Captcha.enabled?() do %> <%= if PlausibleWeb.Captcha.enabled?() do %>
<div class="mt-4"> <div class="mt-4">
<div class="h-captcha" data-sitekey={PlausibleWeb.Captcha.sitekey()}></div> <div class="h-captcha" data-sitekey={PlausibleWeb.Captcha.sitekey()}></div>
<%= if assigns[:captcha_error] do %> <%= if assigns[:captcha_error] do %>
<div class="text-red-500 text-xs mt-3"><%= @captcha_error %></div> <div class="text-red-500 text-xs mt-3">{@captcha_error}</div>
<% end %> <% end %>
<script src="https://hcaptcha.com/1/api.js" async defer> <script src="https://hcaptcha.com/1/api.js" async defer>
</script> </script>

View File

@ -1,7 +1,7 @@
<div class="bg-white dark:bg-gray-800 max-w-md w-full mx-auto shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"> <div class="bg-white dark:bg-gray-800 max-w-md w-full mx-auto shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8">
<h2 class="text-xl font-black dark:text-gray-100">Success!</h2> <h2 class="text-xl font-black dark:text-gray-100">Success!</h2>
<div class="my-4 leading-tight dark:text-gray-100"> <div class="my-4 leading-tight dark:text-gray-100">
We've sent an email containing password reset instructions to <b><%= @email %></b> We've sent an email containing password reset instructions to <b>{@email}</b>
if it's registered in our system. if it's registered in our system.
</div> </div>
<div class="mt-8 text-sm dark:text-gray-100"> <div class="mt-8 text-sm dark:text-gray-100">

View File

@ -25,12 +25,12 @@
<tbody class="bg-white dark:bg-gray-800"> <tbody class="bg-white dark:bg-gray-800">
<tr class="border-b border-gray-200"> <tr class="border-b border-gray-200">
<td class="px-6 py-4 text-sm leading-5 font-bold dark:text-gray-100"> <td class="px-6 py-4 text-sm leading-5 font-bold dark:text-gray-100">
<%= present_currency(@preview_info["immediate_payment"]["currency"]) %><%= @preview_info[ {present_currency(@preview_info["immediate_payment"]["currency"])}{@preview_info[
"immediate_payment" "immediate_payment"
]["amount"] %> ]["amount"]}
</td> </td>
<td class="px-6 py-4 text-sm leading-5 dark:text-gray-100"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100">
<%= present_date(@preview_info["immediate_payment"]["date"]) %> {present_date(@preview_info["immediate_payment"]["date"])}
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -60,12 +60,12 @@
<tbody class="bg-white dark:bg-gray-800"> <tbody class="bg-white dark:bg-gray-800">
<tr class="border-b border-gray-200"> <tr class="border-b border-gray-200">
<td class="px-6 py-4 text-sm leading-5 font-bold dark:text-gray-100"> <td class="px-6 py-4 text-sm leading-5 font-bold dark:text-gray-100">
<%= present_currency(@preview_info["immediate_payment"]["currency"]) %><%= @preview_info[ {present_currency(@preview_info["immediate_payment"]["currency"])}{@preview_info[
"next_payment" "next_payment"
]["amount"] %> ]["amount"]}
</td> </td>
<td class="px-6 py-4 text-sm leading-5 dark:text-gray-100"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100">
<%= present_date(@preview_info["next_payment"]["date"]) %> {present_date(@preview_info["next_payment"]["date"])}
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@ -1,4 +1,4 @@
<%= live_render(@conn, PlausibleWeb.Live.ChoosePlan, {live_render(@conn, PlausibleWeb.Live.ChoosePlan,
id: "choose-plan", id: "choose-plan",
session: %{"remote_ip" => PlausibleWeb.RemoteIP.get(@conn)} session: %{"remote_ip" => PlausibleWeb.RemoteIP.get(@conn)}
) %> )}

View File

@ -1,19 +1,19 @@
<div class="mx-auto mt-6 text-center"> <div class="mx-auto mt-6 text-center">
<h1 class="text-3xl font-black text-black dark:text-gray-100"> <h1 class="text-3xl font-black text-black dark:text-gray-100">
<%= if @subscription_resumable, {if @subscription_resumable,
do: "Change subscription plan", do: "Change subscription plan",
else: "Upgrade to Enterprise" %> else: "Upgrade to Enterprise"}
</h1> </h1>
</div> </div>
<div class="w-full max-w-lg px-4 mx-auto mt-4 text-gray-900 dark:text-gray-100"> <div class="w-full max-w-lg px-4 mx-auto mt-4 text-gray-900 dark:text-gray-100">
<div class="flex-1 p-8 mt-8 rounded bg-white shadow-md dark:bg-gray-800 dark:shadow-none"> <div class="flex-1 p-8 mt-8 rounded bg-white shadow-md dark:bg-gray-800 dark:shadow-none">
<div class="w-full pb-4"> <div class="w-full pb-4">
<span> <span>
<%= if @subscription_resumable, {if @subscription_resumable,
do: do:
"We've prepared your account for an upgrade to custom limits outside the listed plans:", "We've prepared your account for an upgrade to custom limits outside the listed plans:",
else: else:
"We've prepared a custom enterprise plan for your account with the following limits:" %> "We've prepared a custom enterprise plan for your account with the following limits:"}
</span> </span>
</div> </div>
<PlausibleWeb.Components.Billing.present_enterprise_plan plan={@latest_enterprise_plan} /> <PlausibleWeb.Components.Billing.present_enterprise_plan plan={@latest_enterprise_plan} />
@ -21,19 +21,19 @@
<span> <span>
The plan is priced at The plan is priced at
<b> <b>
<%= case @price do {case @price do
%Money{} = money -> Plausible.Billing.format_price(money) %Money{} = money -> Plausible.Billing.format_price(money)
nil -> "N/A" nil -> "N/A"
end %> end}
</b> </b>
</span> </span>
<span> <span>
per <%= if @latest_enterprise_plan.billing_interval == :yearly, per {if @latest_enterprise_plan.billing_interval == :yearly,
do: "year", do: "year",
else: "month" %> + VAT if applicable. <%= if @subscription_resumable, else: "month"} + VAT if applicable. {if @subscription_resumable,
do: do:
"On the next page, our payment provider will calculate the prorated amount that your card will be charged if you decide to upgrade now.", "On the next page, our payment provider will calculate the prorated amount that your card will be charged if you decide to upgrade now.",
else: "Click the button below to upgrade." %> else: "Click the button below to upgrade."}
</span> </span>
</ul> </ul>
<div class="w-max"> <div class="w-max">

View File

@ -3,9 +3,9 @@
<%= for log <- @queries do %> <%= for log <- @queries do %>
<details class="group py-1"> <details class="group py-1">
<summary class="flex cursor-pointer flex-row items-center justify-between py-1 font-semibold text-gray-800 dark:text-gray-200 pt-4"> <summary class="flex cursor-pointer flex-row items-center justify-between py-1 font-semibold text-gray-800 dark:text-gray-200 pt-4">
<%= log["request_method"] %> <%= controller_name(log["phoenix_controller"]) %>.<%= log[ {log["request_method"]} {controller_name(log["phoenix_controller"])}.{log[
"phoenix_action" "phoenix_action"
] %> (<%= log[:query_duration_ms] %>ms) ]} ({log[:query_duration_ms]}ms)
<svg <svg
class="h-6 w-6 rotate-0 transform text-gray-400 dark:text-gray-200 group-open:rotate-180" class="h-6 w-6 rotate-0 transform text-gray-400 dark:text-gray-200 group-open:rotate-180"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -22,7 +22,7 @@
<tbody> <tbody>
<%= for {key, value} <- log do %> <%= for {key, value} <- log do %>
<tr class="table-row"> <tr class="table-row">
<td class="table-cell p-2"><%= key %></td> <td class="table-cell p-2">{key}</td>
<td class="table-cell p-2"> <td class="table-cell p-2">
<%= case key do %> <%= case key do %>
<% :query -> %> <% :query -> %>
@ -30,7 +30,7 @@
<% "params" -> %> <% "params" -> %>
<pre><%= Jason.encode!(value, pretty: true) %></pre> <pre><%= Jason.encode!(value, pretty: true) %></pre>
<% _ -> %> <% _ -> %>
<%= value %> {value}
<% end %> <% end %>
</td> </td>
</tr> </tr>

View File

@ -1 +1 @@
Enter <%= @code %> to verify your email address. This code will expire in 4 hours. Enter {@code} to verify your email address. This code will expire in 4 hours.

View File

@ -1,6 +1,6 @@
You used to have an active account with <%= Plausible.product_name() %>, a simple, lightweight, open source and privacy-first Google Analytics alternative. You used to have an active account with {Plausible.product_name()}, a simple, lightweight, open source and privacy-first Google Analytics alternative.
<br /><br /> <br /><br />
We've noticed that you're still sending us stats so we're writing to inform you that we'll stop accepting stats from your sites <%= @time %>. We're an independent, bootstrapped service and we don't sell your data, so this will reduce our server costs and help keep us sustainable. We've noticed that you're still sending us stats so we're writing to inform you that we'll stop accepting stats from your sites {@time}. We're an independent, bootstrapped service and we don't sell your data, so this will reduce our server costs and help keep us sustainable.
<br /><br /> If you'd like to continue counting your site stats in a privacy-friendly way, please <br /><br /> If you'd like to continue counting your site stats in a privacy-friendly way, please
<a href={plausible_url()}>login to your Plausible account</a> and start a subscription. <a href={plausible_url()}>login to your Plausible account</a> and start a subscription.
<br /><br /> <br /><br />

View File

@ -2,8 +2,7 @@ You've activated
<%= if Plausible.ee?() do %> <%= if Plausible.ee?() do %>
your free 30-day trial of your free 30-day trial of
<% end %> <% end %>
<%= Plausible.product_name() %>, a simple and privacy-friendly website analytics tool. {Plausible.product_name()}, a simple and privacy-friendly website analytics tool. <br /><br />
<br /><br />
<a href={"#{plausible_url()}/sites/new"}>Click here</a> <a href={"#{plausible_url()}/sites/new"}>Click here</a>
to add your website URL, your timezone and install our one-line JavaScript snippet to start collecting visitor statistics. to add your website URL, your timezone and install our one-line JavaScript snippet to start collecting visitor statistics.
<%= if Plausible.ee?() do %> <%= if Plausible.ee?() do %>

View File

@ -1,12 +1,12 @@
<%= if @success do %> <%= if @success do %>
Your CSV import has completed successfully. The Plausible dashboard for <%= @site_import.site.domain %> now contains historical imported data from <%= date_format( Your CSV import has completed successfully. The Plausible dashboard for {@site_import.site.domain} now contains historical imported data from {date_format(
@site_import.start_date @site_import.start_date
) %> to <%= date_format(@site_import.end_date) %> )} to {date_format(@site_import.end_date)}
<br /><br /> <br /><br />
<a href={@link}>Click here</a> <a href={@link}>Click here</a>
to view your dashboard. to view your dashboard.
<% else %> <% else %>
Unfortunately, your CSV import for <%= @site_import.site.domain %> did not complete successfully. Sorry about that! Unfortunately, your CSV import for {@site_import.site.domain} did not complete successfully. Sorry about that!
<br /><br /> Please try to do the import once again. <br /><br /> Please try to do the import once again.
<%= if ee?() do %> <%= if ee?() do %>
<br /> <br /> <br /> <br />

View File

@ -1,18 +1,18 @@
Last week we sent a reminder that your site traffic has exceeded the limits of your <%= Plausible.product_name() %> subscription tier for two consecutive months. Since we haven't received a response, we've had to temporarily lock access to your stats. Last week we sent a reminder that your site traffic has exceeded the limits of your {Plausible.product_name()} subscription tier for two consecutive months. Since we haven't received a response, we've had to temporarily lock access to your stats.
<br /><br /> <br /><br />
Your subscription is still active, we're still counting your stats and haven't deleted any of your data but as you have outgrown your subscription tier, we kindly ask you to upgrade to match your new traffic levels. Upon upgrading to a suitable tier, your dashboard access will be immediately restored. Your subscription is still active, we're still counting your stats and haven't deleted any of your data but as you have outgrown your subscription tier, we kindly ask you to upgrade to match your new traffic levels. Upon upgrading to a suitable tier, your dashboard access will be immediately restored.
<br /><br /> <br /><br />
During the last billing cycle (<%= PlausibleWeb.TextHelpers.format_date_range( During the last billing cycle ({PlausibleWeb.TextHelpers.format_date_range(
@usage.last_cycle.date_range @usage.last_cycle.date_range
) %>), your account recorded <%= PlausibleWeb.AuthView.delimit_integer(@usage.last_cycle.total) %> billable pageviews. In the billing cycle before that (<%= PlausibleWeb.TextHelpers.format_date_range( )}), your account recorded {PlausibleWeb.AuthView.delimit_integer(@usage.last_cycle.total)} billable pageviews. In the billing cycle before that ({PlausibleWeb.TextHelpers.format_date_range(
@usage.penultimate_cycle.date_range @usage.penultimate_cycle.date_range
) %>), the usage was <%= PlausibleWeb.AuthView.delimit_integer(@usage.penultimate_cycle.total) %> billable pageviews. Note that billable pageviews include both standard pageviews and custom events. In your )}), the usage was {PlausibleWeb.AuthView.delimit_integer(@usage.penultimate_cycle.total)} billable pageviews. Note that billable pageviews include both standard pageviews and custom events. In your
<a href={PlausibleWeb.Router.Helpers.settings_url(PlausibleWeb.Endpoint, :subscription)}>account settings</a>, you'll find an overview of your usage and limits. <a href={PlausibleWeb.Router.Helpers.settings_url(PlausibleWeb.Endpoint, :subscription)}>account settings</a>, you'll find an overview of your usage and limits.
<br /><br /> <br /><br />
<%= if @suggested_plan == :enterprise do %> <%= if @suggested_plan == :enterprise do %>
Your usage exceeds our standard plans, so please reply back to this email for a tailored quote. Your usage exceeds our standard plans, so please reply back to this email for a tailored quote.
<% else %> <% else %>
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>Click here to upgrade your subscription</a>. We recommend you upgrade to the <%= @suggested_plan.volume %>/mo plan. The new charge will be prorated to reflect the amount you have already paid and the time until your current subscription is supposed to expire. <a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>Click here to upgrade your subscription</a>. We recommend you upgrade to the {@suggested_plan.volume}/mo plan. The new charge will be prorated to reflect the amount you have already paid and the time until your current subscription is supposed to expire.
<br /><br /> <br /><br />
If your usage decreases in the future, you can switch to a lower plan at any time. Any credit balance will automatically apply to future payments. If your usage decreases in the future, you can switch to a lower plan at any time. Any credit balance will automatically apply to future payments.
<% end %> <% end %>

View File

@ -1,7 +1,7 @@
We've recorded <%= @current_visitors %> visitors on We've recorded {@current_visitors} visitors on
<a href={"https://" <> @site.domain}><%= @site.domain %></a> in the last 12 hours. <a href={"https://" <> @site.domain}><%= @site.domain %></a> in the last 12 hours.
<%= if @dashboard_link do %> <%= if @dashboard_link do %>
<br /><br /> View dashboard: <a href={@dashboard_link}><%= @dashboard_link %></a> <br /><br /> View dashboard: <a href={@dashboard_link}>{@dashboard_link}</a>
<br /><br /> Something looks off? Please <br /><br /> Something looks off? Please
<a href={@installation_link}>review your installation</a> <a href={@installation_link}>review your installation</a>
to verify that Plausible has been integrated correctly. to verify that Plausible has been integrated correctly.

View File

@ -1,15 +1,14 @@
Automated notice about an enterprise account that has gone over their limits. <br /><br /> Automated notice about an enterprise account that has gone over their limits. <br /><br />
Customer email: <%= @user.email %><br /> Customer email: {@user.email}<br />
Last billing cycle: <%= PlausibleWeb.TextHelpers.format_date_range( Last billing cycle: {PlausibleWeb.TextHelpers.format_date_range(
@pageview_usage.last_cycle.date_range @pageview_usage.last_cycle.date_range
) %><br /> )}<br />
Last cycle pageview usage: <%= PlausibleWeb.AuthView.delimit_integer( Last cycle pageview usage: {PlausibleWeb.AuthView.delimit_integer(
@pageview_usage.last_cycle.total @pageview_usage.last_cycle.total
) %> billable pageviews<br /> )} billable pageviews<br />
Penultimate billing cycle: <%= PlausibleWeb.TextHelpers.format_date_range( Penultimate billing cycle: {PlausibleWeb.TextHelpers.format_date_range(
@pageview_usage.penultimate_cycle.date_range @pageview_usage.penultimate_cycle.date_range
) %><br /> )}<br />
Penultimate cycle pageview usage: <%= PlausibleWeb.AuthView.delimit_integer( Penultimate cycle pageview usage: {PlausibleWeb.AuthView.delimit_integer(
@pageview_usage.penultimate_cycle.total @pageview_usage.penultimate_cycle.total
) %> billable pageviews<br /> )} billable pageviews<br /> Site usage: {@site_usage} / {@site_allowance} allowed sites<br />
Site usage: <%= @site_usage %> / <%= @site_allowance %> allowed sites<br />

View File

@ -1,7 +1,7 @@
<h1>Error report</h1> <h1>Error report</h1>
<p> <p>
Reported by: <%= @reported_by %> Reported by: {@reported_by}
<br /> Sentry trace: <a href={sentry_link(@trace_id)}><%= @trace_id %></a> <br /> Sentry trace: <a href={sentry_link(@trace_id)}>{@trace_id}</a>
<br /> <br />
</p> </p>
<h2>User feedback:</h2> <h2>User feedback:</h2>

View File

@ -1,3 +1,3 @@
<%= @inviter.email %> has invited you to the <%= @site.domain %> site on <%= Plausible.product_name() %>. {@inviter.email} has invited you to the {@site.domain} site on {Plausible.product_name()}.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :index)}>Click here</a> to view and respond to the invitation. The invitation <a href={Routes.site_url(PlausibleWeb.Endpoint, :index)}>Click here</a> to view and respond to the invitation. The invitation
will expire 48 hours after this email is sent. will expire 48 hours after this email is sent.

View File

@ -1,3 +1,3 @@
<%= @inviter.email %> has invited you to the "<%= @team.name %>" team on <%= Plausible.product_name() %>. {@inviter.email} has invited you to the "{@team.name}" team on {Plausible.product_name()}.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :index)}>Click here</a> to view and respond to the invitation. The invitation <a href={Routes.site_url(PlausibleWeb.Endpoint, :index)}>Click here</a> to view and respond to the invitation. The invitation
will expire 48 hours after this email is sent. will expire 48 hours after this email is sent.

View File

@ -1,4 +1,4 @@
Your <%= Plausible.product_name() %> export for <%= @site.domain %> has encountered an error and was unsuccessful. Your {Plausible.product_name()} export for {@site.domain} has encountered an error and was unsuccessful.
Sorry for the trouble this may have caused. <br /><br /> Please attempt to export your data again. Sorry for the trouble this may have caused. <br /><br /> Please attempt to export your data again.
<%= if ee?() do %> <%= if ee?() do %>
Should the problem persist, do reply to this email so we can assist. Thanks! Should the problem persist, do reply to this email so we can assist. Thanks!

View File

@ -1,6 +1,6 @@
Your <%= Plausible.product_name() %> export for <%= @site.domain %> is now ready for download. Your {Plausible.product_name()} export for {@site.domain} is now ready for download.
Please click <a href={@download_url}>here</a> Please click <a href={@download_url}>here</a>
to start the download process. to start the download process.
<%= if @expires_in do %> <%= if @expires_in do %>
Note that this link will expire <%= @expires_in %>. Note that this link will expire {@expires_in}.
<% end %> <% end %>

View File

@ -1,12 +1,12 @@
<%= if @success do %> <%= if @success do %>
Your Google Analytics import has completed successfully. The Plausible dashboard for <%= @site_import.site.domain %> now contains historical imported data from <%= date_format( Your Google Analytics import has completed successfully. The Plausible dashboard for {@site_import.site.domain} now contains historical imported data from {date_format(
@site_import.start_date @site_import.start_date
) %> to <%= date_format(@site_import.end_date) %> )} to {date_format(@site_import.end_date)}
<br /><br /> <br /><br />
<a href={@link}>Click here</a> <a href={@link}>Click here</a>
to view your dashboard. to view your dashboard.
<% else %> <% else %>
Unfortunately, your Google Analytics import for <%= @site_import.site.domain %> did not complete successfully. Sorry about that! Unfortunately, your Google Analytics import for {@site_import.site.domain} did not complete successfully. Sorry about that!
<br /><br /> <br /><br />
Please try to do the import once again. Sometimes the Google Analytics API just randomly returns empty data. It's intermittent and random. Trying to do the import again may return what you need. Please try to do the import once again. Sometimes the Google Analytics API just randomly returns empty data. It's intermittent and random. Trying to do the import again may return what you need.
<%= if ee?() do %> <%= if ee?() do %>

View File

@ -1,2 +1,2 @@
<%= @invitee_email %> has accepted your invitation to <%= @site.domain %>. {@invitee_email} has accepted your invitation to {@site.domain}.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @site.domain)}>Click here</a> to view site settings. <a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @site.domain)}>Click here</a> to view site settings.

View File

@ -1,2 +1,2 @@
<%= @guest_invitation.team_invitation.email %> has rejected your invitation to <%= @guest_invitation.site.domain %>. {@guest_invitation.team_invitation.email} has rejected your invitation to {@guest_invitation.site.domain}.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @guest_invitation.site.domain)}>Click here</a> to view site settings. <a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @guest_invitation.site.domain)}>Click here</a> to view site settings.

View File

@ -1,4 +1,4 @@
<%= @inviter.email %> has invited you to join the <%= @site.domain %> site on <%= Plausible.product_name() %>. {@inviter.email} has invited you to join the {@site.domain} site on {Plausible.product_name()}.
<a href={ <a href={
Routes.auth_url( Routes.auth_url(
PlausibleWeb.Endpoint, PlausibleWeb.Endpoint,

View File

@ -1,4 +1,4 @@
<%= @inviter.email %> has invited you to join the "<%= @team.name %>" team on <%= Plausible.product_name() %>. {@inviter.email} has invited you to join the "{@team.name}" team on {Plausible.product_name()}.
<a href={ <a href={
Routes.auth_url( Routes.auth_url(
PlausibleWeb.Endpoint, PlausibleWeb.Endpoint,

View File

@ -1,19 +1,19 @@
Thanks for being a <%= Plausible.product_name() %> subscriber! <br /><br /> Thanks for being a {Plausible.product_name()} subscriber! <br /><br />
This is a friendly reminder that your traffic has exceeded your subscription tier for two consecutive months. Congrats on all that traffic! This is a friendly reminder that your traffic has exceeded your subscription tier for two consecutive months. Congrats on all that traffic!
<br /><br /> <br /><br />
To maintain uninterrupted access to your stats, we kindly ask you to upgrade your account to match your new traffic levels. Please note, if your account isn't upgraded within the next 7 days, access to your stats will be temporarily locked. To maintain uninterrupted access to your stats, we kindly ask you to upgrade your account to match your new traffic levels. Please note, if your account isn't upgraded within the next 7 days, access to your stats will be temporarily locked.
<br /><br /> <br /><br />
During the last billing cycle (<%= PlausibleWeb.TextHelpers.format_date_range( During the last billing cycle ({PlausibleWeb.TextHelpers.format_date_range(
@usage.last_cycle.date_range @usage.last_cycle.date_range
) %>), your account recorded <%= PlausibleWeb.AuthView.delimit_integer(@usage.last_cycle.total) %> billable pageviews. In the billing cycle before that (<%= PlausibleWeb.TextHelpers.format_date_range( )}), your account recorded {PlausibleWeb.AuthView.delimit_integer(@usage.last_cycle.total)} billable pageviews. In the billing cycle before that ({PlausibleWeb.TextHelpers.format_date_range(
@usage.penultimate_cycle.date_range @usage.penultimate_cycle.date_range
) %>), your account used <%= PlausibleWeb.AuthView.delimit_integer(@usage.penultimate_cycle.total) %> billable pageviews. Note that billable pageviews include both standard pageviews and custom events. In your )}), your account used {PlausibleWeb.AuthView.delimit_integer(@usage.penultimate_cycle.total)} billable pageviews. Note that billable pageviews include both standard pageviews and custom events. In your
<a href={plausible_url() <> PlausibleWeb.Router.Helpers.settings_path(PlausibleWeb.Endpoint, :subscription)}>account settings</a>, you'll find an overview of your usage and limits. <a href={plausible_url() <> PlausibleWeb.Router.Helpers.settings_path(PlausibleWeb.Endpoint, :subscription)}>account settings</a>, you'll find an overview of your usage and limits.
<br /><br /> <br /><br />
<%= if @suggested_plan == :enterprise do %> <%= if @suggested_plan == :enterprise do %>
Your usage exceeds our standard plans, so please reply back to this email for a tailored quote. Your usage exceeds our standard plans, so please reply back to this email for a tailored quote.
<% else %> <% else %>
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>Click here to upgrade your subscription</a>. We recommend you upgrade to the <%= @suggested_plan.volume %>/mo plan. The new charge will be prorated to reflect the amount you have already paid and the time until your current subscription is supposed to expire. <a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>Click here to upgrade your subscription</a>. We recommend you upgrade to the {@suggested_plan.volume}/mo plan. The new charge will be prorated to reflect the amount you have already paid and the time until your current subscription is supposed to expire.
<br /><br /> <br /><br />
If your usage decreases in the future, you can switch to a lower plan at any time. Any credit balance will automatically apply to future payments. If your usage decreases in the future, you can switch to a lower plan at any time. Any credit balance will automatically apply to future payments.
<% end %> <% end %>

View File

@ -1,3 +1,3 @@
<%= @new_owner_email %> has accepted the ownership transfer of <%= @site.domain %>. They will be responsible for billing of it going {@new_owner_email} has accepted the ownership transfer of {@site.domain}. They will be responsible for billing of it going
forward and your role has been changed to <b>admin</b>. forward and your role has been changed to <b>admin</b>.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @site.domain)}>Click here</a> to view site settings. <a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @site.domain)}>Click here</a> to view site settings.

View File

@ -1,2 +1,2 @@
<%= @site_transfer.email %> has rejected the ownership transfer of <%= @site_transfer.site.domain %>. {@site_transfer.email} has rejected the ownership transfer of {@site_transfer.site.domain}.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @site_transfer.site.domain)}>Click here</a> to view site settings. <a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @site_transfer.site.domain)}>Click here</a> to view site settings.

View File

@ -1,4 +1,4 @@
<%= @inviter.email %> has requested to transfer the ownership of <%= @site.domain %> site on <%= Plausible.product_name() %> to you. {@inviter.email} has requested to transfer the ownership of {@site.domain} site on {Plausible.product_name()} to you.
<%= if @new_owner_account do %> <%= if @new_owner_account do %>
<a href={Routes.site_url(PlausibleWeb.Endpoint, :index)}>Click here</a> <a href={Routes.site_url(PlausibleWeb.Endpoint, :index)}>Click here</a>
to view and respond to the invitation. to view and respond to the invitation.

View File

@ -1,4 +1,4 @@
An administrator of <%= @guest_membership.site.domain %> has removed you as a member. You won't be able to see the stats anymore. An administrator of {@guest_membership.site.domain} has removed you as a member. You won't be able to see the stats anymore.
<br /><br /> <br /><br />
<a href={Routes.site_url(PlausibleWeb.Endpoint, :index)}>Click here</a> <a href={Routes.site_url(PlausibleWeb.Endpoint, :index)}>Click here</a>
to view your sites. to view your sites.

View File

@ -2,7 +2,7 @@
You signed up for a free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool. You signed up for a free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool.
<br /><br /> <br /><br />
<% end %> <% end %>
To finish your setup for <%= @site.domain %>, review To finish your setup for {@site.domain}, review
<a href={"#{plausible_url()}/#{URI.encode_www_form(@site.domain)}/installation"}>your installation</a> and start collecting visitor statistics. <a href={"#{plausible_url()}/#{URI.encode_www_form(@site.domain)}/installation"}>your installation</a> and start collecting visitor statistics.
<br /><br /> <br /><br />
This Plausible script is 45 times smaller than Google Analytics script so youll have a fast loading site while getting all the important traffic insights on one single page. This Plausible script is 45 times smaller than Google Analytics script so youll have a fast loading site while getting all the important traffic insights on one single page.

View File

@ -1,15 +1,15 @@
There are currently <%= @current_visitors %> visitors on There are currently {@current_visitors} visitors on
<a href={"https://" <> @site.domain}><%= @site.domain %></a>. <a href={"https://" <> @site.domain}><%= @site.domain %></a>.
<%= if Enum.count(@sources) > 0 do %> <%= if Enum.count(@sources) > 0 do %>
<br /> <br />
<br /> The top sources for current visitors:<br /> <br /> The top sources for current visitors:<br />
<%= for %{name: source, count: visitors} <- @sources do %> <%= for %{name: source, count: visitors} <- @sources do %>
<%= source %> - <%= visitors %> visitor<%= if visitors > 1, do: "s" %><br /> {source} - {visitors} visitor{if visitors > 1, do: "s"}<br />
<% end %> <% end %>
<% end %> <% end %>
<%= if @link do %> <%= if @link do %>
<br /><br /> View dashboard: <a href={@link}><%= @link %></a> <br /><br /> View dashboard: <a href={@link}>{@link}</a>
<% end %> <% end %>
<br /><br /> Congrats on the spike in traffic! <br /><br /> Congrats on the spike in traffic!
<%= if Plausible.ce? do %> <%= if Plausible.ce? do %>

View File

@ -1,2 +1,2 @@
<%= @invitee_email %> has accepted your invitation to "<%= @team.name %>" team. {@invitee_email} has accepted your invitation to "{@team.name}" team.
<a href={Routes.settings_url(PlausibleWeb.Endpoint, :team_general)}>Click here</a> to view team settings. <a href={Routes.settings_url(PlausibleWeb.Endpoint, :team_general)}>Click here</a> to view team settings.

View File

@ -1,2 +1,2 @@
<%= @team_invitation.email %> has rejected your invitation to \"<%= @team_invitation.team.name %>\" team. {@team_invitation.email} has rejected your invitation to \"{@team_invitation.team.name}\" team.
<a href={Routes.settings_url(PlausibleWeb.Endpoint, :team_general)}>Click here</a> to view team settings. <a href={Routes.settings_url(PlausibleWeb.Endpoint, :team_general)}>Click here</a> to view team settings.

View File

@ -4,5 +4,4 @@ Your free Plausible trial has now expired. Upgrade your account to continue rece
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}> <a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>
Upgrade now Upgrade now
</a> </a>
<br /><br /> <br /><br /> We will keep recording stats for {@extra_offset} days to give you time to upgrade.
We will keep recording stats for <%= @extra_offset %> days to give you time to upgrade.

View File

@ -1,15 +1,15 @@
Thanks for exploring Plausible, a simple and privacy-friendly alternative to Google Analytics. Your free 30-day trial is ending <%= @day %>, but you can keep using Plausible by upgrading to a paid plan. Thanks for exploring Plausible, a simple and privacy-friendly alternative to Google Analytics. Your free 30-day trial is ending {@day}, but you can keep using Plausible by upgrading to a paid plan.
<br /><br /> <br /><br />
In the last month, your account has used <%= PlausibleWeb.AuthView.delimit_integer(@usage) %> billable pageviews<%= if @custom_events > In the last month, your account has used {PlausibleWeb.AuthView.delimit_integer(@usage)} billable pageviews{if @custom_events >
0, 0,
do: do:
" and custom events in total", " and custom events in total",
else: else:
"" %>. ""}.
<%= if @suggested_plan == :enterprise do %> <%= if @suggested_plan == :enterprise do %>
This is more than our standard plans, so please reply back to this email to get a quote for your volume. This is more than our standard plans, so please reply back to this email to get a quote for your volume.
<% else %> <% else %>
Based on that we recommend you select a <%= @suggested_plan.volume %>/mo plan. <br /><br /> Based on that we recommend you select a {@suggested_plan.volume}/mo plan. <br /><br />
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}> <a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>
Upgrade now Upgrade now
</a> </a>

View File

@ -1,7 +1,7 @@
Time flies! This is a reminder that your annual subscription for <%= Plausible.product_name() %> will expire on <%= @next_bill_date %>. Time flies! This is a reminder that your annual subscription for {Plausible.product_name()} will expire on {@next_bill_date}.
<br /><br /> You need to <br /><br /> You need to
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>renew your subscription</a> if you want to continue using Plausible to count your website stats in a privacy-friendly way. <a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>renew your subscription</a> if you want to continue using Plausible to count your website stats in a privacy-friendly way.
<br /><br /> <br /><br />
If you don't want to continue your subscription, there's no action required. You will lose access to your dashboard on <%= @next_bill_date %> and we'll stop accepting stats on <%= @accept_traffic_until %>. If you don't want to continue your subscription, there's no action required. You will lose access to your dashboard on {@next_bill_date} and we'll stop accepting stats on {@accept_traffic_until}.
<br /><br /> <br /><br />
Have a question, feedback or need some guidance? Just reply to this email to get in touch! Have a question, feedback or need some guidance? Just reply to this email to get in touch!

View File

@ -1,6 +1,6 @@
Time flies! This is a reminder that your annual subscription for <%= Plausible.product_name() %> is due to renew on <%= @date %>. We will automatically charge <%= PlausibleWeb.BillingView.present_currency( Time flies! This is a reminder that your annual subscription for {Plausible.product_name()} is due to renew on {@date}. We will automatically charge {PlausibleWeb.BillingView.present_currency(
@currency @currency
) %><%= @next_bill_amount %> from your preferred billing method. <br /><br /> )}{@next_bill_amount} from your preferred billing method. <br /><br />
There's no action required if you're happy to continue using Plausible to count your website stats in a privacy-friendly way. There's no action required if you're happy to continue using Plausible to count your website stats in a privacy-friendly way.
<br /><br /> If you don't want to continue your subscription, you can cancel it on your <br /><br /> If you don't want to continue your subscription, you can cancel it on your
<a href={"#{plausible_url()}/settings"}>account settings page</a>. <br /><br /> <a href={"#{plausible_url()}/settings"}>account settings page</a>. <br /><br />

View File

@ -1,5 +1,5 @@
<div class="container flex flex-col items-center text-center mt-24"> <div class="container flex flex-col items-center text-center mt-24">
<h1 class="text-5xl font-black dark:text-gray-100"><%= @status %></h1> <h1 class="text-5xl font-black dark:text-gray-100">{@status}</h1>
<div class="mt-4 text-xl dark:text-gray-100">Oops! There's nothing here</div> <div class="mt-4 text-xl dark:text-gray-100">Oops! There's nothing here</div>
<div class="text-xl dark:text-gray-100"> <div class="text-xl dark:text-gray-100">
Trying to access your dashboard? You may need to log in again to see it Trying to access your dashboard? You may need to log in again to see it

View File

@ -1,5 +1,5 @@
<div class="container text-center mt-24"> <div class="container text-center mt-24">
<h1 class="text-5xl font-black dark:text-gray-100"><%= @status %></h1> <h1 class="text-5xl font-black dark:text-gray-100">{@status}</h1>
<div class="my-4 text-xl dark:text-gray-100"><%= @message %></div> <div class="my-4 text-xl dark:text-gray-100">{@message}</div>
<.button_link href={PlausibleWeb.LayoutView.home_dest(@conn)}>Go to homepage</.button_link> <.button_link href={PlausibleWeb.LayoutView.home_dest(@conn)}>Go to homepage</.button_link>
</div> </div>

View File

@ -19,7 +19,7 @@
<div class="mt-6"> <div class="mt-6">
<.label for={f[:property].id}>Google Analytics property</.label> <.label for={f[:property].id}>Google Analytics property</.label>
<span class="block w-full text-base dark:text-gray-100 sm:text-sm dark:bg-gray-800"> <span class="block w-full text-base dark:text-gray-100 sm:text-sm dark:bg-gray-800">
<%= @selected_property_name %> {@selected_property_name}
</span> </span>
<.input type="hidden" value={@selected_property} field={f[:property]} readonly="true" /> <.input type="hidden" value={@selected_property} field={f[:property]} readonly="true" />
</div> </div>
@ -27,7 +27,7 @@
<div class="w-36"> <div class="w-36">
<.label for={f[:start_date].id}>From</.label> <.label for={f[:start_date].id}>From</.label>
<span class="block w-full text-base dark:text-gray-100 sm:text-sm dark:bg-gray-800"> <span class="block w-full text-base dark:text-gray-100 sm:text-sm dark:bg-gray-800">
<%= PlausibleWeb.EmailView.date_format(@start_date) %> {PlausibleWeb.EmailView.date_format(@start_date)}
</span> </span>
<.input type="hidden" value={@start_date} field={f[:start_date]} readonly="true" /> <.input type="hidden" value={@start_date} field={f[:start_date]} readonly="true" />
</div> </div>
@ -35,7 +35,7 @@
<div class="w-36"> <div class="w-36">
<.label for={f[:end_date].id}>To</.label> <.label for={f[:end_date].id}>To</.label>
<span class="block w-full text-base dark:text-gray-100 sm:text-sm dark:bg-gray-800"> <span class="block w-full text-base dark:text-gray-100 sm:text-sm dark:bg-gray-800">
<%= PlausibleWeb.EmailView.date_format(@end_date) %> {PlausibleWeb.EmailView.date_format(@end_date)}
</span> </span>
<.input type="hidden" value={@end_date} field={f[:end_date]} readonly="true" /> <.input type="hidden" value={@end_date} field={f[:end_date]} readonly="true" />
</div> </div>

View File

@ -4,7 +4,7 @@
</:title> </:title>
<:subtitle> <:subtitle>
Choose the property in your Google Analytics account that will be imported to the <%= @site.domain %> dashboard. Choose the property in your Google Analytics account that will be imported to the {@site.domain} dashboard.
</:subtitle> </:subtitle>
<.form <.form
:let={f} :let={f}
@ -27,7 +27,7 @@
/> />
<p class="text-red-600 dark:text-red-700"> <p class="text-red-600 dark:text-red-700">
<%= @conn.assigns[:selected_property_error] %> {@conn.assigns[:selected_property_error]}
</p> </p>
</div> </div>

View File

@ -33,10 +33,10 @@
</div> </div>
<div class="ml-3 w-0 flex-1 pt-0.5"> <div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100"> <p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100">
<%= Phoenix.Flash.get(@flash, :success_title) || "Success!" %> {Phoenix.Flash.get(@flash, :success_title) || "Success!"}
</p> </p>
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200"> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
<%= Phoenix.Flash.get(@flash, :success) %> {Phoenix.Flash.get(@flash, :success)}
</p> </p>
</div> </div>
<div class="ml-4 flex-shrink-0 flex"> <div class="ml-4 flex-shrink-0 flex">
@ -102,10 +102,10 @@
</div> </div>
<div class="ml-3 w-0 flex-1 pt-0.5"> <div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100"> <p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100">
<%= Phoenix.Flash.get(@flash, :error_title) || "Error" %> {Phoenix.Flash.get(@flash, :error_title) || "Error"}
</p> </p>
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200"> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
<%= Phoenix.Flash.get(@flash, :error) %> {Phoenix.Flash.get(@flash, :error)}
</p> </p>
</div> </div>
<div class="ml-4 flex-shrink-0 flex"> <div class="ml-4 flex-shrink-0 flex">

Some files were not shown because too many files have changed in this diff Show More