mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 12:43:39 +08:00
Signup CTA first attempt
This commit is contained in:
parent
1bd0f5b015
commit
f595e562ea
|
@ -0,0 +1,56 @@
|
|||
export default Ember.Component.extend({
|
||||
action: "showCreateAccount",
|
||||
|
||||
actions: {
|
||||
neverShow() {
|
||||
this.keyValueStore.setItem('anon-cta-never', 't');
|
||||
this.session.set('showSignupCta', false);
|
||||
},
|
||||
hideForSession() {
|
||||
this.session.set('hideSignupCta', true);
|
||||
this.keyValueStore.setItem('anon-cta-hidden', new Date().getTime());
|
||||
Em.run.later(() =>
|
||||
this.session.set('showSignupCta', false),
|
||||
20 * 1000);
|
||||
},
|
||||
showCreateAccount() {
|
||||
this.sendAction();
|
||||
}
|
||||
},
|
||||
|
||||
signupMethodsTranslated: function() {
|
||||
const methods = Ember.get('Discourse.LoginMethod.all');
|
||||
const loginWithEmail = this.siteSettings.enable_local_logins;
|
||||
if (this.siteSettings.enable_sso) {
|
||||
return I18n.t('signup_cta.methods.sso');
|
||||
} else if (methods.length === 0) {
|
||||
if (loginWithEmail) {
|
||||
return I18n.t('signup_cta.methods.only_email');
|
||||
} else {
|
||||
return I18n.t('signup_cta.methods.unknown');
|
||||
}
|
||||
} else if (methods.length === 1) {
|
||||
let providerName = methods[0].name.capitalize();
|
||||
if (providerName === "Google_oauth2") {
|
||||
providerName = "Google";
|
||||
}
|
||||
if (loginWithEmail) {
|
||||
return I18n.t('signup_cta.methods.one_and_email', {provider: providerName});
|
||||
} else {
|
||||
return I18n.t('signup_cta.methods.only_other', {provider: providerName});
|
||||
}
|
||||
} else {
|
||||
if (loginWithEmail) {
|
||||
return I18n.t('signup_cta.methods.multiple', {count: methods.length});
|
||||
} else {
|
||||
return I18n.t('signup_cta.methods.multiple_no_email', {count: methods.length});
|
||||
}
|
||||
}
|
||||
}.property(),
|
||||
|
||||
_turnOffIfHidden: function() {
|
||||
if (this.session.get('hideSignupCta')) {
|
||||
this.session.set('showSignupCta', false);
|
||||
}
|
||||
}.on('willDestroyElement')
|
||||
});
|
|
@ -0,0 +1,86 @@
|
|||
import ScreenTrack from 'discourse/lib/screen-track';
|
||||
import Session from 'discourse/models/session';
|
||||
|
||||
const ANON_TOPIC_IDS = 5,
|
||||
ANON_PROMPT_READ_TIME = 15 * 60 * 1000,
|
||||
ANON_PROMPT_VISIT_COUNT = 2,
|
||||
ONE_DAY = 24 * 60 * 60 * 1000,
|
||||
PROMPT_HIDE_DURATION = ONE_DAY;
|
||||
|
||||
export default {
|
||||
name: "signup-cta",
|
||||
|
||||
initialize(container) {
|
||||
const screenTrack = ScreenTrack.current(),
|
||||
session = Session.current(),
|
||||
siteSettings = container.lookup('site-settings:main'),
|
||||
keyValueStore = container.lookup('key-value-store:main'),
|
||||
user = container.lookup('current-user:main');
|
||||
|
||||
// Preconditions
|
||||
|
||||
if (user) return; // must not be logged in
|
||||
if (keyValueStore.get('anon-cta-never')) return; // "never show again"
|
||||
if (!siteSettings.allow_new_registrations) return;
|
||||
if (siteSettings.invite_only) return;
|
||||
if (siteSettings.must_approve_users) return;
|
||||
if (siteSettings.login_required) return;
|
||||
if (!siteSettings.enable_signup_cta) return;
|
||||
|
||||
screenTrack.set('keyValueStore', keyValueStore);
|
||||
|
||||
function checkSignupCtaRequirements() {
|
||||
if (session.get('showSignupCta')) {
|
||||
return; // already shown
|
||||
}
|
||||
|
||||
if (session.get('hideSignupCta')) {
|
||||
return; // hidden for session
|
||||
}
|
||||
|
||||
if (keyValueStore.get('anon-cta-never')) {
|
||||
return; // hidden forever
|
||||
}
|
||||
|
||||
const now = new Date().getTime();
|
||||
const hiddenAt = keyValueStore.getInt('anon-cta-hidden', 0);
|
||||
if (hiddenAt > (now - PROMPT_HIDE_DURATION)) {
|
||||
return; // hidden in last 24 hours
|
||||
}
|
||||
|
||||
const visitCount = keyValueStore.getInt('anon-visit-count');
|
||||
if (visitCount < ANON_PROMPT_VISIT_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
const readTime = keyValueStore.getInt('anon-topic-time');
|
||||
if (readTime < ANON_PROMPT_READ_TIME) {
|
||||
return;
|
||||
}
|
||||
|
||||
const topicIdsString = keyValueStore.get('anon-topic-ids');
|
||||
if (!topicIdsString) { return; }
|
||||
let topicIdsAry = topicIdsString.split(',');
|
||||
if (topicIdsAry.length < ANON_TOPIC_IDS) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Requirements met.
|
||||
session.set('showSignupCta', true);
|
||||
}
|
||||
|
||||
screenTrack.set('anonFlushCallback', checkSignupCtaRequirements);
|
||||
|
||||
// Record a visit
|
||||
const nowVisit = new Date().getTime();
|
||||
const lastVisit = keyValueStore.getInt('anon-last-visit', nowVisit);
|
||||
if (nowVisit - lastVisit > ONE_DAY) {
|
||||
// more than a day
|
||||
const visitCount = keyValueStore.getInt('anon-visit-count', 1);
|
||||
keyValueStore.setItem('anon-visit-count', visitCount + 1);
|
||||
}
|
||||
keyValueStore.setItem('anon-last-visit', nowVisit);
|
||||
|
||||
checkSignupCtaRequirements();
|
||||
}
|
||||
};
|
|
@ -43,6 +43,14 @@ KeyValueStore.prototype = {
|
|||
get(key) {
|
||||
if (!safeLocalStorage) { return null; }
|
||||
return safeLocalStorage[this.context + key];
|
||||
},
|
||||
|
||||
getInt(key, def) {
|
||||
if (!def) { def = 0; }
|
||||
if (!safeLocalStorage) { return def; }
|
||||
const result = parseInt(this.get(key));
|
||||
if (!isFinite(result)) { return def; }
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
import Singleton from 'discourse/mixins/singleton';
|
||||
|
||||
const PAUSE_UNLESS_SCROLLED = 1000 * 60 * 3,
|
||||
MAX_TRACKING_TIME = 1000 * 60 * 6;
|
||||
MAX_TRACKING_TIME = 1000 * 60 * 6,
|
||||
ANON_MAX_TOPIC_IDS = 5;
|
||||
|
||||
const ScreenTrack = Ember.Object.extend({
|
||||
|
||||
init() {
|
||||
this.reset();
|
||||
// TODO fix this
|
||||
this.set('keyValueStore', Discourse.__container__.lookup('key-value-store:main'));
|
||||
},
|
||||
|
||||
start(topicId, topicController) {
|
||||
|
@ -82,9 +85,6 @@ const ScreenTrack = Ember.Object.extend({
|
|||
flush() {
|
||||
if (this.get('cancelled')) { return; }
|
||||
|
||||
// We don't log anything unless we're logged in
|
||||
if (!Discourse.User.current()) return;
|
||||
|
||||
const newTimings = {},
|
||||
totalTimings = this.get('totalTimings'),
|
||||
self = this;
|
||||
|
@ -115,26 +115,52 @@ const ScreenTrack = Ember.Object.extend({
|
|||
Discourse.TopicTrackingState.current().updateSeen(topicId, highestSeen);
|
||||
|
||||
if (!$.isEmptyObject(newTimings)) {
|
||||
Discourse.ajax('/topics/timings', {
|
||||
data: {
|
||||
timings: newTimings,
|
||||
topic_time: this.get('topicTime'),
|
||||
topic_id: topicId
|
||||
},
|
||||
cache: false,
|
||||
type: 'POST',
|
||||
headers: {
|
||||
'X-SILENCE-LOGGER': 'true'
|
||||
if (Discourse.User.current()) {
|
||||
Discourse.ajax('/topics/timings', {
|
||||
data: {
|
||||
timings: newTimings,
|
||||
topic_time: this.get('topicTime'),
|
||||
topic_id: topicId
|
||||
},
|
||||
cache: false,
|
||||
type: 'POST',
|
||||
headers: {
|
||||
'X-SILENCE-LOGGER': 'true'
|
||||
}
|
||||
}).then(function() {
|
||||
const controller = self.get('topicController');
|
||||
if (controller) {
|
||||
const postNumbers = Object.keys(newTimings).map(function(v) {
|
||||
return parseInt(v, 10);
|
||||
});
|
||||
controller.readPosts(topicId, postNumbers);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Anonymous viewer - save to localStorage
|
||||
const store = this.get('keyValueStore');
|
||||
|
||||
// Save total time
|
||||
const existingTime = store.getInt('anon-topic-time');
|
||||
store.setItem('anon-topic-time', existingTime + this.get('topicTime'));
|
||||
|
||||
// Save unique topic IDs up to a max
|
||||
let topicIds = store.get('anon-topic-ids');
|
||||
if (topicIds) {
|
||||
topicIds = topicIds.split(',').map(e => parseInt(e));
|
||||
} else {
|
||||
topicIds = [];
|
||||
}
|
||||
}).then(function(){
|
||||
const controller = self.get('topicController');
|
||||
if(controller){
|
||||
const postNumbers = Object.keys(newTimings).map(function(v){
|
||||
return parseInt(v,10);
|
||||
});
|
||||
controller.readPosts(topicId, postNumbers);
|
||||
if (topicIds.indexOf(topicId) === -1 && topicIds.length < ANON_MAX_TOPIC_IDS) {
|
||||
topicIds.push(topicId);
|
||||
store.setItem('anon-topic-ids', topicIds.join(','));
|
||||
}
|
||||
});
|
||||
|
||||
// Inform the observer
|
||||
if (this.get('anonFlushCallback')) {
|
||||
this.get('anonFlushCallback')();
|
||||
}
|
||||
}
|
||||
|
||||
this.set('topicTime', 0);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<div class="signup-cta alert alert-info">
|
||||
{{#if session.hideSignupCta}}
|
||||
<p>{{i18n "signup_cta.hidden_for_session"}}</p>
|
||||
<div class="buttons">
|
||||
<a {{action "neverShow"}}>{{i18n "signup_cta.hide_forever"}}</a>
|
||||
</div>
|
||||
{{else}}
|
||||
<p>{{i18n "signup_cta.line_1"}}</p>
|
||||
<p>{{i18n "signup_cta.line_2"}}</p>
|
||||
<p>{{signupMethodsTranslated}}</p>
|
||||
|
||||
<div class="buttons">
|
||||
{{d-button action="showCreateAccount" label="signup_cta.sign_up" icon="check" class="btn-primary"}}
|
||||
{{d-button action="hideForSession" label="signup_cta.hide_session" class="no-icon"}}
|
||||
<a {{action "neverShow"}}>{{i18n "signup_cta.hide_forever"}}</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -87,7 +87,12 @@
|
|||
{{#if loadedAllPosts}}
|
||||
|
||||
{{view "topic-closing" topic=model}}
|
||||
{{view "topic-footer-buttons" topic=model}}
|
||||
{{#if session.showSignupCta}}
|
||||
{{! replace "Log In to Reply" with the infobox }}
|
||||
{{signup-cta}}
|
||||
{{else}}
|
||||
{{view "topic-footer-buttons" topic=model}}
|
||||
{{/if}}
|
||||
|
||||
{{#if model.pending_posts_count}}
|
||||
<div class="has-pending-posts">
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
.alert {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.signup-cta {
|
||||
margin-top: 15px;
|
||||
a {
|
||||
float: right;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -712,6 +712,22 @@ en:
|
|||
one: reply
|
||||
other: replies
|
||||
|
||||
signup_cta:
|
||||
sign_up: "Sounds great!"
|
||||
hide_session: "Maybe later"
|
||||
hide_forever: "Never show this again"
|
||||
hidden_for_session: "OK, I'll ask you tomorrow. You can always click the 'Log In' button to create an account, too."
|
||||
line_1: Hey there! Looks like you're enjoying the forum, but you're not signed up for an account.
|
||||
line_2: Logged-in users get their last read position in every topic saved, so you come right back where you left off. You can also "Watch" topics so that you get a notification whenever a new post is made, and give likes to others' posts.
|
||||
methods:
|
||||
sso: "Use your account on the main site to log in."
|
||||
only_email: "You just need a valid email account to sign up."
|
||||
only_other: "Just use your %{provider} account to sign up."
|
||||
one_and_email: "Juse use your %{provider} account or your email to sign up."
|
||||
multiple_no_email: "Choose from any of the %{count} supported login providers to get started."
|
||||
multiple: "Signing up couldn't be easier: use any of the %{count} available login providers, or sign up with an email and password."
|
||||
unknown: "error getting supported login methods"
|
||||
|
||||
summary:
|
||||
enabled_description: "You're viewing a summary of this topic: the most interesting posts as determined by the community."
|
||||
description: "There are <b>{{count}}</b> replies."
|
||||
|
|
|
@ -199,6 +199,9 @@ login:
|
|||
allow_new_registrations:
|
||||
client: true
|
||||
default: true
|
||||
enable_signup_cta:
|
||||
client: true
|
||||
default: true
|
||||
enable_google_oauth2_logins:
|
||||
client: true
|
||||
default: false
|
||||
|
|
Loading…
Reference in New Issue
Block a user