mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-23 23:58:02 -05:00
Update subscribe modal with parent help button
This commit is contained in:
parent
35c04974dd
commit
c842f45786
10 changed files with 350 additions and 143 deletions
BIN
app/assets/images/pages/play/modal/subscribe-heroes.png
Normal file
BIN
app/assets/images/pages/play/modal/subscribe-heroes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
|
@ -381,6 +381,15 @@
|
|||
recovered: "Previous gems purchase recovered. Please refresh the page."
|
||||
|
||||
subscribe:
|
||||
comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
|
||||
feature1: "60+ basic levels across 4 worlds"
|
||||
feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
|
||||
feature3: "30+ bonus levels"
|
||||
feature4: "<strong>3500 bonus gems</strong> every month!"
|
||||
feature5: " Video tutorials"
|
||||
feature6: "Premium email support"
|
||||
free: "Free"
|
||||
month: "month"
|
||||
subscribe_title: "Subscribe"
|
||||
unsubscribe: "Unsubscribe"
|
||||
confirm_unsubscribe: "Confirm Unsubscribe"
|
||||
|
@ -394,12 +403,20 @@
|
|||
heroes: "More powerful heroes!"
|
||||
gems: "3500 bonus gems every month!"
|
||||
items: "Over 250 bonus items!"
|
||||
parent_button: "Ask your parent"
|
||||
parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
|
||||
parent_email_input_invalid: "Email address invalid."
|
||||
parent_email_input_label: "Parent email address"
|
||||
parent_email_input_placeholder: "Enter parent email"
|
||||
parent_email_send: "Send Email"
|
||||
parent_email_sent: "Email sent!"
|
||||
parent_email_title: "What's your parent's email?"
|
||||
parents: "For Parents"
|
||||
parents_title: "Your child will learn to code."
|
||||
parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
|
||||
parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
|
||||
parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
|
||||
subscribe_button: "Subscribe Now"
|
||||
subscribe_button: "Subscribe"
|
||||
stripe_description: "Monthly Subscription"
|
||||
subscription_required_to_play: "You'll need a subscription to play this level."
|
||||
unlock_help_videos: "Subscribe to unlock all video tutorials."
|
||||
|
|
|
@ -85,6 +85,12 @@ _.extend UserSchema.properties,
|
|||
recruitNotes: {$ref: '#/definitions/emailSubscription'}
|
||||
employerNotes: {$ref: '#/definitions/emailSubscription'}
|
||||
|
||||
oneTimes: c.array {title: 'One-time emails'},
|
||||
c.object {title: 'One-time email', required: ['type', 'targetEmail']},
|
||||
type: c.shortString() # E.g 'subscribe modal parent'
|
||||
targetEmail: c.shortString()
|
||||
sent: c.date() # Set when sent
|
||||
|
||||
# server controlled
|
||||
permissions: c.array {}, c.shortString()
|
||||
dateCreated: c.date({title: 'Date Joined'})
|
||||
|
@ -272,7 +278,7 @@ _.extend UserSchema.properties,
|
|||
purchased: c.RewardSchema 'purchased with gems or money'
|
||||
spent: {type: 'number'}
|
||||
stripeCustomerID: { type: 'string' } # TODO: Migrate away from this property
|
||||
|
||||
|
||||
stripe: c.object {}, {
|
||||
customerID: { type: 'string' }
|
||||
planID: { enum: ['basic'] }
|
||||
|
|
|
@ -51,36 +51,11 @@
|
|||
&:hover
|
||||
color: yellow
|
||||
|
||||
|
||||
//- Selling points
|
||||
|
||||
#selling-points
|
||||
position: absolute
|
||||
left: 65px
|
||||
top: 335px
|
||||
width: 650px
|
||||
font-weight: bold
|
||||
line-height: 18px
|
||||
color: black
|
||||
font-family: $headings-font-family
|
||||
font-size: 18px
|
||||
|
||||
.point
|
||||
width: 150px
|
||||
overflow: none
|
||||
float: left
|
||||
text-align: center
|
||||
margin-right: 10px
|
||||
|
||||
#parents-info
|
||||
position: absolute
|
||||
right: 7px
|
||||
top: 56px
|
||||
text-decoration: underline
|
||||
cursor: pointer
|
||||
//- Popovers
|
||||
|
||||
.popover
|
||||
z-index: 1050
|
||||
min-width: 400px
|
||||
|
||||
h3
|
||||
background: transparent
|
||||
|
@ -88,13 +63,67 @@
|
|||
font-size: 30px
|
||||
color: black
|
||||
|
||||
//- Sales image
|
||||
|
||||
.subscribe-image
|
||||
position: absolute
|
||||
top: 114px
|
||||
right: 65px
|
||||
|
||||
//- Feature comparison table
|
||||
|
||||
.comparison-blurb
|
||||
position: absolute
|
||||
left: 10%
|
||||
top: 132px
|
||||
width: 450px
|
||||
background: rgba(0, 0, 0, 0.0)
|
||||
font-weight: normal
|
||||
line-height: 18px
|
||||
color: black
|
||||
font-family: $headings-font-family
|
||||
font-size: 18px
|
||||
|
||||
.comparison-table
|
||||
position: absolute
|
||||
left: 10%
|
||||
top: 160px
|
||||
width: 450px
|
||||
background: rgba(0, 0, 0, 0.0)
|
||||
thead
|
||||
tr
|
||||
th
|
||||
font-size: 24px
|
||||
font-variant: small-caps
|
||||
font-family: "Open Sans Condensed", "Helvetica Neue", Helvetica, Arial, sans-serif
|
||||
font-weight: 700
|
||||
line-height: 1.1
|
||||
color: #317EAC
|
||||
tbody
|
||||
font-size: 14px
|
||||
.center-ok
|
||||
text-align: center
|
||||
|
||||
//- Parent info popover link
|
||||
|
||||
#parents-info
|
||||
position: absolute
|
||||
left: 38px
|
||||
top: 389px
|
||||
text-decoration: underline
|
||||
cursor: pointer
|
||||
font-weight: bold
|
||||
line-height: 18px
|
||||
color: black
|
||||
font-family: $headings-font-family
|
||||
font-size: 18px
|
||||
|
||||
//- Purchase button
|
||||
|
||||
.purchase-button
|
||||
position: absolute
|
||||
left: 73px
|
||||
width: 600px
|
||||
right: 24px
|
||||
width: 400px
|
||||
height: 70px
|
||||
top: 430px
|
||||
font-size: 32px
|
||||
|
@ -116,6 +145,28 @@
|
|||
padding: 2px 0 0 2px
|
||||
color: white
|
||||
|
||||
//- Parent button
|
||||
//- TODO: Add hover and active effects
|
||||
|
||||
.parent-button
|
||||
position: absolute
|
||||
left: 24px
|
||||
width: 250px
|
||||
height: 70px
|
||||
top: 430px
|
||||
font-size: 28px
|
||||
line-height: 38px
|
||||
border-style: solid
|
||||
border-image: url(/images/common/button-background-warning-disabled.png) 14 20 20 20 fill round
|
||||
border-width: 14px 20px 20px 20px
|
||||
color: darken(white, 5%)
|
||||
|
||||
#email-parent-form
|
||||
.email_invalid
|
||||
color: red
|
||||
display: none
|
||||
#email-parent-complete
|
||||
display: none
|
||||
|
||||
//- Errors
|
||||
|
||||
|
|
|
@ -7,27 +7,67 @@
|
|||
#retrying-alert.alert.alert-danger(data-i18n="buy_gems.retrying")
|
||||
|
||||
else
|
||||
img(src="/images/pages/play/modal/subscribe-background.png")#subscribe-background
|
||||
img#subscribe-background(src="/images/pages/play/modal/subscribe-background-blank.png")
|
||||
img.subscribe-image(src="/images/pages/play/modal/subscribe-heroes.png")
|
||||
|
||||
h1(data-i18n="subscribe.subscribe_title") Subscribe
|
||||
|
||||
div#close-modal
|
||||
span.glyphicon.glyphicon-remove
|
||||
|
||||
#selling-points
|
||||
#point-levels.point
|
||||
.blurb(data-i18n="subscribe.levels")
|
||||
#point-heroes.point
|
||||
.blurb(data-i18n="subscribe.heroes")
|
||||
#point-gems.point
|
||||
.blurb(data-i18n="subscribe.gems")
|
||||
#point-items.point
|
||||
.blurb(data-i18n="subscribe.items")
|
||||
|
||||
#parents-info(data-i18n="subscribe.parents")
|
||||
div.comparison-blurb(data-i18n="subscribe.comparison_blurb")
|
||||
table.table.table-condensed.table-bordered.comparison-table
|
||||
thead
|
||||
tr
|
||||
th
|
||||
th(data-i18n="subscribe.free")
|
||||
th
|
||||
//- TODO: find a better way to localize '$9.99/month'
|
||||
span $#{price}/
|
||||
span(data-i18n="subscribe.month")
|
||||
tbody
|
||||
tr
|
||||
td.feature-description
|
||||
span(data-i18n="subscribe.feature1")
|
||||
td.center-ok
|
||||
span.glyphicon.glyphicon-ok
|
||||
td.center-ok
|
||||
span.glyphicon.glyphicon-ok
|
||||
tr
|
||||
td.feature-description
|
||||
span(data-i18n="[html]subscribe.feature2")
|
||||
td
|
||||
td.center-ok
|
||||
span.glyphicon.glyphicon-ok
|
||||
tr
|
||||
td.feature-description
|
||||
span(data-i18n="subscribe.feature3")
|
||||
td
|
||||
td.center-ok
|
||||
span.glyphicon.glyphicon-ok
|
||||
tr
|
||||
td.feature-description
|
||||
span(data-i18n="[html]subscribe.feature4")
|
||||
td
|
||||
td.center-ok
|
||||
span.glyphicon.glyphicon-ok
|
||||
tr
|
||||
td.feature-description
|
||||
span(data-i18n="subscribe.feature5")
|
||||
td
|
||||
td.center-ok
|
||||
span.glyphicon.glyphicon-ok
|
||||
tr
|
||||
td.feature-description
|
||||
span(data-i18n="subscribe.feature6")
|
||||
td
|
||||
td.center-ok
|
||||
span.glyphicon.glyphicon-ok
|
||||
#parents-info(data-i18n="subscribe.parents")
|
||||
|
||||
button.btn.btn-lg.btn-illustrated.purchase-button(data-i18n="subscribe.subscribe_button")
|
||||
|
||||
button.btn.btn-lg.btn-illustrated.parent-button(data-i18n="subscribe.parent_button")
|
||||
|
||||
if state === 'declined'
|
||||
#declined-alert.alert.alert-danger.alert-dismissible
|
||||
span(data-i18n="buy_gems.declined")
|
||||
|
|
|
@ -17,8 +17,9 @@ module.exports = class SubscribeModal extends ModalView
|
|||
'stripe:received-token': 'onStripeReceivedToken'
|
||||
|
||||
events:
|
||||
'click .purchase-button': 'onClickPurchaseButton'
|
||||
'click #close-modal': 'hide'
|
||||
'click #parent-send': 'onClickParentSendButton'
|
||||
'click .purchase-button': 'onClickPurchaseButton'
|
||||
|
||||
constructor: (options) ->
|
||||
super(options)
|
||||
|
@ -34,6 +35,40 @@ module.exports = class SubscribeModal extends ModalView
|
|||
|
||||
afterRender: ->
|
||||
super()
|
||||
@setupParentButtonPopover()
|
||||
@setupParentInfoPopover()
|
||||
|
||||
setupParentButtonPopover: ->
|
||||
popoverTitle = $.i18n.t 'subscribe.parent_email_title'
|
||||
popoverTitle += '<button type="button" class="close" onclick="$('.parent-button').popover('hide');">×</button>'
|
||||
popoverContent = "<div id='email-parent-form'>"
|
||||
popoverContent += "<p>#{$.i18n.t('subscribe.parent_email_description')}</p>"
|
||||
popoverContent += "<form>"
|
||||
popoverContent += " <div class='form-group'>"
|
||||
popoverContent += " <label>#{$.i18n.t('subscribe.parent_email_input_label')}</label>"
|
||||
popoverContent += " <input id='parent-input' type='email' class='form-control' placeholder='#{$.i18n.t('subscribe.parent_email_input_placeholder')}'/>"
|
||||
popoverContent += " <div id='parent-email-validator' class='email_invalid'>#{$.i18n.t('subscribe.parent_email_input_invalid')}</div>"
|
||||
popoverContent += " </div>"
|
||||
popoverContent += " <button id='parent-send' type='submit' class='btn btn-default'>#{$.i18n.t('subscribe.parent_email_send')}</button>"
|
||||
popoverContent += "</form>"
|
||||
popoverContent += "</div>"
|
||||
popoverContent += "<div id='email-parent-complete'>"
|
||||
popoverContent += " <p>#{$.i18n.t('subscribe.parent_email_sent')}</p>"
|
||||
popoverContent += " <button type='button' onclick='$('.parent-button').popover('hide');'>#{$.i18n.t('modal.close')}</button>"
|
||||
popoverContent += "</div>"
|
||||
|
||||
@$el.find('.parent-button').popover(
|
||||
animation: true
|
||||
html: true
|
||||
placement: 'top'
|
||||
trigger: 'click'
|
||||
title: popoverTitle
|
||||
content: popoverContent
|
||||
container: @$el
|
||||
).on 'shown.bs.popover', =>
|
||||
application.tracker?.trackEvent 'Subscription ask parent button click', {}
|
||||
|
||||
setupParentInfoPopover: ->
|
||||
popoverTitle = $.i18n.t 'subscribe.parents_title'
|
||||
popoverContent = "<p>" + $.i18n.t('subscribe.parents_blurb1') + "</p>"
|
||||
popoverContent += "<p>" + $.i18n.t('subscribe.parents_blurb2') + "</p>"
|
||||
|
@ -50,6 +85,26 @@ module.exports = class SubscribeModal extends ModalView
|
|||
).on 'shown.bs.popover', =>
|
||||
application.tracker?.trackEvent 'Subscription parent hover', {}
|
||||
|
||||
onClickParentSendButton: (e) ->
|
||||
# TODO: Popover sometimes dismisses immediately after send
|
||||
|
||||
email = $('#parent-input').val()
|
||||
unless /[\w\.]+@\w+\.\w+/.test email
|
||||
$('#parent-input').parent().addClass('has-error')
|
||||
$('#parent-email-validator').show()
|
||||
return false
|
||||
|
||||
request = @supermodel.addRequestResource 'send_one_time_email', {
|
||||
url: '/db/user/-/send_one_time_email'
|
||||
data: {email: email, type: 'subscribe modal parent'}
|
||||
method: 'POST'
|
||||
}, 0
|
||||
request.load()
|
||||
|
||||
$('#email-parent-form').hide()
|
||||
$('#email-parent-complete').show()
|
||||
false
|
||||
|
||||
onClickPurchaseButton: (e) ->
|
||||
@playSound 'menu-button-click'
|
||||
return @openModalView new AuthModal() if me.get('anonymous')
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
log = require 'winston'
|
||||
mongoose = require 'mongoose'
|
||||
plugins = require '../plugins/plugins'
|
||||
utils = require '../lib/utils'
|
||||
|
||||
AnalyticsLogEventSchema = new mongoose.Schema({
|
||||
u: mongoose.Schema.Types.ObjectId
|
||||
|
@ -14,4 +16,102 @@ AnalyticsLogEventSchema = new mongoose.Schema({
|
|||
|
||||
AnalyticsLogEventSchema.index({event: 1, _id: 1})
|
||||
|
||||
AnalyticsLogEventSchema.statics.logEvent = (user, event, properties) ->
|
||||
unless user?
|
||||
log.warn 'No user given to analytics logEvent.'
|
||||
return
|
||||
|
||||
saveDoc = (eventID, slimProperties) ->
|
||||
doc = new AnalyticsLogEvent
|
||||
u: user
|
||||
e: eventID
|
||||
p: slimProperties
|
||||
# TODO: Remove these legacy properties after we stop querying for them (probably 30 days, ~2/16/15)
|
||||
user: user
|
||||
event: event
|
||||
properties: properties
|
||||
doc.save()
|
||||
|
||||
utils.getAnalyticsStringID event, (eventID) ->
|
||||
if eventID > 0
|
||||
# TODO: properties slimming is pretty ugly
|
||||
slimProperties = _.cloneDeep properties
|
||||
if event in ['Clicked Level', 'Show problem alert', 'Started Level', 'Saw Victory', 'Problem alert help clicked', 'Spell palette help clicked']
|
||||
delete slimProperties.level if event is 'Saw Victory'
|
||||
properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
|
||||
slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
|
||||
if slimProperties.levelID?
|
||||
# levelID: string => l: string ID
|
||||
utils.getAnalyticsStringID slimProperties.levelID, (levelStringID) ->
|
||||
if levelStringID > 0
|
||||
delete slimProperties.levelID
|
||||
slimProperties.l = levelStringID
|
||||
saveDoc eventID, slimProperties
|
||||
return
|
||||
else if event in ['Script Started', 'Script Ended']
|
||||
properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
|
||||
slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
|
||||
if slimProperties.levelID? and slimProperties.label?
|
||||
# levelID: string => l: string ID
|
||||
# label: string => lb: string ID
|
||||
utils.getAnalyticsStringID slimProperties.levelID, (levelStringID) ->
|
||||
if levelStringID > 0
|
||||
delete slimProperties.levelID
|
||||
slimProperties.l = levelStringID
|
||||
utils.getAnalyticsStringID slimProperties.label, (labelStringID) ->
|
||||
if labelStringID > 0
|
||||
delete slimProperties.label
|
||||
slimProperties.lb = labelStringID
|
||||
saveDoc eventID, slimProperties
|
||||
return
|
||||
else if event is 'Heard Sprite'
|
||||
properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
|
||||
slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
|
||||
if slimProperties.message?
|
||||
# message: string => m: string ID
|
||||
utils.getAnalyticsStringID slimProperties.message, (messageStringID) ->
|
||||
if messageStringID > 0
|
||||
delete slimProperties.message
|
||||
slimProperties.m = messageStringID
|
||||
saveDoc eventID, slimProperties
|
||||
return
|
||||
else if event in ['Start help video', 'Finish help video']
|
||||
properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
|
||||
slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
|
||||
if slimProperties.level and slimProperties.style?
|
||||
# level: string => l: string ID
|
||||
# style: string => s: string ID
|
||||
utils.getAnalyticsStringID slimProperties.level, (levelStringID) ->
|
||||
if levelStringID > 0
|
||||
delete slimProperties.level
|
||||
slimProperties.l = levelStringID
|
||||
utils.getAnalyticsStringID slimProperties.style, (styleStringID) ->
|
||||
if styleStringID > 0
|
||||
delete slimProperties.style
|
||||
slimProperties.s = styleStringID
|
||||
saveDoc eventID, slimProperties
|
||||
return
|
||||
else if event is 'Show subscription modal'
|
||||
delete properties.category
|
||||
delete slimProperties.category
|
||||
if slimProperties.label?
|
||||
# label: string => lb: string ID
|
||||
utils.getAnalyticsStringID slimProperties.label, (labelStringID) ->
|
||||
if labelStringID > 0
|
||||
delete slimProperties.label
|
||||
slimProperties.lb = labelStringID
|
||||
if slimProperties.level?
|
||||
# level: string => l: string ID
|
||||
utils.getAnalyticsStringID slimProperties.level, (levelStringID) ->
|
||||
if levelStringID > 0
|
||||
delete slimProperties.level
|
||||
slimProperties.l = levelStringID
|
||||
saveDoc eventID, slimProperties
|
||||
return
|
||||
saveDoc eventID, slimProperties
|
||||
return
|
||||
saveDoc eventID, slimProperties
|
||||
else
|
||||
log.warn "Unable to get analytics string ID for " + event
|
||||
|
||||
module.exports = AnalyticsLogEvent = mongoose.model('analytics.log.event', AnalyticsLogEventSchema)
|
||||
|
|
|
@ -39,102 +39,7 @@ class AnalyticsLogEventHandler extends Handler
|
|||
event = req.query.event or req.body.event
|
||||
properties = req.query.properties or req.body.properties
|
||||
@sendSuccess res # Return request immediately
|
||||
unless user?
|
||||
log.warn 'No user given to analytics logEvent.'
|
||||
return
|
||||
|
||||
saveDoc = (eventID, slimProperties) ->
|
||||
doc = new AnalyticsLogEvent
|
||||
u: user
|
||||
e: eventID
|
||||
p: slimProperties
|
||||
# TODO: Remove these legacy properties after we stop querying for them (probably 30 days, ~2/16/15)
|
||||
user: user
|
||||
event: event
|
||||
properties: properties
|
||||
doc.save()
|
||||
|
||||
utils.getAnalyticsStringID event, (eventID) ->
|
||||
if eventID > 0
|
||||
# TODO: properties slimming is pretty ugly
|
||||
slimProperties = _.cloneDeep properties
|
||||
if event in ['Clicked Level', 'Show problem alert', 'Started Level', 'Saw Victory', 'Problem alert help clicked', 'Spell palette help clicked']
|
||||
delete slimProperties.level if event is 'Saw Victory'
|
||||
properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
|
||||
slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
|
||||
if slimProperties.levelID?
|
||||
# levelID: string => l: string ID
|
||||
utils.getAnalyticsStringID slimProperties.levelID, (levelStringID) ->
|
||||
if levelStringID > 0
|
||||
delete slimProperties.levelID
|
||||
slimProperties.l = levelStringID
|
||||
saveDoc eventID, slimProperties
|
||||
return
|
||||
else if event in ['Script Started', 'Script Ended']
|
||||
properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
|
||||
slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
|
||||
if slimProperties.levelID? and slimProperties.label?
|
||||
# levelID: string => l: string ID
|
||||
# label: string => lb: string ID
|
||||
utils.getAnalyticsStringID slimProperties.levelID, (levelStringID) ->
|
||||
if levelStringID > 0
|
||||
delete slimProperties.levelID
|
||||
slimProperties.l = levelStringID
|
||||
utils.getAnalyticsStringID slimProperties.label, (labelStringID) ->
|
||||
if labelStringID > 0
|
||||
delete slimProperties.label
|
||||
slimProperties.lb = labelStringID
|
||||
saveDoc eventID, slimProperties
|
||||
return
|
||||
else if event is 'Heard Sprite'
|
||||
properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
|
||||
slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
|
||||
if slimProperties.message?
|
||||
# message: string => m: string ID
|
||||
utils.getAnalyticsStringID slimProperties.message, (messageStringID) ->
|
||||
if messageStringID > 0
|
||||
delete slimProperties.message
|
||||
slimProperties.m = messageStringID
|
||||
saveDoc eventID, slimProperties
|
||||
return
|
||||
else if event in ['Start help video', 'Finish help video']
|
||||
properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
|
||||
slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
|
||||
if slimProperties.level and slimProperties.style?
|
||||
# level: string => l: string ID
|
||||
# style: string => s: string ID
|
||||
utils.getAnalyticsStringID slimProperties.level, (levelStringID) ->
|
||||
if levelStringID > 0
|
||||
delete slimProperties.level
|
||||
slimProperties.l = levelStringID
|
||||
utils.getAnalyticsStringID slimProperties.style, (styleStringID) ->
|
||||
if styleStringID > 0
|
||||
delete slimProperties.style
|
||||
slimProperties.s = styleStringID
|
||||
saveDoc eventID, slimProperties
|
||||
return
|
||||
else if event is 'Show subscription modal'
|
||||
delete properties.category
|
||||
delete slimProperties.category
|
||||
if slimProperties.label?
|
||||
# label: string => lb: string ID
|
||||
utils.getAnalyticsStringID slimProperties.label, (labelStringID) ->
|
||||
if labelStringID > 0
|
||||
delete slimProperties.label
|
||||
slimProperties.lb = labelStringID
|
||||
if slimProperties.level?
|
||||
# level: string => l: string ID
|
||||
utils.getAnalyticsStringID slimProperties.level, (levelStringID) ->
|
||||
if levelStringID > 0
|
||||
delete slimProperties.level
|
||||
slimProperties.l = levelStringID
|
||||
saveDoc eventID, slimProperties
|
||||
return
|
||||
saveDoc eventID, slimProperties
|
||||
return
|
||||
saveDoc eventID, slimProperties
|
||||
else
|
||||
log.warn "Unable to get analytics string ID for " + event
|
||||
AnalyticsLogEvent.logEvent user, event, properties
|
||||
|
||||
getLevelCompletionsBySlug: (req, res) ->
|
||||
# Returns an array of per-day level starts and finishes
|
||||
|
@ -204,7 +109,7 @@ class AnalyticsLogEventHandler extends Handler
|
|||
for level of levelDateMap
|
||||
completions[level] = []
|
||||
for created, item of levelDateMap[level]
|
||||
completions[level].push
|
||||
completions[level].push
|
||||
level: level
|
||||
created: created
|
||||
started: Object.keys(item.started).length
|
||||
|
@ -382,7 +287,7 @@ class AnalyticsLogEventHandler extends Handler
|
|||
getUserEventData campaigns
|
||||
|
||||
getCampaignData = () =>
|
||||
# Get campaign data
|
||||
# Get campaign data
|
||||
# Output:
|
||||
# campaigns - per-campaign dictionary of ordered levelIDs
|
||||
# campaignLevelIDs - dictionary of all campaign levelIDs
|
||||
|
|
|
@ -10,6 +10,7 @@ module.exports.api = new sendwithusAPI swuAPIKey, debug
|
|||
if config.unittest
|
||||
module.exports.api.send = ->
|
||||
module.exports.templates =
|
||||
parent_subscribe_email: 'tem_2APERafogvwKhmcnouigud'
|
||||
welcome_email: 'utnGaBHuSU4Hmsi7qrAypU'
|
||||
ladder_update_email: 'JzaZxf39A4cKMxpPZUfWy4'
|
||||
patch_created: 'tem_xhxuNosLALsizTNojBjNcL'
|
||||
|
|
|
@ -9,6 +9,7 @@ errors = require '../commons/errors'
|
|||
async = require 'async'
|
||||
log = require 'winston'
|
||||
moment = require 'moment'
|
||||
AnalyticsLogEvent = require '../analytics/AnalyticsLogEvent'
|
||||
LevelSession = require '../levels/sessions/LevelSession'
|
||||
LevelSessionHandler = require '../levels/sessions/level_session_handler'
|
||||
SubscriptionHandler = require '../payments/subscription_handler'
|
||||
|
@ -17,6 +18,7 @@ EarnedAchievement = require '../achievements/EarnedAchievement'
|
|||
UserRemark = require './remarks/UserRemark'
|
||||
{isID} = require '../lib/utils'
|
||||
hipchat = require '../hipchat'
|
||||
sendwithus = require '../sendwithus'
|
||||
|
||||
serverProperties = ['passwordHash', 'emailLower', 'nameLower', 'passwordReset', 'lastIP']
|
||||
candidateProperties = [
|
||||
|
@ -232,6 +234,7 @@ UserHandler = class UserHandler extends Handler
|
|||
return @getRemark(req, res, args[0]) if args[1] is 'remark'
|
||||
return @searchForUser(req, res) if args[1] is 'admin_search'
|
||||
return @getStripeInfo(req, res, args[0]) if args[1] is 'stripe'
|
||||
return @sendOneTimeEmail(req, res, args[0]) if args[1] is 'send_one_time_email'
|
||||
return @sendNotFoundError(res)
|
||||
super(arguments...)
|
||||
|
||||
|
@ -244,6 +247,35 @@ UserHandler = class UserHandler extends Handler
|
|||
return @sendDatabaseError(res, err) if err
|
||||
@sendSuccess(res, JSON.stringify(customer, null, '\t'))
|
||||
|
||||
sendOneTimeEmail: (req, res) ->
|
||||
# TODO: should this API be somewhere else?
|
||||
# TODO: hipchat tower success message shows up as a misleading PaperTrail error message
|
||||
return @sendForbiddenError(res) unless req.user
|
||||
email = req.query.email or req.body.email
|
||||
type = req.query.type or req.body.type
|
||||
return @sendBadInputError res, 'No email given.' unless email?
|
||||
return @sendBadInputError res, 'No type given.' unless type?
|
||||
|
||||
return @sendBadInputError res, "Unknown one-time email type #{type}" unless type is 'subscribe modal parent'
|
||||
|
||||
emailParams =
|
||||
email_id: sendwithus.templates.parent_subscribe_email
|
||||
recipient:
|
||||
address: email
|
||||
email_data:
|
||||
name: req.user.get('name') or ''
|
||||
sendwithus.api.send emailParams, (err, result) =>
|
||||
if err
|
||||
log.error "sendwithus one-time email error: #{err}, result: #{result}"
|
||||
return @sendError res, 500, 'send mail failed.'
|
||||
req.user.update {$push: {"emails.oneTimes": {type: type, email: email, sent: new Date()}}}, (err) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
req.user.save (err) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
@sendSuccess(res, {result: 'success'})
|
||||
hipchat.sendTowerHipChatMessage "#{req.user.get('name') or req.user.get('email')} just submitted subscribe modal parent email #{email}."
|
||||
AnalyticsLogEvent.logEvent req.user, 'Sent one time email', email: email, type: type
|
||||
|
||||
agreeToCLA: (req, res) ->
|
||||
return @sendForbiddenError(res) unless req.user
|
||||
doc =
|
||||
|
|
Loading…
Reference in a new issue