mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 10:50:26 -06:00
DEV: Convert account activation pages to use Ember (#28206)
This commit is contained in:
parent
df18bcd029
commit
5b78bbd138
@ -0,0 +1,8 @@
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
export default class ActivateAccountRoute extends DiscourseRoute {
|
||||
titleToken() {
|
||||
return I18n.t("login.activate_account");
|
||||
}
|
||||
}
|
@ -110,6 +110,7 @@ export default function () {
|
||||
this.route("resent");
|
||||
this.route("edit-email");
|
||||
});
|
||||
this.route("activate-account", { path: "/u/activate-account/:token" });
|
||||
this.route("confirm-new-email", { path: "/u/confirm-new-email/:token" });
|
||||
this.route("confirm-old-email", { path: "/u/confirm-old-email/:token" });
|
||||
this.route(
|
||||
|
@ -0,0 +1,128 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import RouteTemplate from "ember-route-template";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import hideApplicationHeaderButtons from "discourse/helpers/hide-application-header-buttons";
|
||||
import hideApplicationSidebar from "discourse/helpers/hide-application-sidebar";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { wavingHandURL } from "discourse/lib/waving-hand-url";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
|
||||
export default RouteTemplate(
|
||||
class extends Component {
|
||||
@service siteSettings;
|
||||
|
||||
@tracked accountActivated = false;
|
||||
@tracked isLoading = false;
|
||||
@tracked needsApproval = false;
|
||||
@tracked errorMessage = null;
|
||||
|
||||
@action
|
||||
async activate() {
|
||||
this.isLoading = true;
|
||||
|
||||
let hp;
|
||||
try {
|
||||
const response = await fetch("/session/hp", {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
hp = await response.json();
|
||||
} catch (error) {
|
||||
this.isLoading = false;
|
||||
popupAjaxError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await ajax(
|
||||
`/u/activate-account/${this.args.model.token}.json`,
|
||||
{
|
||||
type: "PUT",
|
||||
data: {
|
||||
password_confirmation: hp.value,
|
||||
challenge: hp.challenge.split("").reverse().join(""),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.success) {
|
||||
this.errorMessage = i18n("user.activate_account.already_done");
|
||||
return;
|
||||
}
|
||||
|
||||
this.accountActivated = true;
|
||||
|
||||
if (response.redirect_to) {
|
||||
window.location.href = response.redirect_to;
|
||||
} else if (response.needs_approval) {
|
||||
this.needsApproval = true;
|
||||
} else {
|
||||
setTimeout(() => (window.location.href = "/"), 2000);
|
||||
}
|
||||
} catch (error) {
|
||||
this.errorMessage = i18n("user.activate_account.already_done");
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
{{hideApplicationSidebar}}
|
||||
{{hideApplicationHeaderButtons "search" "login" "signup"}}
|
||||
<div id="simple-container">
|
||||
{{#if this.errorMessage}}
|
||||
<div class="alert alert-error">
|
||||
{{this.errorMessage}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="activate-account">
|
||||
<h1 class="activate-title">{{i18n
|
||||
"user.activate_account.welcome_to"
|
||||
site_name=this.siteSettings.title
|
||||
}}
|
||||
<img src={{(wavingHandURL)}} alt="" class="waving-hand" />
|
||||
</h1>
|
||||
<br />
|
||||
{{#if this.accountActivated}}
|
||||
<div class="perform-activation">
|
||||
<div class="image">
|
||||
<img
|
||||
src="/images/wizard/tada.svg"
|
||||
class="waving-hand"
|
||||
alt="tada emoji"
|
||||
/>
|
||||
</div>
|
||||
{{#if this.needsApproval}}
|
||||
<p>{{i18n "user.activate_account.approval_required"}}</p>
|
||||
{{else}}
|
||||
<p>{{i18n "user.activate_account.please_continue"}}</p>
|
||||
<p>
|
||||
<DButton
|
||||
class="continue-button"
|
||||
@translatedLabel={{i18n
|
||||
"user.activate_account.continue_button"
|
||||
site_name=this.siteSettings.title
|
||||
}}
|
||||
@href="/"
|
||||
/>
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<DButton
|
||||
id="activate-account-button"
|
||||
class="btn-primary"
|
||||
@action={{this.activate}}
|
||||
@label="user.activate_account.action"
|
||||
@disabled={{this.isLoading}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
}
|
||||
);
|
@ -1,27 +0,0 @@
|
||||
(function () {
|
||||
const activateButton = document.querySelector("#activate-account-button");
|
||||
activateButton.addEventListener("click", async function () {
|
||||
activateButton.setAttribute("disabled", true);
|
||||
const hpPath = document.getElementById("data-activate-account").dataset
|
||||
.path;
|
||||
|
||||
try {
|
||||
const response = await fetch(hpPath, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
const hp = await response.json();
|
||||
|
||||
document.querySelector("#password_confirmation").value = hp.value;
|
||||
document.querySelector("#challenge").value = hp.challenge
|
||||
.split("")
|
||||
.reverse()
|
||||
.join("");
|
||||
document.querySelector("#activate-account-form").submit();
|
||||
} catch (e) {
|
||||
activateButton.removeAttribute("disabled");
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
})();
|
@ -1,6 +0,0 @@
|
||||
(function () {
|
||||
const path = document.getElementById("data-auto-redirect").dataset.path;
|
||||
setTimeout(function () {
|
||||
window.location.href = path;
|
||||
}, 2000);
|
||||
})();
|
@ -1100,7 +1100,11 @@ class UsersController < ApplicationController
|
||||
|
||||
def activate_account
|
||||
expires_now
|
||||
render layout: "no_ember"
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render "default/empty" }
|
||||
format.json { render json: success_json }
|
||||
end
|
||||
end
|
||||
|
||||
def perform_account_activation
|
||||
@ -1130,21 +1134,22 @@ class UsersController < ApplicationController
|
||||
end
|
||||
|
||||
if Wizard.user_requires_completion?(@user)
|
||||
return redirect_to(wizard_path)
|
||||
@redirect_to = wizard_path
|
||||
elsif destination_url.present?
|
||||
return redirect_to(destination_url, allow_other_host: true)
|
||||
@redirect_to = destination_url
|
||||
elsif SiteSetting.enable_discourse_connect_provider &&
|
||||
payload = cookies.delete(:sso_payload)
|
||||
return redirect_to(session_sso_provider_url + "?" + payload)
|
||||
@redirect_to = session_sso_provider_url + "?" + payload
|
||||
end
|
||||
else
|
||||
@needs_approval = true
|
||||
end
|
||||
else
|
||||
flash.now[:error] = I18n.t("activation.already_done")
|
||||
return render_json_error(I18n.t("activation.already_done"))
|
||||
end
|
||||
|
||||
render layout: "no_ember"
|
||||
render json:
|
||||
success_json.merge(redirect_to: @redirect_to, needs_approval: @needs_approval || false)
|
||||
end
|
||||
|
||||
def update_activation_email
|
||||
|
@ -1,19 +0,0 @@
|
||||
<div id='simple-container'>
|
||||
<div class='activate-account'>
|
||||
<h1 class="activate-title"><%= t 'activation.welcome_to', site_name: SiteSetting.title %> <img src="<%= Emoji.url_for("wave") %>" alt="" class="waving-hand"></h1>
|
||||
<br/>
|
||||
<button class='btn btn-primary' id='activate-account-button'><%= t 'activation.action' %></button>
|
||||
|
||||
<%= form_tag(perform_activate_account_path, method: :put, id: 'activate-account-form') do %>
|
||||
<%= hidden_field_tag 'password_confirmation' %>
|
||||
<%= hidden_field_tag 'challenge' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- content_for(:no_ember_head) do %>
|
||||
<%= render_google_universal_analytics_code %>
|
||||
<%= tag.meta id: 'data-activate-account', data: { path: path('/session/hp') } %>
|
||||
<%- end %>
|
||||
|
||||
<%= preload_script "activate-account" %>
|
@ -1,27 +0,0 @@
|
||||
<div id='simple-container'>
|
||||
<%if flash[:error]%>
|
||||
<div class='alert alert-error'>
|
||||
<%=flash[:error]%>
|
||||
</div>
|
||||
<%else%>
|
||||
<div class='activate-account'>
|
||||
<h1 class="activate-title"><%= t 'activation.welcome_to', site_name: SiteSetting.title %> <img src="<%= Emoji.url_for("wave") %>" alt="" class="waving-hand"></h1>
|
||||
<br>
|
||||
<div class='perform-activation'>
|
||||
<div class="image">
|
||||
<img src="<%= Discourse.base_path + '/images/wizard/tada.svg' %>" alt="tada emoji" class="waving-hand">
|
||||
</div>
|
||||
<% if @needs_approval %>
|
||||
<p><%= t 'activation.approval_required' %></p>
|
||||
<% else %>
|
||||
<p><%= t('activation.please_continue') %></p>
|
||||
<p><a class="btn" href="<%= path "/" %>"><%= t('activation.continue_button', site_name: SiteSetting.title) -%></a></p>
|
||||
<%- content_for(:no_ember_head) do %>
|
||||
<%= tag.meta id: 'data-auto-redirect', data: { path: path('/') } %>
|
||||
<%- end %>
|
||||
<%= preload_script 'auto-redirect' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<%end%>
|
||||
</div>
|
@ -1745,6 +1745,14 @@ en:
|
||||
account_specific: "Your %{provider} account '%{account_description}' will be used for authentication."
|
||||
generic: "Your %{provider} account will be used for authentication."
|
||||
|
||||
activate_account:
|
||||
action: "Click here to activate your account"
|
||||
already_done: "Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?"
|
||||
please_continue: "Your new account is confirmed; you will be redirected to the home page."
|
||||
continue_button: "Continue to %{site_name}"
|
||||
welcome_to: "Welcome to %{site_name}!"
|
||||
approval_required: "A moderator must manually approve your new account before you can access this forum. You'll get an email when your account is approved!"
|
||||
|
||||
name:
|
||||
title: "Name"
|
||||
instructions: "Your full name (optional)."
|
||||
@ -2387,6 +2395,7 @@ en:
|
||||
resend_activation_email: "Click here to send the activation email again."
|
||||
omniauth_disallow_totp: "Your account has two-factor authentication enabled. Please log in with your password."
|
||||
|
||||
activate_account: "Activate Account"
|
||||
resend_title: "Resend Activation Email"
|
||||
change_email: "Change Email Address"
|
||||
provide_new_email: "Provide a new address and we'll resend your confirmation email."
|
||||
|
@ -1046,11 +1046,7 @@ en:
|
||||
connected: "(connected)"
|
||||
|
||||
activation:
|
||||
action: "Click here to activate your account"
|
||||
already_done: "Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?"
|
||||
please_continue: "Your new account is confirmed; you will be redirected to the home page."
|
||||
continue_button: "Continue to %{site_name}"
|
||||
welcome_to: "Welcome to %{site_name}!"
|
||||
approval_required: "A moderator must manually approve your new account before you can access this forum. You'll get an email when your account is approved!"
|
||||
missing_session: "We cannot detect if your account was created, please ensure you have cookies enabled."
|
||||
activated: "Sorry, this account has already been activated."
|
||||
|
@ -60,9 +60,8 @@ RSpec.describe UsersController do
|
||||
|
||||
context "with invalid token" do
|
||||
it "return success" do
|
||||
put "/u/activate-account/invalid-tooken"
|
||||
expect(response.status).to eq(200)
|
||||
expect(flash[:error]).to be_present
|
||||
put "/u/activate-account/invalid-token"
|
||||
expect(response.status).to eq(422)
|
||||
end
|
||||
end
|
||||
|
||||
@ -108,12 +107,11 @@ RSpec.describe UsersController do
|
||||
)
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(flash[:error]).to be_blank
|
||||
expect(session[:current_user_id]).to be_present
|
||||
|
||||
expect(CGI.unescapeHTML(response.body)).to_not include(
|
||||
I18n.t("activation.approval_required"),
|
||||
)
|
||||
data = JSON.parse(response.body)
|
||||
expect(data["needs_approval"]).to eq(false)
|
||||
|
||||
expect(session[:current_user_id]).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
@ -124,11 +122,9 @@ RSpec.describe UsersController do
|
||||
put "/u/activate-account/#{email_token.token}"
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
expect(CGI.unescapeHTML(response.body)).to include(I18n.t("activation.approval_required"))
|
||||
data = JSON.parse(response.body)
|
||||
expect(data["needs_approval"]).to eq(true)
|
||||
|
||||
expect(response.body).to_not have_tag(:script, with: { src: "/assets/application.js" })
|
||||
|
||||
expect(flash[:error]).to be_blank
|
||||
expect(session[:current_user_id]).to be_blank
|
||||
end
|
||||
end
|
||||
@ -141,7 +137,8 @@ RSpec.describe UsersController do
|
||||
|
||||
put "/u/activate-account/#{email_token.token}"
|
||||
|
||||
expect(response).to redirect_to(destination_url)
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body["redirect_to"]).to eq(destination_url)
|
||||
end
|
||||
end
|
||||
|
||||
@ -158,7 +155,8 @@ RSpec.describe UsersController do
|
||||
it "should redirect to the topic" do
|
||||
put "/u/activate-account/#{email_token.token}"
|
||||
|
||||
expect(response).to redirect_to(topic.relative_url)
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body["redirect_to"]).to eq(topic.relative_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -4,8 +4,10 @@ require "rotp"
|
||||
|
||||
shared_examples "login scenarios" do
|
||||
let(:login_modal) { PageObjects::Modals::Login.new }
|
||||
let(:activate_account) { PageObjects::Pages::ActivateAccount.new }
|
||||
let(:user_preferences_security_page) { PageObjects::Pages::UserPreferencesSecurity.new }
|
||||
fab!(:user) { Fabricate(:user, username: "john", password: "supersecurepassword") }
|
||||
fab!(:admin) { Fabricate(:admin, username: "admin", password: "supersecurepassword") }
|
||||
let(:user_menu) { PageObjects::Components::UserMenu.new }
|
||||
|
||||
before { Jobs.run_immediately! }
|
||||
@ -43,12 +45,39 @@ shared_examples "login scenarios" do
|
||||
activation_link = wait_for_email_link(user, :activation)
|
||||
visit activation_link
|
||||
|
||||
find("#activate-account-button").click
|
||||
activate_account.click_activate_account
|
||||
activate_account.click_continue
|
||||
|
||||
expect(page).to have_current_path("/")
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
|
||||
it "redirects to the wizard after activating account" do
|
||||
login_modal.open
|
||||
login_modal.fill(username: "admin", password: "supersecurepassword")
|
||||
login_modal.click_login
|
||||
expect(page).to have_css(".not-activated-modal")
|
||||
login_modal.click(".activation-controls button.resend")
|
||||
|
||||
activation_link = wait_for_email_link(admin, :activation)
|
||||
visit activation_link
|
||||
|
||||
activate_account.click_activate_account
|
||||
expect(page).to have_current_path("/wizard")
|
||||
end
|
||||
|
||||
it "shows error when when activation link is invalid" do
|
||||
login_modal.open
|
||||
login_modal.fill(username: "john", password: "supersecurepassword")
|
||||
login_modal.click_login
|
||||
expect(page).to have_css(".not-activated-modal")
|
||||
|
||||
visit "/u/activate-account/invalid"
|
||||
|
||||
activate_account.click_activate_account
|
||||
expect(activate_account).to have_error
|
||||
end
|
||||
|
||||
it "displays the right message when user's email has been marked as expired" do
|
||||
password = "myawesomepassword"
|
||||
user.update!(password:)
|
||||
|
20
spec/system/page_objects/pages/activate_account.rb
Normal file
20
spec/system/page_objects/pages/activate_account.rb
Normal file
@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module PageObjects
|
||||
module Pages
|
||||
class ActivateAccount < PageObjects::Pages::Base
|
||||
def click_activate_account
|
||||
find("#activate-account-button").click
|
||||
end
|
||||
|
||||
def click_continue
|
||||
find(".perform-activation .continue-button").click
|
||||
end
|
||||
|
||||
def has_error?
|
||||
has_css?("#simple-container .alert-error")
|
||||
has_content?(I18n.t("js.user.activate_account.already_done"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
40
spec/system/page_objects/pages/invite_form.rb
Normal file
40
spec/system/page_objects/pages/invite_form.rb
Normal file
@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module PageObjects
|
||||
module Pages
|
||||
class InviteForm < PageObjects::Pages::Base
|
||||
def open(key)
|
||||
visit "/invites/#{key}"
|
||||
end
|
||||
|
||||
def fill_username(username)
|
||||
find("#new-account-username").fill_in(with: username)
|
||||
end
|
||||
|
||||
def fill_password(password)
|
||||
find("#new-account-password").fill_in(with: password)
|
||||
end
|
||||
|
||||
def has_valid_username?
|
||||
find(".username-input").has_css?("#username-validation.good")
|
||||
end
|
||||
|
||||
def has_valid_password?
|
||||
find(".password-input").has_css?("#password-validation.good")
|
||||
end
|
||||
|
||||
def has_valid_fields?
|
||||
has_valid_username?
|
||||
has_valid_password?
|
||||
end
|
||||
|
||||
def click_create_account
|
||||
find(".invitation-cta__accept.btn-primary").click
|
||||
end
|
||||
|
||||
def has_successful_message?
|
||||
has_css?(".invite-success")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -3,11 +3,15 @@
|
||||
shared_examples "signup scenarios" do
|
||||
let(:login_modal) { PageObjects::Modals::Login.new }
|
||||
let(:signup_modal) { PageObjects::Modals::Signup.new }
|
||||
let(:invite_form) { PageObjects::Pages::InviteForm.new }
|
||||
let(:activate_account) { PageObjects::Pages::ActivateAccount.new }
|
||||
let(:invite) { Fabricate(:invite, email: "johndoe@example.com") }
|
||||
let(:topic) { Fabricate(:topic, title: "Super cool topic") }
|
||||
|
||||
context "when anyone can create an account" do
|
||||
it "can signup" do
|
||||
Jobs.run_immediately!
|
||||
before { Jobs.run_immediately! }
|
||||
|
||||
it "can signup" do
|
||||
signup_modal.open
|
||||
signup_modal.fill_email("johndoe@example.com")
|
||||
signup_modal.fill_username("john")
|
||||
@ -18,6 +22,53 @@ shared_examples "signup scenarios" do
|
||||
expect(page).to have_css(".account-created")
|
||||
end
|
||||
|
||||
it "can signup and activate account" do
|
||||
signup_modal.open
|
||||
signup_modal.fill_email("johndoe@example.com")
|
||||
signup_modal.fill_username("john")
|
||||
signup_modal.fill_password("supersecurepassword")
|
||||
expect(signup_modal).to have_valid_fields
|
||||
|
||||
signup_modal.click_create_account
|
||||
expect(page).to have_css(".account-created")
|
||||
|
||||
mail = ActionMailer::Base.deliveries.first
|
||||
expect(mail.to).to contain_exactly("johndoe@example.com")
|
||||
activation_link = mail.body.to_s[%r{/u/activate-account/\S+}]
|
||||
|
||||
visit activation_link
|
||||
|
||||
activate_account.click_activate_account
|
||||
activate_account.click_continue
|
||||
|
||||
expect(page).to have_current_path("/")
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
|
||||
it "redirects to the topic the user was invited to after activating account" do
|
||||
TopicInvite.create!(invite: invite, topic: topic)
|
||||
|
||||
invite_form.open(invite.invite_key)
|
||||
|
||||
invite_form.fill_username("john")
|
||||
invite_form.fill_password("supersecurepassword")
|
||||
|
||||
expect(invite_form).to have_valid_fields
|
||||
|
||||
invite_form.click_create_account
|
||||
expect(invite_form).to have_successful_message
|
||||
|
||||
mail = ActionMailer::Base.deliveries.first
|
||||
expect(mail.to).to contain_exactly("johndoe@example.com")
|
||||
activation_link = mail.body.to_s[%r{/u/activate-account/\S+}]
|
||||
|
||||
visit activation_link
|
||||
|
||||
activate_account.click_activate_account
|
||||
|
||||
expect(page).to have_current_path("/t/#{topic.slug}/#{topic.id}")
|
||||
end
|
||||
|
||||
context "with invite code" do
|
||||
before { SiteSetting.invite_code = "cupcake" }
|
||||
|
||||
@ -124,6 +175,7 @@ shared_examples "signup scenarios" do
|
||||
user = User.find_by(username: "john")
|
||||
EmailToken.confirm(Fabricate(:email_token, user: user).token)
|
||||
|
||||
visit "/"
|
||||
login_modal.open
|
||||
login_modal.fill_username("john")
|
||||
login_modal.fill_password("supersecurepassword")
|
||||
|
Loading…
Reference in New Issue
Block a user