mirror of
https://github.com/plausible/analytics.git
synced 2024-12-22 17:11:36 +03:00
Remove trial banner for admins & viewers (#1308)
* Start trial only when the user creates a site * End trial when ownership is transfered
This commit is contained in:
parent
c8a1b5c73c
commit
700a65c98a
@ -37,14 +37,14 @@ defmodule Plausible.Auth.User do
|
||||
|> validate_length(:password, max: 64, message: "cannot be longer than 64 characters")
|
||||
|> validate_confirmation(:password)
|
||||
|> hash_password()
|
||||
|> change(trial_expiry_date: trial_expiry())
|
||||
|> start_trial
|
||||
|> unique_constraint(:email)
|
||||
end
|
||||
|
||||
def changeset(user, attrs \\ %{}) do
|
||||
user
|
||||
|> cast(attrs, [:email, :name, :email_verified, :theme, :trial_expiry_date])
|
||||
|> validate_required([:email, :name, :email_verified, :trial_expiry_date])
|
||||
|> validate_required([:email, :name, :email_verified])
|
||||
|> unique_constraint(:email)
|
||||
end
|
||||
|
||||
@ -65,6 +65,18 @@ defmodule Plausible.Auth.User do
|
||||
|
||||
def hash_password(changeset), do: changeset
|
||||
|
||||
def remove_trial_expiry(user) do
|
||||
change(user, trial_expiry_date: nil)
|
||||
end
|
||||
|
||||
def start_trial(user) do
|
||||
change(user, trial_expiry_date: trial_expiry())
|
||||
end
|
||||
|
||||
def end_trial(user) do
|
||||
change(user, trial_expiry_date: Timex.today() |> Timex.shift(days: -1))
|
||||
end
|
||||
|
||||
defp trial_expiry() do
|
||||
if Application.get_env(:plausible, :is_selfhost) do
|
||||
Timex.today() |> Timex.shift(years: 100)
|
||||
|
@ -104,6 +104,8 @@ defmodule Plausible.Billing do
|
||||
PaddleApi.update_subscription_preview(subscription.paddle_subscription_id, new_plan_id)
|
||||
end
|
||||
|
||||
def needs_to_upgrade?(%Plausible.Auth.User{trial_expiry_date: nil}), do: true
|
||||
|
||||
def needs_to_upgrade?(user) do
|
||||
if Timex.before?(user.trial_expiry_date, Timex.today()) do
|
||||
!subscription_is_active?(user.subscription)
|
||||
@ -122,6 +124,8 @@ defmodule Plausible.Billing do
|
||||
defp subscription_is_active?(%Subscription{}), do: false
|
||||
defp subscription_is_active?(nil), do: false
|
||||
|
||||
def on_trial?(%Plausible.Auth.User{trial_expiry_date: nil}), do: false
|
||||
|
||||
def on_trial?(user) do
|
||||
!subscription_is_active?(user.subscription) && trial_days_left(user) >= 0
|
||||
end
|
||||
|
@ -22,10 +22,22 @@ defmodule Plausible.Sites do
|
||||
|
||||
repo.insert(membership_changeset)
|
||||
end)
|
||||
|> maybe_start_trial(user)
|
||||
|> Repo.transaction()
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_start_trial(multi, user) do
|
||||
case user.trial_expiry_date do
|
||||
nil ->
|
||||
changeset = Plausible.Auth.User.start_trial(user)
|
||||
Ecto.Multi.update(multi, :user, changeset)
|
||||
|
||||
_ ->
|
||||
multi
|
||||
end
|
||||
end
|
||||
|
||||
def has_stats?(site) do
|
||||
if site.has_stats do
|
||||
true
|
||||
|
@ -98,6 +98,12 @@ defmodule PlausibleWeb.AuthController do
|
||||
invitation = Repo.get_by(Plausible.Auth.Invitation, invitation_id: invitation_id)
|
||||
user = Plausible.Auth.User.new(params["user"])
|
||||
|
||||
user =
|
||||
case invitation.role do
|
||||
:owner -> user
|
||||
_ -> Plausible.Auth.User.remove_trial_expiry(user)
|
||||
end
|
||||
|
||||
if PlausibleWeb.Captcha.verify(params["h-captcha-response"]) do
|
||||
case Repo.insert(user) do
|
||||
{:ok, user} ->
|
||||
|
@ -17,7 +17,9 @@ defmodule PlausibleWeb.InvitationController do
|
||||
|
||||
multi =
|
||||
if invitation.role == :owner do
|
||||
downgrade_previous_owner(Multi.new(), invitation.site)
|
||||
Multi.new()
|
||||
|> downgrade_previous_owner(invitation.site)
|
||||
|> end_trial_of_new_owner(user)
|
||||
else
|
||||
Multi.new()
|
||||
end
|
||||
@ -35,9 +37,10 @@ defmodule PlausibleWeb.InvitationController do
|
||||
|> Multi.delete(:invitation, invitation)
|
||||
|
||||
case Repo.transaction(multi) do
|
||||
{:ok, _} ->
|
||||
{:ok, changes} ->
|
||||
updated_user = Map.get(changes, :user, user)
|
||||
notify_invitation_accepted(invitation)
|
||||
Plausible.Billing.SiteLocker.check_sites_for(user)
|
||||
Plausible.Billing.SiteLocker.check_sites_for(updated_user)
|
||||
|
||||
conn
|
||||
|> put_flash(:success, "You now have access to #{invitation.site.domain}")
|
||||
@ -61,6 +64,14 @@ defmodule PlausibleWeb.InvitationController do
|
||||
Multi.update_all(multi, :prev_owner, prev_owner, set: [role: :admin])
|
||||
end
|
||||
|
||||
defp end_trial_of_new_owner(multi, new_owner) do
|
||||
if Plausible.Billing.on_trial?(new_owner) do
|
||||
Ecto.Multi.update(multi, :user, Plausible.Auth.User.end_trial(new_owner))
|
||||
else
|
||||
multi
|
||||
end
|
||||
end
|
||||
|
||||
def reject_invitation(conn, %{"invitation_id" => invitation_id}) do
|
||||
invitation =
|
||||
Repo.get_by!(Invitation, invitation_id: invitation_id)
|
||||
|
@ -50,11 +50,14 @@ defmodule PlausibleWeb.SiteController do
|
||||
end
|
||||
|
||||
def new(conn, _params) do
|
||||
current_user = conn.assigns[:current_user]
|
||||
site_count = Enum.count(Plausible.Sites.owned_by(current_user))
|
||||
current_user = conn.assigns[:current_user] |> Repo.preload(site_memberships: :site)
|
||||
|
||||
owned_site_count =
|
||||
current_user.site_memberships |> Enum.filter(fn m -> m.role == :owner end) |> Enum.count()
|
||||
|
||||
site_limit = Plausible.Billing.sites_limit(current_user)
|
||||
is_at_limit = site_limit && site_count >= site_limit
|
||||
is_first_site = site_count == 0
|
||||
is_at_limit = site_limit && owned_site_count >= site_limit
|
||||
is_first_site = Enum.empty?(current_user.site_memberships)
|
||||
|
||||
changeset = Plausible.Site.changeset(%Plausible.Site{})
|
||||
|
||||
|
@ -159,7 +159,15 @@
|
||||
You've been invited to the <span x-text="selectedInvitation && selectedInvitation.site.domain"></span> analytics dashboard as <b class="capitalize" x-text="selectedInvitation && selectedInvitation.role">Admin</b>.
|
||||
</p>
|
||||
<p x-show="selectedInvitation && selectedInvitation.role === 'owner'" class="mt-2 text-sm text-gray-500">
|
||||
If you accept the ownership transfer, you will be responsible for billing.
|
||||
If you accept the ownership transfer, you will be responsible for billing going forward.
|
||||
<%= if is_nil(@current_user.trial_expiry_date) && is_nil(@current_user.subscription) do %>
|
||||
<br/><br />
|
||||
You will have to enter your card details immediately with no 30-day trial.
|
||||
<% end %>
|
||||
<%= if Plausible.Billing.on_trial?(@current_user) do %>
|
||||
<br/><br />
|
||||
Your 30-day free trial will end immediately and you will have to enter your card details to keep using Plausible.
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,7 @@
|
||||
<div class="w-full max-w-3xl mt-4 mx-auto flex">
|
||||
<%= form_for @changeset, "/sites", [class: "max-w-lg w-full mx-auto bg-white dark:bg-gray-800 shadow-lg rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
|
||||
<h2 class="text-xl font-black dark:text-gray-100">Your website details</h2>
|
||||
|
||||
<%= if @is_at_limit do %>
|
||||
<div class="rounded-md bg-yellow-50 dark:bg-transparent dark:border border-yellow-200 p-4 mt-4">
|
||||
<div class="flex">
|
||||
@ -22,6 +23,26 @@
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= if is_nil(@current_user.trial_expiry_date) do %>
|
||||
<div class="rounded-md bg-blue-50 dark:bg-transparent dark:border border-blue-200 p-4 mt-4">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-blue-500 dark:text-blue-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<div class="text-sm text-blue-700 dark:text-blue-300">
|
||||
<p>
|
||||
When you create your first site, your account will enter a 30 day free trial.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="my-6">
|
||||
<%= label f, :domain, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-xs mt-1">Just the naked domain or subdomain without 'www'</p>
|
||||
|
@ -0,0 +1,9 @@
|
||||
defmodule Plausible.Repo.Migrations.AllowTrialExpiryToBeNull do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:users) do
|
||||
modify :trial_expiry_date, :date, null: true
|
||||
end
|
||||
end
|
||||
end
|
@ -71,6 +71,116 @@ defmodule PlausibleWeb.AuthControllerTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /register/invitations/:invitation_id" do
|
||||
test "shows the register form", %{conn: conn} do
|
||||
inviter = insert(:user)
|
||||
site = insert(:site, members: [inviter])
|
||||
|
||||
invitation =
|
||||
insert(:invitation,
|
||||
site_id: site.id,
|
||||
inviter: inviter,
|
||||
email: "user@email.co",
|
||||
role: :admin
|
||||
)
|
||||
|
||||
conn = get(conn, "/register/invitation/#{invitation.invitation_id}")
|
||||
|
||||
assert html_response(conn, 200) =~ "Enter your details"
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /register/invitation/:invitation_id" do
|
||||
setup do
|
||||
inviter = insert(:user)
|
||||
site = insert(:site, members: [inviter])
|
||||
|
||||
invitation =
|
||||
insert(:invitation,
|
||||
site_id: site.id,
|
||||
inviter: inviter,
|
||||
email: "user@email.co",
|
||||
role: :admin
|
||||
)
|
||||
|
||||
{:ok, %{site: site, invitation: invitation}}
|
||||
end
|
||||
|
||||
test "registering sends an activation link", %{conn: conn, invitation: invitation} do
|
||||
post(conn, "/register/invitation/#{invitation.invitation_id}",
|
||||
user: %{
|
||||
name: "Jane Doe",
|
||||
email: "user@example.com",
|
||||
password: "very-secret",
|
||||
password_confirmation: "very-secret"
|
||||
}
|
||||
)
|
||||
|
||||
assert_delivered_email_matches(%{to: [{_, user_email}], subject: subject})
|
||||
assert user_email == "user@example.com"
|
||||
assert subject =~ "is your Plausible email verification code"
|
||||
end
|
||||
|
||||
test "creates user record", %{conn: conn, invitation: invitation} do
|
||||
post(conn, "/register/invitation/#{invitation.invitation_id}",
|
||||
user: %{
|
||||
name: "Jane Doe",
|
||||
email: "user@example.com",
|
||||
password: "very-secret",
|
||||
password_confirmation: "very-secret"
|
||||
}
|
||||
)
|
||||
|
||||
user = Repo.get_by(Plausible.Auth.User, email: "user@example.com")
|
||||
assert user.name == "Jane Doe"
|
||||
end
|
||||
|
||||
test "leaves trial_expiry_date null when invitation role is not :owner", %{
|
||||
conn: conn,
|
||||
invitation: invitation
|
||||
} do
|
||||
post(conn, "/register/invitation/#{invitation.invitation_id}",
|
||||
user: %{
|
||||
name: "Jane Doe",
|
||||
email: "user@example.com",
|
||||
password: "very-secret",
|
||||
password_confirmation: "very-secret"
|
||||
}
|
||||
)
|
||||
|
||||
user = Repo.get_by(Plausible.Auth.User, email: "user@example.com")
|
||||
assert is_nil(user.trial_expiry_date)
|
||||
end
|
||||
|
||||
test "logs the user in", %{conn: conn, invitation: invitation} do
|
||||
conn =
|
||||
post(conn, "/register/invitation/#{invitation.invitation_id}",
|
||||
user: %{
|
||||
name: "Jane Doe",
|
||||
email: "user@example.com",
|
||||
password: "very-secret",
|
||||
password_confirmation: "very-secret"
|
||||
}
|
||||
)
|
||||
|
||||
assert get_session(conn, :current_user_id)
|
||||
end
|
||||
|
||||
test "user is redirected to activation after registration", %{conn: conn} do
|
||||
conn =
|
||||
post(conn, "/register",
|
||||
user: %{
|
||||
name: "Jane Doe",
|
||||
email: "user@example.com",
|
||||
password: "very-secret",
|
||||
password_confirmation: "very-secret"
|
||||
}
|
||||
)
|
||||
|
||||
assert redirected_to(conn) == "/activate"
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /activate" do
|
||||
setup [:create_user, :log_in]
|
||||
|
||||
|
@ -101,6 +101,26 @@ defmodule PlausibleWeb.Site.InvitationControllerTest do
|
||||
|
||||
assert Repo.reload!(site).locked
|
||||
end
|
||||
|
||||
test "ownership transfer - will end the trial of the new owner immediately", %{
|
||||
conn: conn,
|
||||
user: user
|
||||
} do
|
||||
Repo.update_all(from(u in Plausible.Auth.User, where: u.id == ^user.id),
|
||||
set: [trial_expiry_date: Timex.today() |> Timex.shift(days: 7)]
|
||||
)
|
||||
|
||||
inviter = insert(:user)
|
||||
site = insert(:site, locked: false)
|
||||
|
||||
invitation =
|
||||
insert(:invitation, site_id: site.id, inviter: inviter, email: user.email, role: :owner)
|
||||
|
||||
post(conn, "/sites/invitations/#{invitation.invitation_id}/accept")
|
||||
|
||||
assert Timex.before?(Repo.reload!(user).trial_expiry_date, Timex.today())
|
||||
assert Repo.reload!(site).locked
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /sites/invitations/:invitation_id/reject" do
|
||||
|
@ -104,6 +104,19 @@ defmodule PlausibleWeb.SiteControllerTest do
|
||||
assert Repo.exists?(Plausible.Site, domain: "example.com")
|
||||
end
|
||||
|
||||
test "starts trial if user does not have trial yet", %{conn: conn, user: user} do
|
||||
Plausible.Auth.User.remove_trial_expiry(user) |> Repo.update!()
|
||||
|
||||
post(conn, "/sites", %{
|
||||
"site" => %{
|
||||
"domain" => "example.com",
|
||||
"timezone" => "Europe/London"
|
||||
}
|
||||
})
|
||||
|
||||
assert Repo.reload!(user).trial_expiry_date
|
||||
end
|
||||
|
||||
test "sends welcome email if this is the user's first site", %{conn: conn} do
|
||||
post(conn, "/sites", %{
|
||||
"site" => %{
|
||||
|
Loading…
Reference in New Issue
Block a user