mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-23 15:48:11 -05:00
Various fixes and changes to teacher-accounts and teacher-dashboard
This commit is contained in:
parent
58a5df7a4f
commit
beb53d9f2f
60 changed files with 520 additions and 223 deletions
|
@ -4,11 +4,20 @@ CocoCollection = require 'collections/CocoCollection'
|
|||
module.exports = class Users extends CocoCollection
|
||||
model: User
|
||||
url: '/db/user'
|
||||
|
||||
fetchForClassroom: (classroom, options) ->
|
||||
classroom = classroom.id or classroom
|
||||
options = _.extend({
|
||||
url: "/db/classroom/#{classroom}/members"
|
||||
}, options)
|
||||
@fetch(options)
|
||||
|
||||
fetchForClassroom: (classroom, options={}) ->
|
||||
classroomID = classroom.id or classroom
|
||||
limit = 10
|
||||
skip = 0
|
||||
size = _.size(classroom.get('members'))
|
||||
options.url = "/db/classroom/#{classroomID}/members"
|
||||
options.data ?= {}
|
||||
options.data.memberLimit = limit
|
||||
options.remove = false
|
||||
jqxhrs = []
|
||||
while skip < size
|
||||
options = _.cloneDeep(options)
|
||||
options.data.memberSkip = skip
|
||||
jqxhrs.push(@fetch(options))
|
||||
skip += limit
|
||||
return jqxhrs
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
go = (path, options) -> -> @routeDirectly path, arguments, options
|
||||
redirect = (path) -> -> @navigate(path, { trigger: true, replace: true })
|
||||
|
||||
|
||||
module.exports = class CocoRouter extends Backbone.Router
|
||||
|
||||
initialize: ->
|
||||
# http://nerds.airbnb.com/how-to-add-google-analytics-page-tracking-to-57536
|
||||
# http://nerds.airbnb.com/how-to-add-google-analytics-page-tracking-to-57536
|
||||
@bind 'route', @_trackPageView
|
||||
Backbone.Mediator.subscribe 'router:navigate', @onNavigate, @
|
||||
@initializeSocialMediaServices = _.once @initializeSocialMediaServices
|
||||
|
@ -156,7 +156,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
return @routeDirectly('teachers/RestrictedToTeachersView')
|
||||
if options.studentsOnly and me.isTeacher()
|
||||
return @routeDirectly('courses/RestrictedToStudentsView')
|
||||
|
||||
|
||||
path = 'play/CampaignView' if window.serverConfig.picoCTF and not /^(views)?\/?play/.test(path)
|
||||
path = "views/#{path}" if not _.string.startsWith(path, 'views/')
|
||||
ViewClass = @tryToLoadModule path
|
||||
|
@ -204,13 +204,13 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
require('core/services/twitter')()
|
||||
|
||||
renderSocialButtons: =>
|
||||
# TODO: Refactor remaining services to Handlers, use loadAPI success callback
|
||||
# TODO: Refactor remaining services to Handlers, use loadAPI success callback
|
||||
@initializeSocialMediaServices()
|
||||
$('.share-buttons, .partner-badges').addClass('fade-in').delay(10000).removeClass('fade-in', 5000)
|
||||
application.facebookHandler.renderButtons()
|
||||
application.gplusHandler.renderButtons()
|
||||
twttr?.widgets?.load?()
|
||||
|
||||
|
||||
activateTab: ->
|
||||
base = _.string.words(document.location.pathname[1..], '/')[0]
|
||||
$("ul.nav li.#{base}").addClass('active')
|
||||
|
@ -246,4 +246,4 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
Backbone.Mediator.publish 'router:navigated', route: fragment
|
||||
|
||||
reload: ->
|
||||
document.location.reload()
|
||||
document.location.reload()
|
||||
|
|
|
@ -18,8 +18,8 @@ ctrlDefaultPrevented = [219, 221, 80, 83]
|
|||
preventBackspace = (event) ->
|
||||
if event.keyCode is 8 and not elementAcceptsKeystrokes(event.srcElement or event.target)
|
||||
event.preventDefault()
|
||||
else if (key.ctrl or key.command) and not key.alt and event.keyCode in ctrlDefaultPrevented
|
||||
console.debug "Prevented keystroke", key
|
||||
else if (event.ctrlKey or event.metaKey) and not event.altKey and event.keyCode in ctrlDefaultPrevented
|
||||
console.debug "Prevented keystroke", key, event
|
||||
event.preventDefault()
|
||||
|
||||
elementAcceptsKeystrokes = (el) ->
|
||||
|
|
|
@ -342,6 +342,9 @@ module.exports = class LevelLoader extends CocoClass
|
|||
|
||||
denormalizeSession: ->
|
||||
return if @headless or @sessionDenormalized or @spectateMode or @sessionless
|
||||
# This is a way (the way?) PUT /db/level.sessions/undefined was happening
|
||||
# See commit c242317d9
|
||||
return if not @session.id
|
||||
patch =
|
||||
'levelName': @level.get('name')
|
||||
'levelID': @level.get('slug') or @level.id
|
||||
|
|
|
@ -9,17 +9,20 @@ module.exports =
|
|||
instance = courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
|
||||
continue if not instance
|
||||
instance.numCompleted = 0
|
||||
instance.numStarted = 0
|
||||
campaign = campaigns.get(course.get('campaignID'))
|
||||
for userID in instance.get('members')
|
||||
allComplete = _.every campaign.getNonLadderLevels().models, (level) ->
|
||||
levelCompletes = _.map campaign.getNonLadderLevels().models, (level) ->
|
||||
return true if level.isLadder()
|
||||
#TODO: Hella slow! Do the mapping first!
|
||||
session = _.find classroom.sessions.models, (session) ->
|
||||
session.get('creator') is userID and session.get('level').original is level.get('original')
|
||||
# sessionMap[userID][level].completed()
|
||||
session?.completed()
|
||||
if allComplete
|
||||
if _.every levelCompletes
|
||||
instance.numCompleted += 1
|
||||
if _.any levelCompletes
|
||||
instance.numStarted += 1
|
||||
|
||||
calculateEarliestIncomplete: (classroom, courses, campaigns, courseInstances, students) ->
|
||||
# Loop through all the combinations of things, return the first one that somebody hasn't finished
|
||||
|
@ -134,7 +137,7 @@ module.exports =
|
|||
campaign = campaigns.get(course.get('campaignID'))
|
||||
for level in campaign.getNonLadderLevels().models
|
||||
levelID = level.get('original')
|
||||
progressData[classroom.id][course.id][levelID] = { completed: true, started: false }
|
||||
progressData[classroom.id][course.id][levelID] = { completed: students.size() > 0, started: false }
|
||||
|
||||
for user in students.models
|
||||
userID = user.id
|
||||
|
|
|
@ -129,7 +129,6 @@
|
|||
jobs: "Jobs"
|
||||
schools: "Schools"
|
||||
educator_wiki: "Educator Wiki"
|
||||
request_quote: "Request a Quote"
|
||||
get_involved: "Get Involved"
|
||||
open_source: "Open source (GitHub)"
|
||||
support: "Support"
|
||||
|
@ -1229,7 +1228,8 @@
|
|||
student_age_range_older: "Older than 18"
|
||||
student_age_range_to: "to"
|
||||
create_class: "Create Class"
|
||||
|
||||
class_name: "Class Name"
|
||||
|
||||
teacher_account_restricted: "Your account is a teacher account, and so cannot access student content."
|
||||
|
||||
teacher:
|
||||
|
@ -1273,6 +1273,7 @@
|
|||
course_progress: "Course Progress"
|
||||
not_applicable: "N/A"
|
||||
edit: "edit"
|
||||
remove: "remove"
|
||||
latest_completed: "Latest Completed"
|
||||
sort_by: "Sort by"
|
||||
progress: "Progress"
|
||||
|
@ -1285,6 +1286,7 @@
|
|||
add_students_manually: "Add Students Manually"
|
||||
bulk_assign: "Bulk-assign"
|
||||
assign_to_selected_students: "Assign to Selected Students"
|
||||
assigned: "Assigned"
|
||||
enroll_selected_students: "Enroll Selected Students"
|
||||
|
||||
guides_coming_soon: "Guides coming soon!" # Courses
|
||||
|
@ -1309,7 +1311,6 @@
|
|||
how_to_enroll_blurb_2: "To bulk-enroll multiple students, select them using the checkboxes on the left side of the classroom page and click the \"Enroll Selected Students\" button."
|
||||
how_to_enroll_blurb_3: "Once a student is enrolled, they will have access to all of the course content forever, even if they leave your class."
|
||||
bulk_pricing_blurb: "Purchasing for more than 15 students? Get in touch with us for bulk pricing quotes."
|
||||
request_quote: "Request Quote"
|
||||
|
||||
classes:
|
||||
archmage_title: "Archmage"
|
||||
|
|
|
@ -89,6 +89,15 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
trackCollection: (collection, value) ->
|
||||
res = @addModelResource(collection, '', {}, value)
|
||||
res.listen()
|
||||
|
||||
trackRequest: (jqxhr, value=1) ->
|
||||
res = new Resource('', value)
|
||||
res.jqxhr = jqxhr
|
||||
jqxhr.done -> res.markLoaded()
|
||||
jqxhr.fail -> res.markFailed()
|
||||
@storeResource(res, value)
|
||||
|
||||
trackRequests: (jqxhrs, value=1) -> @trackRequest(jqxhr) for jqxhr in jqxhrs
|
||||
|
||||
# replace or overwrite
|
||||
shouldSaveBackups: (model) -> false
|
||||
|
|
|
@ -17,6 +17,7 @@ module.exports = class User extends CocoModel
|
|||
isAnonymous: -> @get('anonymous', true)
|
||||
displayName: -> @get('name', true)
|
||||
broadName: ->
|
||||
return '(deleted)' if @get('deleted')
|
||||
name = @get('name')
|
||||
return name if name
|
||||
name = _.filter([@get('firstName'), @get('lastName')]).join(' ')
|
||||
|
|
|
@ -327,7 +327,7 @@ _.extend UserSchema.properties,
|
|||
description: 'Prepaid which has paid for this user\'s course access'
|
||||
})
|
||||
schoolName: {type: 'string'}
|
||||
role: {type: 'string'} # unset, 'student', 'teacher', 'parent', 'technology coordinator', 'advisor', 'principal', 'superintendent', ...
|
||||
role: {type: 'string', enum: ["God", "advisor", "parent", "principal", "student", "superintendent", "teacher", "technology coordinator"]}
|
||||
|
||||
c.extendBasicProperties UserSchema, 'user'
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
width: 50%
|
||||
|
||||
.age-range-select
|
||||
width: 100px
|
||||
width: 170px
|
||||
display: inline-block
|
||||
|
||||
label
|
||||
|
|
|
@ -6,3 +6,13 @@
|
|||
line-height: 18px
|
||||
border: thin gray solid
|
||||
border-radius: 5px
|
||||
|
||||
#students-input
|
||||
width: 100px
|
||||
height: 50px
|
||||
line-height: 30px
|
||||
font-size: 30px
|
||||
|
||||
&::-webkit-inner-spin-button, &::-webkit-outer-spin-button
|
||||
-webkit-appearance: none
|
||||
margin: 0
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
h3 ~ .edit-classroom
|
||||
color: black
|
||||
text-decoration: underline
|
||||
|
||||
.classroom-details
|
||||
.small-details
|
||||
margin-bottom: 4px
|
||||
|
||||
.concept
|
||||
& span
|
||||
|
@ -141,6 +145,12 @@
|
|||
.glyphicon
|
||||
color: $gray-light
|
||||
|
||||
.remove-student-link
|
||||
color: $burgandy
|
||||
font-weight: bold
|
||||
text-decoration: underline
|
||||
line-height: 16px
|
||||
|
||||
// Course Progress tab
|
||||
|
||||
#course-progress-tab
|
||||
|
|
|
@ -164,6 +164,13 @@ $forest: #20572B
|
|||
border: $gold 3px solid
|
||||
width: 33px
|
||||
height: 33px
|
||||
|
||||
// For teacher avatars
|
||||
.border-burgandy
|
||||
border-color: $burgandy
|
||||
|
||||
.border-navy
|
||||
border-color: $navy
|
||||
|
||||
.user-level
|
||||
position: absolute
|
||||
|
@ -229,6 +236,18 @@ $forest: #20572B
|
|||
.btn-lg
|
||||
font-size: 18px
|
||||
|
||||
.btn-gplus
|
||||
color: white
|
||||
background-color: #DD4B39
|
||||
img
|
||||
height: 22px
|
||||
|
||||
.btn-facebook
|
||||
color: white
|
||||
background-color: #3B5998
|
||||
img
|
||||
height: 22px
|
||||
|
||||
// Dropdowns
|
||||
select
|
||||
height: 33px
|
||||
|
@ -346,18 +365,6 @@ $forest: #20572B
|
|||
opacity: 1
|
||||
|
||||
|
||||
.btn-gplus
|
||||
color: white
|
||||
background-color: #DD4B39
|
||||
img
|
||||
height: 22px
|
||||
|
||||
.btn-facebook
|
||||
color: white
|
||||
background-color: #3B5998
|
||||
img
|
||||
height: 22px
|
||||
|
||||
// Classes
|
||||
|
||||
.text-navy
|
||||
|
@ -378,7 +385,7 @@ $forest: #20572B
|
|||
$spacer: 1rem !default
|
||||
$spacer-x: $spacer !default
|
||||
$spacer-y: $spacer !default
|
||||
$spacers: ( 0: ( x: 0, y: 0 ), 1: ( x: $spacer-x, y: $spacer-y ), 2: ( x: ($spacer-x * 1.5), y: ($spacer-y * 1.5) ), 3: ( x: ($spacer-x * 3), y: ($spacer-y * 3) ) ) !default
|
||||
$spacers: ( 0: ( x: 0, y: 0 ), 1: ( x: $spacer-x, y: $spacer-y ), 2: ( x: ($spacer-x * 1.5), y: ($spacer-y * 1.5) ), 3: ( x: ($spacer-x * 3), y: ($spacer-y * 3) ), 4: ( x: ($spacer-x * 4), y: ($spacer-y * 4) ), 5: ( x: ($spacer-x * 5), y: ($spacer-y * 5) ) ) !default
|
||||
|
||||
.m-x-auto
|
||||
margin-right: auto !important
|
||||
|
|
|
@ -1,18 +1,5 @@
|
|||
.style-flat
|
||||
block header
|
||||
.container-fluid.text-center
|
||||
.alert.alert-danger.lt-ie9
|
||||
strong(data-i18n="home.no_ie")
|
||||
|
||||
if view.isIPadBrowser() || view.isMobile()
|
||||
.alert.alert-danger.mobile
|
||||
strong(data-i18n="home.no_mobile")
|
||||
else if view.isOldBrowser()
|
||||
.alert.alert-danger.old-browser
|
||||
strong(data-i18n="home.old_browser")
|
||||
br
|
||||
span(data-i18n="home.old_browser_suffix")
|
||||
|
||||
nav#main-nav.navbar.navbar-default
|
||||
.container
|
||||
.row
|
||||
|
@ -46,13 +33,13 @@
|
|||
else
|
||||
li.dropdown
|
||||
a.dropdown-toggle(href="#", data-toggle="dropdown" role="button" aroa-haspopup="true" aria-expanded="false")
|
||||
img.img-circle.img-circle-small.m-r-1(src=me.getPhotoURL())
|
||||
img.img-circle.img-circle-small.m-r-1(src=me.getPhotoURL() class=(me.isTeacher() ? 'border-navy' : ''))
|
||||
span.spr My Account
|
||||
ul.dropdown-menu
|
||||
li.user-dropdown-header.text-center
|
||||
span.user-level= me.level()
|
||||
a(href="/user/#{me.getSlugOrID()}")
|
||||
img.img-circle(src=me.getPhotoURL())
|
||||
img.img-circle(src=me.getPhotoURL() class=(me.isTeacher() ? 'border-navy' : ''))
|
||||
h5=me.displayName()
|
||||
li
|
||||
a(href="/user/#{me.getSlugOrID()}", data-i18n="nav.profile")
|
||||
|
@ -106,11 +93,11 @@
|
|||
li
|
||||
strong(data-i18n="nav.schools")
|
||||
li
|
||||
a(href="/courses/teachers", data-i18n="nav.teachers")
|
||||
a(href="/teachers/classes", data-i18n="nav.teachers")
|
||||
li
|
||||
a(href="https://sites.google.com/a/codecombat.com/teacher-guides/", data-i18n="nav.educator_wiki")
|
||||
li
|
||||
a(href="/teachers/quote", data-i18n="nav.request_quote")
|
||||
a(href="/teachers/demo", data-i18n="teachers_quote.title")
|
||||
|
||||
.col-sm-3
|
||||
ul.list-unstyled
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
li
|
||||
a.login-btn(data-i18n="login.log_in")
|
||||
li
|
||||
a(href="/courses/teachers", data-i18n="nav.create_a_class")
|
||||
a(href="/teachers/classes", data-i18n="nav.create_a_class")
|
||||
|
||||
.col-sm-3
|
||||
ul.list-unstyled
|
||||
|
|
|
@ -9,7 +9,7 @@ block modal-header-content
|
|||
block modal-body-content
|
||||
form
|
||||
.form-group
|
||||
label(data-i18n="general.name")
|
||||
label(data-i18n="courses.class_name")
|
||||
input#name-input.form-control(name="name" type='text' value=view.classroom.get('name'))
|
||||
|
||||
.form-group
|
||||
|
@ -26,13 +26,13 @@ block modal-body-content
|
|||
select#programming-language-select.form-control(name="language" value=aceConfig.language disabled=languageDisabled)
|
||||
- var aceConfig = view.classroom.get('aceConfig') || {};
|
||||
option(value="" data-i18n="courses.language_select")
|
||||
option(value="python", data-i18n="courses.learn_p")
|
||||
option(value="javascript", data-i18n="courses.learn_j")
|
||||
option(value="python") Python
|
||||
option(value="javascript") JavaScript
|
||||
|
||||
.form-group
|
||||
label
|
||||
span(data-i18n="courses.avg_student_exp_label")
|
||||
span.spl.text-muted(data-i18n="signup.optional")
|
||||
i.spl.text-muted(data-i18n="signup.optional")
|
||||
.help-block.small.text-navy(data-i18n="courses.avg_student_exp_desc")
|
||||
select.form-control(name="averageStudentExp", value=view.classroom.get('averageStudentExp'))
|
||||
option(value="" data-i18n="courses.avg_student_exp_select")
|
||||
|
|
|
@ -2,9 +2,16 @@ extends /templates/base
|
|||
|
||||
block content
|
||||
|
||||
if !me.isAnonymous() && (me.isTeacher() || view.ownedClassrooms.size())
|
||||
.alert.alert-danger.text-center
|
||||
// DNT: Temporary
|
||||
h3 ATTENTION TEACHERS:
|
||||
p We are transitioning to a new classroom management system; this page will soon be student-only.
|
||||
a(href="/teachers/classes") Go to teachers area.
|
||||
|
||||
- var isOwner = view.classroom ? view.classroom.get('ownerID') === me.id : false;
|
||||
if isOwner
|
||||
a(href="/courses/teachers", data-i18n="courses.back_classrooms")
|
||||
a(href="/teachers/classes", data-i18n="courses.back_classrooms")
|
||||
else
|
||||
a(href="/courses", data-i18n="courses.back_courses")
|
||||
|
||||
|
|
|
@ -2,8 +2,15 @@ extends /templates/base
|
|||
|
||||
block content
|
||||
|
||||
if me.isTeacher() || view.ownedClassrooms.size()
|
||||
.alert.alert-danger.text-center
|
||||
// DNT: Temporary
|
||||
h3 ATTENTION TEACHERS:
|
||||
p We are transitioning to a new classroom management system; this page will soon be student-only.
|
||||
a(href="/teachers/classes") Go to teachers area.
|
||||
|
||||
if view.teacherMode
|
||||
a(href="/courses/teachers", data-i18n="courses.back_classrooms")
|
||||
a(href="/teachers/classes", data-i18n="courses.back_classrooms")
|
||||
else
|
||||
a(href="/courses", data-i18n="courses.back_courses")
|
||||
br
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
extends /templates/base
|
||||
|
||||
block content
|
||||
|
||||
if me.isTeacher() || view.ownedClassrooms.size()
|
||||
.alert.alert-danger.text-center
|
||||
// DNT: Temporary
|
||||
h3 ATTENTION TEACHERS:
|
||||
p We are transitioning to a new classroom management system; this page will soon be student-only.
|
||||
a(href="/teachers/classes") Go to teachers area.
|
||||
|
||||
h3.text-right
|
||||
if me.isAnonymous()
|
||||
a(href="/teachers/signup")
|
||||
span(data-i18n="courses.teachers_click")
|
||||
span !
|
||||
else
|
||||
a(href="/courses/teachers")
|
||||
a(href="/teachers/classes")
|
||||
span(data-i18n="courses.teachers_click")
|
||||
span !
|
||||
|
||||
|
|
|
@ -4,21 +4,47 @@ block page_nav
|
|||
include ./teacher-dashboard-nav.jade
|
||||
|
||||
block content
|
||||
.container
|
||||
h3(data-i18n='teacher.enrollments')
|
||||
h4
|
||||
span(data-i18n='teacher.enrollments_blurb_1')
|
||||
span 2–8
|
||||
span(data-i18n='teacher.enrollments_blurb_2')
|
||||
if me.isAnonymous() || (!me.isTeacher() && !view.classrooms.size())
|
||||
.access-restricted.container.text-center.m-y-3
|
||||
h5(data-i18n='teacher.access_restricted')
|
||||
p(data-i18n='teacher.teacher_account_required')
|
||||
if me.isAnonymous()
|
||||
.login-button.btn.btn-lg.btn-primary(data-i18n='login.log_in')
|
||||
a.btn.btn-lg.btn-primary-alt(href="/teachers/signup" data-i18n='teacher.create_teacher_account')
|
||||
else
|
||||
a.btn.btn-lg.btn-primary(href="/teachers/convert" data-i18n="teachers_quote.convert_account_title")
|
||||
button#logout-button.btn.btn-lg.btn-primary-alt(data-i18n="login.log_out")
|
||||
|
||||
.teacher-account-blurb.text-center.col-xs-6.col-xs-offset-3.m-y-3
|
||||
h5(data-i18n='teacher.what_is_a_teacher_account')
|
||||
p(data-i18n='teacher.teacher_account_explanation')
|
||||
|
||||
else
|
||||
if !me.isTeacher()
|
||||
.alert.alert-danger.text-center
|
||||
.container
|
||||
// DNT: Temporary
|
||||
h3 ATTENTION: Please upgrade your account to a Teacher Account.
|
||||
p
|
||||
| We are transitioning to a new improved classroom management system for instructors.
|
||||
| Please convert your account to ensure you retain access to your classrooms.
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/convert") Upgrade to teacher account
|
||||
|
||||
.row
|
||||
.col-xs-4
|
||||
+enrollmentStats
|
||||
.col-xs-4
|
||||
+addCredits
|
||||
.col-xs-3.col-xs-offset-1
|
||||
+howToEnroll
|
||||
+quoteSection
|
||||
.container.m-t-5
|
||||
h3(data-i18n='teacher.enrollments')
|
||||
h4
|
||||
span(data-i18n='teacher.enrollments_blurb_1')
|
||||
span 2–8
|
||||
span(data-i18n='teacher.enrollments_blurb_2')
|
||||
|
||||
.row.m-t-3
|
||||
.col-xs-4
|
||||
+enrollmentStats
|
||||
.col-xs-4
|
||||
+addCredits
|
||||
.col-xs-3.col-xs-offset-1
|
||||
+howToEnroll
|
||||
+quoteSection
|
||||
|
||||
mixin enrollmentStats
|
||||
h5
|
||||
|
@ -43,9 +69,9 @@ mixin enrollmentStats
|
|||
mixin addCredits
|
||||
.text-center
|
||||
h5(data-i18n='teacher.add_enrollment_credits')
|
||||
div
|
||||
div.m-t-1
|
||||
input#students-input.text-center.enrollment-count(value=view.numberOfStudents type='number')
|
||||
div
|
||||
div.m-t-1
|
||||
if view.state === 'purchasing'
|
||||
.purchase-now.btn.btn-lg.btn-forest.disabled
|
||||
span(data-i18n='teacher.purchasing')
|
||||
|
@ -61,12 +87,12 @@ mixin howToEnroll
|
|||
.text-center
|
||||
b(data-i18n='teacher.how_to_enroll')
|
||||
ol
|
||||
li(data-i18n='teacher.how_to_enroll_blurb_1')
|
||||
li(data-i18n='teacher.how_to_enroll_blurb_2')
|
||||
li(data-i18n='teacher.how_to_enroll_blurb_3')
|
||||
li.m-t-1(data-i18n='teacher.how_to_enroll_blurb_1')
|
||||
li.m-t-2(data-i18n='teacher.how_to_enroll_blurb_2')
|
||||
li.m-t-2(data-i18n='teacher.how_to_enroll_blurb_3')
|
||||
|
||||
mixin quoteSection
|
||||
.text-center
|
||||
.text-center.m-t-5
|
||||
h4(data-i18n='teacher.bulk_pricing_blurb')
|
||||
a.request-quote.btn.btn-lg.btn-navy(href='/teachers/quote')
|
||||
span(data-i18n='teacher.request_quote')
|
||||
a.request-quote.btn.btn-lg.btn-navy.m-t-2(href='/teachers/demo')
|
||||
span(data-i18n='teachers_quote.title')
|
||||
|
|
|
@ -7,7 +7,7 @@ block content
|
|||
span(data-i18n="courses.teachers_click")
|
||||
span !
|
||||
else
|
||||
a(href="/courses/teachers")
|
||||
a(href="/teachers/classes")
|
||||
span(data-i18n="courses.teachers_click")
|
||||
span !
|
||||
br
|
||||
|
|
|
@ -17,7 +17,7 @@ block content
|
|||
if view.fromClassroom
|
||||
a(href="/courses/"+view.fromClassroom, data-i18n="courses.return_to_class")
|
||||
else
|
||||
a(href="/courses/teachers", data-i18n="courses.return_to_course_man")
|
||||
a(href="/teachers/classes", data-i18n="courses.return_to_course_man")
|
||||
|
||||
else
|
||||
|
||||
|
|
|
@ -41,11 +41,6 @@ block modal-body-content
|
|||
#errors-alert.alert.alert-danger.hide
|
||||
|
||||
.text-center
|
||||
if view.willPlay
|
||||
input#sign-up-btn.btn.btn-default(type="submit", data-i18n="[value]courses.start_playing", value="Start Playing")
|
||||
p
|
||||
a#skip-link(data-i18n="courses.skip_this")
|
||||
else
|
||||
input#sign-up-btn.btn.btn-default(data-i18n="[value]signup.sign_up", type="submit")
|
||||
input#sign-up-btn.btn.btn-default(data-i18n="[value]signup.sign_up", type="submit")
|
||||
|
||||
block modal-footer-content
|
||||
|
|
|
@ -5,18 +5,28 @@ block page_nav
|
|||
|
||||
block content
|
||||
- var classroom = view.classroom
|
||||
if !me.isTeacher()
|
||||
.alert.alert-danger.text-center
|
||||
.container
|
||||
// DNT: Temporary
|
||||
h3 ATTENTION: Please upgrade your account to a Teacher Account.
|
||||
p
|
||||
| We are transitioning to a new improved classroom management system for instructors.
|
||||
| Please convert your account to ensure you retain access to your classrooms.
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/convert") Upgrade to teacher account
|
||||
|
||||
if classroom.loaded
|
||||
.container
|
||||
+breadcrumbs
|
||||
h3= classroom.get('name')
|
||||
h3.m-t-2= classroom.get('name')
|
||||
a.label.edit-classroom(data-classroom-id=classroom.id)
|
||||
span(data-i18n='teacher.edit_class_settings')
|
||||
h4= classroom.get('description')
|
||||
|
||||
.classroom-info-row.row
|
||||
.classroom-info-row.row.m-t-5
|
||||
.classroom-details.col-md-3
|
||||
- var stats = view.classStats()
|
||||
h4(data-i18n='teacher.class_overview')
|
||||
h4.m-b-2(data-i18n='teacher.class_overview')
|
||||
|
||||
.language.small-details
|
||||
span(data-i18n='teacher.language')
|
||||
|
@ -61,28 +71,30 @@ block content
|
|||
//- span(data-i18n='concepts.'+name)
|
||||
|
||||
.completeness-info.col-md-4
|
||||
h4.m-b-2
|
||||
|
||||
if view.earliestIncompleteLevel
|
||||
div
|
||||
div.small-details
|
||||
span(data-i18n='teacher.earliest_incomplete')
|
||||
span :
|
||||
+longLevelName(view.earliestIncompleteLevel)
|
||||
+inlineUserList(view.earliestIncompleteLevel.users)
|
||||
|
||||
if view.latestCompleteLevel
|
||||
div
|
||||
div.small-details.m-t-3
|
||||
span(data-i18n='teacher.latest_complete')
|
||||
span :
|
||||
+longLevelName(view.latestCompleteLevel)
|
||||
+inlineUserList(view.latestCompleteLevel.users)
|
||||
|
||||
.adding-students.col-md-5
|
||||
h4
|
||||
h4.m-b-2
|
||||
span(data-i18n='teacher.adding_students')
|
||||
span :
|
||||
+copyCodes
|
||||
+addStudentsButton
|
||||
|
||||
ul.nav.nav-tabs(role='tablist')
|
||||
ul.nav.nav-tabs.m-t-5(role='tablist')
|
||||
li.active
|
||||
a(href='#students-tab' data-toggle='tab')
|
||||
.small-details.text-center(data-i18n='teacher.students')
|
||||
|
@ -162,6 +174,8 @@ mixin studentRow(student)
|
|||
label.checkmark(for='checkbox-student-' + student.id)
|
||||
td.student-info-col
|
||||
.student-info
|
||||
if student.get('deleted')
|
||||
em (deleted)
|
||||
div.student-name= student.get('name')
|
||||
div.student-email.small-details= student.get('email')
|
||||
td.hidden
|
||||
|
@ -186,6 +200,10 @@ mixin studentRow(student)
|
|||
+enrollStudentButton(student)
|
||||
//- td
|
||||
//- span.view-class-arrow.glyphicon.glyphicon-chevron-right
|
||||
td
|
||||
a.remove-student-link.small.center-block.text-center.pull-right.m-r-2(data-student-id=student.id)
|
||||
div.glyphicon.glyphicon-remove
|
||||
div(data-i18n='teacher.remove')
|
||||
|
||||
mixin enrollStudentButton(student)
|
||||
a.enroll-student-button.btn.btn-lg.btn-primary(data-classroom-id=view.classroom.id data-user-id=student.id)
|
||||
|
@ -227,7 +245,7 @@ mixin studentLevelsRow(student)
|
|||
.student-levels-row.alternating-background
|
||||
div.student-info
|
||||
div.student-name= student.get('name')
|
||||
div.student-email.small-details emailaddress@school.edu
|
||||
div.student-email.small-details= student.get('email')
|
||||
div.student-levels-progress
|
||||
- var course = view.selectedCourse
|
||||
- var campaign = view.campaigns.get(course.get('campaignID'))
|
||||
|
@ -250,14 +268,14 @@ mixin progressDotLabel(label)
|
|||
= label
|
||||
|
||||
mixin copyCodes
|
||||
div.copy-button-group.form-inline
|
||||
div.copy-button-group.form-inline.m-b-3
|
||||
.form-group
|
||||
input.text-h4.semibold#join-code-input(value=view.classCode)
|
||||
button#copy-code-btn.form-control.btn.btn-lg.btn-forest
|
||||
span(data-i18n='teacher.copy_class_code')
|
||||
div.text-center.small(data-i18n='teacher.class_code_blurb')
|
||||
|
||||
div.copy-button-group.form-inline
|
||||
div.copy-button-group.form-inline.m-b-3
|
||||
.form-group
|
||||
input.form-control.text-h4.semibold#join-url-input(value=view.joinURL)
|
||||
button#copy-url-btn.form-control.btn.btn-lg.btn-forest
|
||||
|
|
|
@ -4,24 +4,51 @@ block page_nav
|
|||
include ./teacher-dashboard-nav.jade
|
||||
|
||||
block content
|
||||
.container
|
||||
h3(data-i18n='teacher.current_classes')
|
||||
if !me.isTeacher() && !view.classrooms.size()
|
||||
.access-restricted.container.text-center.m-y-3
|
||||
h5(data-i18n='teacher.access_restricted')
|
||||
p(data-i18n='teacher.teacher_account_required')
|
||||
if me.isAnonymous()
|
||||
.login-button.btn.btn-lg.btn-primary(data-i18n='login.log_in')
|
||||
a.btn.btn-lg.btn-primary-alt(href="/teachers/signup" data-i18n='teacher.create_teacher_account')
|
||||
else
|
||||
a.btn.btn-lg.btn-primary(href="/teachers/convert" data-i18n="teachers_quote.convert_account_title")
|
||||
button#logout-button.btn.btn-lg.btn-primary-alt(data-i18n="login.log_out")
|
||||
|
||||
.teacher-account-blurb.text-center.col-xs-6.col-xs-offset-3.m-y-3
|
||||
h5(data-i18n='teacher.what_is_a_teacher_account')
|
||||
p(data-i18n='teacher.teacher_account_explanation')
|
||||
|
||||
else
|
||||
if !me.isTeacher()
|
||||
.alert.alert-danger.text-center
|
||||
.container
|
||||
// DNT: Temporary
|
||||
h3 ATTENTION: Please upgrade your account to a Teacher Account.
|
||||
p
|
||||
| We are transitioning to a new improved classroom management system for instructors.
|
||||
| Please convert your account to ensure you retain access to your classrooms.
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/convert") Upgrade to teacher account
|
||||
|
||||
.container
|
||||
h3(data-i18n='teacher.current_classes')
|
||||
|
||||
.classes.container
|
||||
// Loop each class
|
||||
each classroom in view.classrooms.models
|
||||
unless classroom.get('archived')
|
||||
+classRow(classroom)
|
||||
|
||||
+createClassButton
|
||||
|
||||
.classes.container
|
||||
// Loop each class
|
||||
each classroom in view.classrooms.models
|
||||
unless classroom.get('archived')
|
||||
+classRow(classroom)
|
||||
- var archivedClassrooms = view.classrooms.where({archived: true});
|
||||
if _.size(archivedClassrooms)
|
||||
.container
|
||||
h3(data-i18n='teacher.archived_classes')
|
||||
p(data-i18n='teacher.archived_classes_blurb')
|
||||
|
||||
+createClassButton
|
||||
|
||||
.container
|
||||
h3(data-i18n='teacher.archived_classes')
|
||||
h4(data-i18n='teacher.archived_classes_blurb')
|
||||
|
||||
.classes.container
|
||||
each classroom in view.classrooms.models
|
||||
if classroom.get('archived')
|
||||
.classes.container
|
||||
each classroom in archivedClassrooms
|
||||
+archivedClassRow(classroom)
|
||||
|
||||
mixin classRow(classroom)
|
||||
|
@ -76,9 +103,11 @@ mixin progressDot(classroom, course, index)
|
|||
- var total = classroom.get('members').length
|
||||
- var complete = 0;
|
||||
- var dotClass = '';
|
||||
- var started = 0;
|
||||
if courseInstance
|
||||
- complete = courseInstance.numCompleted
|
||||
- dotClass = complete === total ? 'forest' : 'gold';
|
||||
- started = courseInstance.numStarted
|
||||
- dotClass = complete === total ? 'forest' : started ? 'gold' : '';
|
||||
- var progressDotContext = {total: total, complete: complete};
|
||||
.progress-dot(class=dotClass, data-title=view.progressDotTemplate(progressDotContext))
|
||||
+progressDotLabel(index)
|
||||
|
|
|
@ -4,6 +4,16 @@ block page_nav
|
|||
include ./teacher-dashboard-nav.jade
|
||||
|
||||
block content
|
||||
if !me.isTeacher() && view.ownedClassrooms.size()
|
||||
.alert.alert-danger.text-center
|
||||
.container
|
||||
// DNT: Temporary
|
||||
h3 ATTENTION: Please upgrade your account to a Teacher Account.
|
||||
p
|
||||
| We are transitioning to a new improved classroom management system for instructors.
|
||||
| Please convert your account to ensure you retain access to your classrooms.
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/convert") Upgrade to teacher account
|
||||
|
||||
.container
|
||||
h1(data-i18n="courses.title")
|
||||
h2(data-i18n="courses.subtitle")
|
||||
|
@ -61,13 +71,15 @@ mixin course-info(course)
|
|||
span(data-i18n="concepts." + concept)
|
||||
if course.get('concepts').indexOf(concept) !== course.get('concepts').length - 1
|
||||
span.spr ,
|
||||
if view.guideLinks[course.id]
|
||||
//- a.btn.btn-primary(href=view.guideLinks[course.id] class=(me.isAnonymous() ? 'disabled' : ''))
|
||||
//- span(data-i18n="courses.print_guide")
|
||||
a.btn.btn-primary(href=view.guideLinks[course.id] class=(me.isAnonymous() ? 'disabled' : ''))
|
||||
span(data-i18n="courses.view_guide_online")
|
||||
else
|
||||
i.small
|
||||
| (
|
||||
span(data-i18n='teacher.guides_coming_soon')
|
||||
| )
|
||||
|
||||
if me.isTeacher() && view.ownedClassrooms.size()
|
||||
if view.guideLinks[course.id]
|
||||
//- a.btn.btn-primary(href=view.guideLinks[course.id] class=(me.isAnonymous() ? 'disabled' : ''))
|
||||
//- span(data-i18n="courses.print_guide")
|
||||
a.btn.btn-primary(href=view.guideLinks[course.id] class=(me.isAnonymous() ? 'disabled' : ''))
|
||||
span(data-i18n="courses.view_guide_online")
|
||||
else
|
||||
i.small
|
||||
| (
|
||||
span(data-i18n='teacher.guides_coming_soon')
|
||||
| )
|
||||
|
|
|
@ -78,12 +78,12 @@ mixin box
|
|||
div
|
||||
button.teacher-btn.btn.btn-forest.btn-lg.btn-block(data-i18n="new_home.goto_classes")
|
||||
div
|
||||
if false
|
||||
if view.isTeacherWithDemo
|
||||
h6(data-i18n="new_home.check_out_wiki")
|
||||
a.btn.btn-primary.btn-lg.btn-block(href="https://sites.google.com/a/codecombat.com/teacher-guides/course-guides", data-i18n="new_home.educator_wiki")
|
||||
else
|
||||
h6(data-i18n="new_home.want_coco")
|
||||
a.btn.btn-primary.btn-lg.btn-block(href="/teachers/convert", data-i18n="new_home.get_started")
|
||||
a.btn.btn-primary.btn-lg.btn-block(href=view.demoRequestURL, data-i18n="new_home.get_started")
|
||||
|
||||
else if view.justPlaysCourses()
|
||||
div
|
||||
|
@ -258,7 +258,7 @@ block content
|
|||
span(data-i18n="new_home.agency")
|
||||
|
||||
.request-demo-row.text-center
|
||||
if me.isTeacher()
|
||||
if view.isTeacherWithDemo
|
||||
h3(data-i18n="new_home.get_started_title")
|
||||
else
|
||||
h3(data-i18n="new_home.request_demo_title")
|
||||
|
@ -277,14 +277,14 @@ block content
|
|||
.clearfix.hidden-xs
|
||||
small(data-i18n="new_home.teacher_screenshots_hint")
|
||||
|
||||
if me.isTeacher()
|
||||
if view.isTeacherWithDemo
|
||||
h4(data-i18n="new_home.get_started_subtitle")
|
||||
div
|
||||
a.btn.btn-primary.btn-lg(href="/courses/teachers", data-i18n="new_home.setup_a_class")
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/classes", data-i18n="new_home.setup_a_class")
|
||||
else
|
||||
h4(data-i18n="new_home.request_demo_subtitle")
|
||||
div
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/demo", data-i18n="new_home.request_demo")
|
||||
a.btn.btn-primary.btn-lg(href=view.demoRequestURL, data-i18n="new_home.request_demo")
|
||||
if me.isAnonymous()
|
||||
.have-an-account
|
||||
span.spr(data-i18n="new_home.have_an_account")
|
||||
|
@ -371,12 +371,12 @@ block content
|
|||
|
||||
.request-demo-row.text-center
|
||||
h3(data-i18n="new_home.run_class")
|
||||
if me.isTeacher()
|
||||
if view.isTeacherWithDemo
|
||||
div
|
||||
a.btn.btn-primary.btn-lg(href="/courses/teachers", data-i18n="new_home.setup_a_class")
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/classes", data-i18n="new_home.setup_a_class")
|
||||
else
|
||||
div
|
||||
a.btn.btn-primary.btn-lg(href="/teachers/demo", data-i18n="new_home.request_demo")
|
||||
a.btn.btn-primary.btn-lg(href=view.demoRequestURL, data-i18n="new_home.request_demo")
|
||||
if me.isAnonymous()
|
||||
.have-an-account
|
||||
span.spr(data-i18n="new_home.have_an_account")
|
||||
|
|
|
@ -28,7 +28,7 @@ block content
|
|||
a.btn-login-account Log in here.
|
||||
else
|
||||
.text-center
|
||||
a.btn-enter-courses(href="/courses/teachers") set up a free class
|
||||
a.btn-enter-courses(href="/teachers/classes") set up a free class
|
||||
|
||||
p.text-center
|
||||
a(href='#getting-started')
|
||||
|
|
|
@ -122,14 +122,14 @@ block content
|
|||
label
|
||||
input(type="checkbox" name="educationLevel" value="Elementary")
|
||||
span(data-i18n="teachers_quote.elementary_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="High")
|
||||
span(data-i18n="teachers_quote.high_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="Middle")
|
||||
span(data-i18n="teachers_quote.middle_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="High")
|
||||
span(data-i18n="teachers_quote.high_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="College+")
|
||||
|
|
|
@ -141,14 +141,14 @@ block content
|
|||
label
|
||||
input(type="checkbox" name="educationLevel" value="Elementary")
|
||||
span(data-i18n="teachers_quote.elementary_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="High")
|
||||
span(data-i18n="teachers_quote.high_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="Middle")
|
||||
span(data-i18n="teachers_quote.middle_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="High")
|
||||
span(data-i18n="teachers_quote.high_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="College+")
|
||||
|
|
|
@ -144,14 +144,14 @@ block content
|
|||
label
|
||||
input(type="checkbox" name="educationLevel" value="Elementary")
|
||||
span(data-i18n="teachers_quote.elementary_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="High")
|
||||
span(data-i18n="teachers_quote.high_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="Middle")
|
||||
span(data-i18n="teachers_quote.middle_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="High")
|
||||
span(data-i18n="teachers_quote.high_school")
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox" name="educationLevel" value="College+")
|
||||
|
@ -202,7 +202,7 @@ block content
|
|||
.row
|
||||
.col-md-offset-2.col-md-4
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.name")
|
||||
label.control-label(data-i18n="general.username")
|
||||
input.form-control(name="name")
|
||||
|
||||
.row
|
||||
|
|
|
@ -9,7 +9,7 @@ block content
|
|||
p(data-i18n='teacher.teacher_account_required')
|
||||
if me.isAnonymous()
|
||||
.login-button.btn.btn-lg.btn-primary(data-i18n='login.log_in')
|
||||
.teacher-signup-button.btn.btn-lg.btn-primary-alt(data-i18n='teacher.create_teacher_account')
|
||||
a.btn.btn-lg.btn-primary-alt(href="/teachers/signup" data-i18n='teacher.create_teacher_account')
|
||||
else
|
||||
a.btn.btn-lg.btn-primary(href="/teachers/convert" data-i18n="teachers_quote.convert_account_title")
|
||||
button#logout-button.btn.btn-lg.btn-primary-alt(data-i18n="login.log_out")
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/new-home-view'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
TrialRequest = require 'models/TrialRequest'
|
||||
TrialRequests = require 'collections/TrialRequests'
|
||||
Course = require 'models/Course'
|
||||
utils = require 'core/utils'
|
||||
storage = require 'core/storage'
|
||||
|
@ -39,6 +41,11 @@ module.exports = class NewHomeView extends RootView
|
|||
if @getQueryVariable 'hour_of_code'
|
||||
application.router.navigate "/hoc", trigger: true
|
||||
|
||||
if me.isTeacher()
|
||||
@trialRequests = new TrialRequests()
|
||||
@trialRequests.fetchOwn()
|
||||
@supermodel.loadCollection(@trialRequests)
|
||||
|
||||
isHourOfCodeWeek = false # Temporary: default to /hoc flow during the main event week
|
||||
if isHourOfCodeWeek and (@isNewPlayer() or (@justPlaysCourses() and me.isAnonymous()))
|
||||
# Go/return straight to playing single-player HoC course on Play click
|
||||
|
@ -53,6 +60,12 @@ module.exports = class NewHomeView extends RootView
|
|||
else
|
||||
@playURL = '/play'
|
||||
|
||||
onLoaded: ->
|
||||
@trialRequest = @trialRequests.first() if @trialRequests?.size()
|
||||
@isTeacherWithDemo = @trialRequest and @trialRequest.get('status') in ['approved', 'submitted']
|
||||
@demoRequestURL = if me.isTeacher() then '/teachers/convert' else '/teachers/demo'
|
||||
super()
|
||||
|
||||
onClickPlayButton: (e) ->
|
||||
@playSound 'menu-button-click'
|
||||
e.preventDefault()
|
||||
|
|
|
@ -20,7 +20,7 @@ module.exports = class TeachersView extends RootView
|
|||
application.router.navigate "/schools", trigger: true
|
||||
unless me.isAnonymous()
|
||||
_.defer ->
|
||||
application.router.navigate "/courses/teachers", trigger: true
|
||||
application.router.navigate "/teachers/courses", trigger: true
|
||||
|
||||
onClickLogin: (e) ->
|
||||
@openModalView new AuthModal() if me.get('anonymous')
|
||||
|
|
|
@ -29,8 +29,8 @@ module.exports = class ActivateLicensesModal extends ModalView
|
|||
success: =>
|
||||
@classrooms.each (classroom) =>
|
||||
classroom.users = new Users()
|
||||
classroom.users.fetchForClassroom(classroom)
|
||||
@supermodel.trackCollection(classroom.users)
|
||||
jqxhrs = classroom.users.fetchForClassroom(classroom)
|
||||
@supermodel.trackRequests(jqxhrs)
|
||||
})
|
||||
@supermodel.trackCollection(@classrooms)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ CocoCollection = require 'collections/CocoCollection'
|
|||
Course = require 'models/Course'
|
||||
CourseInstance = require 'models/CourseInstance'
|
||||
Classroom = require 'models/Classroom'
|
||||
Classrooms = require 'collections/Classrooms'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
Prepaids = require 'collections/Prepaids'
|
||||
RootView = require 'views/core/RootView'
|
||||
|
@ -46,11 +47,14 @@ module.exports = class ClassroomView extends RootView
|
|||
@prepaids.comparator = '_id'
|
||||
@prepaids.fetchByCreator(me.id)
|
||||
@supermodel.loadCollection(@prepaids)
|
||||
@users = new CocoCollection([], { url: "/db/classroom/#{classroomID}/members", model: User })
|
||||
@users = new CocoCollection([], { url: "/db/classroom/#{classroomID}/members?memberLimit=100", model: User })
|
||||
@users.comparator = (user) => user.broadName().toLowerCase()
|
||||
@supermodel.loadCollection(@users)
|
||||
@listenToOnce @courseInstances, 'sync', @onCourseInstancesSync
|
||||
@sessions = new CocoCollection([], { model: LevelSession })
|
||||
@ownedClassrooms = new Classrooms()
|
||||
@ownedClassrooms.fetchMine({data: {project: '_id'}})
|
||||
@supermodel.trackCollection(@ownedClassrooms)
|
||||
|
||||
onCourseInstancesSync: ->
|
||||
@sessions = new CocoCollection([], { model: LevelSession })
|
||||
|
@ -79,7 +83,6 @@ module.exports = class ClassroomView extends RootView
|
|||
onLoaded: ->
|
||||
@teacherMode = me.isAdmin() or @classroom.get('ownerID') is me.id
|
||||
userSessions = @sessions.groupBy('creator')
|
||||
@users.remove(@users.where({ deleted: true }))
|
||||
for user in @users.models
|
||||
user.sessions = new CocoCollection(userSessions[user.id], { model: LevelSession })
|
||||
user.sessions.comparator = 'changed'
|
||||
|
|
|
@ -3,6 +3,7 @@ CocoCollection = require 'collections/CocoCollection'
|
|||
Course = require 'models/Course'
|
||||
CourseInstance = require 'models/CourseInstance'
|
||||
Classroom = require 'models/Classroom'
|
||||
Classrooms = require 'collections/Classrooms'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/courses/course-details'
|
||||
|
@ -23,6 +24,9 @@ module.exports = class CourseDetailsView extends RootView
|
|||
|
||||
constructor: (options, @courseID, @courseInstanceID) ->
|
||||
super options
|
||||
@ownedClassrooms = new Classrooms()
|
||||
@ownedClassrooms.fetchMine({data: {project: '_id'}})
|
||||
@supermodel.trackCollection(@ownedClassrooms)
|
||||
@courseID ?= options.courseID
|
||||
@courseInstanceID ?= options.courseInstanceID
|
||||
@classroom = new Classroom()
|
||||
|
|
|
@ -9,6 +9,7 @@ CourseInstance = require 'models/CourseInstance'
|
|||
CocoCollection = require 'collections/CocoCollection'
|
||||
Course = require 'models/Course'
|
||||
Classroom = require 'models/Classroom'
|
||||
Classrooms = require 'collections/Classrooms'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
Campaign = require 'models/Campaign'
|
||||
utils = require 'core/utils'
|
||||
|
@ -33,6 +34,9 @@ module.exports = class CoursesView extends RootView
|
|||
@supermodel.loadCollection(@courseInstances)
|
||||
@classrooms = new CocoCollection([], { url: "/db/classroom", model: Classroom })
|
||||
@supermodel.loadCollection(@classrooms, { data: {memberID: me.id} })
|
||||
@ownedClassrooms = new Classrooms()
|
||||
@ownedClassrooms.fetchMine({data: {project: '_id'}})
|
||||
@supermodel.trackCollection(@ownedClassrooms)
|
||||
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
||||
@supermodel.loadCollection(@courses)
|
||||
@campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign })
|
||||
|
@ -58,7 +62,7 @@ module.exports = class CoursesView extends RootView
|
|||
|
||||
onLoaded: ->
|
||||
super()
|
||||
if utils.getQueryVariable('_cc', false)
|
||||
if utils.getQueryVariable('_cc', false) and not me.isAnonymous()
|
||||
@joinClass()
|
||||
|
||||
onClickStartNewGameButton: ->
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
app = require 'core/application'
|
||||
CreateAccountModal = require 'views/core/CreateAccountModal'
|
||||
Classroom = require 'models/Classroom'
|
||||
Classrooms = require 'collections/Classrooms'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Course = require 'models/Course'
|
||||
Prepaids = require 'collections/Prepaids'
|
||||
|
@ -14,10 +15,13 @@ Products = require 'collections/Products'
|
|||
module.exports = class EnrollmentsView extends RootView
|
||||
id: 'enrollments-view'
|
||||
template: template
|
||||
numberOfStudents: 30
|
||||
numberOfStudents: 15
|
||||
pricePerStudent: 0
|
||||
|
||||
initialize: (options) ->
|
||||
@ownedClassrooms = new Classrooms()
|
||||
@ownedClassrooms.fetchMine({data: {project: '_id'}})
|
||||
@supermodel.trackCollection(@ownedClassrooms)
|
||||
@listenTo stripeHandler, 'received-token', @onStripeReceivedToken
|
||||
@fromClassroom = utils.getQueryVariable('from-classroom')
|
||||
@members = new CocoCollection([], { model: User })
|
||||
|
|
|
@ -11,7 +11,6 @@ module.exports = class StudentSignUpModal extends ModalView
|
|||
template: template
|
||||
|
||||
events:
|
||||
'click #sign-up-btn': 'onClickSignUpButton'
|
||||
'submit form': 'onSubmitForm'
|
||||
'click #skip-link': 'onClickSkipLink'
|
||||
|
||||
|
@ -31,10 +30,7 @@ module.exports = class StudentSignUpModal extends ModalView
|
|||
onSubmitForm: (e) ->
|
||||
e.preventDefault()
|
||||
@signupClassroomPrecheck()
|
||||
|
||||
onClickSignUpButton: ->
|
||||
@signupClassroomPrecheck()
|
||||
|
||||
|
||||
emailCheck: ->
|
||||
email = @$('#email').val()
|
||||
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i # https://news.ycombinator.com/item?id=5763990
|
||||
|
@ -97,5 +93,9 @@ module.exports = class StudentSignUpModal extends ModalView
|
|||
classCode = @$('#class-code-input').val()
|
||||
if classCode
|
||||
url = "/courses?_cc="+classCode
|
||||
application.router.navigate(url)
|
||||
window.location.reload()
|
||||
document.location.href = url
|
||||
# This was a terrible hack to make navigating trigger when just adding query params
|
||||
# application.router.navigate('/thisisahack')
|
||||
# application.router.navigate(url, { trigger: true })
|
||||
else
|
||||
window.location.reload()
|
||||
|
|
|
@ -4,6 +4,7 @@ helper = require 'lib/coursesHelper'
|
|||
ClassroomSettingsModal = require 'views/courses/ClassroomSettingsModal'
|
||||
InviteToClassroomModal = require 'views/courses/InviteToClassroomModal'
|
||||
ActivateLicensesModal = require 'views/courses/ActivateLicensesModal'
|
||||
RemoveStudentModal = require 'views/courses/RemoveStudentModal'
|
||||
|
||||
Classroom = require 'models/Classroom'
|
||||
Classrooms = require 'collections/Classrooms'
|
||||
|
@ -26,6 +27,7 @@ module.exports = class TeacherClassView extends RootView
|
|||
'click .sort-by-progress': 'sortByProgress'
|
||||
'click #copy-url-btn': 'copyURL'
|
||||
'click #copy-code-btn': 'copyCode'
|
||||
'click .remove-student-link': 'onClickRemoveStudentLink'
|
||||
'click .enroll-student-button': 'onClickEnroll'
|
||||
'click .assign-to-selected-students': 'onClickBulkAssign'
|
||||
'click .enroll-selected-students': 'onClickBulkEnroll'
|
||||
|
@ -46,15 +48,15 @@ module.exports = class TeacherClassView extends RootView
|
|||
|
||||
@listenTo @classroom, 'sync', ->
|
||||
@students = new Users()
|
||||
@students.fetchForClassroom(@classroom)
|
||||
@supermodel.trackCollection(@students)
|
||||
jqxhrs = @students.fetchForClassroom(@classroom)
|
||||
if jqxhrs.length > 0
|
||||
@supermodel.trackCollection(@students)
|
||||
@listenTo @students, 'sync', @sortByName
|
||||
@listenTo @students, 'sort', @renderSelectors.bind(@, '.students-table', '.student-levels-table')
|
||||
|
||||
@classroom.sessions = new LevelSessions()
|
||||
if @classroom.get('members')?.length > 0
|
||||
@classroom.sessions.fetchForAllClassroomMembers(@classroom)
|
||||
@supermodel.trackCollection(@classroom.sessions)
|
||||
requests = @classroom.sessions.fetchForAllClassroomMembers(@classroom)
|
||||
@supermodel.trackRequests(requests)
|
||||
|
||||
@courses = new Courses()
|
||||
@courses.fetch()
|
||||
|
@ -69,7 +71,6 @@ module.exports = class TeacherClassView extends RootView
|
|||
@supermodel.trackCollection(@courseInstances)
|
||||
|
||||
onLoaded: ->
|
||||
console.log("loaded!")
|
||||
|
||||
@classCode = @classroom.get('codeCamel') or @classroom.get('code')
|
||||
@joinURL = document.location.origin + "/courses?_cc=" + @classCode
|
||||
|
@ -109,6 +110,21 @@ module.exports = class TeacherClassView extends RootView
|
|||
modal = new ClassroomSettingsModal({ classroom: classroom })
|
||||
@openModalView(modal)
|
||||
@listenToOnce modal, 'hide', @render
|
||||
|
||||
onClickRemoveStudentLink: (e) ->
|
||||
user = @students.get($(e.currentTarget).data('student-id'))
|
||||
modal = new RemoveStudentModal({
|
||||
classroom: @classroom
|
||||
user: user
|
||||
courseInstances: @courseInstances
|
||||
})
|
||||
@openModalView(modal)
|
||||
modal.once 'remove-student', @onStudentRemoved, @
|
||||
|
||||
onStudentRemoved: (e) ->
|
||||
@students.remove(e.user)
|
||||
@render()
|
||||
application.tracker?.trackEvent 'Classroom removed student', category: 'Courses', classroomID: @classroom.id, userID: e.user.id
|
||||
|
||||
onClickAddStudents: (e) =>
|
||||
modal = new InviteToClassroomModal({ classroom: @classroom })
|
||||
|
@ -180,8 +196,7 @@ module.exports = class TeacherClassView extends RootView
|
|||
|
||||
if courseInstance
|
||||
courseInstance.addMembers members, {
|
||||
success: =>
|
||||
@render() unless @destroyed
|
||||
success: @onBulkAssignSuccess
|
||||
}
|
||||
else
|
||||
courseInstance = new CourseInstance {
|
||||
|
@ -194,12 +209,15 @@ module.exports = class TeacherClassView extends RootView
|
|||
courseInstance.save {}, {
|
||||
success: =>
|
||||
courseInstance.addMembers members, {
|
||||
success: =>
|
||||
@render() unless @destroyed
|
||||
success: @onBulkAssignSuccess
|
||||
}
|
||||
}
|
||||
null
|
||||
|
||||
onBulkAssignSuccess: =>
|
||||
@render() unless @destroyed
|
||||
noty text: $.i18n.t('teacher.assigned'), layout: 'center', type: 'information', killer: true, timeout: 5000
|
||||
|
||||
onClickSelectAll: (e) ->
|
||||
e.preventDefault()
|
||||
checkboxes = $('.student-checkbox input')
|
||||
|
|
|
@ -4,6 +4,7 @@ CocoCollection = require 'collections/CocoCollection'
|
|||
CocoModel = require 'models/CocoModel'
|
||||
Course = require 'models/Course'
|
||||
Classroom = require 'models/Classroom'
|
||||
Classrooms = require 'collections/Classrooms'
|
||||
InviteToClassroomModal = require 'views/courses/InviteToClassroomModal'
|
||||
User = require 'models/User'
|
||||
CourseInstance = require 'models/CourseInstance'
|
||||
|
@ -33,6 +34,9 @@ module.exports = class TeacherCoursesView extends RootView
|
|||
|
||||
constructor: (options) ->
|
||||
super(options)
|
||||
@ownedClassrooms = new Classrooms()
|
||||
@ownedClassrooms.fetchMine({data: {project: '_id'}})
|
||||
@supermodel.trackCollection(@ownedClassrooms)
|
||||
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
||||
@supermodel.loadCollection(@courses, 'courses')
|
||||
@classrooms = new CocoCollection([], { url: "/db/classroom", model: Classroom })
|
||||
|
|
|
@ -31,7 +31,7 @@ module.exports = class ConvertToTeacherAccountView extends RootView
|
|||
|
||||
onLoaded: ->
|
||||
if @trialRequests.size() and me.isTeacher()
|
||||
return application.router.navigate('/courses/teachers', { trigger: true, replace: true })
|
||||
return application.router.navigate('/teachers', { trigger: true, replace: true })
|
||||
|
||||
super()
|
||||
|
||||
|
@ -115,7 +115,7 @@ module.exports = class ConvertToTeacherAccountView extends RootView
|
|||
onTrialRequestSubmit: ->
|
||||
me.setRole @trialRequest.get('properties').role.toLowerCase(), true
|
||||
storage.remove(FORM_KEY)
|
||||
application.router.navigate('/courses/teachers', {trigger: true})
|
||||
application.router.navigate('/teachers/classes', {trigger: true})
|
||||
|
||||
formSchema = {
|
||||
type: 'object'
|
||||
|
|
|
@ -8,7 +8,7 @@ errors = require 'core/errors'
|
|||
User = require 'models/User'
|
||||
|
||||
FORM_KEY = 'request-quote-form'
|
||||
SIGNUP_REDIRECT = '/courses/teachers'
|
||||
SIGNUP_REDIRECT = '/teachers/classes'
|
||||
|
||||
module.exports = class CreateTeacherAccountView extends RootView
|
||||
id: 'create-teacher-account-view'
|
||||
|
@ -134,6 +134,7 @@ module.exports = class CreateTeacherAccountView extends RootView
|
|||
onTrialRequestSubmit: ->
|
||||
storage.remove(FORM_KEY)
|
||||
attrs = _.pick(forms.formToObject(@$('form')), 'name', 'email', 'role')
|
||||
attrs.role = attrs.role.toLowerCase()
|
||||
options = {}
|
||||
newUser = new User(attrs)
|
||||
if @gplusAttrs
|
||||
|
|
|
@ -8,7 +8,7 @@ errors = require 'core/errors'
|
|||
ConfirmModal = require 'views/editor/modal/ConfirmModal'
|
||||
|
||||
FORM_KEY = 'request-quote-form'
|
||||
SIGNUP_REDIRECT = '/courses/teachers'
|
||||
SIGNUP_REDIRECT = '/teachers'
|
||||
|
||||
module.exports = class RequestQuoteView extends RootView
|
||||
id: 'request-quote-view'
|
||||
|
@ -131,6 +131,8 @@ module.exports = class RequestQuoteView extends RootView
|
|||
|
||||
onTrialRequestSubmit: ->
|
||||
me.setRole @trialRequest.get('properties').role.toLowerCase(), true
|
||||
defaultName = [@trialRequest.get('firstName'), @trialRequest.get('lastName')].join(' ')
|
||||
@$('input[name="name"]').val(defaultName)
|
||||
storage.remove(FORM_KEY)
|
||||
@$('#request-form, #form-submit-success').toggleClass('hide')
|
||||
@scrollToTop(0)
|
||||
|
|
|
@ -1,17 +1,44 @@
|
|||
// Removes all users with a teacher-like role from classroom membership
|
||||
// Usage: copy and paste into mongo
|
||||
|
||||
|
||||
// Set all users with trial requests to a teacher or teacher-like role, depending on trial request.
|
||||
|
||||
var hasTrialRequest = {};
|
||||
|
||||
db.trial.requests.find().forEach(function(trialRequest) {
|
||||
var role = trialRequest.properties.role || 'teacher';
|
||||
var user = db.users.findOne({_id: trialRequest.applicant}, {role:1, name:1, email:1});
|
||||
print(JSON.stringify(user), JSON.stringify(trialRequest.properties), role);
|
||||
if (!user.role) {
|
||||
print(db.users.update({_id: trialRequest.applicant}, {$set: {role: role}}));
|
||||
}
|
||||
hasTrialRequest[user._id.str] = true;
|
||||
});
|
||||
|
||||
var teacherRoles = ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent'];
|
||||
|
||||
// Unset all teacher-like roles for users without a trial request.
|
||||
// AND removes all remaining users with a teacher-like role from classroom membership (after conversion period)
|
||||
|
||||
db.users.find({'role': {$in: teacherRoles}}, {_id: 1, name: 1, email: 1, role: 1}).forEach(function(user) {
|
||||
print('Updating user', JSON.stringify(user));
|
||||
print(db.classrooms.find({members: user._id}, {name: 1}).toArray().length);
|
||||
print(db.classrooms.update({members: user._id}, {$pull: {members: user._id}}, {multi: true}));
|
||||
if (!hasTrialRequest.user._id.str) {
|
||||
print('\tunset role');
|
||||
//db.users.update({_id: user._id}, {$unset: {role: ''}});
|
||||
}
|
||||
else {
|
||||
var count = db.classrooms.count({members: user._id}, {name: 1});
|
||||
if (count) {
|
||||
print('\tWill remove from classrooms');
|
||||
//print(db.classrooms.update({members: user._id}, {$pull: {members: user._id}}, {multi: true}));
|
||||
}
|
||||
else {
|
||||
print('\tRole correct, in no classrooms. No action')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Finds all members of classrooms, sets their role to 'student' if they do not already have a role
|
||||
// Usage: copy and paste into mongo
|
||||
// Find all members of classrooms, set their role to 'student' if they do not already have a role
|
||||
|
||||
db.classrooms.find({}, {members: 1}).forEach(function(classroom) {
|
||||
if(!classroom.members) {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
// Usage: paste into mongodb
|
||||
|
||||
// In separating student and teacher accounts, need to see
|
||||
// * Who has trial requests
|
||||
// * Who owns a classroom
|
||||
// * Who is in a classroom
|
||||
//
|
||||
// People who do not have a trial request and are both in a classroom
|
||||
// and own a classroom are the most up in the air.
|
||||
|
||||
var creators = {};
|
||||
var members = {};
|
||||
db.classrooms.find({}, {ownerID:1, members:1}).forEach(function(classroom) {
|
||||
if(classroom.ownerID) { creators[classroom.ownerID.str] = false; }
|
||||
if(classroom.members) {
|
||||
for (var index in classroom.members) {
|
||||
members[classroom.members[index].str] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
db.trial.requests.find({}, {applicant:1}).forEach(function(trialRequest) {
|
||||
if(!trialRequest.applicant) { return; }
|
||||
creators[trialRequest.applicant.str] = true;
|
||||
});
|
||||
|
||||
var isMemberAndNoTrialRequestCount = 0;
|
||||
var noTrialRequestCount = 0;
|
||||
for(var userID in creators) {
|
||||
if (!creators[userID]) {
|
||||
noTrialRequestCount += 1;
|
||||
if (members[userID]) {
|
||||
isMemberAndNoTrialRequestCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
print('count', count);
|
|
@ -52,6 +52,7 @@ ClassroomHandler = class ClassroomHandler extends Handler
|
|||
@sendSuccess(res, cleandocs)
|
||||
|
||||
joinClassroomAPI: (req, res, classroomID) ->
|
||||
return @sendUnauthorizedError(res, 'Cannot join a classroom while anonymous') if req.user.isAnonymous()
|
||||
return @sendBadInputError(res, 'Need an object with a code') unless req.body?.code
|
||||
return @sendForbiddenError(res, 'Cannot join a classroom as a teacher') if req.user.isTeacher()
|
||||
code = req.body.code.toLowerCase()
|
||||
|
|
|
@ -20,7 +20,7 @@ module.exports =
|
|||
|
||||
checkLoggedIn: ->
|
||||
return (req, res, next) ->
|
||||
if not req.user
|
||||
if (not req.user) or (req.user.isAnonymous())
|
||||
return next new errors.Unauthorized('You must be logged in.')
|
||||
next()
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ module.exports =
|
|||
unless _.isUndefined(options.archived)
|
||||
# Handles when .archived is true, vs false-or-null
|
||||
sanitizedOptions.archived = { $ne: not (options.archived is 'true') }
|
||||
console.log sanitizedOptions
|
||||
dbq = Classroom.find _.merge sanitizedOptions, { ownerID: mongoose.Types.ObjectId(ownerID) }
|
||||
dbq.select(parse.getProjectFromReq(req))
|
||||
classrooms = yield dbq
|
||||
|
@ -37,11 +36,11 @@ module.exports =
|
|||
throw new errors.NotFound('Classroom not found.') if not classroom
|
||||
throw new errors.Forbidden('You do not own this classroom.') unless req.user.isAdmin() or classroom.get('ownerID').equals(req.user._id)
|
||||
members = classroom.get('members') or []
|
||||
members = members.slice(memberSkip, memberLimit)
|
||||
members = members.slice(memberSkip, memberSkip + memberLimit)
|
||||
dbqs = []
|
||||
select = 'state.complete level creator playtime'
|
||||
for member in members
|
||||
dbqs.push(LevelSession.find({creator: member.toHexString(), team: {$exists: false}}).select(select).exec())
|
||||
dbqs.push(LevelSession.find({creator: member.toHexString()}).select(select).exec())
|
||||
results = yield dbqs
|
||||
sessions = _.flatten(results)
|
||||
res.status(200).send(sessions)
|
||||
|
@ -57,7 +56,7 @@ module.exports =
|
|||
unless req.user.isAdmin() or isOwner or isMember
|
||||
throw new errors.Forbidden('You do not own this classroom.')
|
||||
memberIDs = classroom.get('members') or []
|
||||
memberIDs = memberIDs.slice(memberSkip, memberLimit)
|
||||
memberIDs = memberIDs.slice(memberSkip, memberSkip + memberLimit)
|
||||
|
||||
members = yield User.find({ _id: { $in: memberIDs }}).select(parse.getProjectFromReq(req))
|
||||
memberObjects = (member.toObject({ req: req, includedPrivates: ["name", "email"] }) for member in members)
|
||||
|
|
|
@ -34,7 +34,9 @@ module.exports =
|
|||
unless _.all(userIDs, (userID) -> _.contains classroomMembers, userID)
|
||||
throw new errors.Forbidden('Users must be members of classroom')
|
||||
|
||||
unless classroom.get('ownerID').equals(req.user._id)
|
||||
ownsClassroom = classroom.get('ownerID').equals(req.user._id)
|
||||
addingSelf = userIDs.length is 1 and userIDs[0] is req.user.id
|
||||
unless ownsClassroom or addingSelf
|
||||
throw new errors.Forbidden('You must own the classroom to add members')
|
||||
|
||||
# Only the enrolled users
|
||||
|
|
|
@ -108,6 +108,7 @@ PrepaidHandler = class PrepaidHandler extends Handler
|
|||
return @sendForbiddenError(res)
|
||||
|
||||
user.set('coursePrepaidID', prepaid.get('_id'))
|
||||
user.set('role', 'student') if not user.get('role')
|
||||
user.save (err, user) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
# return prepaid with new redeemer added locally
|
||||
|
|
|
@ -26,6 +26,7 @@ module.exports.setup = (app) ->
|
|||
app.get('/db/classroom', mw.classrooms.getByOwner)
|
||||
app.get('/db/classroom/:handle/member-sessions', mw.classrooms.fetchMemberSessions)
|
||||
app.get('/db/classroom/:handle/members', mw.classrooms.fetchMembers) # TODO: Use mw.auth?
|
||||
app.get('/db/classroom/:handle', mw.auth.checkLoggedIn()) # TODO: Finish migrating route, adding now so 401 is returned
|
||||
|
||||
Course = require '../models/Course'
|
||||
app.get('/db/course', mw.rest.get(Course))
|
||||
|
|
|
@ -10,7 +10,7 @@ requestAsync = Promise.promisify(request, {multiArgs: true})
|
|||
classroomsURL = getURL('/db/classroom')
|
||||
|
||||
describe 'GET /db/classroom?ownerID=:id', ->
|
||||
|
||||
|
||||
beforeEach utils.wrap (done) ->
|
||||
yield utils.clearModels([User, Classroom])
|
||||
@user1 = yield utils.initUser()
|
||||
|
@ -20,19 +20,19 @@ describe 'GET /db/classroom?ownerID=:id', ->
|
|||
yield utils.loginUser(@user2)
|
||||
@classroom2 = yield new Classroom({name: 'Classroom 2', ownerID: @user2.get('_id') }).save()
|
||||
done()
|
||||
|
||||
|
||||
it 'returns an array of classrooms with the given owner', utils.wrap (done) ->
|
||||
[res, body] = yield request.getAsync getURL('/db/classroom?ownerID='+@user2.id), { json: true }
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.length).toBe(1)
|
||||
expect(body[0].name).toBe('Classroom 2')
|
||||
done()
|
||||
|
||||
|
||||
it 'returns 403 when a non-admin tries to get classrooms for another user', utils.wrap (done) ->
|
||||
[res, body] = yield request.getAsync getURL('/db/classroom?ownerID='+@user1.id), { json: true }
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
|
||||
|
||||
describe 'GET /db/classroom/:id', ->
|
||||
it 'clears database users and classrooms', (done) ->
|
||||
|
@ -54,7 +54,7 @@ describe 'GET /db/classroom/:id', ->
|
|||
done()
|
||||
|
||||
describe 'POST /db/classroom', ->
|
||||
|
||||
|
||||
it 'clears database users and classrooms', (done) ->
|
||||
clearModels [User, Classroom], (err) ->
|
||||
throw err if err
|
||||
|
@ -71,7 +71,7 @@ describe 'POST /db/classroom', ->
|
|||
expect(body.members.length).toBe(0)
|
||||
expect(body.ownerID).toBe(user1.id)
|
||||
done()
|
||||
|
||||
|
||||
it 'does not work for anonymous users', (done) ->
|
||||
logoutUser ->
|
||||
data = { name: 'Classroom 2' }
|
||||
|
@ -85,8 +85,8 @@ describe 'POST /db/classroom', ->
|
|||
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
|
||||
|
||||
|
||||
describe 'PUT /db/classroom', ->
|
||||
|
||||
it 'clears database users and classrooms', (done) ->
|
||||
|
@ -107,7 +107,7 @@ describe 'PUT /db/classroom', ->
|
|||
expect(body.name).toBe('Classroom 3')
|
||||
expect(body.description).toBe('New Description')
|
||||
done()
|
||||
|
||||
|
||||
it 'is not allowed if you are just a member', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('role', 'teacher')
|
||||
|
@ -125,7 +125,7 @@ describe 'PUT /db/classroom', ->
|
|||
request.put { uri: url, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
|
||||
describe 'POST /db/classroom/~/members', ->
|
||||
|
||||
it 'clears database users and classrooms', (done) ->
|
||||
|
@ -173,7 +173,7 @@ describe 'POST /db/classroom/~/members', ->
|
|||
Classroom.findById classroomID, (err, classroom) ->
|
||||
expect(classroom.get('members').length).toBe(0)
|
||||
done()
|
||||
|
||||
|
||||
it 'does not work if the user is anonymous', utils.wrap (done) ->
|
||||
yield utils.clearModels([User, Classroom])
|
||||
teacher = yield utils.initUser({role: 'teacher'})
|
||||
|
@ -234,9 +234,9 @@ describe 'POST /db/classroom/:id/invite-members', ->
|
|||
expect(res.statusCode).toBe(200)
|
||||
done()
|
||||
|
||||
|
||||
|
||||
describe 'GET /db/classroom/:handle/member-sessions', ->
|
||||
|
||||
|
||||
beforeEach utils.wrap (done) ->
|
||||
yield utils.clearModels([User, Classroom, LevelSession, Level])
|
||||
@artisan = yield utils.initUser()
|
||||
|
@ -262,18 +262,18 @@ describe 'GET /db/classroom/:handle/member-sessions', ->
|
|||
expect(res.statusCode).toBe(200)
|
||||
expect(body.length).toBe(4)
|
||||
done()
|
||||
|
||||
|
||||
it 'does not work if you are not the owner of the classroom', utils.wrap (done) ->
|
||||
yield utils.loginUser(@student1)
|
||||
[res, body] = yield request.getAsync getURL("/db/classroom/#{@classroom.id}/member-sessions"), { json: true }
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
|
||||
it 'does not work if you are not logged in', utils.wrap (done) ->
|
||||
[res, body] = yield request.getAsync getURL("/db/classroom/#{@classroom.id}/member-sessions"), { json: true }
|
||||
expect(res.statusCode).toBe(401)
|
||||
done()
|
||||
|
||||
|
||||
it 'accepts memberSkip and memberLimit GET parameters', utils.wrap (done) ->
|
||||
yield utils.loginUser(@teacher)
|
||||
[res, body] = yield request.getAsync getURL("/db/classroom/#{@classroom.id}/member-sessions?memberLimit=1"), { json: true }
|
||||
|
@ -285,9 +285,9 @@ describe 'GET /db/classroom/:handle/member-sessions', ->
|
|||
expect(body.length).toBe(2)
|
||||
expect(session.creator).toBe(@student2.id) for session in body
|
||||
done()
|
||||
|
||||
|
||||
describe 'GET /db/classroom/:handle/members', ->
|
||||
|
||||
|
||||
beforeEach utils.wrap (done) ->
|
||||
yield utils.clearModels([User, Classroom])
|
||||
@teacher = yield utils.initUser()
|
||||
|
@ -296,25 +296,25 @@ describe 'GET /db/classroom/:handle/members', ->
|
|||
@classroom = yield new Classroom({name: 'Classroom', ownerID: @teacher._id, members: [@student1._id, @student2._id] }).save()
|
||||
@emptyClassroom = yield new Classroom({name: 'Empty Classroom', ownerID: @teacher._id, members: [] }).save()
|
||||
done()
|
||||
|
||||
|
||||
it 'does not work if you are not the owner of the classroom', utils.wrap (done) ->
|
||||
yield utils.loginUser(@student1)
|
||||
[res, body] = yield request.getAsync getURL("/db/classroom/#{@classroom.id}/member-sessions"), { json: true }
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
|
||||
it 'does not work if you are not logged in', utils.wrap (done) ->
|
||||
[res, body] = yield request.getAsync getURL("/db/classroom/#{@classroom.id}/member-sessions"), { json: true }
|
||||
expect(res.statusCode).toBe(401)
|
||||
done()
|
||||
|
||||
|
||||
it 'works on an empty classroom', utils.wrap (done) ->
|
||||
yield utils.loginUser(@teacher)
|
||||
[res, body] = yield request.getAsync getURL("/db/classroom/#{@emptyClassroom.id}/members?name=true&email=true"), { json: true }
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body).toEqual([])
|
||||
done()
|
||||
|
||||
|
||||
it 'returns all members with name and email', utils.wrap (done) ->
|
||||
yield utils.loginUser(@teacher)
|
||||
[res, body] = yield request.getAsync getURL("/db/classroom/#{@classroom.id}/members?name=true&email=true"), { json: true }
|
||||
|
@ -324,4 +324,4 @@ describe 'GET /db/classroom/:handle/members', ->
|
|||
expect(user.name).toBeDefined()
|
||||
expect(user.email).toBeDefined()
|
||||
expect(user.passwordHash).toBeUndefined()
|
||||
done()
|
||||
done()
|
||||
|
|
|
@ -71,6 +71,7 @@ describe '/db/prepaid', ->
|
|||
expect(prepaid.get('redeemers').length).toBe(1)
|
||||
User.findById otherUser.id, (err, user) ->
|
||||
expect(user.get('coursePrepaidID').equals(prepaid.get('_id'))).toBe(true)
|
||||
expect(user.get('role')).toBe('student')
|
||||
done()
|
||||
|
||||
it 'does not allow more redeemers than maxRedeemers', (done) ->
|
||||
|
|
|
@ -56,6 +56,10 @@ module.exports = mw =
|
|||
options = _.extend({permissions: ['artisan']}, options)
|
||||
return @initUser(options)
|
||||
|
||||
becomeAnonymous: Promise.promisify (done) ->
|
||||
request.post mw.getURL('/auth/logout'), ->
|
||||
request.get mw.getURL('/auth/whoami'), done
|
||||
|
||||
logout: Promise.promisify (done) ->
|
||||
request.post mw.getURL('/auth/logout'), done
|
||||
|
||||
|
|
|
@ -3,6 +3,25 @@ User = require 'models/User'
|
|||
ComponentsCollection = require 'collections/ComponentsCollection'
|
||||
|
||||
describe 'SuperModel', ->
|
||||
|
||||
describe '.trackRequest(jqxhr, value)', ->
|
||||
it 'takes a jqxhr and tracks its progress', (done) ->
|
||||
s = new SuperModel()
|
||||
jqxhrA = $.get('/db/a')
|
||||
reqA = jasmine.Ajax.requests.mostRecent()
|
||||
jqxhrB = $.get('/db/b')
|
||||
reqB = jasmine.Ajax.requests.mostRecent()
|
||||
s.trackRequest(jqxhrA, 1)
|
||||
s.trackRequest(jqxhrB, 3)
|
||||
expect(s.progress).toBe(0)
|
||||
reqA.respondWith({status: 200, responseText: '[]'})
|
||||
_.defer ->
|
||||
expect(s.progress).toBe(0.25)
|
||||
reqB.respondWith({status: 200, responseText: '[]'})
|
||||
_.defer ->
|
||||
expect(s.progress).toBe(1)
|
||||
done()
|
||||
|
||||
describe 'progress (property)', ->
|
||||
it 'is finished by default', ->
|
||||
s = new SuperModel()
|
||||
|
|
|
@ -41,6 +41,7 @@ describe 'ConvertToTeacherAccountView (/teachers/convert)', ->
|
|||
}
|
||||
|
||||
beforeEach ->
|
||||
spyOn(application.router, 'navigate')
|
||||
me.clear()
|
||||
me.set({
|
||||
_id: '1234'
|
||||
|
@ -54,11 +55,13 @@ describe 'ConvertToTeacherAccountView (/teachers/convert)', ->
|
|||
jasmine.demoEl(view.$el)
|
||||
|
||||
spyOn(storage, 'load').and.returnValue({ lastName: 'Saved Changes' })
|
||||
|
||||
afterEach (done) ->
|
||||
_.defer(done) # let everything finish loading, keep navigate spied on
|
||||
|
||||
|
||||
describe 'when the user already has a TrialRequest and is a teacher', ->
|
||||
beforeEach (done) ->
|
||||
spyOn(application.router, 'navigate')
|
||||
spyOn(me, 'isTeacher').and.returnValue(true)
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
|
@ -73,15 +76,17 @@ describe 'ConvertToTeacherAccountView (/teachers/convert)', ->
|
|||
})
|
||||
_.defer done # Let SuperModel finish
|
||||
|
||||
it 'redirects to /courses/teachers', ->
|
||||
# TODO: re-enable when student and teacher areas are enforced
|
||||
xit 'redirects to /teachers/courses', ->
|
||||
expect(application.router.navigate).toHaveBeenCalled()
|
||||
args = application.router.navigate.calls.argsFor(0)
|
||||
expect(args[0]).toBe('/courses/teachers')
|
||||
expect(args[0]).toBe('/teachers/courses')
|
||||
|
||||
|
||||
describe 'when the user has role "student"', ->
|
||||
beforeEach ->
|
||||
me.set('role', 'student')
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({ status: 200, responseText: JSON.stringify('[]') })
|
||||
view.render()
|
||||
|
||||
it 'shows a warning that they will convert to a teacher account', ->
|
||||
|
@ -109,6 +114,8 @@ describe 'ConvertToTeacherAccountView (/teachers/convert)', ->
|
|||
expect(request.method).toBe('POST')
|
||||
|
||||
describe '"Log out" link', ->
|
||||
beforeEach ->
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({ status: 200, responseText: JSON.stringify('[]') })
|
||||
|
||||
it 'logs out the user and redirects them to /teachers/signup', ->
|
||||
spyOn(me, 'logout')
|
||||
|
@ -117,6 +124,7 @@ describe 'ConvertToTeacherAccountView (/teachers/convert)', ->
|
|||
|
||||
describe 'submitting the form', ->
|
||||
beforeEach ->
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({ status: 200, responseText: JSON.stringify('[]') })
|
||||
form = view.$('form')
|
||||
forms.objectToForm(form, successForm, {overwriteExisting: true})
|
||||
form.submit()
|
||||
|
@ -128,8 +136,7 @@ describe 'ConvertToTeacherAccountView (/teachers/convert)', ->
|
|||
attrs = JSON.parse(request.params)
|
||||
expect(attrs.properties?.firstName).toBe('Mr')
|
||||
|
||||
it 'redirects to /courses/teachers', ->
|
||||
spyOn(application.router, 'navigate')
|
||||
it 'redirects to /teachers/classes', ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
status: 201
|
||||
|
@ -137,10 +144,9 @@ describe 'ConvertToTeacherAccountView (/teachers/convert)', ->
|
|||
})
|
||||
expect(application.router.navigate).toHaveBeenCalled()
|
||||
args = application.router.navigate.calls.argsFor(0)
|
||||
expect(args[0]).toBe('/courses/teachers')
|
||||
expect(args[0]).toBe('/teachers/classes')
|
||||
|
||||
it 'sets a teacher role', ->
|
||||
spyOn(application.router, 'navigate')
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
request.respondWith({
|
||||
status: 201
|
||||
|
|
|
@ -234,7 +234,7 @@ describe 'CreateTeacherAccountView', ->
|
|||
responseText: JSON.stringify(_.extend({_id:'fraghlarghl'}, JSON.parse(request.params)))
|
||||
})
|
||||
|
||||
it 'redirects to "/courses/teachers"', ->
|
||||
it 'redirects to "/teachers/courses"', ->
|
||||
expect(application.router.navigate).toHaveBeenCalled()
|
||||
expect(application.router.reload).toHaveBeenCalled()
|
||||
|
||||
|
|
|
@ -137,6 +137,9 @@ describe 'RequestQuoteView', ->
|
|||
beforeEach ->
|
||||
application.facebookHandler.fakeAPI()
|
||||
application.gplusHandler.fakeAPI()
|
||||
|
||||
it 'fills the username field with the given first and last names', ->
|
||||
expect(view.$('input[name="name"]').val()).toBe('A B')
|
||||
|
||||
it 'includes a facebook button which will sign them in immediately', ->
|
||||
view.$('#facebook-signup-btn').click()
|
||||
|
|
Loading…
Reference in a new issue