diff --git a/app/assets/images/pages/home/desert.png b/app/assets/images/pages/home/desert.png new file mode 100644 index 000000000..e6b1fba4a Binary files /dev/null and b/app/assets/images/pages/home/desert.png differ diff --git a/app/core/Router.coffee b/app/core/Router.coffee index 999a6742a..255d3da2b 100644 --- a/app/core/Router.coffee +++ b/app/core/Router.coffee @@ -133,6 +133,7 @@ module.exports = class CocoRouter extends Backbone.Router 'teachers': go('NewHomeView') 'teachers/freetrial': go('RequestQuoteView') 'teachers/quote': go('RequestQuoteView') + 'teachers/demo': go('RequestQuoteView') 'test(/*subpath)': go('TestView') diff --git a/app/core/Tracker.coffee b/app/core/Tracker.coffee index 85fa41ffc..0b779c6da 100644 --- a/app/core/Tracker.coffee +++ b/app/core/Tracker.coffee @@ -70,7 +70,7 @@ module.exports = class Tracker extends CocoClass for userTrait in ['email', 'anonymous', 'dateCreated', 'name', 'testGroupNumber', 'gender', 'lastLevel', 'siteref', 'ageRange', 'schoolName', 'coursePrepaidID', 'role'] traits[userTrait] ?= me.get(userTrait) - if @isTeacher() + if me.isTeacher() traits.teacher = true console.log 'Would identify', me.id, traits if debugAnalytics @@ -90,7 +90,7 @@ module.exports = class Tracker extends CocoClass mixpanel.identify(me.id) mixpanel.register(traits) - if @isTeacher() and @segmentLoaded + if me.isTeacher() and @segmentLoaded traits.createdAt = me.get 'dateCreated' # Intercom, at least, wants this analytics.identify me.id, traits @@ -109,7 +109,7 @@ module.exports = class Tracker extends CocoClass mixpanelIncludes = ['', 'courses', 'courses/purchase', 'courses/teachers', 'courses/students', 'schools', 'teachers', 'teachers/freetrial', 'teachers/quote', 'play', 'play/level/dungeons-of-kithgard'] mixpanel.track('page viewed', 'page name' : name, url : url) if name in mixpanelIncludes - if @isTeacher() and @segmentLoaded + if me.isTeacher() and @segmentLoaded options = {} if includeIntegrations?.length options.integrations = All: false @@ -140,7 +140,7 @@ module.exports = class Tracker extends CocoClass # Only log explicit events for now mixpanel.track(action, properties) if 'Mixpanel' in includeIntegrations - if @isTeacher() and @segmentLoaded + if me.isTeacher() and @segmentLoaded options = {} if includeIntegrations # https://segment.com/docs/libraries/analytics.js/#selecting-integrations @@ -186,11 +186,8 @@ module.exports = class Tracker extends CocoClass return unless me and @isProduction and not me.isAdmin() ga? 'send', 'timing', category, variable, duration, label - isTeacher: -> - return me.get('role') in ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent'] - updateRole: -> - return unless @isTeacher() + return unless me.isTeacher() return require('core/services/segment')() unless @segmentLoaded @identify() #analytics.page() # It looks like we don't want to call this here because it somehow already gets called once in addition to this. diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 8f3a5aa79..7dd280531 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -25,8 +25,9 @@ im_a_student: "I'm a Student" learn_more: "Learn more" classroom_in_a_box: "A classroom in-a-box for teaching computer science." - codecombat_is: "CodeCombat is a platform for students to learn computer science while playing through a real game." - our_courses: "Our courses have been specifically playtested to excel in a classroom setting, even by teachers with little to no prior programming experience." + codecombat_is: "CodeCombat is a platform <strong>for students</strong> to learn computer science while playing through a real game." # {change} + our_courses: "Our courses have been specifically playtested to <strong>excel in the classroom</strong>, even by teachers with little to no prior programming experience." # {change} + top_screenshots_hint: "Students write code and see their changes update in real-time" designed_with: "Designed with teachers in mind" real_code: "Real, typed code" from_the_first_level: "from the first level" @@ -57,9 +58,15 @@ great_game: "A great game is more than just badges and achievements - it’s about a player’s journey, well-designed puzzles, and the ability to tackle challenges with agency and confidence." agency: "CodeCombat is a game that gives players that agency and confidence with our robust typed code engine, which helps beginner and advanced students alike write proper, valid code." curious: "Curious? Request a demo and we'll show you the ropes" + request_demo_title: "Get your students started today!" + request_demo_subtitle: "Request a demo and get your students started in less than an hour." + get_started_title: "Set up your class today" + get_started_subtitle: "Set up a class, add your students, and monitor their progress as they learn computer science." create_class: "Or create a class and see it for yourself!" + teacher_screenshots_hint: "Students write code and see their changes update in real-time" request_demo: "Request a Demo" create_a_class: "Create a Class" + setup_a_class: "Set Up a Class" have_an_account: "Already have an account?" logged_in_as: "You are currently logged in as" view_my_classes: "View my classes" @@ -702,9 +709,9 @@ more_info_3: "is a good place to connect with fellow educators who are using CodeCombat." teachers_quote: - name: "Quote Form" - title: "Request a Quote" - subtitle: "Get CodeCombat in your classroom, club, school or district!" + name: "Demo Form" # {change} + title: "Request a Demo" # {change} + subtitle: "Get your students started in less than an hour. You'll be able to <strong>create a class, add students, and monitor their progress</strong> as they learn computer science." # {change} email_exists: "User exists with this email." phone_number: "Phone number" phone_number_help: "Where can we reach you during the workday?" @@ -728,10 +735,10 @@ middle_school: "Middle School" college_plus: "College or higher" anything_else: "Anything else we should know?" - thanks_header: "Thanks for requesting a quote!" + thanks_header: "Thanks for requesting a demo!" # {change} thanks_p: "We'll be in touch soon. Questions? Email us:" - thanks_anon: "Login or sign up with your account below to access your two free enrollments (we’ll notify you by email when they have been approved, which usually takes less than 48 hours). As always, the first hour of content is free for an unlimited number of students." - thanks_logged_in: "Your two free enrollments are pending approval. We’ll notify you by email when they have been approved (usually within 48 hours). As always, the first hour of content is free for an unlimited number of students." + thanks_anon: "Log in or create an account to set up a class, add your students, and monitor their progress as they learn computer science." # {change} + thanks_logged_in: "Set up a class, add your students, and monitor their progress as they learn computer science." # {change} versions: save_version_title: "Save New Version" diff --git a/app/models/User.coffee b/app/models/User.coffee index 4e1fa714f..a7ac52acd 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -59,6 +59,9 @@ module.exports = class User extends CocoModel isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled + isTeacher: -> + return @get('role') in ['teacher', 'technology coordinator', 'advisor', 'principal', 'superintendent'] + setRole: (role, force=false) -> return if me.isAdmin() oldRole = @get 'role' diff --git a/app/styles/new-home-view.sass b/app/styles/new-home-view.sass index 2f14dcf87..9171e673d 100644 --- a/app/styles/new-home-view.sass +++ b/app/styles/new-home-view.sass @@ -72,7 +72,41 @@ #classroom-in-box-row margin: 40px 0 - + + .top-screenshots + margin-bottom: 100px + margin-top: 50px + .label + color: black + display: block + .screenshots + text-align: center + .screenshot-grid + img + display: inline-block + margin: 6.5px + width: 800px + border-radius: 8px + + .teacher-screenshots + padding: 10px + .label + color: black + display: block + .screenshots + text-align: center + .screenshot-grid + img + display: inline-block + margin: 6.5px + width: 300px + border-radius: 4px + + #screenshot-lightbox + .modal-dialog + width: auto + max-width: 1024px + #feature-spread-row .col-sm-4 padding: 40px @@ -177,6 +211,9 @@ left: 100% top: 0 + .have-an-account + font-size: 10pt + #school-level-label margin: 15px 15px 0 0 display: inline-block diff --git a/app/templates/new-home-view.jade b/app/templates/new-home-view.jade index 81e9f72ea..153c084a1 100644 --- a/app/templates/new-home-view.jade +++ b/app/templates/new-home-view.jade @@ -44,8 +44,18 @@ block content .col-sm-6 h2.text-navy(data-i18n="new_home.classroom_in_a_box") .col-sm-6 - p(data-i18n="new_home.codecombat_is") - p(data-i18n="new_home.our_courses") + p(data-i18n="[html]new_home.codecombat_is") + p(data-i18n="[html]new_home.our_courses") + + .top-screenshots + .screenshots + .hidden-sm.hidden-md.hidden-lg + small(data-i18n="new_home.top_screenshots_hint") + .screenshot-grid(title='Click to view full size') + a.screen-thumbnail(data-toggle="modal", data-target="#screenshot-lightbox", data-index='0') + img(src="/images/pages/home/desert.png") + .clearfix.hidden-xs + small(data-i18n="new_home.top_screenshots_hint") #feature-spread-row.row.text-center h3(data-i18n="new_home.designed_with") @@ -148,24 +158,37 @@ block content p span(data-i18n="new_home.agency") - .request-demo-row.text-center - h3(data-i18n="new_home.curious") - h4(data-i18n="new_home.create_class") - div - a.btn.btn-primary.btn-lg(href="/teachers/freetrial", data-i18n="new_home.request_demo") - a.btn.btn-primary-alt.btn-lg(href="/courses/teachers", data-i18n="new_home.create_a_class") - div - if me.isAnonymous() + if me.isTeacher() + h3(data-i18n="new_home.get_started_title") + else + h3(data-i18n="new_home.request_demo_title") + + .teacher-screenshots + .screenshots + .hidden-sm.hidden-md.hidden-lg + small(data-i18n="new_home.teacher_screenshots_hint") + .screenshot-grid(title='Click to view full size') + a.screen-thumbnail(data-toggle="modal", data-target="#screenshot-lightbox", data-index='1') + img(src="/images/pages/about/forest.png") + a.screen-thumbnail(data-toggle="modal", data-target="#screenshot-lightbox", data-index='2') + img(src="/images/pages/about/dungeon.png") + a.screen-thumbnail(data-toggle="modal", data-target="#screenshot-lightbox", data-index='3') + img(src="/images/pages/about/glacier.png") + .clearfix.hidden-xs + small(data-i18n="new_home.teacher_screenshots_hint") + + if me.isTeacher() + 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") + 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") + .have-an-account span.spr(data-i18n="new_home.have_an_account") a.login-button Login - else - span.spr(data-i18n="new_home.logged_in_as") - span= me.broadName() - span.spr . - a(href="/courses/teachers", data-i18n="new_home.view_my_classes") - span.spr.spl(data-i18n="general.or") - a#logout-button logout h3.text-center(data-i18n="new_home.computer_science") h4.text-center @@ -246,9 +269,41 @@ block content h6 PC Mag .small pcmag.com - .request-demo-row.text-center h3(data-i18n="new_home.run_class") - p - a.btn.btn-primary.btn-lg(href="/teachers/freetrial", data-i18n="new_home.request_demo") - a.btn.btn-primary-alt.btn-lg(href="/courses/teachers", data-i18n="new_home.create_a_class") + if me.isTeacher() + div + a.btn.btn-primary.btn-lg(href="/courses/teachers", data-i18n="new_home.setup_a_class") + else + div + a.btn.btn-primary.btn-lg(href="/teachers/demo", data-i18n="new_home.request_demo") + .have-an-account + span.spr(data-i18n="new_home.have_an_account") + a.login-button Login + + + #screenshot-lightbox.modal.fade(data-show="false") + .modal-dialog + .modal-content + #screenshot-carousel.carousel + ol.carousel-indicators + li(data-target=".screenshot-display", data-slide-to="0").active + li(data-target=".screenshot-display", data-slide-to="1") + li(data-target=".screenshot-display", data-slide-to="2") + li(data-target=".screenshot-display", data-slide-to="3") + + .carousel-inner + .item.active + img#screenshot-desert(src="/images/pages/home/desert.png") + .item + img#screenshot-forest(src="/images/pages/about/forest.png") + .item + img#screenshot-dungeon(src="/images/pages/about/dungeon.png") + .item + img#screenshot-glacier(src="/images/pages/about/glacier.png") + a#carousel-left.left.carousel-control(href="#screenshot-carousel", role="button") + span.glyphicon.glyphicons-chevron-left.glyphicon-chevron-left(aria-hidden="true") + span.sr-only(data-i18n="about.previous") + a#carousel-right.right.carousel-control(href="#screenshot-carousel", role="button") + span.glyphicon.glyphicons-chevron-right.glyphicon-chevron-right(aria-hidden="true") + span.sr-only(data-i18n="about.next") diff --git a/app/templates/request-quote-view.jade b/app/templates/request-quote-view.jade index 9a445ab26..10761b4c0 100644 --- a/app/templates/request-quote-view.jade +++ b/app/templates/request-quote-view.jade @@ -4,7 +4,7 @@ block content .container form.form(class=view.trialRequest.isNew() ? '' : 'hide') h3.text-center(data-i18n="teachers_quote.title") - h4.text-center(data-i18n="teachers_quote.subtitle") + h4.text-center(data-i18n="[html]teachers_quote.subtitle") #form-teacher-info.section .row @@ -133,7 +133,7 @@ block content textarea.form-control(rows=8, name="notes") #buttons-row.row.text-center - input#submit-request-btn.btn.btn-lg.btn-primary(type="submit" data-i18n="[value]common.send") + input#submit-request-btn.btn.btn-lg.btn-primary(type="submit" data-i18n="[value]teachers_quote.title") #form-submit-success.text-center(class=view.trialRequest.isNew() ? 'hide' : '') @@ -149,4 +149,6 @@ block content button#login-btn.btn.btn-info(data-i18n="login.log_in") button#signup-btn.btn.btn-info(data-i18n="login.sign_up") else - p.text-center(data-i18n="teachers_quote.thanks_logged_in") \ No newline at end of file + p.text-center(data-i18n="teachers_quote.thanks_logged_in") + div + a.btn.btn-primary.btn-lg(href="/courses/teachers", data-i18n="teachers_quote.setup_a_class") diff --git a/app/views/NewHomeView.coffee b/app/views/NewHomeView.coffee index dfd6405c1..b1e658e0b 100644 --- a/app/views/NewHomeView.coffee +++ b/app/views/NewHomeView.coffee @@ -16,6 +16,14 @@ module.exports = class NewHomeView extends RootView 'change #school-level-dropdown': 'onChangeSchoolLevelDropdown' 'click #teacher-btn': 'onClickTeacherButton' 'click #learn-more-link': 'onClickLearnMoreLink' + 'click .screen-thumbnail': 'onClickScreenThumbnail' + 'click #carousel-left': 'onLeftPressed' + 'click #carousel-right': 'onRightPressed' + + shortcuts: + 'right': 'onRightPressed' + 'left': 'onLeftPressed' + 'esc': 'onEscapePressed' initialize: (options) -> @jumbotron = options.jumbotron or utils.getQueryVariable('jumbotron') or 'student' @@ -47,6 +55,11 @@ module.exports = class NewHomeView extends RootView afterRender: -> @onChangeSchoolLevelDropdown() + @$('#screenshot-lightbox').modal() + @$('#screenshot-carousel').carousel({ + interval: 0 + keyboard: false + }) super() onChangeSchoolLevelDropdown: (e) -> @@ -75,5 +88,26 @@ module.exports = class NewHomeView extends RootView onClickTeacherButton: -> @scrollToLink('.request-demo-row', 600) + onRightPressed: (event) -> + # Special handling, otherwise after you click the control, keyboard presses move the slide twice + return if event.type is 'keydown' and $(document.activeElement).is('.carousel-control') + if $('#screenshot-lightbox').data('bs.modal')?.isShown + event.preventDefault() + $('#screenshot-carousel').carousel('next') - + onLeftPressed: (event) -> + return if event.type is 'keydown' and $(document.activeElement).is('.carousel-control') + if $('#screenshot-lightbox').data('bs.modal')?.isShown + event.preventDefault() + $('#screenshot-carousel').carousel('prev') + + onEscapePressed: (event) -> + if $('#screenshot-lightbox').data('bs.modal')?.isShown + event.preventDefault() + $('#screenshot-lightbox').modal('hide') + + onClickScreenThumbnail: (event) -> + unless $('#screenshot-lightbox').data('bs.modal')?.isShown + event.preventDefault() + # Modal opening happens automatically from bootstrap + $('#screenshot-carousel').carousel($(event.currentTarget).data("index")) diff --git a/server/sendwithus.coffee b/server/sendwithus.coffee index a43c53834..75a5ead1a 100644 --- a/server/sendwithus.coffee +++ b/server/sendwithus.coffee @@ -24,3 +24,4 @@ module.exports.templates = course_invite_email: 'tem_u6D2EFWYC5Ptk38bSykjsU' teacher_free_trial: 'tem_R7d9Hpoba9SceQNiYSXBak' teacher_free_trial_hoc: 'tem_4ZSY9wsA9Qwn4wBFmZgPdc' + teacher_request_demo: 'tem_cwG3HZjEyb6QE493hZuUra' diff --git a/server/trial_requests/TrialRequest.coffee b/server/trial_requests/TrialRequest.coffee index b803d8ab0..cd7e35434 100644 --- a/server/trial_requests/TrialRequest.coffee +++ b/server/trial_requests/TrialRequest.coffee @@ -6,6 +6,7 @@ hipchat = require '../hipchat' sendwithus = require '../sendwithus' Prepaid = require '../prepaids/Prepaid' jsonSchema = require '../../app/schemas/models/trial_request.schema' +Classroom = require '../classrooms/Classroom' User = require '../users/User' TrialRequestSchema = new mongoose.Schema {}, {strict: false, minimize: false, read:config.mongo.readpref} @@ -41,9 +42,21 @@ TrialRequestSchema.post 'save', (doc) -> emailParams = recipient: address: email - email_id: sendwithus.templates.teacher_free_trial - sendwithus.api.send emailParams, (err, result) => - log.error "sendwithus trial request approved error: #{err}, result: #{result}" if err + email_id: sendwithus.templates.teacher_request_demo + email_data: + account_exists: user?.get('anonymous') is false + classes_exist: false + if user?.get('anonymous') is false + Classroom.findOne {ownerID: user.get('_id')}, (err, classroom) => + if err + log.error "Trial request classroom find error: #{err}" + return + emailParams.email_data.classes_exist = classroom? + sendwithus.api.send emailParams, (err, result) => + log.error "sendwithus trial request approved error: #{err}, result: #{result}" if err + else + sendwithus.api.send emailParams, (err, result) => + log.error "sendwithus trial request approved error: #{err}, result: #{result}" if err closeIO.createSalesLead(user, email, name: trialProperties.name