mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-14 07:00:01 -04:00
Update courses landing page
This commit is contained in:
parent
4d68080c4c
commit
a2577cc521
13 changed files with 334 additions and 35 deletions
|
@ -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)]
|
||||
|
|
|
@ -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.'}
|
||||
|
||||
|
|
68
app/styles/courses/courses.sass
Normal file
68
app/styles/courses/courses.sass
Normal 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
|
|
@ -1,4 +1,4 @@
|
|||
#course-details-view
|
||||
#course-details-mock-view
|
||||
|
||||
.section-selector
|
||||
margin-bottom: 0px
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#course-enroll-view
|
||||
#course-enroll-mock-view
|
||||
|
||||
.btn-buy
|
||||
margin: 20px 0px
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#courses-view
|
||||
#courses-mock-view
|
||||
|
||||
.center
|
||||
text-align: center
|
||||
|
|
|
@ -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 ×
|
||||
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 ×
|
||||
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'}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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='$('.courses-faq').popover('hide');'>×</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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue