mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 17:45:40 -05:00
Add class invite system
This commit is contained in:
parent
212ee8a65a
commit
3a90356f59
7 changed files with 90 additions and 17 deletions
|
@ -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"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#course-details-view
|
#course-details-view
|
||||||
|
|
||||||
.invite-emails
|
#invite-emails-textarea
|
||||||
width: 50%
|
width: 50%
|
||||||
|
|
||||||
.progress-cell
|
.progress-cell
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue