Add class invite system

This commit is contained in:
Scott Erickson 2015-10-05 16:00:47 -07:00
parent 212ee8a65a
commit 3a90356f59
7 changed files with 90 additions and 17 deletions

View file

@ -875,6 +875,10 @@
play_time: "Play time:" play_time: "Play time:"
completed: "Completed:" completed: "Completed:"
invite_students: "Invite students to join this class." invite_students: "Invite students to join this class."
invite_link_header: "Link to join course"
invite_link_p_1: "Give this link to students you would like to have join the course."
invite_link_p_2: "Or have us email them directly:"
capacity_used: "Course slots used:"
enter_emails: "Enter student emails to invite, one per line" enter_emails: "Enter student emails to invite, one per line"
send_invites: "Send Invites" send_invites: "Send Invites"
title: "Title" title: "Title"

View file

@ -1,6 +1,6 @@
#course-details-view #course-details-view
.invite-emails #invite-emails-textarea
width: 50% width: 50%
.progress-cell .progress-cell

View file

@ -247,18 +247,24 @@ mixin progress-members-popup-started(i, level)
mixin invite-tab mixin invite-tab
p(data-i18n="courses.invite_students") p(data-i18n="courses.invite_students")
// TODO: finalize text here and then i18n it h3(data-i18n="courses.invite_link_header")
h3 Link to join course p(data-i18n="courses.invite_link_p_1")
p Give this link to students you would like to have join the course. .alert.alert-info
a= document.location.origin + "/account/prepaid?_ppc=" + view.prepaid.get('code') strong= document.location.origin + "/account/prepaid?_ppc=" + view.prepaid.get('code')
p Or have us email them directly: p(data-i18n="courses.invite_link_p_2")
textarea.invite-emails(rows=3, data-i18n="[placeholder]courses.enter_emails", placeholder="Enter student emails to invite, one per line") .form
div(style='margin-top:10px;') .form-group
button.btn.btn-success.btn-invite(data-i18n="courses.send_invites") textarea#invite-emails-textarea.form-control(
rows=3, data-i18n="[placeholder]courses.enter_emails", placeholder="Enter student emails to invite, one per line")
.form-group
button#invite-btn.btn.btn-success(data-i18n="courses.send_invites")
#invite-emails-sending-alert.alert.alert-info.hide(data-i18n="common.sending")
#invite-emails-success-alert.alert.alert-success.hide(data-i18n="play_level.done")
h3 Class Capacity h3 Class Capacity
if view.prepaid.loaded if view.prepaid.loaded
p p
span.spr Course slots used: span.spr(data-i18n="courses.capacity_used")
span #{view.prepaid.get('redeemers').length} / #{view.prepaid.get('maxRedeemers')}. span #{view.prepaid.get('redeemers').length} / #{view.prepaid.get('maxRedeemers')}.
mixin levels-tab mixin levels-tab

View file

@ -23,7 +23,7 @@ module.exports = class CourseDetailsView extends RootView
'click .progress-level-cell': 'onClickProgressLevelCell' 'click .progress-level-cell': 'onClickProgressLevelCell'
'mouseenter .progress-level-cell': 'onMouseEnterPoint' 'mouseenter .progress-level-cell': 'onMouseEnterPoint'
'mouseleave .progress-level-cell': 'onMouseLeavePoint' 'mouseleave .progress-level-cell': 'onMouseLeavePoint'
'click .btn-invite': 'onClickButtonInvite' 'click #invite-btn': 'onClickInviteButton'
constructor: (options, @courseID, @courseInstanceID) -> constructor: (options, @courseID, @courseInstanceID) ->
super options super options
@ -123,11 +123,7 @@ module.exports = class CourseDetailsView extends RootView
@members = new CocoCollection([], { url: "/db/course_instance/#{@courseInstance.id}/members", model: User, comparator: 'nameLower' }) @members = new CocoCollection([], { url: "/db/course_instance/#{@courseInstance.id}/members", model: User, comparator: 'nameLower' })
@listenToOnce @members, 'sync', @onMembersSync @listenToOnce @members, 'sync', @onMembersSync
@supermodel.loadCollection @members, 'members', cache: false @supermodel.loadCollection @members, 'members', cache: false
if @adminMode if @adminMode and prepaidID = @courseInstance.get('prepaidID')
prepaidID = @course.get('prepaidID')
if not prepaidID
prepaidID = '560ef835444e5c9a847e0218'
# TODO: Just abort if no prepaidID
@prepaid = @supermodel.getModel(Prepaid, prepaidID) or new Prepaid _id: prepaidID @prepaid = @supermodel.getModel(Prepaid, prepaidID) or new Prepaid _id: prepaidID
@listenTo @prepaid, 'sync', @onPrepaidSync @listenTo @prepaid, 'sync', @onPrepaidSync
if @prepaid.loaded if @prepaid.loaded
@ -244,8 +240,25 @@ module.exports = class CourseDetailsView extends RootView
viewArgs: [{}, levelSlug] viewArgs: [{}, levelSlug]
} }
onClickButtonInvite: (e) -> onClickInviteButton: (e) ->
emails = @$('#invite-emails-textarea').val()
emails = emails.split('\n')
emails = _.filter((_.string.trim(email) for email in emails))
if not emails.length
return
url = @courseInstance.url() + '/invite_students'
@$('#invite-btn, #invite-emails-textarea').addClass('hide')
@$('#invite-emails-sending-alert').removeClass('hide')
$.ajax({
url: url
data: {emails: emails}
method: 'POST'
context: @
success: ->
@$('#invite-emails-sending-alert').addClass('hide')
@$('#invite-emails-success-alert').removeClass('hide')
})
onMouseEnterPoint: (e) -> onMouseEnterPoint: (e) ->
$('.progress-popup-container').hide() $('.progress-popup-container').hide()

View file

@ -9,6 +9,7 @@ PrepaidHandler = require '../prepaids/prepaid_handler'
User = require '../users/User' User = require '../users/User'
UserHandler = require '../users/user_handler' UserHandler = require '../users/user_handler'
utils = require '../../app/core/utils' utils = require '../../app/core/utils'
sendwithus = require '../sendwithus'
CourseInstanceHandler = class CourseInstanceHandler extends Handler CourseInstanceHandler = class CourseInstanceHandler extends Handler
modelClass: CourseInstance modelClass: CourseInstance
@ -31,6 +32,7 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler
return @createAPI(req, res) if relationship is 'create' return @createAPI(req, res) if relationship is 'create'
return @getLevelSessionsAPI(req, res, args[0]) if args[1] is 'level_sessions' return @getLevelSessionsAPI(req, res, args[0]) if args[1] is 'level_sessions'
return @getMembersAPI(req, res, args[0]) if args[1] is 'members' return @getMembersAPI(req, res, args[0]) if args[1] is 'members'
return @inviteStudents(req, res, args[0]) if relationship is 'invite_students'
super arguments... super arguments...
createAPI: (req, res) -> createAPI: (req, res) ->
@ -101,4 +103,28 @@ CourseInstanceHandler = class CourseInstanceHandler extends Handler
cleandocs = (UserHandler.formatEntity(req, doc) for doc in users) cleandocs = (UserHandler.formatEntity(req, doc) for doc in users)
@sendSuccess(res, cleandocs) @sendSuccess(res, cleandocs)
inviteStudents: (req, res, courseInstanceID) ->
if not req.body.emails
return @sendBadInputError(res, 'Emails not included')
CourseInstance.findById courseInstanceID, (err, courseInstance) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) unless courseInstance
return @sendForbiddenError(res) unless @hasAccessToDocument(req, courseInstance)
Prepaid.findById courseInstance.get('prepaidID'), (err, prepaid) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) unless prepaid
return @sendForbiddenError(res) unless prepaid.get('maxRedeemers') > prepaid.get('redeemers').length
for email in req.body.emails
context =
email_id: sendwithus.templates.course_invite_email
recipient:
address: email
email_data:
class_name: courseInstance.get('name')
join_link: "https://codecombat.com/account/prepaid?_ppc=" + prepaid.get('code')
sendwithus.api.send context, _.noop
return @sendSuccess(res, {})
module.exports = new CourseInstanceHandler() module.exports = new CourseInstanceHandler()

View file

@ -22,3 +22,5 @@ module.exports.templates =
generic_email: 'tem_JhRnQ4pvTS4KdQjYoZdbei' generic_email: 'tem_JhRnQ4pvTS4KdQjYoZdbei'
plain_text_email: 'tem_85UvKDCCNPXsFckERTig6Y' plain_text_email: 'tem_85UvKDCCNPXsFckERTig6Y'
next_steps_email: 'tem_RDHhTG5inXQi8pthyqWr5D' next_steps_email: 'tem_RDHhTG5inXQi8pthyqWr5D'
course_invite_email: 'tem_u6D2EFWYC5Ptk38bSykjsU'

View file

@ -196,3 +196,25 @@ describe 'CourseInstance', ->
expect(prepaid.get('maxRedeemers')).toEqual(50) expect(prepaid.get('maxRedeemers')).toEqual(50)
expect(prepaid.get('properties')?.courseIDs?.length).toEqual(courses.length) expect(prepaid.get('properties')?.courseIDs?.length).toEqual(courses.length)
done() done()
describe 'Invite to course', ->
it 'takes a list of emails and sends invites', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
loginNewUser (user1) ->
createCourse 0, (err, course) ->
expect(err).toBeNull()
return done(err) if err
createCourseInstances user1, course.get('_id'), 1, token.id, (err, courseInstances) ->
expect(err).toBeNull()
return done(err) if err
expect(courseInstances.length).toEqual(1)
inviteStudentsURL = getURL("/db/course_instance/#{courseInstances[0]._id}/invite_students")
requestBody = {
emails: ['test@test.com']
}
request.post { uri: inviteStudentsURL, json: requestBody }, (err, res) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
done()