Signup CTA first attempt

This commit is contained in:
Kane York 2015-09-01 16:16:19 -07:00
parent 1bd0f5b015
commit f595e562ea
9 changed files with 248 additions and 23 deletions

View File

@ -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')
});

View File

@ -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();
}
};

View File

@ -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;
}
};

View File

@ -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);
}

View File

@ -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>

View File

@ -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">

View File

@ -1,3 +1,10 @@
.alert {
margin-bottom: 15px;
}
.signup-cta {
margin-top: 15px;
a {
float: right;
text-decoration: underline;
}
}

View File

@ -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."

View File

@ -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