Update courses landing page

This commit is contained in:
Matt Lott 2015-09-10 10:37:32 -07:00
parent 4d68080c4c
commit a2577cc521
13 changed files with 334 additions and 35 deletions

View file

@ -206,3 +206,40 @@ module.exports.getCoursesPrice = getSponsoredSubsAmount = (courses, seats=20) ->
else
pricePerSeat = parseInt(totalPricePerSeat)
seats * pricePerSeat
module.exports.getCoursePraise = getCoursePraise = ->
praise = [
{
quote: "The kids love it."
source: "Leo Joseph Tran, Athlos Leadership Academy"
},
{
quote: "My students have been using the site for a couple of weeks and they love it."
source: "Scott Hatfield, Computer Applications Teacher, School Technology Coordinator, Eastside Middle School"
},
{
quote: "Thanks for the captivating site. My eighth graders love it."
source: "Janet Cook, Ansbach Middle/High School"
},
{
quote: "My students have started working on CodeCombat and love it! I love that they are learning coding and problem solving skills without them even knowing it!!"
source: "Kristin Huff, Special Education Teacher, Webb City School District"
},
{
quote: "I recently introduced Code Combat to a few of my fifth graders and they are loving it!"
source: "Shauna Hamman, Fifth Grade Teacher, Four Peaks Elementary School"
},
{
quote: "Overall I think it's a fantastic service. Variables, arrays, loops, all covered in very fun and imaginative ways. Every kid who has tried it is a fan."
source: "Aibinder Andrew, Technology Teacher"
},
{
quote: "I love what you have created. The kids are so engaged."
source: "Desmond Smith, 4KS Academy"
},
{
quote: "My students love the website and I hope on having content structured around it in the near future."
source: "Michael Leonard, Science Teacher, Clearwater Central Catholic High School"
}
]
praise[_.random(0, praise.length - 1)]

View file

@ -7,6 +7,7 @@ _.extend CourseSchema.properties,
campaignID: c.objectId()
concepts: c.array {title: 'Programming Concepts', uniqueItems: true}, c.concept
description: {type: 'string'}
duration: {trype: 'number', description: 'Approximate hours of content'}
pricePerSeat: {type: 'number', description: 'Price per seat in USD cents.'}
screenshot: c.url {title: 'URL', description: 'Link to course screenshot.'}

View file

@ -0,0 +1,68 @@
#courses-view
.center
text-align: center
.code-input
width: 100%
.course-image
width: 100%
.course-panel
margin: 20px
.faq-blurb
font-size: 14px
.continue-dialog .modal-dialog
background-color: white
max-width: 400px
.instruction-label
font-size: 14pt
.or
margin-bottom: 20px
font-size: 14pt
.btn-enroll
margin-top: 20px
.center
text-align: center
.concepts-container
width: 200px
.contact-container
margin-top: 20px
text-align: center
.info-container
margin: 0% 10%
font-size: 18px
.monitoring-img-container
margin-top: 10px
.praise-caption
font-size: 14px
.praise-quote
font-size: 20px
font-style: italic
.progress-container
font-size: 20px
.img-quote
height: 160px
.popover
z-index: 1050
min-width: 400px
h3
background: transparent
border: 0
font-size: 30px

View file

@ -1,4 +1,4 @@
#course-details-view
#course-details-mock-view
.section-selector
margin-bottom: 0px

View file

@ -1,4 +1,4 @@
#course-enroll-view
#course-enroll-mock-view
.btn-buy
margin: 20px 0px

View file

@ -1,4 +1,4 @@
#courses-view
#courses-mock-view
.center
text-align: center

View file

@ -8,19 +8,136 @@ block content
span *UNDER CONSTRUCTION, please send feedback to
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
h1(style='text-align: center;') Courses
br
if studentMode
+student-main
else
+teacher-main
.container-fluid
.row(style='font-size: 20px;')
.col-md-3
.col-md-3 Name
.col-md-3 Description
.col-md-3 Concepts
each course in courses
.row(style='border-top: 1px solid gray; padding: 10px;')
.col-md-3
img(src="#{course.get('screenshot')}", style="width: 100%;")
.col-md-3
p= course.get('name')
a(href="/editor/campaign/#{course.get('campaignID')}") Campaign (levels)
.col-md-3= course.get('description')
.col-md-3= course.get('concepts').join(' ')
- var i = 0
while i < courses.length
.row
+course-block(courses[i], instances)
- i++
if i < courses.length
+course-block(courses[i], instances)
- i++
mixin student-main
button.btn.btn-warning.btn-teacher Teachers Click Here
h1.center Courses on CodeCombat
mixin teacher-main
button.btn.btn-warning.btn-student Students Click Here
h1.center Courses on CodeCombat
.info-container
p Courses are designed to introduce computer science concepts using CodeCombat's fun and engaging environment. CodeCombat levels are organized around key topics to encourage progressive learning, over the course of 5 hours.
.container-fluid
.row
.col-md-6
ul
li Learn more in less time
li No coding experience necesssary
li Easily monitor student progress
p Purchase a course for your entire class. It's easy to sign up your students!
p.faq-blurb
span.spr See the courses
a.spr.courses-faq FAQ
span for more information.
.col-md-6
img.img-quote(src="/images/pages/courses/coco_complab.png")
p
.well.well-sm
div.praise-quote "#{praise.quote}"
div.praise-caption - #{praise.source}
h2.center Choose Your Course:
mixin student-dialog(course)
.modal.continue-dialog(id="continueModal#{course.id}")
.modal-dialog
.modal-header
button.close(data-dismiss='modal')
span &times;
h3.modal-title= course.get('name')
.modal-body
.container-fluid
.row.button-row
.col-md-12
.well.well-sm
p
div.instruction-label Enter an unlock code
.container-fluid
.row
.col-md-8
input.code-input(type='text', placeholder="Enter unlock code")
.col-md-4
button.btn.btn-success.btn-enroll Enroll
mixin teacher-dialog(course)
.modal.continue-dialog(id="continueModal#{course.id}")
.modal-dialog
.modal-header
button.close(data-dismiss='modal')
span &times;
h3.modal-title= course.get('name')
.modal-body
.container-fluid
if enrolledCourses[course.id]
.row.button-row.row-pick-class
.col-md-12
.well.well-sm
p
div.instruction-label Pick from your current classes
.container-fluid
.row
.col-md-8
select.form-control.select-session
each inst in instances
if inst.get('name')
option(value="#{inst.id}")= inst.get('name')
else
option(value="#{inst.id}") *unnamed*
.col-md-4
button.btn.btn-success.btn-enter(data-course-id="#{course.id}") Enter
.row.button-row.center.row-pick-class
.col-md-12
div.or Or
.row.button-row.center
.col-md-12
button.btn.btn-success.btn-lg.btn-buy(data-course-id="#{course.id}") Buy this course
mixin course-block(course)
if studentMode
+student-dialog(course)
else
+teacher-dialog(course)
.col-md-6
.well.panel.course-panel(class=enrolledCourses[course.id] ? 'panel-success' : 'panel-info')
.panel-heading
.panel-title
span.spr #{course.get('name')}
strong #{enrolledCourses[course.id] ? '[ enrolled ]' : ''}
.panel-body
.container-fluid
.row
.col-md-12
p
img.course-image(src="#{course.get('screenshot')}")
.row.button-row
.col-md-6
strong Topics
ul
each concept in course.get('concepts')
li(data-i18n="concepts." + concept)
strong Hours of content: #{course.get('duration')}
.col-md-6.center(style='margin-top: 40px;')
if studentMode
if enrolledCourses[course.id]
a.btn.btn-lg.btn-success.btn-continue(href="/courses/#{course.id}?student=true") Continue
else
button.btn.btn-lg.btn-success.btn-continue(data-toggle='modal', data-target="#continueModal#{course.id}") Enter
else if enrolledCourses[course.id]
button.btn.btn-lg.btn-success.btn-continue(data-toggle='modal', data-target="#continueModal#{course.id}") Continue
else
button.btn.btn-lg.btn-success.btn-buy(data-course-id="#{course.id}") #{course.get('pricePerSeat') === 0 ? 'Get FREE course' : 'Buy course'}

View file

@ -20,7 +20,7 @@ module.exports = class CourseEnrollView extends RootView
subscriptions:
'stripe:received-token': 'onStripeReceivedToken'
constructor: (options, @courseID=0) ->
constructor: (options, @courseID) ->
super options
@courseID ?= options.courseID
@seats = 20
@ -99,7 +99,7 @@ module.exports = class CourseEnrollView extends RootView
createClass: (token) ->
data =
name: $('.class-name').val()
name: @className
seats: @seats
token: token
data.courseID = @selectedCourse.id if @selectedCourse

View file

@ -1,18 +1,95 @@
RootView = require 'views/core/RootView'
template = require 'templates/courses/courses'
app = require 'core/application'
CocoCollection = require 'collections/CocoCollection'
Course = require 'models/Course'
CourseInstance = require 'models/CourseInstance'
RootView = require 'views/core/RootView'
template = require 'templates/courses/courses'
utils = require 'core/utils'
module.exports = class CoursesView extends RootView
id: 'courses-view'
template: template
events:
'click .btn-buy': 'onClickBuy'
'click .btn-enroll': 'onClickEnroll'
'click .btn-enter': 'onClickEnter'
'click .btn-student': 'onClickStudent'
'click .btn-teacher': 'onClickTeacher'
constructor: (options) ->
super options
super(options)
@praise = utils.getCoursePraise()
@studentMode = utils.getQueryVariable('student', false) or options.studentMode
@courses = new CocoCollection([], { url: "/db/course", model: Course})
@supermodel.loadCollection(@courses, 'courses')
@courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
@listenToOnce @courseInstances, 'sync', @onCourseInstancesLoaded
@supermodel.loadCollection(@courseInstances, 'course_instances')
getRenderData: ->
context = super()
context.courses = @courses.models ? []
context.enrolledCourses = @enrolledCourses ? {}
context.instances = @courseInstances.models ? []
context.praise = @praise
context.studentMode = @studentMode
context
afterRender: ->
super()
@setupCoursesFAQPopover()
onCourseInstancesLoaded: ->
@enrolledCourses = {}
@enrolledCourses[courseInstance.get('courseID')] = true for courseInstance in @courseInstances.models
setupCoursesFAQPopover: ->
popoverTitle = "<h3>Courses FAQ<button type='button' class='close' onclick='$(&#39;.courses-faq&#39;).popover(&#39;hide&#39;);'>&times;</button></h3>"
popoverContent = "<p><strong>Q:</strong> What's the difference between these courses and the single player game?</p>"
popoverContent += "<p><strong>A:</strong> The single player game is designed for individuals, while the courses are designed for classes.</p>"
popoverContent += "<p>The single player game has items, gems, hero selection, leveling up, and in-app purchases. Courses have classroom management features and streamlined student-focused level pacing.</p>"
@$el.find('.courses-faq').popover(
animation: true
html: true
placement: 'top'
trigger: 'click'
title: popoverTitle
content: popoverContent
container: @$el
).on 'shown.bs.popover', =>
application.tracker?.trackEvent 'Subscription payment methods hover'
onClickBuy: (e) ->
courseID = $(e.target).data('course-id')
route = "/courses/enroll/#{courseID}"
viewClass = require 'views/courses/CourseEnrollView'
viewArgs = [{}, courseID]
navigationEvent = route: route, viewClass: viewClass, viewArgs: viewArgs
Backbone.Mediator.publish 'router:navigate', navigationEvent
onClickEnroll: (e) ->
alert('TODO: redeem course prepaid and navigate to correct course instance')
onClickEnter: (e) ->
$('.continue-dialog').modal('hide')
courseID = $(e.target).data('course-id')
courseInstanceID = $('.select-session').val()
viewClass = require 'views/courses/CourseDetailsView'
viewArgs = [{courseInstanceID:courseInstanceID}, courseID]
navigationEvent = route: "/courses/#{courseID}", viewClass: viewClass, viewArgs: viewArgs
Backbone.Mediator.publish 'router:navigate', navigationEvent
onClickStudent: (e) ->
route = "/courses?student=true"
viewClass = require 'views/courses/CoursesView'
viewArgs = [studentMode: true]
navigationEvent = route: route, viewClass: viewClass, viewArgs: viewArgs
Backbone.Mediator.publish 'router:navigate', navigationEvent
onClickTeacher: (e) ->
route = "/courses?student=false"
viewClass = require 'views/courses/CoursesView'
viewArgs = [studentMode: false]
navigationEvent = route: route, viewClass: viewClass, viewArgs: viewArgs
Backbone.Mediator.publish 'router:navigate', navigationEvent

View file

@ -6,7 +6,7 @@ CocoCollection = require 'collections/CocoCollection'
Campaign = require 'models/Campaign'
module.exports = class CourseDetailsView extends RootView
id: 'course-details-view'
id: 'course-details-mock-view'
template: template
events:

View file

@ -3,7 +3,7 @@ RootView = require 'views/core/RootView'
template = require 'templates/courses/mock1/course-enroll'
module.exports = class CourseEnrollView extends RootView
id: 'course-enroll-view'
id: 'course-enroll-mock-view'
template: template
events:

View file

@ -4,7 +4,7 @@ RootView = require 'views/core/RootView'
template = require 'templates/courses/mock1/courses'
module.exports = class CoursesView extends RootView
id: 'courses-view'
id: 'courses-mock-view'
template: template
events:

View file

@ -4,10 +4,11 @@
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
// NOTE: uses name as unique identifier, so changing the name will insert a new course
// NOTE: concepts should match actual campaign levels
// NOTE: pricePerSeat in USD cents
var documents =
// TODO: calculate concepts from campaign
var courses =
[
{
name: "Introduction to Computer Science",
@ -15,6 +16,7 @@ var documents =
campaignID: ObjectId("55b29efd1cd6abe8ce07db0d"),
concepts: ['basic_syntax', 'arguments', 'while_loops', 'strings', 'variables'],
description: "Learn basic syntax, while loops, and the CodeCombat environment.",
duration: NumberInt(1),
pricePerSeat: NumberInt(0),
screenshot: "/images/pages/courses/101_info.png"
},
@ -24,6 +26,7 @@ var documents =
campaignID: ObjectId("55b29efd1cd6abe8ce07db0d"),
concepts: ['basic_syntax', 'arguments', 'while_loops', 'strings', 'variables', 'if_statements'],
description: "Introduce Arguments, Variables, If Statements, and Arithmetic.",
duration: NumberInt(5),
pricePerSeat: NumberInt(400),
screenshot: "/images/pages/courses/102_info.png"
},
@ -33,16 +36,12 @@ var documents =
campaignID: ObjectId("55b29efd1cd6abe8ce07db0d"),
concepts: ['if_statements', 'arithmetic'],
description: "Learn how to handle input.",
duration: NumberInt(5),
pricePerSeat: NumberInt(400),
screenshot: "/images/pages/courses/103_info.png"
}
];
for (var i = 0; i < documents.length; i++) {
var doc = documents[i];
db.courses.update({name: doc.name}, doc, {upsert: true});
}
function log(str) {
print(new Date().toISOString() + " " + str);
for (var i = 0; i < courses.length; i++) {
db.courses.update({name: courses[i].name}, courses[i], {upsert: true});
}