VRFS-4037 - create a interested in school landing page

This commit is contained in:
Seth Call 2016-04-25 22:01:15 -05:00
parent 2835941834
commit fea26d82f5
31 changed files with 980 additions and 68 deletions

View File

@ -342,4 +342,5 @@ jamblaster_connection.sql
teacher_progression.sql teacher_progression.sql
teacher_complete.sql teacher_complete.sql
lessons.sql lessons.sql
lessons_unread_messages.sql lessons_unread_messages.sql
track_school_signups.sql

View File

@ -0,0 +1 @@
ALTER TABLE USERS ADD COLUMN school_interest BOOLEAN DEFAULT FALSE;

View File

@ -36,6 +36,14 @@ module JamRuby
subject: options[:subject]) subject: options[:subject])
end end
def partner(options)
mail(to: APP_CONFIG.email_partners_alias,
from: APP_CONFIG.email_generic_from,
body: options[:body],
content_type: "text/plain",
subject: options[:subject])
end
def recurly_alerts(user, options) def recurly_alerts(user, options)
body = options[:body] body = options[:body]

View File

@ -114,7 +114,7 @@ module JamRuby
end end
def self.analyse_sessions def self.analyse_sessions
MusicSession.joins(lesson_session: :lesson_booking).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = false').each do |music_session| MusicSession.joins(lesson_session: :lesson_booking).where('lesson_sessions.status = ?', LessonSession::STATUS_APPROVED).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = false').each do |music_session|
lession_session = music_session.lesson_session lession_session = music_session.lesson_session
lession_session.analyse lession_session.analyse
end end
@ -122,13 +122,13 @@ module JamRuby
def self.complete_sessions def self.complete_sessions
# this will find any paid session (recurring monthly paid, recurring single paid, single paid) # this will find any paid session (recurring monthly paid, recurring single paid, single paid)
MusicSession.joins(lesson_session: [:lesson_booking, :lesson_payment_charge]).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = true').where('lesson_sessions.post_processed = false').where('billing_should_retry = true').each do |music_session| MusicSession.joins(lesson_session: [:lesson_booking, :lesson_payment_charge]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = true').where('lesson_sessions.post_processed = false').where('billing_should_retry = true').each do |music_session|
lession_session = music_session.lesson_session lession_session = music_session.lesson_session
lession_session.session_completed lession_session.session_completed
end end
# test drives don't have a lesson_payment_charge, so we don't join against them # test drives don't have a lesson_payment_charge, so we don't join against them
MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.lesson_type = ?', LESSON_TYPE_TEST_DRIVE).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = true').where('lesson_sessions.post_processed = false').each do |music_session| MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where('lesson_sessions.lesson_type = ?', LESSON_TYPE_TEST_DRIVE).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = true').where('lesson_sessions.post_processed = false').each do |music_session|
lession_session = music_session.lesson_session lession_session = music_session.lesson_session
lession_session.session_completed lession_session.session_completed
end end
@ -142,7 +142,7 @@ module JamRuby
half_hour_from_now = Time.zone.local_to_utc(half_hour_from_now) half_hour_from_now = Time.zone.local_to_utc(half_hour_from_now)
end end
MusicSession.joins(lesson_session: [:lesson_booking]).where('sent_starting_notice = false').where('(scheduled_start > ? and scheduled_start < ?)', now, half_hour_from_now).each do |music_session| MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_APPROVED).where('sent_starting_notice = false').where('(scheduled_start > ? and scheduled_start < ?)', now, half_hour_from_now).each do |music_session|
lession_session = music_session.lesson_session lession_session = music_session.lesson_session
lession_session.send_starting_notice lession_session.send_starting_notice
end end
@ -162,7 +162,7 @@ module JamRuby
self.status = STATUS_COMPLETED self.status = STATUS_COMPLETED
if lesson_booking.requires_teacher_distribution?(self) if success && lesson_booking.requires_teacher_distribution?(self)
self.teacher_distribution = TeacherDistribution.create_for_lesson(self) self.teacher_distribution = TeacherDistribution.create_for_lesson(self)
end end
@ -304,7 +304,7 @@ module JamRuby
else else
if !sent_notices if !sent_notices
# bad session; just poke user # bad session; just poke user
UserMailer.student_weekly_recurring_no_bill(student, self).deliver UserMailer.student_lesson_normal_no_bill(self).deliver
self.sent_notices = true self.sent_notices = true
self.sent_notices_at = Time.now self.sent_notices_at = Time.now
self.post_processed = true self.post_processed = true

View File

@ -16,6 +16,7 @@ module JamRuby
STUDENT_NOT_THERE_WHEN_JOINED = 'student_not_there_when_joined' STUDENT_NOT_THERE_WHEN_JOINED = 'student_not_there_when_joined'
JOINED_LATE = 'did_not_join_on_time' JOINED_LATE = 'did_not_join_on_time'
NO_SHOW = 'no_show' NO_SHOW = 'no_show'
NEITHER_SHOW = 'neither_show'
# what are the potential results? # what are the potential results?
@ -62,14 +63,13 @@ module JamRuby
teacher_ranges = merge_overlapping_ranges(all_teacher_ranges) teacher_ranges = merge_overlapping_ranges(all_teacher_ranges)
intersecting = intersecting_ranges(student_ranges, teacher_ranges) intersecting = intersecting_ranges(student_ranges, teacher_ranges)
student_analysis = analyse_intersection(lesson_session, student_ranges) student_analysis = analyse_intersection(lesson_session, student_ranges)
teacher_analysis = analyse_intersection(lesson_session, teacher_ranges) teacher_analysis = analyse_intersection(lesson_session, teacher_ranges)
together_analysis = analyse_intersection(lesson_session, intersecting) together_analysis = analyse_intersection(lesson_session, intersecting)
# spec: https://jamkazam.atlassian.net/wiki/display/PS/Product+Specification+-+JamClass#ProductSpecification-JamClass-TeacherReceives&RespondstoLessonBookingRequest # spec: https://jamkazam.atlassian.net/wiki/display/PS/Product+Specification+-+JamClass#ProductSpecification-JamClass-TeacherReceives&RespondstoLessonBookingRequest
if music_session.session_removed_at.nil? || (music_session.scheduled_start + (lesson_session.duration * 60)) < Time.now if music_session.session_removed_at.nil? && !((music_session.scheduled_start + (lesson_session.duration * 60)) < Time.now)
reason = SESSION_ONGOING reason = SESSION_ONGOING
bill = false bill = false
else else
@ -107,6 +107,8 @@ module JamRuby
reason = STUDENT_FAULT reason = STUDENT_FAULT
elsif teacher elsif teacher
reason = TEACHER_FAULT reason = TEACHER_FAULT
else
reason = NEITHER_SHOW
end end
end end
@ -150,7 +152,8 @@ module JamRuby
def self.analyse_intersection(lesson_session, ranges) def self.analyse_intersection(lesson_session, ranges)
start = lesson_session.scheduled_start # be sure to call .to_time on any ActiveRecord time, because we get a ton of deprecation warninsg about Time#succ if you use ActiveSupport:: TimeZone
start = lesson_session.scheduled_start.to_time
planned_duration_seconds = lesson_session.duration * 60 planned_duration_seconds = lesson_session.duration * 60
end_time = start + planned_duration_seconds end_time = start + planned_duration_seconds
@ -286,7 +289,7 @@ module JamRuby
end end
def self.ranges_overlap?(a, b) def self.ranges_overlap?(a, b)
a.include?(b.begin) || b.include?(a.begin) a.cover?(b.begin) || b.cover?(a.begin)
end end
def self.merge_ranges(a, b) def self.merge_ranges(a, b)

View File

@ -26,7 +26,9 @@ module JamRuby
end end
def range def range
Range.new(created_at, session_removed_at || Time.now) # to_time to not use ActiveSupport::TimeWithZone
session_removed_at_time = session_removed_at.to_time if session_removed_at
Range.new(created_at.to_time, session_removed_at_time || Time.now.to_time)
end end
def music_session def music_session

View File

@ -1090,6 +1090,7 @@ module JamRuby
teacher = options[:teacher] teacher = options[:teacher]
school_invitation_code = options[:school_invitation_code] school_invitation_code = options[:school_invitation_code]
school_id = options[:school_id] school_id = options[:school_id]
school_interest = options[:school_interest]
user = User.new user = User.new
user.validate_instruments = true user.validate_instruments = true
@ -1115,6 +1116,7 @@ module JamRuby
user.has_redeemable_jamtrack = true user.has_redeemable_jamtrack = true
user.is_a_student = !!student user.is_a_student = !!student
user.is_a_teacher = !!teacher user.is_a_teacher = !!teacher
user.school_interest = !!school_interest
if user.is_a_student || user.is_a_teacher if user.is_a_student || user.is_a_teacher
musician = true musician = true
end end
@ -1331,6 +1333,10 @@ module JamRuby
UserMailer.welcome_message(user).deliver UserMailer.welcome_message(user).deliver
end end
if user.school_interest
AdminMailer.partner({body: "#{user.email} signed up via the /landing/jamclass/schools page.", subject: "#{user.email} is interested in schools"})
end
if !user.email_confirmed if !user.email_confirmed
# any errors here should also rollback the transaction; that's OK. If emails aren't going to be delivered, # any errors here should also rollback the transaction; that's OK. If emails aren't going to be delivered,
# it's already a really bad situation; make user signup again # it's already a really bad situation; make user signup again

View File

@ -13,6 +13,32 @@ describe LessonSessionAnalyser do
let(:valid_recurring_slots) { [lesson_booking_slot_recurring1, lesson_booking_slot_recurring2] } let(:valid_recurring_slots) { [lesson_booking_slot_recurring1, lesson_booking_slot_recurring2] }
describe "analyse" do describe "analyse" do
after{
Timecop.return
}
it "neither show" do
lesson = testdrive_lesson(user, teacher)
music_session = lesson.music_session
Timecop.freeze((lesson.music_session.scheduled_start + lesson.duration * 60) + 1)
analysis = LessonSessionAnalyser.analyse(lesson)
analysis[:reason].should eql LessonSessionAnalyser::NEITHER_SHOW
analysis[:student].should eql nil
analysis[:bill].should be false
student = analysis[:student_analysis]
student[:joined_on_time].should be false
student[:joined_late].should be false
student[:waited_correctly].should be false
student[:initial_waiting_pct].should eql nil
student[:potential_waiting_time].should eql nil
together = analysis[:together_analysis]
together[:session_time].should eql 0
end
it "teacher joined on time, waited, student no show" do it "teacher joined on time, waited, student no show" do
lesson = testdrive_lesson(user, teacher) lesson = testdrive_lesson(user, teacher)
music_session = lesson.music_session music_session = lesson.music_session
@ -57,7 +83,6 @@ describe LessonSessionAnalyser do
analysis = LessonSessionAnalyser.analyse(lesson) analysis = LessonSessionAnalyser.analyse(lesson)
puts analysis
analysis[:reason].should eql LessonSessionAnalyser::STUDENT_FAULT analysis[:reason].should eql LessonSessionAnalyser::STUDENT_FAULT
analysis[:student].should eql LessonSessionAnalyser::JOINED_LATE analysis[:student].should eql LessonSessionAnalyser::JOINED_LATE
analysis[:bill].should be true analysis[:bill].should be true

View File

@ -7,6 +7,10 @@ def app_config
'http://localhost:3333' 'http://localhost:3333'
end end
def email_partners_alias
'partners@jamkazam.com'
end
def email_social_alias def email_social_alias
'social@jamkazam.com' 'social@jamkazam.com'
end end

View File

@ -286,19 +286,22 @@ UserStore = context.UserStore
links = [] links = []
matches = {} matches = {}
for problem, isPresent of user.teacher.profile_pct_summary if user.teacher?
data = @lookup[problem] for problem, isPresent of user.teacher.profile_pct_summary
if data? && !isPresent data = @lookup[problem]
matches[data.name] = data if data? && !isPresent
matches[data.name] = data
for name, data of matches for name, data of matches
if !data.text? if !data.text?
links.push(`<a key={data.name} className="missing-profile" onClick={this.missingProfileClick.bind(this, data)}>{data.name}</a>`) links.push(`<a key={data.name} className="missing-profile" onClick={this.missingProfileClick.bind(this, data)}>{data.name}</a>`)
for name, data of matches for name, data of matches
if data.text? if data.text?
links.push(`<a key={data.name} onClick={this.onProfileHelp.bind(this, data)} className="missing-profile">{data.name}</a>`) links.push(`<a key={data.name} onClick={this.onProfileHelp.bind(this, data)} className="missing-profile">{data.name}</a>`)
`<ul className="missing-profile-list">{links}</ul>` `<ul className="missing-profile-list">{links}</ul>`
else
null
onProfileHelp: (data, e) -> onProfileHelp: (data, e) ->
e.preventDefault() e.preventDefault()
@ -388,6 +391,9 @@ UserStore = context.UserStore
teacherProfileUri = "/client#/profile/teacher/#{this.state.user.id}" teacherProfileUri = "/client#/profile/teacher/#{this.state.user.id}"
pct = Math.round(this.state.user.teacher?.profile_pct) pct = Math.round(this.state.user.teacher?.profile_pct)
if !pct?
pct = 0
if pct == 100 if pct == 100
pctCompleteMsg = `<p className="pct-complete">Your teacher profile is {pct}% complete.</p>` pctCompleteMsg = `<p className="pct-complete">Your teacher profile is {pct}% complete.</p>`
else else

View File

@ -0,0 +1,433 @@
context = window
rest = context.JK.Rest()
@JamClassSchoolLandingBottomPage = React.createClass({
render: () ->
`<div className="top-container">
<div className="row awesome jam-class teachers">
<h2 className="awesome">How JamClass by JamKazam Can Help Your Music School</h2>
<p>Online music lessons offer obvious advantages to your school. You can reach students who live
more than 30 minutes away even students across the country. Teach during normal off
hours, when students in your area are tied up at school or at work. And students and parents
are increasingly interested in online lessons to avoid the time and hassle of traveling to/from
your school.</p>
<p>Several companies have built online marketplaces of music teachers, but these markets havent
offered a partnering model for existing schools. And even if they had, online music lessons
havent taken off like other online Internet markets. Why? Because every one of these markets
has relied on Skype or similar apps built for voice chat to deliver online music lessons. This is
a major problem. Voice technology makes music sound awful in online sessions so bad that
teachers cant assess the students tone and sometimes even the pitch of what they are
playing. These apps also have very high latency a technical term that means that the student
and teacher cannot play together, another critical requirement for productive lessons. Since
Skype wasnt built for music, it also lacks many other basic features to support effective lessons,
like a metronome, mixers, backing tracks, etc.</p>
<p>At JamKazam, weve spent years designing, patenting, and building technology specifically to
enable musicians to play online live in sync with studio quality audio. Weve built a wide variety
of critical online music performance features into this platform. And now weve built a lesson
marketplace on top of this foundation to match students to teachers, and were investing in
marketing to drive students into the market.</p>
<p>
The bottom line is that your school can now effectively offer and teach online lessons to
students using our incredible technology. If you secure students through your own marketing
initiatives, its free to use JamKazam. And if we bring new students to your school, we keep a
portion of the lesson income for these students, helping you to grow your business, increasing
your revenues and profitability.</p>
<p>If this sounds interesting to you, read on to learn more about some of the top features of
JamClass by JamKazam.</p>
<div className="testimonials jam-class teachers">
<h3>JamClass Kudos</h3>
<div className="testimonial">
<img src="/assets/landing/Julie Bonk - Jam Class - Speech Bubble.png" className="testimonial-speech-bubble"/>
<img src="/assets/landing/Julie Bonk - Avatar.png" className="testimonial-avatar"/>
<h4><strong>Julie Bonk</strong></h4>
<div className="testiminal-background">
Oft-recorded pianist, teacher, mentor to Grammy winner Norah Jones and Scott Hoying of Pentatonix
</div>
</div>
<div className="testimonial">
<img src="/assets/landing/Carl Brown - Jam Class - Speech Bubble.png" className="testimonial-speech-bubble"/>
<img src="/assets/landing/Carl Brown - Avatar.png" className="testimonial-avatar"/>
<a rel="external" href="https://www.youtube.com/channel/UCvnfBBzEizi1T5unOXNCxdQ"><img
src="/assets/landing/Carl Brown - YouTube.png" className="testimonial-youtube"/></a>
<h4><strong>Carl Brown</strong> of GuitarLessions365</h4>
</div>
<div className="testimonial">
<img src="/assets/landing/Justin Pierce - Jam Class - Speech Bubble.png" className="testimonial-speech-bubble"/>
<img src="/assets/landing/Justin Pierce - Avatar.png" className="testimonial-avatar"/>
<h4><strong>Justin Pierce</strong></h4>
<div className="testiminal-background">
Masters degree in jazz studies, performer in multiple bands, saxophone instructor
</div>
</div>
<div className="testimonial">
<img src="/assets/landing/Dave Sebree - Jam Class - Speech Bubble.png" className="testimonial-speech-bubble"/>
<img src="/assets/landing/Dave Sebree - Avatar.png" className="testimonial-avatar"/>
<h4><strong>Dave Sebree</strong></h4>
<div className="testiminal-background">
Founder of Austin School of Music, Gibson-endorsed guitarist, touring musician
</div>
</div>
<div className="testimonial">
<img src="/assets/landing/Sara Nelson - Jam Class - Speech Bubble.png" className="testimonial-speech-bubble"/>
<img src="/assets/landing/Sara Nelson - Avatar.png" className="testimonial-avatar"/>
<h4><strong>Sara Nelson</strong></h4>
<div className="testiminal-background">
Cellist for Austin Lyric Opera, frequently recorded with major artists
</div>
</div>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">1</div>
Play Live In Sync From Different Locations
</h3>
<p>
<div className="video-wrapper right">
<div className="video-container">
<iframe src="//www.youtube.com/embed/I2reeNKtRjg" frameborder="0" allowfullscreen="allowfullscreen"/>
</div>
</div>
<p>Teacher and student need to be able to play together to enable effective lessons. As any teacher who has
attempted to teach using Skype will tell you, Skype doesn't let you play together. JamKazam's patented
technologies deliver on this requirement at an amazing level. Click the video above to watch 6 bands play
together from different locations to see our tech in action. And for an even more impressive feat, <a
href="https://www.youtube.com/watch?v=2Zk7-04IAx4" target="_blank">watch this video</a> with a band
playing together from Austin, Atlanta, Chicago, and Brooklyn using JamKazam tech.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">2</div>
Studio Quality Audio
</h3>
<p>
<div className="audio-wrapper">
<a href="https://www.jamkazam.com/recordings/94c5d5aa-2c61-440a-93a4-c661bf77d4a8" target="_blank">Sample
Session Audio #1</a>
<div className="sample-audio-text">Electric Guitars & Drum</div>
<a href="https://www.jamkazam.com/recordings/4916dbfe-0eeb-4bfb-b08a-4085dfecedcb" target="_blank">Sample
Session Audio #2</a>
<div className="sample-audio-text">Acoustic Guitar, Bass & Voice</div>
<a href="https://www.jamkazam.com/recordings/5875be7e-2cc3-4555-825c-046bd2f849e7" target="_blank">Sample
Session Audio #3</a>
<div className="sample-audio-text">Trumpet & Keys</div>
<p className="listening-note">These audio links will open a new tab in your browser. When done listening,
close the tab and return to this page.</p>
</div>
<p>Skype was built for voice - for people talking with each other. It uses something called a "voice codec".
This just means it processes all audio as a spoken human voice, and the result is that music, whether
instrumental or vocal, sounds very bad in Skype, as it has been processed through tech built for talking.
JamKazam delivers very high quality audio. You will be amazed at how good it sounds. It sounds like you're
sitting next to each other playing. This is also critical for a good lesson. Poor audio is hard to endure
in lessons.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">3</div>
Get Student Referrals from the Marketplace
</h3>
<p>
<img className="teacher-marketplace awesome-image right" src="/assets/landing/marketplace.png" width="221" height="223"/>
<p>You can use JamKazam's superior applications and services to conduct better online music
lessons with your existing students, and to attract new students through your own marketing
efforts. In addition, even schools are challenged to invest the resources to attract new students.
Our JamClass marketplace adds value to your business in this area as well, as JamKazam invests
every month to drive new students into our marketplace, and delivers these qualified students
to you, set up and ready to go for online lessons. This is explained in more detail later on this
page.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">4</div>
Record Lessons & Student Performances
</h3>
<p>
<div className="video-wrapper left">
<div className="video-container">
<iframe src="//www.youtube.com/embed/OcnIxqmfkfU" frameborder="0" allowfullscreen="allowfullscreen"/>
</div>
<div className="cta-text">watch this sample video recording from a lesson</div>
</div>
<p>Many times a student thinks they've got it during a lesson, but they get home and realize "I don't got it", and then they've wasted a week. In JamClass the instructor can record all or portions of a lesson that the student can easily refer back to later. You can also have students record their performances, and you can review them together.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">5</div>
Use JamTracks to Motivate Students
</h3>
<p>
<div className="video-wrapper right">
<div className="video-container">
<iframe src="//www.youtube.com/embed/07zJC7C2ICA" frameborder="0" allowfullscreen="allowfullscreen"/>
</div>
</div>
<p>Teachers usually apply the techniques taught in lessons to playing songs - ideally songs your student loves. JamKazam makes this better too, as we offer a catalog of 3,700+ songs. Each song is is a complete multi-track recording, with fully isolated tracks for each part of the music - e.g. lead vocal, backing vocals, lead guitar, rhythm guitar, keys, bass, drums, etc. So your student can listen to just the part they're learning in isolation, turn around and mute that one part to play along with the rest of the band, slow down playback for practice, record and share your performances, and more. It's really fun! And a great way to keep your students movitated and engaged.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">6</div>
Broadcast Recitals
</h3>
<p>
<img src="/assets/landing/YouTube Logo.png" className="awesome-image right" width="264" height="117"/>
<p>Recitals are an important tool for teachers, but recitals are lost using Skype. With JamClass, you can live broadcast video and audio of student recitals through YouTube. This enables other students, family members, and friends to "tune in" for recital performances. And when students share the link to their recitals with their friends, it can also serve as great exposure for your lessons, attracting friends of your existing students as new students.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">7</div>
Apply VST & AU Audio Plug-In Effects
</h3>
<p>
<img src="/assets/landing/Top 10 Image - Number 6.png" className="awesome-image left" width="350"
height="240"/>
<p>The free JamKazam app lets you easily apply VST & AU plugin effects to your live performance in lessons.
For example, guitarists can apply popular amp sims like AmpliTube to get any kind of guitar tone without
pedal boards or amps, and vocalists can apply effects like reverb, pitch correction, etc.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">8</div>
Use MIDI Instruments
</h3>
<p>
<img src="/assets/landing/Top 10 Image - Number 7.png" className="awesome-image" width="320" height="257"/>
<p>The free JamKazam app also lets you use MIDI instruments in online lesson sessions. For example, keys
players can use MIDI keyboard controllers with VST & AU plugins to generate traditional piano sounds,
Rhodes electric piano, Hammond organ, and other classic keys tones. And drummers who use electronic kits
can use their favorite plugins to power their percussive audio.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class multi-para">
<div className="awesome-item">
<h3>
<div className="awesome-number">9</div>
And So Much More...
</h3>
<p>
<p>There are many other features that are specifically useful for online lessons built into JamClass by JamKazam, including a metronome feature, the ability for either teacher or student to open any audio file and use it as a backing track for session acccompaniment, and too many more to list.</p>
<p>In addition to the lesson features, an awesome bonus is that once your students are set up to
play with your teachers in online lessons, they can also play completely FREE with anyone else
in the JamKazam community any time to use the skills theyre learning in lessons to play with
others, which again reinforces and motivates students to stay engaged, as its more fun to play
with others than alone. If you teach ensembles and rock bands, your students can practice in
groups between lessons without having to find rehearsal space, pack gear, and travel. Plus
there are thousands of online sessions played every month on the JamKazam service, including
open jam sessions set up by our user community, and students can hop into these sessions,
create their own improptu sessions, etc. It's a vibrant and welcoming community of fellow
musicians.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome jam-class">
<h2 className="awesome">What Does Your School Need to Host JamClass Lessons?</h2>
<p>
At a mininum, you need:</p>
<ul>
<li>A computer running either
<ul>
<li>Windows 7 or a later version (32-bit or 64-bit)
</li>
<li>Mac OS X 10.7 or a later version (64-bit)<br/>
<a className="how-do-i" href="https://support.apple.com/en-us/HT201948" target="_blank">how do I know
which version I'm running and if it's 64-bit on Mac?</a>
</li>
</ul>
</li>
<li>A built-in microphone and built-in webcam on your computer</li>
<li>Internet service with at least 1Mbps upload bandwidth<br/>
<a className="how-do-i" href="http://www.speedtest.net/" target="_blank">how do I know how much upload
bandwidth my Internet service delivers?</a></li>
</ul>
<p>
To have very high quality audio in your sessions, rather than using the built-in microphone on your computer
to capture your instrumental and/or vocal audio, we strongly recommend using an external audio interface. An
audio interface is a hardware product that connects to your computer and processes audio better than your
computer alone. If you already own/use an audio interface, you can use the one you have. And if you don't,
please <a href="https://jamkazam.desk.com/customer/portal/articles/1292412-choosing-an-audio-device"
target="_blank">read this help article on audio interfaces</a> that can guide you to get what you
need. You can pick up a perfectly good interface very inexpensively, typically for less than $50. And you can
use your new interface not just for JamClass, but also to make home recordings of your performances, etc. So
it's a great thing to have for any musician.
</p>
<p>
If you feel worried or confused about getting set up because you're not a "tech genius", we are happy to work
with you 1:1 to answer your questions, and walk you through picking gear and setting it up.
We'll even hop into an online test session with you and your schools teachers, show you
around the key features, and make sure all your teachers are ready to rock and roll online!
</p>
</div>
<div className="row awesome jam-class">
<h2 className="awesome">How Do the Business Aspects of JamClass Work?</h2>
<p>You can use JamClass by JamKazam to teach your own existing students if they'd like to take
online lessons, and its free to use in this way for schools, much like Skype.</p>
<p>If you would like JamKazam to bring new students to your school through the JamClass
marketplace, we will be making substantial marketing investments in attracting, equipping, and
delivering these students to you. JamKazam will bill and collect payments directly from these
referred students. You and your teachers set your prices for lessons just as you do today. We
retain a minority percentage of the lesson revenue from these referred students, and we will
transfer the majority balance of these lesson revenues to your school. The school is then
responsible to distribute lesson payments to its teachers according to the schools agreements
with its teachers, whatever those may be. Also, as the school operator, you may choose to have
all lesson booking requests from the JamClass marketplace come directly to you as the school
administrator, so that you handle scheduling and booking for all these online lessons, just as
you already do for lessons on premise at your school. Once you have set the date/time for
these lessons, they will then appear on each teachers JamClass dashboard. So when using
JamClass you, as the school owner, may continue to manage scheduling and distribution of
earnings to your schools teachers.</p>
<p>Finally, please note that to participate in the JamClass marketplace, each teacher will need to
opt in to participate in our TestDrive program. TestDrive is a core component of our JamClass
marketing programs, providing students interested in taking online lessons with discounted
introductory packages to get started. To participate in the marketplace, each teacher must be
willing to teach at least 2 TestDrive lessons per week. Your school is paid $10 for each 30-
minute TestDrive lesson, and many TestDrive students will become long-term students who pay
your normal rates. Teachers may opt to accept more than 2 TestDrive lessons per week if they
would like to grow their student base more rapidly.</p>
</div>
<div id="what-now" className="row awesome jam-class">
<h2 className="awesome">What Now?</h2>
<p>
If you're ready to sign up your school, or you think this might be good for your school but are
not sure yet, scroll back up to the top of this page, and enter your email address and a
password to sign up. Once you've done this, we'll reach out to you to answer any and all
questions you have. If you find you want to move forward, well work with you directly to help
you get your school ready to go.
</p>
</div>
</div>`
})

View File

@ -0,0 +1,161 @@
context = window
rest = context.JK.Rest()
@JamClassSchoolLandingPage = React.createClass({
render: () ->
loggedIn = context.JK.currentUserId?
if this.state.done
ctaButtonText = 'sending you in...'
else if this.state.processing
ctaButtonText = 'hold on...'
else
if loggedIn
ctaButtonText = "SIGN UP"
else
ctaButtonText = "SIGN UP"
if loggedIn
register = `<button className={classNames({'cta-button' : true, 'processing': this.state.processing})} onClick={this.ctaClick}>{ctaButtonText}</button>`
else
if this.state.loginErrors?
for key, value of this.state.loginErrors
break
errorText = context.JK.getFullFirstError(key, this.state.loginErrors, {email: 'Email', password: 'Password', 'terms_of_service' : 'The terms of service'})
register = `<div className="register-area jam-class">
<div className={classNames({'errors': true, 'active': this.state.loginErrors})}>
{errorText}
</div>
<form className="jamtrack-signup-form">
<label>Email: </label><input type="text" name="email" />
<label>Password: </label><input type="password" name="password" />
<div className="clearall"/>
<input className="terms-checkbox" type="checkbox" name="terms" /><label className="terms-help">I have read and agree to the JamKazam <a href="/corp/terms" onClick={this.termsClicked}>terms of service</a></label>
<div className="clearall"/>
<button className={classNames({'cta-button' : true, 'processing': this.state.processing})} onClick={this.ctaClick}>{ctaButtonText}</button>
</form>
</div>`
`<div className="top-container">
<div className="full-row name-and-artist">
<div>
<img className="jam-class-teacher" width="375" height="215" src="/assets/landing/jam_class.png" alt="teacher instructing a jam class"/>
<h1 className="jam-track-name">GROW YOUR SCHOOLS MARKET REACH & INCOME</h1>
<h2 className="original-artist">Do you own/operate a music school?</h2>
<div className="clearall"/>
</div>
<div className="preview-and-action-box jamclass">
<img src="/assets/landing/arrow-1-student.png" className="arrow1-jamclass" />
<div className="preview-jamtrack-header">
Sign Up Your School
</div>
<div className={classNames({'preview-area': true, 'jam-class': true})}>
<p>Sign up to let us know youre interested in partnering, and well follow up to answer your
questions.</p>
<p>If this is a good fit for your school, well give you all the 1:1 help you need to get your school
and staff up and running.</p>
<p>We will not share your email. See our <a href="/corp/privacy" onClick={this.privacyPolicy}>privacy policy</a></p>
{register}
<p>Learn how we can help you greatly extend your reach to new markets while increasing your
revenues.</p>
</div>
</div>
</div>
<div className="row summary-text">
<p className="top-summary">
Founded by a team that has built and sold companies to Google, eBay, GameStop and more,
JamKazam has developed incredibly unique technology that lets musicians play together live in
sync with studio quality audio from different locations over the Internet. Now JamKazam has
launched an online music lesson marketplace, and weve set up a program specifically to
partner with music schools to help you attract and engage students across the country,
extending your schools reach and generating more income.
</p>
</div>
</div>`
getInitialState: () ->
{loginErrors: null, processing:false}
privacyPolicy: (e) ->
e.preventDefault()
context.JK.popExternalLink('/corp/privacy')
termsClicked: (e) ->
e.preventDefault()
context.JK.popExternalLink('/corp/terms')
componentDidMount:() ->
$root = $(this.getDOMNode())
$checkbox = $root.find('.terms-checkbox')
context.JK.checkbox($checkbox)
# add item to cart, create the user if necessary, and then place the order to get the free JamTrack.
ctaClick: (e) ->
e.preventDefault()
return if @state.processing
@setState({loginErrors: null})
loggedIn = context.JK.currentUserId?
if loggedIn
@markTeacher()
else
@createUser()
@setState({processing:true})
markTeacher: () ->
rest.updateUser({school_interest: true})
.done((response) =>
this.setState({done: true})
context.location = '/client#/home'
)
.fail((jqXHR) =>
this.setState({processing: false})
context.JK.app.notifyServerError(jqXHR, "Unable to Mark As Interested in School")
)
createUser: () ->
$form = $('.jamtrack-signup-form')
email = $form.find('input[name="email"]').val()
password = $form.find('input[name="password"]').val()
terms = $form.find('input[name="terms"]').is(':checked')
rest.signup({email: email, password: password, first_name: null, last_name: null, terms:terms, school_interest: true})
.done((response) =>
context.location = '/client#/home'
).fail((jqXHR) =>
@setState({processing:false})
if jqXHR.status == 422
response = JSON.parse(jqXHR.responseText)
if response.errors
@setState({loginErrors: response.errors})
else
context.JK.app.notify({title: 'Unknown Signup Error', text: jqXHR.responseText})
else
context.JK.app.notifyServerError(jqXHR, "Unable to Sign Up")
)
@setState({processing:true})
})

View File

@ -101,7 +101,23 @@ rest = context.JK.Rest()
loggedIn = context.JK.currentUserId? loggedIn = context.JK.currentUserId?
if loggedIn
@markStudent()
else
@createUser()
@setState({processing:true})
markStudent: () ->
rest.updateUser({student: true})
.done((response) =>
this.setState({done: true})
context.location = '/client#/profile/' + context.JK.currentUserId
)
.fail((jqXHR) =>
this.setState({processing: false})
context.JK.app.notifyServerError(jqXHR, "Unable to Mark As Student")
)
createUser: () -> createUser: () ->
$form = $('.jamtrack-signup-form') $form = $('.jamtrack-signup-form')
@ -109,10 +125,10 @@ rest = context.JK.Rest()
password = $form.find('input[name="password"]').val() password = $form.find('input[name="password"]').val()
terms = $form.find('input[name="terms"]').is(':checked') terms = $form.find('input[name="terms"]').is(':checked')
rest.signup({email: email, password: password, first_name: null, last_name: null, terms:terms}) rest.signup({email: email, password: password, first_name: null, last_name: null, terms:terms, student : true})
.done((response) => .done((response) =>
@setState({done: true})
context.location = '/client#/profile/' + response.id
).fail((jqXHR) => ).fail((jqXHR) =>
@setState({processing:false}) @setState({processing:false})
if jqXHR.status == 422 if jqXHR.status == 422

View File

@ -92,6 +92,7 @@ ApiUsersController < ApiController
teacher: params[:teacher], teacher: params[:teacher],
school_invitation_code: params[:school_invitation_code], school_invitation_code: params[:school_invitation_code],
school_id: params[:school_id], school_id: params[:school_id],
school_interest: params[:school_interest],
affiliate_referral_id: cookies[:affiliate_visitor] affiliate_referral_id: cookies[:affiliate_visitor]
} }
@ -176,6 +177,7 @@ ApiUsersController < ApiController
@user.update_calendars(params[:calendars]) if params.has_key?(:calendars) @user.update_calendars(params[:calendars]) if params.has_key?(:calendars)
@user.is_a_student = params[:student] if params.has_key?(:student) @user.is_a_student = params[:student] if params.has_key?(:student)
@user.is_a_teacher = params[:teacher] if params.has_key?(:teacher) @user.is_a_teacher = params[:teacher] if params.has_key?(:teacher)
@user.school_interest = !!params[:school_interest]
@user.save @user.save
if @user.errors.any? if @user.errors.any?

View File

@ -109,6 +109,14 @@ class LandingsController < ApplicationController
end end
def jam_class_schools
enable_olark
@no_landing_tag = true
@landing_tag_play_learn_earn = true
@show_after_black_bar_border = true
render 'jam_class_schools', layout: 'web'
end
def individual_jamtrack def individual_jamtrack
enable_olark enable_olark

View File

@ -0,0 +1,14 @@
- provide(:page_name, 'landing_page full individual_jamtrack')
- provide(:description, @description)
- provide(:title, @title)
= react_component 'JamClassSchoolLandingPage', @page_data.to_json
- content_for :after_black_bar do
.row.cta-row
h2 SIGN UP YOUR SCHOOL NOW!
p To reach and effectively teach students more than 30 minutes away.
p.cta-text Not sure if our school partner program is for you? Scroll down to learn more.
- content_for :white_bar do
= react_component 'JamClassSchoolLandingBottomPage', @page_data.to_json

View File

@ -242,6 +242,7 @@ if defined?(Bundler)
config.estimated_slow_mixdown_time = 80 config.estimated_slow_mixdown_time = 80
config.num_packaging_nodes = 2 config.num_packaging_nodes = 2
config.email_partners_alias = 'partners@jamkazam.com'
config.email_social_alias = 'social@jamkazam.com' config.email_social_alias = 'social@jamkazam.com'
config.email_crashes_alias = 'clientcrash@jamkazam.com' config.email_crashes_alias = 'clientcrash@jamkazam.com'
config.email_alerts_alias = 'alerts@jamkazam.com' # should be used for 'oh no' server down/service down sorts of emails config.email_alerts_alias = 'alerts@jamkazam.com' # should be used for 'oh no' server down/service down sorts of emails

View File

@ -97,6 +97,7 @@ SampleApp::Application.configure do
config.email_alerts_alias = ENV['ALERT_EMAIL'] || 'alerts-dev@jamkazam.com' config.email_alerts_alias = ENV['ALERT_EMAIL'] || 'alerts-dev@jamkazam.com'
config.email_crashes_alias = ENV['ALERT_EMAIL'] || 'clientcrash-dev@jamkazam.com' config.email_crashes_alias = ENV['ALERT_EMAIL'] || 'clientcrash-dev@jamkazam.com'
config.email_social_alias = ENV['ALERT_EMAIL'] || 'social-dev@jamkazam.com' config.email_social_alias = ENV['ALERT_EMAIL'] || 'social-dev@jamkazam.com'
config.email_partners_alias = ENV['ALERT_EMAIL'] || 'partners-dev@jamkazam.com'
config.guard_against_fraud = true config.guard_against_fraud = true
config.guard_against_browser_fraud = true config.guard_against_browser_fraud = true

View File

@ -46,6 +46,7 @@ SampleApp::Application.routes.draw do
match '/landing/jamclass/free/students', to: 'landings#jam_class_students_free', via: :get match '/landing/jamclass/free/students', to: 'landings#jam_class_students_free', via: :get
match '/landing/jamclass/teachers', to: 'landings#jam_class_teachers', via: :get, as: 'jamclass_teacher_signup' match '/landing/jamclass/teachers', to: 'landings#jam_class_teachers', via: :get, as: 'jamclass_teacher_signup'
match '/landing/jamclass/affiliates', to: 'landings#jam_class_affiliates', via: :get match '/landing/jamclass/affiliates', to: 'landings#jam_class_affiliates', via: :get
match '/landing/jamclass/schools', to: 'landings#jam_class_schools', via: :get
match '/affiliateProgram', to: 'landings#affiliate_program', via: :get, as: 'affiliate_program' match '/affiliateProgram', to: 'landings#affiliate_program', via: :get, as: 'affiliate_program'

View File

@ -34,6 +34,7 @@ class UserManager < BaseManager
teacher = options[:teacher] teacher = options[:teacher]
school_invitation_code = options[:school_invitation_code] school_invitation_code = options[:school_invitation_code]
school_id = options[:school_id] school_id = options[:school_id]
school_interest = options[:school_interest]
recaptcha_failed = false recaptcha_failed = false
unless options[:skip_recaptcha] # allow callers to opt-of recaptcha unless options[:skip_recaptcha] # allow callers to opt-of recaptcha
@ -82,7 +83,8 @@ class UserManager < BaseManager
student: student, student: student,
teacher: teacher, teacher: teacher,
school_invitation_code: school_invitation_code, school_invitation_code: school_invitation_code,
school_id: school_id) school_id: school_id,
school_interest: school_interest)
user user
end end

View File

@ -111,6 +111,7 @@ FactoryGirl.define do
factory :teacher_user do factory :teacher_user do
after(:create) do |user, evaluator| after(:create) do |user, evaluator|
user.password_confirmation = nil
teacher = FactoryGirl.create(:teacher, user: user, price_per_lesson_60_cents: 3000, price_per_month_60_cents: 3000, lesson_duration_60: true, prices_per_lesson: true, prices_per_month: true) teacher = FactoryGirl.create(:teacher, user: user, price_per_lesson_60_cents: 3000, price_per_month_60_cents: 3000, lesson_duration_60: true, prices_per_lesson: true, prices_per_month: true)
end end
end end

View File

@ -8,8 +8,14 @@ describe "Book Single Recurring Lesson", :js => true, :type => :feature, :capyba
let(:teacher_user) {FactoryGirl.create(:teacher_user, first_name: "Teacher1", ready_for_session_at: Time.now)} let(:teacher_user) {FactoryGirl.create(:teacher_user, first_name: "Teacher1", ready_for_session_at: Time.now)}
let(:teacher_user2) {FactoryGirl.create(:teacher_user, ready_for_session_at: Time.now)} let(:teacher_user2) {FactoryGirl.create(:teacher_user, ready_for_session_at: Time.now)}
after(:each) do
Timecop.return
end
before(:each) do before(:each) do
LessonBooking.destroy_all LessonBooking.destroy_all
Recording.delete_all Recording.delete_all
Diagnostic.delete_all Diagnostic.delete_all
@ -22,22 +28,14 @@ describe "Book Single Recurring Lesson", :js => true, :type => :feature, :capyba
teacher_user.teacher.ready_for_session_at = Time.now teacher_user.teacher.ready_for_session_at = Time.now
teacher_user.teacher.save! teacher_user.teacher.save!
teacher_user.password.should eql 'foobar'
teacher_user.teacher.price_per_lesson_60_cents.should eql 3000 teacher_user.teacher.price_per_lesson_60_cents.should eql 3000
Teacher.index(user, {})[:query].count.should eql 1 Teacher.index(user, {})[:query].count.should eql 1
end
def finish_lesson_successfully
end end
describe "register via showing interesting in teacher 1st" do describe "register via showing interesting in teacher 1st" do
after(:each) do
Timecop.return
end
it "succeeds" do it "succeeds" do
visit "/client#/teachers/search" visit "/client#/teachers/search"
@ -145,11 +143,11 @@ describe "Book Single Recurring Lesson", :js => true, :type => :feature, :capyba
# dismiss banner # dismiss banner
find('a.button-orange', text:'CLOSE').trigger(:click) find('a.button-orange', text:'CLOSE').trigger(:click)
lesson_booking = LessonBooking.where(teacher_id: teacher_user2).first lesson_booking = LessonBooking.where(teacher_id: teacher_user2).first
lesson_booking.should_not be_nil lesson_booking.should_not be_nil
lesson_booking.recurring.should be false lesson_booking.recurring.should be false
lesson_session = LessonSession.where(teacher_id: teacher_user2).first LessonSession.where(teacher_id: teacher_user2).count.should eql 1
lesson_session.teacher.should eql teacher_user2
lesson_package_purchase = LessonPackagePurchase.where(user_id: user.id).first lesson_package_purchase = LessonPackagePurchase.where(user_id: user.id).first
lesson_package_purchase.should be_nil lesson_package_purchase.should be_nil
@ -157,7 +155,58 @@ describe "Book Single Recurring Lesson", :js => true, :type => :feature, :capyba
user.remaining_test_drives.should eql 0 user.remaining_test_drives.should eql 0
user.sales.count.should eql 0 user.sales.count.should eql 0
finish_lesson_successfully
# approve by teacher:
lesson_session1 = LessonSession.where(teacher_id: teacher_user).first
teacher_approve(lesson_session1)
lesson_booking1 = LessonBooking.where(teacher_id: teacher_user).first
lesson_booking1.lesson_sessions.count.should eql 2
lesson_session2 = LessonSession.where(teacher_id: teacher_user).last
successful_lesson(lesson_session1)
LessonSession.hourly_check
lesson_session1.reload
lesson_session1.analysed.should be_true
analysis = JSON.parse(lesson_session1.analysis)
analysis["reason"].should eql LessonSessionAnalyser::SUCCESS
lesson_session1.billing_attempts.should be_true
lesson_session1.billed.should eql true
lesson_session1.success.should be_true
LessonBooking.hourly_check
lesson_session1.reload
teacher_distribution = lesson_session1.teacher_distribution
teacher_distribution.amount_in_cents.should eql 3000
teacher_distribution.ready.should be_true
teacher_distribution.distributed.should be_false
# check the second session, which hasn't come due yet because it's 1 week ahead of the other session
lesson_session2.reload
lesson_session2.analysed.should be_true
analysis = JSON.parse(lesson_session2.analysis)
analysis["reason"].should eql LessonSessionAnalyser::SESSION_ONGOING
lesson_session2.billing_attempts.should eql 0
lesson_session2.billed.should eql false
lesson_session2.success.should be_false
lesson_session2.teacher_distribution.should be_nil
failed_lesson(lesson_session2)
LessonSession.hourly_check
LessonBooking.hourly_check
# check the 2nd session again, which should now have
lesson_session2.reload
lesson_session2.analysed.should be_true
analysis = JSON.parse(lesson_session2.analysis)
analysis["reason"].should eql LessonSessionAnalyser::SESSION_ONGOING
lesson_session2.billing_attempts.should eql 0
lesson_session2.billed.should eql false
lesson_session2.success.should be_false
lesson_session2.teacher_distribution.should be_nil
end end
end end
end end

View File

@ -28,10 +28,6 @@ describe "Single Lesson", :js => true, :type => :feature, :capybara_feature => t
Teacher.index(user, {})[:query].count.should eql 1 Teacher.index(user, {})[:query].count.should eql 1
end
def finish_lesson_successfully
end end
describe "register via showing interesting in teacher 1st" do describe "register via showing interesting in teacher 1st" do
@ -148,10 +144,12 @@ describe "Single Lesson", :js => true, :type => :feature, :capybara_feature => t
# dismiss banner # dismiss banner
find('a.button-orange', text:'CLOSE').trigger(:click) find('a.button-orange', text:'CLOSE').trigger(:click)
lesson_session1 = lesson_session
lesson_booking = LessonBooking.where(teacher_id: teacher_user2).first lesson_booking = LessonBooking.where(teacher_id: teacher_user2).first
lesson_booking.should_not be_nil lesson_booking.should_not be_nil
lesson_session = LessonSession.where(teacher_id: teacher_user2).first lesson_session = LessonSession.where(teacher_id: teacher_user2).first
lesson_session.teacher.should eql teacher_user2 lesson_session.teacher.should eql teacher_user2
lesson_session2 = lesson_session
lesson_package_purchase = LessonPackagePurchase.where(user_id: user.id).first lesson_package_purchase = LessonPackagePurchase.where(user_id: user.id).first
lesson_package_purchase.should be_nil lesson_package_purchase.should be_nil
@ -159,7 +157,48 @@ describe "Single Lesson", :js => true, :type => :feature, :capybara_feature => t
user.remaining_test_drives.should eql 0 user.remaining_test_drives.should eql 0
user.sales.count.should eql 0 user.sales.count.should eql 0
finish_lesson_successfully # approve by teacher:
teacher_approve(lesson_session1)
successful_lesson(lesson_session1)
LessonSession.hourly_check
lesson_session1.reload
lesson_session1.analysed.should be_true
analysis = JSON.parse(lesson_session1.analysis)
analysis["reason"].should eql LessonSessionAnalyser::SUCCESS
lesson_session1.billing_attempts.should be_true
lesson_session1.billed.should eql true
lesson_session1.success.should be_true
LessonBooking.hourly_check
lesson_session1.reload
teacher_distribution = lesson_session1.teacher_distribution
teacher_distribution.amount_in_cents.should eql 3000
teacher_distribution.ready.should be_true
teacher_distribution.distributed.should be_false
# check the second session, which no one went too
lesson_session2.reload
lesson_session2.analysed.should be_false
# approve by teacher:
teacher_approve(lesson_session2)
LessonSession.hourly_check
LessonBooking.hourly_check
lesson_session2.reload
lesson_session2.analysed.should be_true
analysis = JSON.parse(lesson_session2.analysis)
analysis["reason"].should eql LessonSessionAnalyser::NEITHER_SHOW
lesson_session2.billing_attempts.should eql 0
lesson_session2.billed.should eql false
lesson_session2.success.should be_false
lesson_session2.teacher_distribution.should be_nil
end end
end end
end end

View File

@ -27,8 +27,8 @@ describe "Test Drive", :js => true, :type => :feature, :capybara_feature => true
end end
def finish_lesson_successfully def finish_lesson_successfully(lesson_session)
successful_lesson(lesson_session)
end end
describe "register via showing interesting in teacher 1st" do describe "register via showing interesting in teacher 1st" do
@ -101,6 +101,8 @@ describe "Test Drive", :js => true, :type => :feature, :capybara_feature => true
lesson_booking = user.student_lesson_bookings.first lesson_booking = user.student_lesson_bookings.first
lesson_booking.is_requested?.should be_true lesson_booking.is_requested?.should be_true
user.remaining_test_drives.should eql 3 user.remaining_test_drives.should eql 3
lesson_booking.lesson_sessions.count.should eql 1
lesson_session1 = lesson_booking.lesson_sessions.first
# let's make sure we can ask for another test drive too! # let's make sure we can ask for another test drive too!
@ -116,8 +118,31 @@ describe "Test Drive", :js => true, :type => :feature, :capybara_feature => true
find('h2', text: 'book testdrive lesson') find('h2', text: 'book testdrive lesson')
find('.booking-info', text: '3 TestDrive lesson credits') find('.booking-info', text: '3 TestDrive lesson credits')
# dismiss banner
find('a.button-orange', text:'CLOSE').trigger(:click)
# approve by teacher:
teacher_approve(lesson_session1)
successful_lesson(lesson_session1)
LessonSession.hourly_check
lesson_session1.reload
lesson_session1.analysed.should be_true
analysis = JSON.parse(lesson_session1.analysis)
analysis["reason"].should eql LessonSessionAnalyser::SUCCESS
lesson_session1.billing_attempts.should be_nil
lesson_session1.billed.should eql false
lesson_session1.success.should be_true
LessonBooking.hourly_check
lesson_session1.reload
teacher_distribution = lesson_session1.teacher_distribution
teacher_distribution.amount_in_cents.should eql 1000
teacher_distribution.ready.should be_true
teacher_distribution.distributed.should be_false
finish_lesson_successfully
end end
end end
end end

View File

@ -0,0 +1,63 @@
require 'spec_helper'
describe "School Landing", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
before(:all) do
ShoppingCart.delete_all
JamTrackRight.delete_all
JamTrack.delete_all
JamTrackTrack.delete_all
JamTrackLicensor.delete_all
end
let(:user) { FactoryGirl.create(:user, country: 'US') }
it "logged out" do
visit "/landing/jamclass/schools"
find('h1.jam-track-name', 'GROW YOUR SCHOOLS MARKET REACH & INCOME')
find('h2.original-artist', 'Do you own/operate a music school?')
find('button.cta-button', text: 'SIGN UP').trigger(:click)
# should fail because we haven't filled out email/password/terms
find('.register-area .errors', text: "Email can't be blank")
fill_in "email", with: 'school_interest_123@jamkazam.com'
fill_in "password", with: 'jam123'
find('.register-area ins', visible: false).trigger(:click)
find('button.cta-button', text: 'SIGN UP').trigger(:click)
# this should show on the /client#/home page (WILL CHANGE)
find('h2', text: 'sessions')
user = User.find_by_email('school_interest_123@jamkazam.com')
user.is_a_student.should be false
user.is_a_teacher.should be false
user.school_interest.should be true
user.musician.should be true
end
it "logged in" do
fast_signin(user,"/landing/jamclass/schools")
find('h1.jam-track-name', 'GROW YOUR SCHOOLS MARKET REACH & INCOME')
find('h2.original-artist', 'Do you own/operate a music school?')
find('button.cta-button', text: 'SIGN UP').trigger(:click)
# this should show on the /client#/home page (WILL CHANGE)
find('h2', text: 'sessions')
user.reload
user.is_a_student.should be false
user.is_a_teacher.should be false
user.school_interest.should be true
user.musician.should be true
end
end

View File

@ -33,7 +33,7 @@ describe "Student Landing", :js => true, :type => :feature, :capybara_feature =>
find('button.cta-button', text: 'SIGN UP').trigger(:click) find('button.cta-button', text: 'SIGN UP').trigger(:click)
# this should show on the /client#/home page (WILL CHANGE) # this should show on the /client#/home page (WILL CHANGE)
find('h2', text: 'create session') find('h1', text: 'musician profile')
user = User.find_by_email('student_123@jamkazam.com') user = User.find_by_email('student_123@jamkazam.com')
user.is_a_student.should be true user.is_a_student.should be true
@ -50,7 +50,7 @@ describe "Student Landing", :js => true, :type => :feature, :capybara_feature =>
find('button.cta-button', text: 'TRY TESTDRIVE').trigger(:click) find('button.cta-button', text: 'TRY TESTDRIVE').trigger(:click)
# this should show on the /client#/home page (WILL CHANGE) # this should show on the /client#/home page (WILL CHANGE)
find('h2', text: 'create session') find('h1', text: 'musician profile')
user.reload user.reload
user.is_a_student.should be true user.is_a_student.should be true

View File

@ -22,7 +22,7 @@ describe "Teacher Landing", :js => true, :type => :feature, :capybara_feature =>
find('h1.jam-track-name', 'JAMCLASS') find('h1.jam-track-name', 'JAMCLASS')
find('h2.original-artist', 'Finally, online music lessons that really work!') find('h2.original-artist', 'Finally, online music lessons that really work!')
find('button.cta-button', text: 'SIGN UP').trigger(:click) find('button.cta-button', text: 'Sign Up').trigger(:click)
# should fail because we haven't filled out email/password/terms # should fail because we haven't filled out email/password/terms
find('.register-area .errors', text: "Email can't be blank") find('.register-area .errors', text: "Email can't be blank")
@ -30,15 +30,16 @@ describe "Teacher Landing", :js => true, :type => :feature, :capybara_feature =>
fill_in "email", with: 'teacher_123@jamkazam.com' fill_in "email", with: 'teacher_123@jamkazam.com'
fill_in "password", with: 'jam123' fill_in "password", with: 'jam123'
find('.register-area ins', visible: false).trigger(:click) find('.register-area ins', visible: false).trigger(:click)
find('button.cta-button', text: 'SIGN UP').trigger(:click) find('button.cta-button', text: 'Sign Up').trigger(:click)
# this should show on the /client#/home page (WILL CHANGE) # this should show on the /client#/home page (WILL CHANGE)
find('h2', text: 'create session') find('h2', text: 'edit teacher profile: introduction')
user = User.find_by_email('teacher_123@jamkazam.com') user = User.find_by_email('teacher_123@jamkazam.com')
user.is_a_student.should be false user.is_a_student.should be false
user.is_a_teacher.should be true user.is_a_teacher.should be true
user.musician.should be true user.musician.should be true
user.school_interest.should be false
end end
it "logged in" do it "logged in" do
@ -47,15 +48,16 @@ describe "Teacher Landing", :js => true, :type => :feature, :capybara_feature =>
find('h1.jam-track-name', 'JAMCLASS') find('h1.jam-track-name', 'JAMCLASS')
find('h2.original-artist', 'Finally, online music lessons that really work!') find('h2.original-artist', 'Finally, online music lessons that really work!')
find('button.cta-button', text: 'BECOME A TEACHER').trigger(:click) find('button.cta-button', text: 'Enter Teacher Profile').trigger(:click)
# this should show on the /client#/home page (WILL CHANGE) # this should show on the /client#/home page (WILL CHANGE)
find('h2', text: 'create session') find('h2', text: 'edit teacher profile: introduction')
user.reload user.reload
user.is_a_student.should be false user.is_a_student.should be false
user.is_a_teacher.should be true user.is_a_teacher.should be true
user.musician.should be true user.musician.should be true
user.school_interest.should be false
end end
end end

View File

@ -215,7 +215,7 @@ describe UserManager do
user.reload user.reload
expect(user.affiliate_referral).to eq(partner) expect(user.affiliate_referral).to eq(partner)
UserMailer.deliveries.length.should == 1 UserMailer.deliveries.length.should == 2
end end
it "signup successfully with instruments" do it "signup successfully with instruments" do
@ -345,7 +345,7 @@ describe UserManager do
musician: true, musician: true,
signup_confirm_url: "http://localhost:3000/confirm") signup_confirm_url: "http://localhost:3000/confirm")
UserMailer.deliveries.length.should == 1 UserMailer.deliveries.length.should == 2
user.errors.any?.should be_false user.errors.any?.should be_false
# exactly the same parameters; should dup on email, and send no email # exactly the same parameters; should dup on email, and send no email
@ -359,7 +359,7 @@ describe UserManager do
instruments: @instruments, instruments: @instruments,
musician: true, musician: true,
signup_confirm_url: "http://localhost:3000/confirm") signup_confirm_url: "http://localhost:3000/confirm")
UserMailer.deliveries.length.should == 1 UserMailer.deliveries.length.should == 2
user.errors.any?.should be_true user.errors.any?.should be_true
user.errors[:email][0].should == "has already been taken" user.errors[:email][0].should == "has already been taken"
end end
@ -375,7 +375,7 @@ describe UserManager do
instruments: @instruments, instruments: @instruments,
musician: true, musician: true,
signup_confirm_url: "http://localhost:3000/confirm") signup_confirm_url: "http://localhost:3000/confirm")
UserMailer.deliveries.length.should == 1 UserMailer.deliveries.length.should == 2
user.errors.any?.should be_false user.errors.any?.should be_false
user.name.should eq 'Anonymous' user.name.should eq 'Anonymous'
end end
@ -545,7 +545,7 @@ describe UserManager do
user.friends?(@some_user).should be_true user.friends?(@some_user).should be_true
user.friends?(@some_user).should be_true user.friends?(@some_user).should be_true
UserMailer.deliveries.length.should == 1 UserMailer.deliveries.length.should == 2
end end
it "signup successfully with facebook signup additional info" do it "signup successfully with facebook signup additional info" do
@ -603,7 +603,7 @@ describe UserManager do
user.user_authorizations[0].token = fb_signup.token user.user_authorizations[0].token = fb_signup.token
user.user_authorizations[0].token_expiration = fb_signup.token_expires_at user.user_authorizations[0].token_expiration = fb_signup.token_expires_at
UserMailer.deliveries.length.should == 1 UserMailer.deliveries.length.should == 2
end end
it "fail to signup when facebook UID already taken" do it "fail to signup when facebook UID already taken" do
@ -721,7 +721,7 @@ describe UserManager do
location: @loca, location: @loca,
student: true, student: true,
signup_confirm_url: "http://localhost:3000/confirm") signup_confirm_url: "http://localhost:3000/confirm")
UserMailer.deliveries.length.should == 1 UserMailer.deliveries.length.should == 2
user.errors.any?.should be_false user.errors.any?.should be_false
user = User.find(user.id) user = User.find(user.id)
user.has_redeemable_jamtrack.should be_true user.has_redeemable_jamtrack.should be_true
@ -742,7 +742,7 @@ describe UserManager do
location: @loca, location: @loca,
teacher: true, teacher: true,
signup_confirm_url: "http://localhost:3000/confirm") signup_confirm_url: "http://localhost:3000/confirm")
UserMailer.deliveries.length.should == 1 UserMailer.deliveries.length.should == 2
user.errors.any?.should be_false user.errors.any?.should be_false
user = User.find(user.id) user = User.find(user.id)
user.has_redeemable_jamtrack.should be_true user.has_redeemable_jamtrack.should be_true
@ -763,7 +763,7 @@ describe UserManager do
location: @loca, location: @loca,
teacher: true, teacher: true,
signup_confirm_url: "http://localhost:3000/confirm") signup_confirm_url: "http://localhost:3000/confirm")
UserMailer.deliveries.length.should == 1 UserMailer.deliveries.length.should == 2
user.errors.any?.should be_false user.errors.any?.should be_false
user = User.find(user.id) user = User.find(user.id)
user.has_redeemable_jamtrack.should be_true user.has_redeemable_jamtrack.should be_true
@ -782,7 +782,7 @@ describe UserManager do
terms_of_service: true, terms_of_service: true,
location: @loca, location: @loca,
signup_confirm_url: "http://localhost:3000/confirm") signup_confirm_url: "http://localhost:3000/confirm")
UserMailer.deliveries.length.should == 1 UserMailer.deliveries.length.should == 2
user.errors.any?.should be_false user.errors.any?.should be_false
user = User.find(user.id) user = User.find(user.id)
user.has_redeemable_jamtrack.should be_true user.has_redeemable_jamtrack.should be_true

View File

@ -142,6 +142,10 @@ def web_config
:source_customer => 'cus_88Vp44SLnBWMXq' # seth@jamkazam.com in JamKazam-test account :source_customer => 'cus_88Vp44SLnBWMXq' # seth@jamkazam.com in JamKazam-test account
} }
end end
def email_partners_alias
"partner-dev@jamkazam.com"
end
end end
klass.new klass.new
end end

View File

@ -0,0 +1,31 @@
def successful_lesson(lesson_session, advance_to_end = true)
# teacher & student get into session
start = lesson_session.scheduled_start
end_time = lesson_session.scheduled_start + (60 * lesson_session.duration)
uh2 = FactoryGirl.create(:music_session_user_history, user: lesson_session.teacher, history: lesson_session.music_session, created_at: start, session_removed_at: end_time)
uh2 = FactoryGirl.create(:music_session_user_history, user: lesson_session.student, history: lesson_session.music_session, created_at: start, session_removed_at: end_time)
# artificially end the session, which is covered by other background jobs
lesson_session.music_session.session_removed_at = end_time
lesson_session.music_session.save!
if advance_to_end
Timecop.travel(end_time + 60 * 60)
end
end
def failed_lesson(lesson_session, advance_to_end = true)
start = lesson_session.scheduled_start
end_time = lesson_session.scheduled_start + (60 * lesson_session.duration)
if advance_to_end
Timecop.travel(end_time + 60 * 60)
end
end
def teacher_approve(lesson_session)
sign_out_poltergeist(validate: true)
sign_in_poltergeist(lesson_session.teacher, password: 'foobar')
visit "/client#/jamclass/lesson-booking/" + lesson_session.id
find('.schedule.button-orange').trigger(:click)
visit "/client#/jamclass"
find('tr[data-lesson-session-id="' + lesson_session.id + '"] .displayStatusColumn', text: 'Scheduled')
end

View File

@ -141,6 +141,9 @@ def sign_in_poltergeist(user, options = {})
validate = options[:validate] validate = options[:validate]
validate = true if validate.nil? validate = true if validate.nil?
if user.password.nil? && !options[:password]
raise "user has no password. Use a user newly created so that it's password is still present"
end
uri = URI.parse(current_url) uri = URI.parse(current_url)
# in tests, we often have an issue where an old signin screen is unloading # in tests, we often have an issue where an old signin screen is unloading
@ -157,7 +160,7 @@ def sign_in_poltergeist(user, options = {})
within('#landing-inner form.signin-form') do within('#landing-inner form.signin-form') do
fill_in "Email Address:", with: user.email fill_in "Email Address:", with: user.email
fill_in "Password:", with: user.password fill_in "Password:", with: options[:password] || user.password
click_button "SIGN IN" click_button "SIGN IN"
end end
@ -186,7 +189,7 @@ def set_login_cookie(user)
page.driver.set_cookie(:remember_token, user.remember_token) page.driver.set_cookie(:remember_token, user.remember_token)
end end
def sign_out() def sign_out
if Capybara.javascript_driver == :poltergeist if Capybara.javascript_driver == :poltergeist
page.driver.remove_cookie(:remember_token) page.driver.remove_cookie(:remember_token)
else else