Merge branch 'develop' into feature/packages

This commit is contained in:
Seth Call 2016-05-31 19:43:52 -05:00
commit 1abacf0ec6
87 changed files with 1437 additions and 388 deletions

View File

@ -1,3 +1,4 @@
#!/usr/bin/env rake #!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake, # Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

View File

@ -0,0 +1,32 @@
ActiveAdmin.register JamRuby::User, :as => 'UserSource' do
menu :label => 'User Campaigns', :parent => 'Users'
config.sort_order = 'created_at DESC'
config.batch_actions = false
config.clear_action_items!
config.filters = false
scope("Most Recent First", default: true) { |scope| scope.unscoped.order('created_at desc')}
index do
column "Email" do |user|
user.email
end
column "Bought TestDrive" do |user|
!user.most_recent_test_drive_purchase.nil? ? "Yes" : "No"
end
column "UTM Source" do |user|
user.origin_utm_source
end
column "UTM Medium" do |user|
user.origin_utm_medium
end
column "UTM Campaign" do |user|
user.origin_utm_campaign
end
column "Referrer" do |user|
user.origin_referrer
end
end
end

View File

@ -297,7 +297,10 @@ require "jam_ruby/models/affiliate_distribution"
require "jam_ruby/models/teacher_intent" require "jam_ruby/models/teacher_intent"
require "jam_ruby/models/school" require "jam_ruby/models/school"
require "jam_ruby/models/school_invitation" require "jam_ruby/models/school_invitation"
require "jam_ruby/models/teacher_instrument"
require "jam_ruby/models/teacher_subject"
require "jam_ruby/models/teacher_language"
require "jam_ruby/models/teacher_genre"
include Jampb include Jampb
module JamRuby module JamRuby

View File

@ -1770,5 +1770,28 @@ module JamRuby
format.html { render :layout => "from_user_mailer" } format.html { render :layout => "from_user_mailer" }
end end
end end
def lesson_attachment(sender, target, lesson_session, attachment)
@sender = sender
@target = target
@lesson_session = lesson_session
@attachment = attachment
email = target.email
@subject = "An attachment has been added to your lesson by #{sender.name}"
unique_args = {:type => "lesson_attachment"}
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
sendgrid_recipients([email])
sendgrid_substitute('@USERID', [@target.id])
mail(:to => email, :subject => @subject) do |format|
format.text
format.html { render :layout => "from_user_mailer" }
end
end
end end
end end

View File

@ -0,0 +1,20 @@
<% provide(:title, @subject) %>
<% provide(:photo_url, @sender.resolved_photo_url) %>
<% content_for :note do %>
<p>
<% if @attachment.is_a?(JamRuby::MusicNotation) %>
<% if @attachment.is_notation? %>
A music notation has been added to your lesson. You can download <a style="color:#fc0" href="<%= APP_CONFIG.external_root_url + "/api/music_notations/#{@attachment.id}" %>"><%= @attachment.file_name %></a> directly or at any time in the message window for this lesson.
<% else %>
A audio file has been added to your lesson. You can download <a style="color:#fc0" href="<%= APP_CONFIG.external_root_url + "/api/music_notations/#{@attachment.id}" %>"><%= @attachment.file_name %></a> directly or at any time in the message window for this lesson.
<% end %>
<% else %>
A recording named "<%= @attachment.name %>" has been added to your lesson. It can be viewed <a style="color:#fc0" href="<%= APP_CONFIG.external_root_url + "/recordings/#{@attachment.id}" %>">here</a> or found within the message window for this lesson.
<% end %>
<p>
<a href="<%= @lesson_session.web_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW LESSON DETAILS</a>
</p>
<% end %>

View File

@ -0,0 +1,12 @@
<% if @attachment.is_a?(JamRuby::MusicNotation) %>
<% if @attachment.is_notation? %>
A music notation has been added to your lesson. You can download (<%= APP_CONFIG.external_root_url + "/api/music_notations/#{@attachment.id}" %>">)<%= @attachment.file_name %> directly or at any time in the message window for this lesson.
<% else %>
A audio file has been added to your lesson. You can download (<%= APP_CONFIG.external_root_url + "/api/music_notations/#{@attachment.id}" %>">)<%= @attachment.file_name %> directly or at any time in the message window for this lesson.
<% end %>
<% else %>
A recording named "<%= @attachment.name %>" has been added to your lesson. It can be viewed (<%= APP_CONFIG.external_root_url + "/recordings/#{@attachment.id}" %>">) here or found within the message window for this lesson.
<% end %>
VIEW LESSON DETAILS (<%= @lesson_session.web_url %>)

View File

@ -16,7 +16,8 @@ module JamRuby
has_and_belongs_to_many :recordings, :class_name => "JamRuby::Recording", :join_table => "recordings_genres" has_and_belongs_to_many :recordings, :class_name => "JamRuby::Recording", :join_table => "recordings_genres"
# teachers # teachers
has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_genres" has_many :teachers, :class_name => "JamRuby::Teacher", :through => :teachers_genres
has_many :teachers_genres, :class_name => "JamRuby::TeacherGenre"
# jam tracks # jam tracks
has_many :genres_jam_tracks, :class_name => "JamRuby::GenreJamTrack", :foreign_key => "genre_id" has_many :genres_jam_tracks, :class_name => "JamRuby::GenreJamTrack", :foreign_key => "genre_id"

View File

@ -44,7 +44,8 @@ module JamRuby
has_and_belongs_to_many :music_sessions, :class_name => "JamRuby::ActiveMusicSession", :join_table => "genres_music_sessions" has_and_belongs_to_many :music_sessions, :class_name => "JamRuby::ActiveMusicSession", :join_table => "genres_music_sessions"
# teachers # teachers
has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_instruments" has_many :teachers, :class_name => "JamRuby::Teacher", through: :teachers_instruments
has_many :teachers_instruments, class_name: "JamRuby::TeacherInstrument"
def self.standard_list def self.standard_list
return Instrument.where('instruments.popularity > 0').order('instruments.description ASC') return Instrument.where('instruments.popularity > 0').order('instruments.description ASC')

View File

@ -2,7 +2,8 @@ module JamRuby
class Language < ActiveRecord::Base class Language < ActiveRecord::Base
include HtmlSanitize include HtmlSanitize
html_sanitize strict: [:name, :description] html_sanitize strict: [:name, :description]
has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_languages" has_many :teachers, :class_name => "JamRuby::Teacher", :through => :teachers_languages
has_many :teachers_languages, class_name: "JamRuby::TeacherLanguage"
def self.english_sort def self.english_sort
languages = Language.order(:description) languages = Language.order(:description)

View File

@ -243,7 +243,12 @@ module JamRuby
minimum_start_time = create_minimum_booking_time minimum_start_time = create_minimum_booking_time
# get all sessions that are already scheduled for this booking ahead of the minimum time # get all sessions that are already scheduled for this booking ahead of the minimum time
sessions = MusicSession.joins(:lesson_session).where("lesson_sessions.lesson_booking_id = ?", id).where("scheduled_start is not null").where("scheduled_start > ?", minimum_start_time).order(:created_at)
sessions= MusicSession.joins(:lesson_session).where("lesson_sessions.lesson_booking_id = ?", id).where("scheduled_start is not null").order(:created_at)
if recurring
# only want times ahead of this for recurring
sessions = sessions.where("scheduled_start > ?", minimum_start_time)
end
if @default_slot_did_change if @default_slot_did_change
# # adjust all session times # # adjust all session times
@ -421,6 +426,9 @@ module JamRuby
self.errors.add(:status, "This lesson is already #{self.status}.") self.errors.add(:status, "This lesson is already #{self.status}.")
end end
if self.accepter.nil?
self.errors.add(:accepter, "No one has been indicated as accepting the lesson")
end
self.accepting = false self.accepting = false
end end

View File

@ -33,6 +33,9 @@ module JamRuby
validate :validate_proposer validate :validate_proposer
before_validation :before_validation before_validation :before_validation
def is_recurring?
slot_type == SLOT_TYPE_RECURRING
end
def before_validation def before_validation
if proposer.nil? if proposer.nil?
self.proposer = container.student self.proposer = container.student
@ -81,6 +84,7 @@ module JamRuby
candidate = scheduled_time(i + week_offset) candidate = scheduled_time(i + week_offset)
#puts "#{i}: candidate #{candidate} week_offset:#{week_offset}" #puts "#{i}: candidate #{candidate} week_offset:#{week_offset}"
#puts "DAY_OF_WEEK #{day_of_week}"
if day_of_week && candidate <= minimum_start_time if day_of_week && candidate <= minimum_start_time
# move it up a week # move it up a week
week_offset += 1 week_offset += 1
@ -173,7 +177,7 @@ module JamRuby
duration = lesson_length * 60 # convert from minutes to seconds duration = lesson_length * 60 # convert from minutes to seconds
end_time = start_time + duration end_time = start_time + duration
if with_timezone if with_timezone
"#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} #{tz.pretty_name}" "#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} (#{tz.pretty_name})"
else else
"#{start_time.strftime("%A, %B %e")} - #{start_time.strftime("%l:%M%P").strip}" "#{start_time.strftime("%A, %B %e")} - #{start_time.strftime("%l:%M%P").strip}"
end end

View File

@ -5,7 +5,7 @@ module JamRuby
include HtmlSanitize include HtmlSanitize
html_sanitize strict: [:cancel_message] html_sanitize strict: [:cancel_message]
attr_accessor :accepting, :creating, :countering, :autocanceling, :countered_slot, :countered_lesson, :canceling, :assigned_student attr_accessor :accepting, :creating, :countering, :countering_flag, :autocanceling, :countered_slot, :countered_lesson, :canceling, :assigned_student
@@log = Logging.logger[LessonSession] @@log = Logging.logger[LessonSession]
@ -61,6 +61,7 @@ module JamRuby
validates :post_processed, inclusion: {in: [true, false]} validates :post_processed, inclusion: {in: [true, false]}
validate :validate_creating, :if => :creating validate :validate_creating, :if => :creating
validate :validate_countering, :if => :countering_flag
validate :validate_accepted, :if => :accepting validate :validate_accepted, :if => :accepting
validate :validate_canceled, :if => :canceling validate :validate_canceled, :if => :canceling
validate :validate_autocancel, :if => :autocanceling validate :validate_autocancel, :if => :autocanceling
@ -165,7 +166,7 @@ module JamRuby
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.status = ?', LessonSession::STATUS_COMPLETED).where('lesson_sessions.lesson_type = ?', LESSON_TYPE_TEST_DRIVE).where("session_removed_at IS NOT NULL OR ? > scheduled_start + (INTERVAL '1 minutes' * duration)", Time.now).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("? > scheduled_start + (INTERVAL '1 minutes' * duration)", Time.now).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
@ -366,7 +367,9 @@ module JamRuby
end end
def recurring_completed def recurring_completed
puts "RECURRING COMPLETED #{success}"
if success if success
if lesson_booking.is_monthly_payment? if lesson_booking.is_monthly_payment?
# monthly payments are handled at beginning of month; just poke with email, and move on # monthly payments are handled at beginning of month; just poke with email, and move on
@ -399,6 +402,7 @@ module JamRuby
end end
else else
puts "STUDENT NO BILL SENT #{self.id}"
if !sent_notices if !sent_notices
if !school_on_school? if !school_on_school?
# bad session; just poke user # bad session; just poke user
@ -422,6 +426,7 @@ module JamRuby
else else
if !sent_notices if !sent_notices
if !school_on_school? if !school_on_school?
puts "STUDENT NO BILL SENT #{success}"
UserMailer.student_lesson_normal_no_bill(self).deliver UserMailer.student_lesson_normal_no_bill(self).deliver
UserMailer.teacher_lesson_normal_no_bill(self).deliver UserMailer.teacher_lesson_normal_no_bill(self).deliver
end end
@ -501,6 +506,22 @@ module JamRuby
end end
end end
def validate_countering
if counter_slot.nil?
errors.add(:counter_slot, "must be specified")
elsif !approved_before? && (status == STATUS_REQUESTED || status == STATUS_COUNTERED)
if recurring && !counter_slot.update_all
errors.add(:counter_slot, "Only 'update all' counter-proposals are allowed for un-approved, recurring lessons")
end
if recurring && !counter_slot.is_recurring?
errors.add(:counter_slot, "Only 'recurring' counter-proposals are allowed for un-approved, recurring lessons")
end
end
self.countering_flag = false
end
def validate_accepted def validate_accepted
if self.status_was != STATUS_REQUESTED && self.status_was != STATUS_COUNTERED if self.status_was != STATUS_REQUESTED && self.status_was != STATUS_COUNTERED
self.errors.add(:status, "This session is already #{self.status_was}.") self.errors.add(:status, "This session is already #{self.status_was}.")
@ -787,6 +808,7 @@ module JamRuby
update_all = slot.update_all || !lesson_booking.recurring update_all = slot.update_all || !lesson_booking.recurring
self.countering = true self.countering = true
self.countering_flag = true
slot.proposer = proposer slot.proposer = proposer
slot.lesson_session = self slot.lesson_session = self
slot.message = message slot.message = message
@ -796,11 +818,12 @@ module JamRuby
self.countered_slot = slot self.countered_slot = slot
self.countered_lesson = self self.countered_lesson = self
self.status = STATUS_COUNTERED self.status = STATUS_COUNTERED
if !update_all #if !update_all
self.counter_slot = slot self.counter_slot = slot
end #end
if self.save if self.save
if update_all && !lesson_booking.counter(self, proposer, slot) #if update_all && !lesson_booking.counter(self, proposer, slot)
if !lesson_booking.counter(self, proposer, slot)
response = lesson_booking response = lesson_booking
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end

View File

@ -48,6 +48,9 @@ module JamRuby
s3_manager.sign_url(self[:file_url], {:expires => expiration_time, :secure => true}) s3_manager.sign_url(self[:file_url], {:expires => expiration_time, :secure => true})
end end
def is_notation?
self.attachment_type == TYPE_NOTATION
end
private private
def self.construct_filename(notation) def self.construct_filename(notation)

View File

@ -2,6 +2,7 @@ module JamRuby
class Subject < ActiveRecord::Base class Subject < ActiveRecord::Base
include HtmlSanitize include HtmlSanitize
html_sanitize strict: [:name, :description] html_sanitize strict: [:name, :description]
has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_subjects" has_many :teachers, :class_name => "JamRuby::Teacher", :through => :teachers_subjects
has_many :teachers_subjects, class_name: "JamRuby::TeacherSubject"
end end
end end

View File

@ -4,10 +4,14 @@ module JamRuby
html_sanitize strict: [:biography, :website] html_sanitize strict: [:biography, :website]
attr_accessor :validate_introduction, :validate_basics, :validate_pricing attr_accessor :validate_introduction, :validate_basics, :validate_pricing
attr_accessible :genres, :teacher_experiences, :experiences_teaching, :experiences_education, :experiences_award attr_accessible :genres, :teacher_experiences, :experiences_teaching, :experiences_education, :experiences_award
has_and_belongs_to_many :genres, :class_name => "JamRuby::Genre", :join_table => "teachers_genres", :order => "description" has_many :genres, :class_name => "JamRuby::Genre", :through => :teachers_genres # , :order => "description"
has_and_belongs_to_many :instruments, :class_name => "JamRuby::Instrument", :join_table => "teachers_instruments", :order => "description" has_many :teachers_genres, :class_name => "JamRuby::TeacherGenre"
has_and_belongs_to_many :subjects, :class_name => "JamRuby::Subject", :join_table => "teachers_subjects", :order => "description" has_many :instruments, :class_name => "JamRuby::Instrument", through: :teachers_instruments # , :order => "description"
has_and_belongs_to_many :languages, :class_name => "JamRuby::Language", :join_table => "teachers_languages", :order => "description" has_many :teachers_instruments, class_name: "JamRuby::TeacherInstrument"
has_many :subjects, :class_name => "JamRuby::Subject", :through => :teachers_subjects # , :order => "description"
has_many :teachers_subjects, class_name: "JamRuby::TeacherSubject"
has_many :languages, :class_name => "JamRuby::Language", :through => :teachers_languages # , :order => "description"
has_many :teachers_languages, class_name: "JamRuby::TeacherLanguage"
has_many :teacher_experiences, :class_name => "JamRuby::TeacherExperience" has_many :teacher_experiences, :class_name => "JamRuby::TeacherExperience"
has_many :experiences_teaching, :class_name => "JamRuby::TeacherExperience", conditions: {experience_type: 'teaching'} has_many :experiences_teaching, :class_name => "JamRuby::TeacherExperience", conditions: {experience_type: 'teaching'}
has_many :experiences_education, :class_name => "JamRuby::TeacherExperience", conditions: {experience_type: 'education'} has_many :experiences_education, :class_name => "JamRuby::TeacherExperience", conditions: {experience_type: 'education'}
@ -36,7 +40,7 @@ module JamRuby
validate :offer_duration, :if => :validate_pricing validate :offer_duration, :if => :validate_pricing
validate :teaches_ages, :if => :validate_basics validate :teaches_ages, :if => :validate_basics
default_scope { includes(:genres).order('created_at desc') } #default_scope { includes(:genres).order('created_at desc') }
after_save :update_profile_pct after_save :update_profile_pct
@ -53,7 +57,7 @@ module JamRuby
limit ||= 20 limit ||= 20
limit = limit.to_i limit = limit.to_i
query = User.joins(:teacher) query = User.unscoped.joins(:teacher)
# only show teachers with ready for session set to true # only show teachers with ready for session set to true
query = query.where('teachers.ready_for_session_at IS NOT NULL') query = query.where('teachers.ready_for_session_at IS NOT NULL')

View File

@ -0,0 +1,11 @@
module JamRuby
class TeacherGenre < ActiveRecord::Base
self.table_name = "teachers_genres"
belongs_to :teacher, class_name: "JamRuby::Teacher"
belongs_to :genre, class_name: "JamRuby::Genre"
validates :teacher, presence:true
validates :genre, presence: true
end
end

View File

@ -0,0 +1,11 @@
module JamRuby
class TeacherInstrument < ActiveRecord::Base
self.table_name = "teachers_instruments"
belongs_to :teacher, class_name: "JamRuby::Teacher"
belongs_to :instrument, class_name: "JamRuby::Instrument"
validates :teacher, presence:true
validates :instrument, presence: true
end
end

View File

@ -0,0 +1,11 @@
module JamRuby
class TeacherLanguage < ActiveRecord::Base
self.table_name = "teachers_languages"
belongs_to :teacher, class_name: "JamRuby::Teacher"
belongs_to :language, class_name: "JamRuby::Language"
validates :teacher, presence:true
validates :language, presence: true
end
end

View File

@ -0,0 +1,11 @@
module JamRuby
class TeacherSubject < ActiveRecord::Base
self.table_name = "teachers_subjects"
belongs_to :teacher, class_name: "JamRuby::Teacher"
belongs_to :subject, class_name: "JamRuby::Subject"
validates :teacher, presence:true
validates :subject, presence: true
end
end

View File

@ -51,7 +51,7 @@ module JamRuby
has_many :user_authorizations, :class_name => "JamRuby::UserAuthorization" has_many :user_authorizations, :class_name => "JamRuby::UserAuthorization"
has_many :reviews, :class_name => "JamRuby::Review" has_many :reviews, :class_name => "JamRuby::Review"
has_one :review_summary, :class_name => "JamRuby::ReviewSummary" has_one :review_summary, :class_name => "JamRuby::ReviewSummary", as: :target
# calendars (for scheduling NOT in music_session) # calendars (for scheduling NOT in music_session)
has_many :calendars, :class_name => "JamRuby::Calendar" has_many :calendars, :class_name => "JamRuby::Calendar"
@ -2090,7 +2090,7 @@ module JamRuby
LessonBooking.unprocessed(self).where(lesson_type: LessonBooking::LESSON_TYPE_PAID).first LessonBooking.unprocessed(self).where(lesson_type: LessonBooking::LESSON_TYPE_PAID).first
end end
def most_recent_test_drive_purchase def most_recent_test_drive_purchase
lesson_purchases.where('lesson_package_type_id in (?)', LessonPackageType.test_drive_package_ids).order('created_at desc').first lesson_purchases.where('lesson_package_type_id in (?)', LessonPackageType.test_drive_package_ids).order('created_at desc').first
end end

View File

@ -1057,5 +1057,13 @@ FactoryGirl.define do
factory :email_blacklist, class: "JamRuby::EmailBlacklist" do factory :email_blacklist, class: "JamRuby::EmailBlacklist" do
sequence(:email) { |n| "person_#{n}@example.com"} sequence(:email) { |n| "person_#{n}@example.com"}
end end
factory :music_notation, class: "JamRuby::MusicNotation" do
attachment_type {JamRuby::MusicNotation::TYPE_NOTATION}
association :user, factory: :user
file_url 'abc'
size 100
file_name 'some_file.jpg'
end
end end

View File

@ -42,7 +42,6 @@ describe "Monthly Recurring Lesson Flow" do
########## Need validate their credit card ########## Need validate their credit card
token = create_stripe_token token = create_stripe_token
result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id}) result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id})
puts "result #{result.inspect}"
booking.reload booking.reload
booking.card_presumed_ok.should be_true booking.card_presumed_ok.should be_true
booking.errors.any?.should be_false booking.errors.any?.should be_false
@ -118,7 +117,7 @@ describe "Monthly Recurring Lesson Flow" do
######## Teacher accepts slot ######## Teacher accepts slot
UserMailer.deliveries.clear UserMailer.deliveries.clear
lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false}) lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user})
UserMailer.deliveries.each do |del| UserMailer.deliveries.each do |del|
# puts del.inspect # puts del.inspect
end end
@ -203,7 +202,7 @@ describe "Monthly Recurring Lesson Flow" do
payment = TeacherPayment.first payment = TeacherPayment.first
payment.amount_in_cents.should eql 3000 payment.amount_in_cents.should eql 3000
payment.fee_in_cents.should eql (3000 * 0.28).round payment.fee_in_cents.should eql (3000 * 0.28).round
payment.teacher_payment_charge.amount_in_cents.should eql 3000 payment.teacher_payment_charge.amount_in_cents.should eql (3000 + 3000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql (3000 * 0.28).round payment.teacher_payment_charge.fee_in_cents.should eql (3000 * 0.28).round
payment.teacher.should eql teacher_user payment.teacher.should eql teacher_user
payment.teacher_distribution.should eql teacher_distribution payment.teacher_distribution.should eql teacher_distribution
@ -320,7 +319,7 @@ describe "Monthly Recurring Lesson Flow" do
######## Teacher accepts slot ######## Teacher accepts slot
UserMailer.deliveries.clear UserMailer.deliveries.clear
lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false}) lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user})
UserMailer.deliveries.each do |del| UserMailer.deliveries.each do |del|
# puts del.inspect # puts del.inspect
end end
@ -419,6 +418,7 @@ describe "Monthly Recurring Lesson Flow" do
it "affiliate gets their cut" do it "affiliate gets their cut" do
Timecop.travel(2016, 05, 15)
user.affiliate_referral = affiliate_partner user.affiliate_referral = affiliate_partner
user.save! user.save!
teacher_user.affiliate_referral = affiliate_partner2 teacher_user.affiliate_referral = affiliate_partner2
@ -429,7 +429,6 @@ describe "Monthly Recurring Lesson Flow" do
user.reload user.reload
puts "user.lesson_purchases #{user.lesson_purchases}"
user.lesson_purchases.count.should eql 1 user.lesson_purchases.count.should eql 1
lesson_package_purchase = user.lesson_purchases.first lesson_package_purchase = user.lesson_purchases.first
teacher_distribution = lesson_package_purchase.teacher_distribution teacher_distribution = lesson_package_purchase.teacher_distribution
@ -447,6 +446,7 @@ describe "Monthly Recurring Lesson Flow" do
end end
it "school affiliate gets nothing when teacher school is involved" do it "school affiliate gets nothing when teacher school is involved" do
Timecop.travel(2016, 05, 15)
teacher.school = school teacher.school = school
teacher.save! teacher.save!
@ -469,6 +469,8 @@ describe "Monthly Recurring Lesson Flow" do
end end
it "student school affiliates gets cut when student school is involved. so does teacher's" do it "student school affiliates gets cut when student school is involved. so does teacher's" do
# in the middle of the month so that we don't get the next month's in-advance purchase put on us
Timecop.travel(2016, 05, 15)
user.affiliate_referral = school.affiliate_partner user.affiliate_referral = school.affiliate_partner
user.save! user.save!

View File

@ -68,7 +68,7 @@ describe "Normal Lesson Flow" do
######## Teacher accepts slot ######## Teacher accepts slot
UserMailer.deliveries.clear UserMailer.deliveries.clear
lesson_session.accept({message: 'Yeah I got this', slot: booking.default_slot.id, update_all: false}) lesson_session.accept({message: 'Yeah I got this', slot: booking.default_slot.id, update_all: false, accepter: teacher_user})
lesson_session.errors.any?.should be_false lesson_session.errors.any?.should be_false
lesson_session.reload lesson_session.reload
lesson_session.slot.should eql booking.default_slot lesson_session.slot.should eql booking.default_slot
@ -251,7 +251,7 @@ describe "Normal Lesson Flow" do
payment = TeacherPayment.first payment = TeacherPayment.first
payment.amount_in_cents.should eql 3000 payment.amount_in_cents.should eql 3000
payment.fee_in_cents.should eql (3000 * 0.28).round payment.fee_in_cents.should eql (3000 * 0.28).round
payment.teacher_payment_charge.amount_in_cents.should eql 3000 payment.teacher_payment_charge.amount_in_cents.should eql (3000 + 3000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql (3000 * 0.28).round payment.teacher_payment_charge.fee_in_cents.should eql (3000 * 0.28).round
payment.teacher.should eql teacher_user payment.teacher.should eql teacher_user
payment.teacher_distribution.should eql teacher_distribution payment.teacher_distribution.should eql teacher_distribution
@ -352,7 +352,7 @@ describe "Normal Lesson Flow" do
######## Teacher accepts slot ######## Teacher accepts slot
UserMailer.deliveries.clear UserMailer.deliveries.clear
lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false}) lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false,accepter: teacher_user})
lesson_session.errors.any?.should be_false lesson_session.errors.any?.should be_false
lesson_session.reload lesson_session.reload
lesson_session.slot.should eql student_counter lesson_session.slot.should eql student_counter
@ -508,7 +508,7 @@ describe "Normal Lesson Flow" do
######## Teacher accepts slot ######## Teacher accepts slot
UserMailer.deliveries.clear UserMailer.deliveries.clear
lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false}) lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user})
lesson_session.errors.any?.should be_false lesson_session.errors.any?.should be_false
lesson_session.reload lesson_session.reload
lesson_session.slot.should eql student_counter lesson_session.slot.should eql student_counter

View File

@ -55,7 +55,7 @@ describe "Recurring Lesson Flow" do
booking.status.should eql LessonBooking::STATUS_REQUESTED booking.status.should eql LessonBooking::STATUS_REQUESTED
######### Teacher counters with new slot ######### Teacher counters with new slot
teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_single, hour: 14, update_all: true) teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 14, update_all: true)
UserMailer.deliveries.clear UserMailer.deliveries.clear
lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'}) lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'})
booking.reload booking.reload
@ -81,7 +81,7 @@ describe "Recurring Lesson Flow" do
#notification.message.should eql "Instructor has proposed a different time for your lesson." #notification.message.should eql "Instructor has proposed a different time for your lesson."
######### Student counters with new slot ######### Student counters with new slot
student_countered_slot = FactoryGirl.build(:lesson_booking_slot_single, hour: 16, update_all: true) student_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 16, update_all: true)
UserMailer.deliveries.clear UserMailer.deliveries.clear
lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'}) lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'})
lesson_session.errors.any?.should be false lesson_session.errors.any?.should be false
@ -105,7 +105,7 @@ describe "Recurring Lesson Flow" do
######## Teacher accepts slot ######## Teacher accepts slot
UserMailer.deliveries.clear UserMailer.deliveries.clear
lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false}) lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, accepter: teacher_user})
UserMailer.deliveries.each do |del| UserMailer.deliveries.each do |del|
# puts del.inspect # puts del.inspect
end end
@ -132,7 +132,11 @@ describe "Recurring Lesson Flow" do
notification.student_directed.should eql true notification.student_directed.should eql true
notification.purpose.should eql 'accept' notification.purpose.should eql 'accept'
notification.description.should eql NotificationTypes::LESSON_MESSAGE notification.description.should eql NotificationTypes::LESSON_MESSAGE
user.reload
user.sales.length.should eql 0
booking.reload
booking.lesson_sessions[0].scheduled_start.should_not eql booking.lesson_sessions[1].scheduled_start
# teacher & student get into session # teacher & student get into session
start = lesson_session.scheduled_start start = lesson_session.scheduled_start
@ -181,6 +185,9 @@ describe "Recurring Lesson Flow" do
lesson_session.sent_billing_notices.should be true lesson_session.sent_billing_notices.should be true
user.reload user.reload
user.remaining_test_drives.should eql 0 user.remaining_test_drives.should eql 0
UserMailer.deliveries.each do |d|
puts d.subject
end
UserMailer.deliveries.length.should eql 2 # one for student, one for teacher UserMailer.deliveries.length.should eql 2 # one for student, one for teacher
end end
end end

View File

@ -140,7 +140,7 @@ describe "TestDrive Lesson Flow" do
######## Teacher accepts slot ######## Teacher accepts slot
UserMailer.deliveries.clear UserMailer.deliveries.clear
lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false}) lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user})
lesson_session.errors.any?.should be_false lesson_session.errors.any?.should be_false
lesson_session.reload lesson_session.reload
lesson_session.slot.should eql student_counter lesson_session.slot.should eql student_counter
@ -237,7 +237,7 @@ describe "TestDrive Lesson Flow" do
teacher_distribution.ready.should be_true teacher_distribution.ready.should be_true
teacher_distribution.distributed.should be_true teacher_distribution.distributed.should be_true
teacher_payment.teacher_payment_charge.amount_in_cents.should eql 1000 teacher_payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
teacher_payment.teacher_payment_charge.fee_in_cents.should eql 0 teacher_payment.teacher_payment_charge.fee_in_cents.should eql 0
user.sales.count.should eql 1 user.sales.count.should eql 1

View File

@ -741,6 +741,14 @@ describe LessonBooking do
UserMailer.deliveries.clear UserMailer.deliveries.clear
Timecop.freeze(7.days.ago) Timecop.freeze(7.days.ago)
mailer = mock
mailer.should_receive(:deliver).exactly(2).times
UserMailer.should_receive(:student_lesson_booking_canceled).and_return(mailer)
UserMailer.should_receive(:teacher_lesson_booking_canceled).and_return(mailer)
UserMailer.should_receive(:student_lesson_canceled).exactly(0).times
UserMailer.should_receive(:teacher_lesson_canceled).exactly(0).times
lesson_session.cancel({canceler: user, message: 'meh', slot: booking.default_slot.id, update_all: true}) lesson_session.cancel({canceler: user, message: 'meh', slot: booking.default_slot.id, update_all: true})
lesson_session.errors.any?.should be_false lesson_session.errors.any?.should be_false
lesson_session.reload lesson_session.reload
@ -748,14 +756,32 @@ describe LessonBooking do
booking.reload booking.reload
booking.status.should eql LessonSession::STATUS_CANCELED booking.status.should eql LessonSession::STATUS_CANCELED
booking.canceler.should eql user booking.canceler.should eql user
UserMailer.deliveries.length.should eql 2
end end
end end
describe "rescheduling" do describe "rescheduling" do
after do it "initial slot is in the past" do
Timecop.return booking = LessonBooking.book_normal(user, teacher_user, valid_single_slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60)
lesson_session = booking.lesson_sessions[0]
initial_scheduled_time = lesson_session.scheduled_start
counter = FactoryGirl.build(:lesson_booking_slot_single, preferred_day: Date.today + 20)
lesson_session.counter({proposer: user, slot: counter, message: 'ACtually, let\'s do this instead for just this one'})
Timecop.travel(initial_scheduled_time + 1)
lesson_session.accept({accepter: teacher_user, message: 'Yeah I got this', slot: counter, update_all: false})
booking.reload
booking.status.should eql LessonBooking::STATUS_APPROVED
booking.lesson_sessions.count.should eql 1
lesson_session.errors.any?.should be_false
lesson_session.reload
lesson_session.status.should eql LessonSession::STATUS_APPROVED
lesson_session.scheduled_start.should eql counter.scheduled_time(0)
end end
it "non-recurring, accepted with new slot" do it "non-recurring, accepted with new slot" do
booking = LessonBooking.book_normal(user, teacher_user, valid_single_slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60) booking = LessonBooking.book_normal(user, teacher_user, valid_single_slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60)
lesson_session = booking.lesson_sessions[0] lesson_session = booking.lesson_sessions[0]

View File

@ -10,6 +10,63 @@ describe LessonSession do
let(:lesson_booking) {b = LessonBooking.book_normal(user, teacher, [slot1, slot2], "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60); b.card_presumed_ok = true; b.save!; b} let(:lesson_booking) {b = LessonBooking.book_normal(user, teacher, [slot1, slot2], "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60); b.card_presumed_ok = true; b.save!; b}
let(:lesson_session) {lesson_booking.lesson_sessions[0]} let(:lesson_session) {lesson_booking.lesson_sessions[0]}
describe "counter" do
describe "recurring" do
it "counter madness" do
lesson = monthly_lesson(user, teacher, {accept:false})
# start with the student
invalid = FactoryGirl.build(:lesson_booking_slot_single, update_all: false)
lesson.counter({proposer: user, message: "crumble and bumble", slot: invalid})
lesson.errors.any?.should be_true
lesson.errors[:counter_slot].should eql ["Only 'update all' counter-proposals are allowed for un-approved, recurring lessons", "Only 'recurring' counter-proposals are allowed for un-approved, recurring lessons"]
lesson.reload
lesson.counter_slot.should be_nil
counter1 = FactoryGirl.build(:lesson_booking_slot_recurring, update_all: true)
lesson.counter({proposer: user, message: "crumble and bumble take 2", slot: counter1})
lesson.errors.any?.should be_false
lesson.reload
lesson.status.should eql LessonSession::STATUS_COUNTERED
lesson.counter_slot.id.should eql counter1.id
lesson.lesson_booking.counter_slot.id.should eql counter1.id
counter2 = FactoryGirl.build(:lesson_booking_slot_recurring, update_all: true)
lesson.counter({proposer: teacher, message: "crumble and bumble take 3", slot: counter2})
lesson.errors.any?.should be_false
lesson.reload
lesson.status.should eql LessonSession::STATUS_COUNTERED
lesson.counter_slot.id.should eql counter2.id
lesson.lesson_booking.counter_slot.id.should eql counter2.id
lesson.accept({accepter: user, message: "burp", slot: counter2})
lesson.errors.any?.should be_false
lesson.reload
lesson.status.should eql LessonSession::STATUS_APPROVED
counter3 = FactoryGirl.build(:lesson_booking_slot_recurring, update_all: false)
lesson.counter({proposer: user, message: "crumble and bumble take 4", slot: counter3})
lesson.errors.any?.should be_false
lesson.reload
lesson.status.should eql LessonSession::STATUS_COUNTERED
lesson.counter_slot.id.should eql counter3.id
lesson.lesson_booking.counter_slot.id.should eql counter3.id
counter4 = FactoryGirl.build(:lesson_booking_slot_recurring, update_all: true)
lesson.counter({proposer: teacher, message: "crumble and bumble take 5", slot: counter4})
lesson.errors.any?.should be_false
lesson.reload
lesson.status.should eql LessonSession::STATUS_COUNTERED
lesson.counter_slot.id.should eql counter4.id
lesson.lesson_booking.counter_slot.id.should eql counter4.id
end
end
end
describe "autocancel" do describe "autocancel" do
it "can't autocancel in the past" do it "can't autocancel in the past" do
lesson_session.status.should eql LessonSession::STATUS_REQUESTED lesson_session.status.should eql LessonSession::STATUS_REQUESTED

View File

@ -45,18 +45,20 @@ describe Teacher do
it "instruments" do it "instruments" do
teacher = FactoryGirl.create(:teacher, ready_for_session_at: Time.now) teacher = FactoryGirl.create(:teacher, ready_for_session_at: Time.now)
teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query] #teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query]
teachers.length.should eq 0 #teachers.length.should eq 0
teacher.instruments << Instrument.find('acoustic guitar') teacher.instruments << Instrument.find('acoustic guitar')
teacher.save! teacher.save!
teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query] #teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query]
teachers.length.should eq 1 #teachers.length.should eq 1
teachers[0].should eq(teacher.user) #teachers[0].should eq(teacher.user)
teacher.instruments << Instrument.find('electric guitar') #teacher.instruments << Instrument.find('electric guitar')
teacher.save! #teacher.save!
puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query] teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query]
puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!---"
teachers.length.should eq 1 teachers.length.should eq 1
teachers[0].should eq(teacher.user) teachers[0].should eq(teacher.user)
end end

View File

@ -9,6 +9,7 @@ describe GoogleAnalyticsEvent do
end end
describe "track band analytics" do describe "track band analytics" do
pending "job is commented out"
it 'reports first recording' do it 'reports first recording' do
ResqueSpec.reset! ResqueSpec.reset!
user = FactoryGirl.create(:user) user = FactoryGirl.create(:user)
@ -26,6 +27,7 @@ describe GoogleAnalyticsEvent do
end end
it 'reports first real session' do it 'reports first real session' do
pending "job is commented out"
ResqueSpec.reset! ResqueSpec.reset!
JamRuby::GoogleAnalyticsEvent::BandSessionTracker.should have_schedule_size_of(0) JamRuby::GoogleAnalyticsEvent::BandSessionTracker.should have_schedule_size_of(0)
user = FactoryGirl.create(:user) user = FactoryGirl.create(:user)
@ -72,6 +74,7 @@ describe GoogleAnalyticsEvent do
ResqueSpec.reset! ResqueSpec.reset!
end end
it 'reports size increment' do it 'reports size increment' do
pending "job is commented out"
user = FactoryGirl.create(:user) user = FactoryGirl.create(:user)
music_session = FactoryGirl.create(:active_music_session, music_session = FactoryGirl.create(:active_music_session,
:creator => user, :creator => user,
@ -86,6 +89,7 @@ describe GoogleAnalyticsEvent do
end end
it 'reports duration' do it 'reports duration' do
pending "job is commented out"
user = FactoryGirl.create(:user) user = FactoryGirl.create(:user)
JamRuby::GoogleAnalyticsEvent::SessionDurationTracker.should have_schedule_size_of(0) JamRuby::GoogleAnalyticsEvent::SessionDurationTracker.should have_schedule_size_of(0)
music_session = FactoryGirl.create(:active_music_session, music_session = FactoryGirl.create(:active_music_session,

View File

@ -162,6 +162,26 @@ describe "RenderMailers", :slow => true do
UserMailer.deliveries.clear UserMailer.deliveries.clear
UserMailer.lesson_starting_soon_student(lesson).deliver UserMailer.lesson_starting_soon_student(lesson).deliver
end end
it "music_notation_attachment" do
@filename = "music_notation_attachment"
lesson = testdrive_lesson(user, teacher)
UserMailer.deliveries.clear
notation = FactoryGirl.create(:music_notation, user: user)
UserMailer.lesson_attachment(user, teacher, lesson, notation).deliver
end
it "recording_attachment" do
@filename = "recording_attachment"
lesson = testdrive_lesson(user, teacher)
UserMailer.deliveries.clear
claim = FactoryGirl.create(:claimed_recording, user: user)
UserMailer.lesson_attachment(user, teacher, lesson, claim).deliver
end
end end
end end

View File

@ -38,7 +38,7 @@ require 'timecop'
require 'resque_spec/scheduler' require 'resque_spec/scheduler'
# uncomment this to see active record logs # uncomment this to see active record logs
# ActiveRecord::Base.logger = Logger.new(STDOUT) if defined?(ActiveRecord::Base) ActiveRecord::Base.logger = Logger.new(STDOUT) if defined?(ActiveRecord::Base)
include JamRuby include JamRuby

View File

@ -65,7 +65,6 @@ def book_lesson(user, teacher, options)
LessonPackagePurchase.create(user, booking, LessonPackageType.package_for_test_drive_count(options[:package_count])) LessonPackagePurchase.create(user, booking, LessonPackageType.package_for_test_drive_count(options[:package_count]))
end end
elsif options[:monthly] elsif options[:monthly]
puts "did it"
LessonPackagePurchase.create(user, booking, LessonPackageType.single, Date.today.year, Date.today.month) LessonPackagePurchase.create(user, booking, LessonPackageType.single, Date.today.year, Date.today.month)
end end
@ -83,7 +82,7 @@ def book_lesson(user, teacher, options)
end end
if options[:accept] if options[:accept]
lesson.accept({message: 'Yeah I got this', slot: slots[0]}) lesson.accept({message: 'Yeah I got this', slot: slots[0], accepter: teacher})
lesson.errors.any?.should be_false lesson.errors.any?.should be_false
lesson.reload lesson.reload
lesson.slot.should eql slots[0] lesson.slot.should eql slots[0]

View File

@ -581,6 +581,9 @@
if(!defaults.show_checkbox) { if(!defaults.show_checkbox) {
$feedItem.find('.select-box').hide(); $feedItem.find('.select-box').hide();
} }
else {
context.JK.checkbox($feedItem.find('.select-box'))
}
if(defaults.hide_avatar) { if(defaults.hide_avatar) {
$feedItem.find('.avatar-small.ib').hide(); $feedItem.find('.avatar-small.ib').hide();
} }
@ -598,6 +601,7 @@
$feedItem.data('original-max-height', $feedItem.css('height')); $feedItem.data('original-max-height', $feedItem.css('height'));
context.JK.bindHoverEvents($feedItem); context.JK.bindHoverEvents($feedItem);
context.JK.bindProfileClickEvents($feedItem); context.JK.bindProfileClickEvents($feedItem);
context.JK.popExternalLinks($feedItem)
} }
else { else {
logger.warn("skipping feed type: " + feed.type); logger.warn("skipping feed type: " + feed.type);

View File

@ -99,6 +99,10 @@
ioTargetFail : 'ioTargetFail' ioTargetFail : 'ioTargetFail'
} }
var jamClassReasons = {
testDrive: 'TestDrive'
}
var networkTestFailReasons = { var networkTestFailReasons = {
stun : 'STUN', stun : 'STUN',
bandwidth : 'Bandwidth', bandwidth : 'Bandwidth',
@ -129,7 +133,8 @@
jkFollow : 'jkFollow', jkFollow : 'jkFollow',
jkFavorite : 'jkFavorite', jkFavorite : 'jkFavorite',
jkComment : 'jkComment', jkComment : 'jkComment',
fileDownload: "DownloadFile" fileDownload: "DownloadFile",
jamclass: 'JamClass'
}; };
// JamTrack categories and actions: // JamTrack categories and actions:
@ -204,6 +209,11 @@
context.ga('send', 'event', categories.register, action, registrationType); context.ga('send', 'event', categories.register, action, registrationType);
} }
function trackTestDrivePurchase(count) {
context.ga('send', 'event', categories.jamclass, jamClassReasons.testDrive, count);
}
function trackDownload(platform) { function trackDownload(platform) {
var normalizedPlatform = translatePlatformForGA(platform); var normalizedPlatform = translatePlatformForGA(platform);
@ -490,6 +500,7 @@
GA.virtualPageView = virtualPageView; GA.virtualPageView = virtualPageView;
GA.trackTiming = trackTiming; GA.trackTiming = trackTiming;
GA.trackFileDownload = trackFileDownload; GA.trackFileDownload = trackFileDownload;
GA.trackTestDrivePurchase = trackTestDrivePurchase;
context.JK.GA = GA; context.JK.GA = GA;

View File

@ -228,7 +228,7 @@
"Electric Guitar": { "client_id": 50, "server_id": "electric guitar" }, "Electric Guitar": { "client_id": 50, "server_id": "electric guitar" },
"Keyboard": { "client_id": 60, "server_id": "keyboard" }, "Keyboard": { "client_id": 60, "server_id": "keyboard" },
"Piano": { "client_id": 61, "server_id": "piano" }, "Piano": { "client_id": 61, "server_id": "piano" },
"Upright Bass": { "client_id": 62, "server_id": "upright bass" }, "Upright Bass": { "client_id": 62, "server_id": "double bass" },
"Voice": { "client_id": 70, "server_id": "voice" }, "Voice": { "client_id": 70, "server_id": "voice" },
"Flute": { "client_id": 80, "server_id": "flute" }, "Flute": { "client_id": 80, "server_id": "flute" },
"Clarinet": { "client_id": 90, "server_id": "clarinet" }, "Clarinet": { "client_id": 90, "server_id": "clarinet" },
@ -250,6 +250,9 @@
"Other": { "client_id": 250, "server_id": "other" } "Other": { "client_id": 250, "server_id": "other" }
}; };
context.JK.client_to_server_instrument_map = { context.JK.client_to_server_instrument_map = {
10: { "server_id": "acoustic guitar" }, 10: { "server_id": "acoustic guitar" },
20: { "server_id": "bass guitar" }, 20: { "server_id": "bass guitar" },
@ -259,7 +262,7 @@
50: { "server_id": "electric guitar" }, 50: { "server_id": "electric guitar" },
60: { "server_id": "keyboard" }, 60: { "server_id": "keyboard" },
61: { "server_id": "piano"} , 61: { "server_id": "piano"} ,
62: { "server_id": "upright bass"} , 62: { "server_id": "double bass"} ,
70: { "server_id": "voice" }, 70: { "server_id": "voice" },
80: { "server_id": "flute" }, 80: { "server_id": "flute" },
90: { "server_id": "clarinet" }, 90: { "server_id": "clarinet" },
@ -283,10 +286,21 @@
context.JK.instrument_id_to_instrument = {}; context.JK.instrument_id_to_instrument = {};
context.JK.server_to_client_instrument_alpha = [];
(function() { (function() {
$.each(context.JK.server_to_client_instrument_map, function(key, value) { $.each(context.JK.server_to_client_instrument_map, function(key, value) {
context.JK.instrument_id_to_instrument[value.server_id] = { client_id: value.client_id, display: key } context.JK.instrument_id_to_instrument[value.server_id] = { client_id: value.client_id, display: key }
context.JK.server_to_client_instrument_alpha.push({ client_id: value.client_id, display: key, server_id: value.server_id })
}); });
context.JK.server_to_client_instrument_alpha.sort(function(a, b){
if ( a.display < b.display )
return -1;
if ( a.display > b.display )
return 1;
return 0;
});
})(); })();

View File

@ -82,7 +82,7 @@
} }
function subtlePulse($element) { function subtlePulse($element) {
$element.find('.bt-content').pulse({'background-color' : '#868686'}, {pulses: 3}, function() { $element.css('background-color', '#980006')}) $element.find('.bt-content').pulse({'background-color' : '#868686'}, {pulses: 2, duration: 1000, interval:300}, function() { $element.css('background-color', '#980006')})
} }
helpBubble.rotateJamTrackLandingBubbles = function($preview, $video, $ctaButton, $alternativeCta) { helpBubble.rotateJamTrackLandingBubbles = function($preview, $video, $ctaButton, $alternativeCta) {
@ -203,7 +203,7 @@
helpBubble.showBuyTestDrive = function($element, $offsetParent, user, callback) { helpBubble.showBuyTestDrive = function($element, $offsetParent, user, callback) {
return context.JK.onceBubble($element, 'side-buy-test-drive', user, {offsetParent:$offsetParent, width:260, positions:['right'], postShow: function(container) { return context.JK.onceBubble($element, 'side-buy-test-drive', user, {offsetParent:$offsetParent, width:260, positions:['right'], postShow: function(container) {
subtlePulse(container) subtlePulse(container)
var $bookNow = container('a.book-now') var $bookNow = container.find('a.book-now')
$bookNow.off('click').on('click', function(e) { $bookNow.off('click').on('click', function(e) {
e.preventDefault() e.preventDefault()
callback() callback()

View File

@ -24,7 +24,7 @@ AttachmentStore = context.AttachmentStore
notationUploadDone: () -> notationUploadDone: () ->
logger.debug("AttachmentStatus: notationUploadDone") logger.debug("AttachmentStatus: notationUploadDone")
context.JK.Banner.showNotice('Notation Uploaded', 'The music notation file has been uploaded, and can be accessed from the Messages window for this lesson.') #context.JK.Banner.showNotice('Notation Uploaded', 'The music notation file has been uploaded, and can be accessed from the Messages window for this lesson.')
notationUploadFail: () -> notationUploadFail: () ->
logger.debug("AttachmentStatus: notationUploadFail") logger.debug("AttachmentStatus: notationUploadFail")
@ -32,11 +32,11 @@ AttachmentStore = context.AttachmentStore
audioSelected: (e) -> audioSelected: (e) ->
files = $(e.target).get(0).files files = $(e.target).get(0).files
logger.debug("audio files selected: ", files) logger.debug("audio files selected: ", files)
window.AttachmentActions.uploadAudio.trigger(files, @notationUploadDone, @notationUploadFail) window.AttachmentActions.uploadAudios.trigger(files, @notationUploadDone, @notationUploadFail)
audioUploadDone: () -> audioUploadDone: () ->
logger.debug("AttachmentStatus: audioUploadDone") logger.debug("AttachmentStatus: audioUploadDone")
context.JK.Banner.showNotice('Audio file Uploaded', 'The audio file has been uploaded, and can be accessed from the Messages window for this lesson.') #context.JK.Banner.showNotice('Audio file Uploaded', 'The audio file has been uploaded, and can be accessed from the Messages window for this lesson.')
audioUploadFail: () -> audioUploadFail: () ->
logger.debug("AttachmentStatus: audioUploadFail") logger.debug("AttachmentStatus: audioUploadFail")

View File

@ -1,3 +1,4 @@
context = window context = window
rest = context.JK.Rest() rest = context.JK.Rest()
logger = context.JK.logger logger = context.JK.logger

View File

@ -125,6 +125,7 @@ ChatActions = @ChatActions
else else
purpose = null purpose = null
additional = null
if msg.purpose == 'Notation File' if msg.purpose == 'Notation File'
additional = `<a className="additional" onClick={this.notationClicked.bind(this, msg.music_notation)}>{msg.music_notation.file_name}</a>` additional = `<a className="additional" onClick={this.notationClicked.bind(this, msg.music_notation)}>{msg.music_notation.file_name}</a>`
else if msg.purpose == 'Audio File' else if msg.purpose == 'Audio File'

View File

@ -44,7 +44,7 @@ logger = context.JK.logger
for object in this.props.sourceObjects for object in this.props.sourceObjects
nm = "check_#{object.id}" nm = "check_#{object.id}"
checked = @isChecked(object.id) checked = @isChecked(object.id)
object_options.push `<div className='checkItem'><input type='checkbox' key={object.id} name={nm} data-object-id={object.id} checked={checked}></input><label htmlFor={nm}>{object.description}</label></div>` object_options.push `<div className='checkItem'><input type='checkbox' key={object.id} name={nm} data-object-id={object.id} checked={checked}></input><label htmlFor={nm}>{object.description}</label><br className="clearall"/></div>`
`<div className="CheckBoxList react-component"> `<div className="CheckBoxList react-component">
<div className="checkbox-scroller left"> <div className="checkbox-scroller left">

View File

@ -29,8 +29,8 @@ ConfigureTracksStore = @ConfigureTracksStore
instruments = [] instruments = []
instruments.push(`<option key="" value="">Select the instrument for this track</option>`) instruments.push(`<option key="" value="">Select the instrument for this track</option>`)
for displayName, value of context.JK.server_to_client_instrument_map for instrument in context.JK.server_to_client_instrument_alpha
instruments.push(`<option key={value.server_id} value={value.server_id}>{displayName}</option>`) instruments.push(`<option key={instrument.server_id} value={instrument.server_id}>{instrument.display}</option>`)
vsts = [] vsts = []
@ -134,8 +134,8 @@ ConfigureTracksStore = @ConfigureTracksStore
midiInstruments = [] midiInstruments = []
instruments = [] instruments = []
for displayName, value of context.JK.server_to_client_instrument_map for instrument in context.JK.server_to_client_instrument_alpha
instruments.push(`<option key={value.server_id} value={value.server_id}>{displayName}</option>`) instruments.push(`<option key={instrument.server_id} value={instrument.server_id}>{instrument.display}</option>`)
selectedMidiInterface = '' selectedMidiInterface = ''
selectedInstrument = context.JK.client_to_server_instrument_map[50].server_id # default to electric guitar selectedInstrument = context.JK.client_to_server_instrument_map[50].server_id # default to electric guitar

View File

@ -129,9 +129,9 @@ LessonTimerActions = context.LessonTimerActions
rest.checkLessonReschedule({id: lesson.id, update_all: recurring}) rest.checkLessonReschedule({id: lesson.id, update_all: recurring})
.done((response) => ( .done((response) => (
if recurring if recurring
window.location.href = '/client#/jamclass/lesson-booking/' + lesson.lesson_booking_id window.location.href = '/client#/jamclass/lesson-booking/' + lesson.lesson_booking_id + "_rescheduling"
else else
window.location.href = '/client#/jamclass/lesson-booking/' + lesson.id window.location.href = '/client#/jamclass/lesson-booking/' + lesson.id + "_rescheduling"
)) ))
.fail((jqXHR) => ( .fail((jqXHR) => (
if jqXHR.status == 422 if jqXHR.status == 422
@ -179,7 +179,13 @@ LessonTimerActions = context.LessonTimerActions
context.JK.Banner.showAlert('late cancellation warning', context.JK.Banner.showAlert('late cancellation warning',
'Cancelling a lesson less than 24 hours before its scheduled to start should be avoided, as its an inconvenience to the student. Repeated violations of this policy will negatively affect your teacher score.') 'Cancelling a lesson less than 24 hours before its scheduled to start should be avoided, as its an inconvenience to the student. Repeated violations of this policy will negatively affect your teacher score.')
@refreshLesson(lesson.id) lessonsFromBooking = []
for check in @lessons()
if check.lesson_booking_id == lesson.lesson_booking_id
lessonsFromBooking.push(check)
for check in lessonsFromBooking
@refreshLesson(check.id)
cancelLessonBookingFail: (jqXHR) -> cancelLessonBookingFail: (jqXHR) ->
@app.ajaxError(jqXHR) @app.ajaxError(jqXHR)
@ -224,12 +230,12 @@ LessonTimerActions = context.LessonTimerActions
if lesson.recurring if lesson.recurring
buttons = [] buttons = []
buttons.push({ buttons.push({
name: 'THIS SESSION', name: 'THIS LESSON',
buttonStyle: 'button-orange', buttonStyle: 'button-orange',
click: (() => (@rescheduleSelected(lesson, false))) click: (() => (@rescheduleSelected(lesson, false)))
}) })
buttons.push({ buttons.push({
name: 'ALL SESSIONS', name: 'ALL LESSONS',
buttonStyle: 'button-orange', buttonStyle: 'button-orange',
click: (() => (@rescheduleSelected(lesson, true))) click: (() => (@rescheduleSelected(lesson, true)))
}) })
@ -251,13 +257,13 @@ LessonTimerActions = context.LessonTimerActions
verbLower = 'cancel' verbLower = 'cancel'
if !lesson.isRequested || lesson.recurring if !lesson.isRequested || lesson.recurring
buttons = [] buttons = []
buttons.push({name: 'CANCEL', buttonStyle: 'button-grey'}) buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'})
buttons.push({ buttons.push({
name: 'THIS SESSION', name: 'CANCEL THIS LESSON',
buttonStyle: 'button-orange', buttonStyle: 'button-orange',
click: (() => (@cancelSelected(lesson, false))) click: (() => (@cancelSelected(lesson, false)))
}) })
buttons.push({name: 'ALL SESSIONS', buttonStyle: 'button-orange', click: (() => (@cancelSelected(lesson, true)))}) buttons.push({name: 'CANCEL ALL LESSONS', buttonStyle: 'button-orange', click: (() => (@cancelSelected(lesson, true)))})
context.JK.Banner.show({ context.JK.Banner.show({
title: 'Select One', title: 'Select One',
html: "Do you wish to all #{verbLower} all lessons or just the one selected?", html: "Do you wish to all #{verbLower} all lessons or just the one selected?",
@ -521,7 +527,10 @@ LessonTimerActions = context.LessonTimerActions
else else
unreadMessages = null unreadMessages = null
timeStmt = lessonData.music_session.pretty_scheduled_start_with_timezone if lessonData.status == 'countered'
timeStmt = lessonData.counter_slot.pretty_scheduled_start_with_timezone
else
timeStmt = lessonData.music_session.pretty_scheduled_start_with_timezone
if lessonData.times? && lessonData.displayStatus == 'Scheduled' if lessonData.times? && lessonData.displayStatus == 'Scheduled'
if lessonData.times.startingSoon if lessonData.times.startingSoon

View File

@ -0,0 +1,135 @@
@JamClassSearchHeader = React.createClass({
mixins: [Reflux.listenTo(@UserStore, "onUserChanged"), Reflux.listenTo(@TeacherSearchStore, "onTeacherSearchStore")]
onTeacherSearchStore: ()->
getInitialState: () ->
{user: null}
onUserChanged: (@user) ->
@setState({user: @user?.user})
createSearchDescription: () ->
searchOptions = TeacherSearchStore.getState()
summary = ''
if searchOptions.onlyMySchool && @state.user?.school_id?
summary += "From My School Only"
instruments = searchOptions.instruments
if instruments? && instruments.length > 0
if instruments.length == 1
bit = "Instrument = #{InstrumentStore.display(instruments[0])}"
else
instruments.length > 1
bit = "Instruments = #{InstrumentStore.display(instruments[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
subjects = searchOptions.subjects
if subjects? && subjects.length > 0
if subjects.length == 1
bit = "Subject = #{SubjectStore.display(subjects[0])}"
else
subjects.length > 1
bit = "Subjects = #{SubjectStore.display(subjects[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
genres = searchOptions.genres
if genres? && genres.length > 0
if genres.length == 1
bit = "Genre = #{GenreStore.display(genres[0])}"
else
genres.length > 1
bit = "Genres = #{GenreStore.display(genres[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
languages = searchOptions.languages
if languages? && languages.length > 0
if languages.length == 1
bit = "Language = #{LanguageStore.display(languages[0])}"
else
languages.length > 1
bit = "Languages = #{LanguageStore.display(languages[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
if searchOptions.teaches_beginner || searchOptions.teaches_intermediate || searchOptions.teaches_advanced
bit = "Teaches "
qualifier = ''
if searchOptions.teaches_beginner
qualifier += "Beginner"
if searchOptions.teaches_intermediate
if qualifier.length > 0
qualifier += ", "
qualifier += "Intermediate"
if searchOptions.teaches_advanced
if qualifier.length > 0
qualifier += ", "
qualifier += "Advanced"
if summary.length > 0
summary += ', '
summary += " #{bit}#{qualifier}"
if searchOptions.student_age?
if summary.length > 0
summary += ', '
summary += "Student Age = #{searchOptions.student_age}"
if searchOptions.years_teaching?
if summary.length > 0
summary += ', '
summary += "Years Teaching = #{searchOptions.years_teaching}"
if searchOptions.location?.country?
if summary.length > 0
summary += ', '
summary += "Country = #{searchOptions.location.country}"
if searchOptions.location?.region?
if summary.length > 0
summary += ', '
summary += "Region = #{searchOptions.location.region}"
if summary.length == 0
summary = 'all teachers'
summary
render: () ->
searchDesc = @createSearchDescription()
if @props.teacher
complete = `<span className="search-results-options">
<a href="/client#/teachers/search" className="results-text link">Search Results</a>&nbsp;:&nbsp;
<span className="teacher-name">{this.props.teacher.name}</span>
</span>`
else
complete =
`<span className="search-results-options">
<span className="search-description">
<span className="results-text">Search Results / </span>
<span className="search-summary">{searchDesc}</span>
</span>
</span>`
`<div className="jamclass-search-header">
<a href="/client#/home">JamKazam Home</a>&nbsp;:&nbsp;
<a href="/client#/jamclass">JamClass Home</a>&nbsp;:&nbsp;
<a className="teacher-search-options" href="/client#/jamclass/searchOptions">Teachers Search</a><span
className="teacher-quote"> : </span>
{complete}
</div>`
})

View File

@ -50,6 +50,44 @@ UserStore = context.UserStore
slot.creatorRoleRelative = "your" slot.creatorRoleRelative = "your"
slot.mySlot = @mySlot(slot) slot.mySlot = @mySlot(slot)
processBooking:(booking) ->
booking.neverAccepted = booking.accepter_id?
booking.isCounter = booking.counter_slot? && booking.status != 'canceled' && booking.status != 'suspended'
booking.studentViewing = booking.user_id == context.JK.currentUserId
booking.teacherViewing = !booking.studentViewing
booking.isRequested = booking.status == 'requested' && !booking.isCounter
booking.isCanceled = booking.status == 'canceled'
booking.isSuspended = booking.status == 'suspended'
if booking.isCounter
if booking.counter_slot['is_teacher_created?']
booking.countererId = booking.teacher_id
else
booking.countererId = booking.user_id
selfLastToAct = false
if booking.isRequested
selfLastToAct = booking.studentViewing
else if booking.isCounter
selfLastToAct = booking.countererId == context.JK.currentUserId
else if booking.isCanceled
selfLastToAct = booking.canceler_id == context.JK.currentUserId
else if booking.isSuspended
selfLastToAct = booking.studentViewing
else
selfLastToAct = false
booking.selfLastToAct = selfLastToAct
multipleOptions = false
if booking.neverAccepted
multipleOptions = !(!booking.isCounter && booking.selfLastToAct)
else if booking.isCounter
multipleOptions = !booking.selfLastToAct
else
multipleOptions = false
onlyOption = !multipleOptions
booking.onlyOption = onlyOption
#nextProps.slot_decision = 'counter'
componentWillUpdate: (nextProps, nextState) -> componentWillUpdate: (nextProps, nextState) ->
if nextState.booking? if nextState.booking?
booking = nextState.booking booking = nextState.booking
@ -61,6 +99,35 @@ UserStore = context.UserStore
@processSlot(booking.default_slot, booking) @processSlot(booking.default_slot, booking)
@processSlot(booking.alt_slot, booking) @processSlot(booking.alt_slot, booking)
onlyOption:() ->
#(this.props.initial && !this.props.selfLastToAct )|| !(this.props.counter && !this.props.selfLastToAct)
#@initialRequestSlotsVisible() || !@counteredSlotVisible()
#(this.props.initial && this.props.selfLastToAct ) || !(this.props.counter && !this.props.selfLastToAct)
#(@neverAccepted() && @selfLastToAct()) || !(@isCounter() && !@selfLastToAct())
!@multipleOptions()
multipleOptions: () ->
if @neverAccepted()
!(!@isCounter() && @selfLastToAct())
else if this.props.counter
!@selfLastToAct()
else
false
initialRequestSlotsVisible: () ->
console.log("initialRequestSlotsVisible: " + this.neverAccepted() )
#this.neverAccepted() && this.selfLastToAct()
# is there a counter slot showing
counteredSlotVisible: () ->
console.log("isCounter " + this.isCounter() + ", this.selfLastToAct()" + this.selfLastToAct())
this.isCounter() && !this.selfLastToAct()
counterSlotVisible: () ->
true
getInitialState: () -> getInitialState: () ->
{ {
user: null, user: null,
@ -73,14 +140,28 @@ UserStore = context.UserStore
beforeShow: (e) -> beforeShow: (e) ->
parseId: (id) ->
result = {purpose: null}
bits = id.split('_')
if bits.length == 1
result.id = id
else if bits.length > 1
result.id =bits[0]
result.purpose = bits[1]
else
result.id = id
result
afterShow: (e) -> afterShow: (e) ->
@setState({updating: true, counterErrors: null, cancelErrors: null}) parsed = @parseId(e.id)
@setState({updating: true, counterErrors: null, cancelErrors: null, purpose: parsed.purpose})
rest.getLessonBooking({ rest.getLessonBooking({
id: e.id, id: parsed.id,
}).done((response) => @getLessonBookingDone(response)).fail(@app.ajaxError) }).done((response) => @getLessonBookingDone(response)).fail(@app.ajaxError)
hasFocusedLesson: () -> hasFocusedLesson: () ->
this.state.booking.focused_lesson?.id? @focusedLesson()?
focusedLesson: () -> focusedLesson: () ->
this.state?.booking?.focused_lesson this.state?.booking?.focused_lesson
@ -105,6 +186,14 @@ UserStore = context.UserStore
#booking.next_lesson.lesson_booking = booking #booking.next_lesson.lesson_booking = booking
@postProcessLesson(booking.next_lesson) @postProcessLesson(booking.next_lesson)
@processBooking(booking)
if booking.onlyOption
# you see two options (accept, and propose new) if it's a counter and you are not the last to act
# the only choice possible in most cases is to propose a new time
#nextState.slot_decision = 'counter'
startSlotDecision = 'counter'
@setState({booking: booking, updating: false, slot_decision: startSlotDecision, updatingLesson: false, update_all: update_all}) @setState({booking: booking, updating: false, slot_decision: startSlotDecision, updatingLesson: false, update_all: update_all})
getLessonBookingDone: (response) -> getLessonBookingDone: (response) ->
@ -213,7 +302,8 @@ UserStore = context.UserStore
minute = $slot.find('.minute').val() minute = $slot.find('.minute').val()
am_pm = $slot.find('.am_pm').val() am_pm = $slot.find('.am_pm').val()
update_all = $slot.find('input.update-all').is(':checked') && @isRecurring() #update_all = $slot.find('input.update-all').is(':checked') && @isRecurring()
update_all = @state.update_all && @isRecurring()
if hour? and hour != '' if hour? and hour != ''
hour = new Number(hour) hour = new Number(hour)
@ -227,7 +317,7 @@ UserStore = context.UserStore
else else
minute = null minute = null
if !@isRecurring() if !update_all
date = picker.datepicker("getDate") date = picker.datepicker("getDate")
if date? if date?
date = context.JK.formatDateYYYYMMDD(date) date = context.JK.formatDateYYYYMMDD(date)
@ -399,10 +489,10 @@ UserStore = context.UserStore
text = "Preferred day/time for lesson is #{this.slotTime(defaultSlot)}. Secondary option is #{this.slotTime(altSlot)}." text = "Preferred day/time for lesson is #{this.slotTime(defaultSlot)}. Secondary option is #{this.slotTime(altSlot)}."
slotTime: (slot, booking = this.state.booking) -> slotTime: (slot, booking = this.state.booking) ->
if @isRecurring(booking) if @hasFocusedLesson() || !@isRecurring(booking)
"#{this.dayOfWeek(slot)} at #{this.dayTime(slot)}"
else
slot.pretty_start_time slot.pretty_start_time
else
"#{this.dayOfWeek(slot)} at #{this.dayTime(slot)}"
slotTimePhrase: (slot) -> slotTimePhrase: (slot) ->
if @isRecurring() if @isRecurring()
@ -704,9 +794,10 @@ UserStore = context.UserStore
renderStudentRequested: () -> renderStudentRequested: () ->
`<div className="contents"> `<div className="contents">
<div className="row"> <div className="row request-sent">
{this.userHeader(this.myself())} {this.userHeader(this.myself())}
Your request has been sent. You will receive an email when {this.teacher().name} responds. Your request has been sent. You will receive an email when {this.teacher().name} responds.
{this.createDetail()}
</div> </div>
<LessonBookingDecision {...this.decisionProps([])} /> <LessonBookingDecision {...this.decisionProps([])} />
</div>` </div>`
@ -719,6 +810,22 @@ UserStore = context.UserStore
updateCreditCard: (e) -> updateCreditCard: (e) ->
window.location.href="/client#/account/paymentHistory" window.location.href="/client#/account/paymentHistory"
createDetail: () ->
if @hasFocusedLesson() || !@isRecurring()
if @onlyOption() && @rescheduling()
detail = `<p className="proposing-new-time">You are proposing to change the date/time of the lesson currently scheduled for {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p className="generic-time-stmt">Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
else
if @onlyOption() && @rescheduling()
detail = `<p className="proposing-new-time">You are proposing to change the date/time of the lesson currently scheduled for {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p className="generic-time-stmt">Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
detail
rescheduling: () ->
@state.purpose == 'rescheduling'
renderStudentComplete: () -> renderStudentComplete: () ->
@renderStudentApproved() @renderStudentApproved()
@ -746,10 +853,7 @@ UserStore = context.UserStore
if @studentMadeDefaultSlot() if @studentMadeDefaultSlot()
message = this.slotMessage(this.state.booking.default_slot, 'accept') message = this.slotMessage(this.state.booking.default_slot, 'accept')
if @isRecurring() detail = @createDetail()
detail = `<p>Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p>Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
summary = `<div className="row"> summary = `<div className="row">
{this.userHeader(this.teacher())} {this.userHeader(this.teacher())}
<p>Has accepted your lesson request.</p> <p>Has accepted your lesson request.</p>
@ -766,10 +870,7 @@ UserStore = context.UserStore
if @studentMadeDefaultSlot() if @studentMadeDefaultSlot()
message = this.slotMessage(this.state.booking.default_slot, 'accept') message = this.slotMessage(this.state.booking.default_slot, 'accept')
if @isRecurring() detail = @createDetail()
detail = `<p>Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p>Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
summary = `<div className="row"> summary = `<div className="row">
{this.userHeader(this.teacher())} {this.userHeader(this.teacher())}
<p>Has accepted your lesson request.</p> <p>Has accepted your lesson request.</p>
@ -780,10 +881,7 @@ UserStore = context.UserStore
if @studentMadeDefaultSlot() if @studentMadeDefaultSlot()
message = this.slotMessage(this.state.booking.default_slot, 'accept') message = this.slotMessage(this.state.booking.default_slot, 'accept')
if @isRecurring() detail = @createDetail()
detail = `<p className="lesson-time">Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p className="lesson-time">Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
summary = `<div className="row"> summary = `<div className="row">
{this.userHeader(this.teacher())} {this.userHeader(this.teacher())}
@ -847,10 +945,7 @@ UserStore = context.UserStore
if @studentMadeDefaultSlot() if @studentMadeDefaultSlot()
message = this.slotMessage(this.state.booking.default_slot, 'accept') message = this.slotMessage(this.state.booking.default_slot, 'accept')
if @isRecurring() detail = @createDetail()
detail = `<p>Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p>Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
summary = `<div className="row"> summary = `<div className="row">
{this.userHeader(this.student())} {this.userHeader(this.student())}
<p>Is ready to take the lesson.</p> <p>Is ready to take the lesson.</p>
@ -869,10 +964,7 @@ UserStore = context.UserStore
if @studentMadeDefaultSlot() if @studentMadeDefaultSlot()
message = this.slotMessage(this.state.booking.default_slot, 'accept') message = this.slotMessage(this.state.booking.default_slot, 'accept')
if @isRecurring() detail = @createDetail()
detail = `<p className="lesson-time">Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}</p>`
else
detail = `<p className="lesson-time">Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}</p>`
summary = `<div className="row"> summary = `<div className="row">
{this.userHeader(this.teacher())} {this.userHeader(this.teacher())}

View File

@ -57,6 +57,11 @@
@checkboxes = [{selector: 'input.slot-decision', stateKey: 'slot-decision'}, {selector: 'input.update-all', propsKey: 'update_all'}] @checkboxes = [{selector: 'input.slot-decision', stateKey: 'slot-decision'}, {selector: 'input.update-all', propsKey: 'update_all'}]
@root = $(@getDOMNode()) @root = $(@getDOMNode())
@iCheckify() @iCheckify()
@slotDate = @root.find('.date-picker')
@slotDate.datepicker({
dateFormat: "D M d yy",
onSelect: ((e) => @toggleDate(e))
})
componentDidUpdate: () -> componentDidUpdate: () ->
@iCheckify() @iCheckify()
@ -66,6 +71,14 @@
onSelect: ((e) => @toggleDate(e)) onSelect: ((e) => @toggleDate(e))
}) })
toggleDate: (e) ->
componentWillReceiveProps: (nextProps) ->
if @onlyOption()
console.log("setting it counter")
# if this isn't a counter situation, then there is no 'Accept their time', so there should only be one radio button, and we'll select that value already
@setState({"slot-decision": "counter"})
checkboxChanged: (e) -> checkboxChanged: (e) ->
@ -126,10 +139,21 @@
nullOp: ()-> nullOp: ()->
onlyOption: () ->
# (!this.props.initial && this.props.selfLastToAct ) || !(this.props.counter && !this.props.selfLastToAct)
!@multipleOptions()
multipleOptions: () ->
if this.props.initial
!(!this.props.counter && this.props.selfLastToAct)
else if this.props.counter
!this.props.selfLastToAct
else
false
render: () -> render: () ->
#showUpdateAll = !this.props.initial
showUpdateAll = !this.props.initial
if (!this.props.initial && !this.props.counter) || this.props.selfLastToAct if (!this.props.initial && !this.props.counter) || this.props.selfLastToAct
userPromptHeader = `<h3>Would you like to change this lesson?</h3>` userPromptHeader = `<h3>Would you like to change this lesson?</h3>`
@ -150,7 +174,7 @@
else else
verb = "CANCEL" verb = "CANCEL"
if this.props.update_all && showUpdateAll if this.props.update_all
actionBtnText = "#{verb} ALL LESSONS" actionBtnText = "#{verb} ALL LESSONS"
else else
actionBtnText = "#{verb} LESSON" actionBtnText = "#{verb} LESSON"
@ -159,13 +183,13 @@
else else
actionBtnText = "ACCEPT & UPDATE LESSON" actionBtnText = "ACCEPT & UPDATE LESSON"
counterClasses={field: true, 'slot-decision-field': true, error: this.props.counterErrors?, counterSelected: this.props.slot_decision == 'counter'} counterClasses={field: true, 'slot-decision-field': true, error: this.props.counterErrors?, counterSelected: this.props.slot_decision == 'counter', onlyOption: @onlyOption()}
if this.props.counterErrors? if this.props.counterErrors?
errorText = window.JK.reactErrors(this.props.counterErrors, {day_of_week: 'Day' }) errorText = window.JK.reactErrors(this.props.counterErrors, {day_of_week: 'Day' })
if this.props.is_recurring if this.props.is_recurring && this.props.update_all
slotAltPrompt = `<div className="slot-alt-prompt"> slotAltPrompt = `<div className="slot-alt-prompt">
<span className="alt-date-block"> <span className="alt-date-block">
@ -182,17 +206,18 @@
</span> </span>
</div>` </div>`
if showUpdateAll #if @props.update_all
updateAllField = # updateAllField =
`<div className="field update-all-field"> # `<div className="field update-all-field">
<input disabled={this.props.disabled} className="update-all" type="checkbox" name="update-all" readyOnly="true" onChange={this.nullOp} checked={this.props.update_all} /><label>Update all lessons</label> # <input disabled={this.props.disabled} className="update-all" type="checkbox" name="update-all" readyOnly="true" onChange={this.nullOp} checked={this.props.update_all} /><label>Update all lessons</label>
</div>` # </div>`
updateAllField = null
else else
slotAltPrompt = `<div className="slot-alt-prompt"> slotAltPrompt = `<div className="slot-alt-prompt">
<span className="alt-date-block"> <span className="alt-date-block">
<span className="alt-date">Date:</span> <span className="alt-date">Date:</span>
<input className="date-picker" type="text" data-slot={i}></input> <input className="date-picker" name="alt-date-input" type="text" data-slot={i}></input>
</span> </span>
<span className="alt-time-block"> <span className="alt-time-block">
<span className="alt-time">Time:</span> <span className="alt-time">Time:</span>
@ -222,7 +247,7 @@
else else
slotDetail = `<div className="slot-detail">{slot.slotTime}</div>` slotDetail = `<div className="slot-detail">{slot.slotTime}</div>`
slots.push(`<div key={slot.id} className="field slot-decision-field"> slots.push(`<div key={slot.id} data-slot-id={slot.id} className="field slot-decision-field">
<div className="label-area"> <div className="label-area">
<input disabled={this.props.disabled} className="slot-decision" type="radio" name="slot-decision" <input disabled={this.props.disabled} className="slot-decision" type="radio" name="slot-decision"
value={slot.id} readyOnly={true} defaultChecked={slot.id == this.props.slot_decision} /><label>{this.slotLabelText(i, slot)}</label> value={slot.id} readyOnly={true} defaultChecked={slot.id == this.props.slot_decision} /><label>{this.slotLabelText(i, slot)}</label>
@ -234,6 +259,12 @@
# if you have issued a counter, you should be able to withdraw it # if you have issued a counter, you should be able to withdraw it
# TODO # TODO
#cancelField = `<div className="field slot-decision-field">
# <input disabled={this.props.disabled} className="slot-decision" type="radio" name="slot-decision" value="decline" readyOnly={true} defaultChecked={this.props.slot_decision == 'decline'} /><label>{declineVerb} lesson
# request</label>
#</div>`
cancelField = null
`<div className="row"> `<div className="row">
<div className="column column-left"> <div className="column column-left">
{userPromptHeader} {userPromptHeader}
@ -247,10 +278,7 @@
</div> </div>
{slotAltPrompt} {slotAltPrompt}
</div> </div>
<div className="field slot-decision-field"> {cancelField}
<input disabled={this.props.disabled} className="slot-decision" type="radio" name="slot-decision" value="decline" readyOnly={true} defaultChecked={this.props.slot_decision == 'decline'} /><label>{declineVerb} lesson
request</label>
</div>
{updateAllField} {updateAllField}
</div> </div>
</div> </div>

View File

@ -294,18 +294,24 @@ UserStore = context.UserStore
logger.debug("testDriveCount: " + testDriveCount) logger.debug("testDriveCount: " + testDriveCount)
testDriveCountInt = parseInt(testDriveCount);
if context._.isNaN(testDriveCountInt)
testDriveCountInt = 3
context.JK.GA.trackTestDrivePurchase(testDriveCountInt);
if response.test_drive?.teacher_id if response.test_drive?.teacher_id
teacher_id = response.test_drive.teacher_id teacher_id = response.test_drive.teacher_id
if testDriveCount == 1 if testDriveCount == '1'
text = "You have purchased a TestDrive credit and have used it to request a JamClass with #{@state.teacher.name}. The teacher has received your request and should respond shortly." text = "You have purchased 1 TestDrive credit and have used it to request a JamClass with #{@state.teacher.name}. The teacher has received your request and should respond shortly."
else else
text = "You have purchased #{testDriveCount} TestDrive credits and have used 1 credit it to request a JamClass with #{@state.teacher.name}. The teacher has received your request and should respond shortly." text = "You have purchased #{testDriveCount} TestDrive credits and have used 1 credit to request a JamClass with #{@state.teacher.name}. The teacher has received your request and should respond shortly."
location = "/client#/jamclass" location = "/client#/jamclass"
else else
if @state.teacher?.id if @state.teacher?.id
# the user bought the testdrive, and there is a teacher of interest in context (but no booking) # the user bought the testdrive, and there is a teacher of interest in context (but no booking)
if testDriveCount == 1 if testDriveCount == '1'
text = "You now have 1 TestDrive credit.<br/><br/>We've taken you to the lesson booking screen for the teacher you initially showed interest in." text = "You now have 1 TestDrive credit.<br/><br/>We've taken you to the lesson booking screen for the teacher you initially showed interest in."
location = "/client#/jamclass/book-lesson/test-drive_" + teacher_id location = "/client#/jamclass/book-lesson/test-drive_" + teacher_id
else else
@ -313,7 +319,7 @@ UserStore = context.UserStore
location = "/client#/jamclass/book-lesson/test-drive_" + teacher_id location = "/client#/jamclass/book-lesson/test-drive_" + teacher_id
else else
# the user bought test drive, but 'cold' , i.e., no teacher in context # the user bought test drive, but 'cold' , i.e., no teacher in context
if testDriveCount == 1 if testDriveCount == '1'
text = "You now have 1 TestDrive credit.<br/><br/>We've taken you to the Teacher Search screen, so you can search for teachers right for you." text = "You now have 1 TestDrive credit.<br/><br/>We've taken you to the Teacher Search screen, so you can search for teachers right for you."
location = "/client#/teachers/search" location = "/client#/teachers/search"
else else

View File

@ -0,0 +1,49 @@
context = window
@MusicNotationUploadDialog = React.createClass({
mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(AttachmentStore, "onAttachmentStore")]
getInitialState: () ->
{
uploading: false
}
onAppInit: (@app) ->
onAttachmentStore: (attachmentState) ->
@setState(attachmentState)
handleCloseMessage: (e) ->
e.preventDefault()
@app.layout.closeDialog('music-notation-upload-dialog')
render: () ->
if @state.uploading
state =
`<div>
<h3>Please wait while we upload your attachment to the lesson...</h3>
<div className="spinner spinner-large"></div>
</div>`
else
state =
`<div>
<h3>Your file has been uploaded.</h3>
</div>`
`<div className="MusicNotationUploadDialog">
<div className="content-head">
<img className="content-icon" src="/assets/content/icon_add.png" height={19} width={19}/>
<h1>Uploading Attachment</h1>
</div>
<div className="dialog-inner">
{state}
<div className="actions">
<a className="button-orange " onClick={this.handleCloseMessage}>CLOSE</a>
</div>
</div>
</div>`
})

View File

@ -704,14 +704,21 @@ proficiencyDescriptionMap = {
render: () -> render: () ->
if @state.user? if @state.user?
avatar = context.JK.resolveAvatarUrl(@state.user.photo_url); avatar = context.JK.resolveAvatarUrl(@state.user.photo_url);
if @state.user?.teacher? if @state.user?
mainContent = @mainContent() if @state.user.teacher?
profileLeft = @profileLeft() mainContent = @mainContent()
else profileLeft = @profileLeft()
if context.JK.currentUserId? && @state.user?.id == context.JK.currentUserId
noTeacherProfile = `<div className="no-teacher-profile">You have no teacher profile defined yet. <a onClick={this.editTeacherProfile}>Start making one now.</a></div>`
else else
noTeacherProfile = `<div className="no-teacher-profile">This user has no teacher profile.</div>` if context.JK.currentUserId? && @state.user?.id == context.JK.currentUserId
noTeacherProfile = `<div className="no-teacher-profile">You have no teacher profile defined yet. <a onClick={this.editTeacherProfile}>Start making one now.</a></div>`
else
noTeacherProfile = `<div className="no-teacher-profile">This user has no teacher profile.</div>`
mainContent = `<div className="">
{noTeacherProfile}
</div>`
else
noTeacherProfile = `<div><div className="loading-profile">Loading profile...</div><div className="spinner spinner-large"></div></div>`
mainContent = `<div className=""> mainContent = `<div className="">
{noTeacherProfile} {noTeacherProfile}
</div>` </div>`
@ -730,10 +737,7 @@ proficiencyDescriptionMap = {
</div>` </div>`
`<div className="content-body-scroller"> `<div className="content-body-scroller">
<div className="profile-header profile-head"> <div className="profile-header profile-head">
<JamClassSearchHeader teacher={this.state.user}/>
<div className="user-header">
<h2 id="username"></h2>
</div>
{actionButtons} {actionButtons}
<br clear="all"/><br /> <br clear="all"/><br />

View File

@ -5,11 +5,25 @@ LocationActions = @LocationActions
@TeacherSearchOptionsScreen = React.createClass({ @TeacherSearchOptionsScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged"), mixins: [ICheckMixin, Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged"),
Reflux.listenTo(@TeacherSearchStore, "onTeacherSearchChanged")] Reflux.listenTo(@TeacherSearchStore, "onTeacherSearchChanged")]
LIMIT: 20 LIMIT: 20
componentDidMount: () ->
@checkboxes = [
{selector: 'input.onlyMySchool', stateKey: 'onlyMySchool'},
{selector: 'input.beginner-level', stateKey: 'beginner-level'},
{selector: 'input.intermediate-level', stateKey: 'intermediate-level'},
{selector: 'input.advanced-level', stateKey: 'advanced-level'}]
@root = $(@getDOMNode())
@iCheckify()
componentDidUpdate: () ->
@iCheckify()
getInitialState: () -> getInitialState: () ->
{options: {onlyMySchool: true}} {options: {onlyMySchool: true}}
@ -71,6 +85,20 @@ LocationActions = @LocationActions
options.onlyMySchool = checked options.onlyMySchool = checked
@setState(options) @setState(options)
checkboxChanged: (e) ->
$target = $(e.target)
if $target.is('.onlyMySchool')
state = {}
state['onlyMySchool'] = $target.is(':checked')
@setState(state)
@onlyMySchoolChange(e)
else
state = {}
state[$target.attr('name')] = $target.is(':checked')
@setState(state)
@levelChanged(e)
render: () -> render: () ->
if @state.user?.school_id? if @state.user?.school_id?
onlySchoolOption = onlySchoolOption =
@ -114,15 +142,15 @@ LocationActions = @LocationActions
<h3>Student Levels Taught:</h3> <h3>Student Levels Taught:</h3>
<div className="teaching-level beginner-level"> <div className="teaching-level beginner-level">
<input name="beginner-level" type="checkbox" data-purpose="teaches_beginner" onChange={this.levelChanged} checked={this.state.options.teaches_beginner}></input> <input className="teachlevel beginner-level" name="beginner-level" type="checkbox" data-purpose="teaches_beginner" onChange={this.levelChanged} checked={this.state.options.teaches_beginner}></input>
<label htmlFor="beginner-level">Beginner</label> <label htmlFor="beginner-level">Beginner</label>
</div> </div>
<div className="teaching-level intermediate-level"> <div className="teaching-level intermediate-level">
<input name="intermediate-level" type="checkbox" data-purpose="teaches_intermediate" onChange={this.levelChanged} checked={this.state.options.teaches_intermediate}></input> <input className="teachlevel intermediate-level" name="intermediate-level" type="checkbox" data-purpose="teaches_intermediate" onChange={this.levelChanged} checked={this.state.options.teaches_intermediate}></input>
<label htmlFor="intermediate-level">Intermediate</label> <label htmlFor="intermediate-level">Intermediate</label>
</div> </div>
<div className="teaching-level advanced-level"> <div className="teaching-level advanced-level">
<input name="advanced-level" type="checkbox" data-purpose="teaches_advanced" onChange={this.levelChanged} checked={this.state.options.teaches_advanced}></input> <input className="teachlevel advanced-level" name="advanced-level" type="checkbox" data-purpose="teaches_advanced" onChange={this.levelChanged} checked={this.state.options.teaches_advanced}></input>
<label htmlFor="advanced-level">Advanced</label> <label htmlFor="advanced-level">Advanced</label>
</div> </div>
</div> </div>

View File

@ -28,7 +28,7 @@ ProfileActions = @ProfileActions
refreshing: false refreshing: false
getInitialState: () -> getInitialState: () ->
{searchOptions: {}, results: [], user: null} {searchOptions: {}, results: [], user: null, searching: false}
onAppInit: (@app) -> onAppInit: (@app) ->
@app.bindScreen('teachers/search', {beforeShow: @beforeShow, afterShow: @afterShow, afterHide: @afterHide}) @app.bindScreen('teachers/search', {beforeShow: @beforeShow, afterShow: @afterShow, afterHide: @afterHide})
@ -58,7 +58,7 @@ ProfileActions = @ProfileActions
@needToSearch = true @needToSearch = true
onTeacherSearchResultsStore: (results) -> onTeacherSearchResultsStore: (results) ->
results.searching = false #results.searching = false
@refreshing = false @refreshing = false
@contentBodyScroller.find('.infinite-scroll-loader-2').remove() @contentBodyScroller.find('.infinite-scroll-loader-2').remove()
@setState(results) @setState(results)
@ -89,7 +89,7 @@ ProfileActions = @ProfileActions
if $scroller.scrollTop() + $scroller.innerHeight() + 100 >= $scroller[0].scrollHeight if $scroller.scrollTop() + $scroller.innerHeight() + 100 >= $scroller[0].scrollHeight
$scroller.append('<div class="infinite-scroll-loader-2">... Loading more Teachers ...</div>') $scroller.append('<div class="infinite-scroll-loader-2">... Loading more Teachers ...</div>')
@refreshing = true @refreshing = true
@setState({searching: true}) #@setState({searching: true})
logger.debug("refreshing more teachers for infinite scroll") logger.debug("refreshing more teachers for infinite scroll")
TeacherSearchResultsActions.nextPage() TeacherSearchResultsActions.nextPage()
) )
@ -149,7 +149,9 @@ ProfileActions = @ProfileActions
moreAboutTeacher: (user, e) -> moreAboutTeacher: (user, e) ->
e.preventDefault() e.preventDefault()
ProfileActions.viewTeacherProfile(user, '/client#/teachers/search', 'BACK TO TEACHER SEARCH') context.location = "/client#/profile/teacher/#{user.id}"
#ProfileActions.viewTeacherProfile(user, '/client#/teachers/search', 'BACK TO TEACHER SEARCH')
bookTestDrive: (user, e) -> bookTestDrive: (user, e) ->
e.preventDefault() e.preventDefault()
@ -210,162 +212,60 @@ ProfileActions = @ProfileActions
target.trigger( 'destroy.dot' ); target.trigger( 'destroy.dot' );
teacherBio.css('height', 'auto') teacherBio.css('height', 'auto')
createSearchDescription: () ->
searchOptions = TeacherSearchStore.getState()
summary = ''
if searchOptions.onlyMySchool
summary += "From My School Only"
instruments = searchOptions.instruments
if instruments? && instruments.length > 0
if instruments.length == 1
bit = "Instrument = #{InstrumentStore.display(instruments[0])}"
else
instruments.length > 1
bit = "Instruments = #{InstrumentStore.display(instruments[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
subjects = searchOptions.subjects
if subjects? && subjects.length > 0
if subjects.length == 1
bit = "Subject = #{SubjectStore.display(subjects[0])}"
else
subjects.length > 1
bit = "Subjects = #{SubjectStore.display(subjects[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
genres = searchOptions.genres
if genres? && genres.length > 0
if genres.length == 1
bit = "Genre = #{GenreStore.display(genres[0])}"
else
genres.length > 1
bit = "Genres = #{GenreStore.display(genres[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
languages = searchOptions.languages
if languages? && languages.length > 0
if languages.length == 1
bit = "Language = #{LanguageStore.display(languages[0])}"
else
languages.length > 1
bit = "Languages = #{LanguageStore.display(languages[0])} ... "
if summary.length > 0
summary += ', '
summary += " #{bit}"
if searchOptions.teaches_beginner || searchOptions.teaches_intermediate || searchOptions.teaches_advanced
bit = "Teaches "
qualifier = ''
if searchOptions.teaches_beginner
qualifier += "Beginner"
if searchOptions.teaches_intermediate
if qualifier.length > 0
qualifier += ", "
qualifier += "Intermediate"
if searchOptions.teaches_advanced
if qualifier.length > 0
qualifier += ", "
qualifier += "Advanced"
if summary.length > 0
summary += ', '
summary += " #{bit}#{qualifier}"
if searchOptions.student_age?
if summary.length > 0
summary += ', '
summary += "Student Age = #{searchOptions.student_age}"
if searchOptions.years_teaching?
if summary.length > 0
summary += ', '
summary += "Years Teaching = #{searchOptions.years_teaching}"
if searchOptions.location?.country?
if summary.length > 0
summary += ', '
summary += "Country = #{searchOptions.location.country}"
if searchOptions.location?.region?
if summary.length > 0
summary += ', '
summary += "Region = #{searchOptions.location.region}"
if summary.length == 0
summary = 'all teachers'
summary
render: () -> render: () ->
searchDesc = @createSearchDescription()
resultsJsx = [] resultsJsx = []
for user in @state.results if @state.currentPage == 1 && @state.searching
resultsJsx = `<div className="spinner spinner-large"></div>`
else
for user in @state.results
photo_url = user.photo_url photo_url = user.photo_url
if !photo_url? if !photo_url?
photo_url = '/assets/shared/avatar_generic.png' photo_url = '/assets/shared/avatar_generic.png'
bio = user.teacher.biography bio = user.teacher.biography
if !bio? if !bio?
bio = 'No bio' bio = 'No bio'
school_on_school = user.teacher.school_id? && @state.user?.school_id? && user.teacher.school_id == @state.user.school_id school_on_school = user.teacher.school_id? && @state.user?.school_id? && user.teacher.school_id == @state.user.school_id
bookSingleBtn = null bookSingleBtn = null
bookTestDriveBtn = null bookTestDriveBtn = null
if !school_on_school && (!@state.user? || @state.user.remaining_test_drives > 0 || @state.user['can_buy_test_drive?']) if !school_on_school && (!@state.user? || @state.user.remaining_test_drives > 0 || @state.user['can_buy_test_drive?'])
bookTestDriveBtn = `<a className="button-orange try-test-drive" onClick={this.bookTestDrive.bind(this, user)}>BOOK TESTDRIVE LESSON</a>` bookTestDriveBtn = `<a className="button-orange try-test-drive" onClick={this.bookTestDrive.bind(this, user)}>BOOK TESTDRIVE LESSON</a>`
else else
bookSingleBtn = `<a className="button-orange try-normal" onClick={this.bookNormalLesson.bind(this, user)}>BOOK LESSON</a>` bookSingleBtn = `<a className="button-orange try-normal" onClick={this.bookNormalLesson.bind(this, user)}>BOOK LESSON</a>`
resultsJsx.push(`<div key={user.id} className="teacher-search-result" data-teacher-id={user.id}> resultsJsx.push(`<div key={user.id} className="teacher-search-result" data-teacher-id={user.id}>
<div className="user-avatar"> <div className="user-avatar">
<div className="avatar small"> <div className="avatar small">
<img src={photo_url} /> <img src={photo_url} />
</div>
<div className="user-name">
{user.name}
</div>
</div> </div>
<div className="user-name"> <div className="user-info">
{user.name} <div className="teacher-bio">
{bio}
<a className="readmore" onClick={this.readMore}>more</a>
</div>
<div className="teacher-actions">
<a className="button-orange more-about-teacher" onClick={this.moreAboutTeacher.bind(this, user)}>MORE ABOUT THIS TEACHER</a>
{bookTestDriveBtn}
{bookSingleBtn}
</div>
</div> </div>
</div> <br className="clearall" />
<div className="user-info"> </div>`)
<div className="teacher-bio">
{bio}
<a className="readmore" onClick={this.readMore}>more</a>
</div>
<div className="teacher-actions">
<a className="button-orange more-about-teacher" onClick={this.moreAboutTeacher.bind(this, user)}>MORE ABOUT THIS TEACHER</a>
{bookTestDriveBtn}
{bookSingleBtn}
</div>
</div>
<br className="clearall" />
</div>`)
`<div className="content-body-scroller"> `<div className="content-body-scroller">
<div className="screen-content"> <div className="screen-content">
<div className="header"> <div className="header">
<a href="/client#/home">JamKazam Home</a>&nbsp;:&nbsp; <JamClassSearchHeader/>
<a href="/client#/jamclass">JamClass Home</a>&nbsp;:&nbsp;
<a className="teacher-search-options" href="/client#/jamclass/searchOptions" >Teachers Search</a><span className="teacher-quote"> :</span>
<span className="search-results-options">
<span className="search-description">
<span className="results-text">Search Results / </span>
<span className="search-summary">{searchDesc}</span>
</span>
</span>
</div> </div>
<div className="results"> <div className="results">
{resultsJsx} {resultsJsx}

View File

@ -5,4 +5,5 @@ context = window
startAttachNotation: {} startAttachNotation: {}
startAttachAudio: {} startAttachAudio: {}
uploadNotations: {} uploadNotations: {}
uploadAudios: {}
}) })

View File

@ -45,8 +45,8 @@ rest = context.JK.Rest()
</li> </li>
</ol> </ol>
<p>While you're getting this done, if you want to learn more about all the nifty features you can access in <p>While you're getting this done, if you want to learn more about all the nifty features you can access in
JamClass and in JamKazam in general, you can check out our online <a href="" target="_blank" JamClass and in JamKazam in general, you can check out our online <a target="_blank"
onClick={alert.bind('not yet')}>JamClass href="https://jamkazam.desk.com/customer/en/portal/topics/926073-jamclass-online-music-lessons---for-students/articles">JamClass
User Guide</a>.</p> User Guide</a>.</p>
</div>` </div>`
`<div className="top-container"> `<div className="top-container">

View File

@ -40,14 +40,18 @@ teacherActions = window.JK.Actions.Teacher
enableICheck: (e) -> enableICheck: (e) ->
if !@root? if !@root?
return return
checkBoxes = @root.find('input[type="checkbox"]')
if checkBoxes.length > 0 for checkbox in @checkboxes
context.JK.checkbox(checkBoxes) selector = checkbox.selector
checkBoxes.on('ifChanged', (e) => @checkIfCanFire(e))
radioBoxes = @root.find('input[type="radio"]') checkBoxes = @root.find(selector + '[type="checkbox"]')
if radioBoxes.length > 0 if checkBoxes.length > 0
context.JK.checkbox(radioBoxes) context.JK.checkbox(checkBoxes)
radioBoxes.on('ifChanged', (e) => @checkIfCanFire(e)) checkBoxes.on('ifChanged', (e) => @checkIfCanFire(e))
radioBoxes = @root.find(selector + '[type="radio"]')
if radioBoxes.length > 0
context.JK.checkbox(radioBoxes)
radioBoxes.on('ifChanged', (e) => @checkIfCanFire(e))
true true

View File

@ -23,7 +23,7 @@ teacherActions = window.JK.Actions.Teacher
lesson.isAdmin = context.JK.currentUserAdmin lesson.isAdmin = context.JK.currentUserAdmin
lesson.schoolOnSchool = lesson['school_on_school?'] lesson.schoolOnSchool = lesson['school_on_school?']
lesson.cardNotOk = !lesson.schoolOnSchool && !lesson.lesson_booking.card_presumed_ok lesson.cardNotOk = !lesson.schoolOnSchool && !lesson.lesson_booking.card_presumed_ok
lesson.isActive = lesson['is_active?']
if (lesson.status == 'requested' || lesson.status == 'countered') if (lesson.status == 'requested' || lesson.status == 'countered')
lesson.isRequested = true lesson.isRequested = true
if lesson.cardNotOk if lesson.cardNotOk

View File

@ -105,6 +105,8 @@ AttachmentActions = @AttachmentActions
formData.append('lesson_session_id', @lessonId); formData.append('lesson_session_id', @lessonId);
formData.append('attachment_type', 'notation') formData.append('attachment_type', 'notation')
@app.layout.showDialog('music-notation-upload-dialog')
rest.uploadMusicNotations(formData) rest.uploadMusicNotations(formData)
.done((response) => @doneUploadingNotatations(notations, response, doneCallback, failCallback)) .done((response) => @doneUploadingNotatations(notations, response, doneCallback, failCallback))
.fail((jqXHR) => @failUploadingNotations(jqXHR, failCallback)) .fail((jqXHR) => @failUploadingNotations(jqXHR, failCallback))
@ -137,6 +139,71 @@ AttachmentActions = @AttachmentActions
else else
@app.notifyServerError(jqXHR, "Unable to upload music notations"); @app.notifyServerError(jqXHR, "Unable to upload music notations");
onUploadAudios: (notations, doneCallback, failCallback) ->
logger.debug("beginning upload of audio", notations)
@uploading = true
@changed()
formData = new FormData()
maxExceeded = false;
$.each(notations, (i, file) => (
max = 10 * 1024 * 1024;
if file.size > max
maxExceeded = true
return false
formData.append('files[]', file)
))
if maxExceeded
@app.notify({
title: "Maximum Music Audio Size Exceeded",
text: "You can only upload files up to 10 megabytes in size."
})
failCallback()
@uploading = false
@changed()
return
formData.append('lesson_session_id', @lessonId);
formData.append('attachment_type', 'audio')
@app.layout.showDialog('music-notation-upload-dialog')
rest.uploadMusicNotations(formData)
.done((response) => @doneUploadingAudios(notations, response, doneCallback, failCallback))
.fail((jqXHR) => @failUploadingAudios(jqXHR, failCallback))
doneUploadingAudios: (notations, response, doneCallback, failCallback) ->
@uploading = false
@changed()
error_files = [];
$.each(response, (i, music_notation) => (
if music_notation.errors
error_files.push(notations[i].name)
)
)
if error_files.length > 0
failCallback()
@app.notifyAlert("Failed to upload audio files.", error_files.join(', '));
else
doneCallback()
failUploadingAudios: (jqXHR, failCallback) ->
@uploading = false
@changed()
if jqXHR.status == 413
# the file is too big. Let the user know.
# This should happen when they select the file, but a misconfiguration on the server could cause this.
@app.notify({
title: "Maximum Music Audio Size Exceeded",
text: "You can only upload files up to 10 megabytes in size."
})
else
@app.notifyServerError(jqXHR, "Unable to upload music audio files");
changed: () -> changed: () ->
this.trigger({lessonId: @lessonId, uploading: @uploading}) this.trigger({lessonId: @lessonId, uploading: @uploading})
} }

View File

@ -11,6 +11,7 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions
results: [] results: []
page: 1 page: 1
limit: 20 limit: 20
searching: false
init: -> init: ->
# Register with the app store to get @app # Register with the app store to get @app
@ -22,6 +23,7 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions
onReset: () -> onReset: () ->
@results = [] @results = []
@page = 1 @page = 1
@searching = true
@changed() @changed()
query = @createQuery() query = @createQuery()
@ -29,11 +31,13 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions
rest.searchTeachers(query) rest.searchTeachers(query)
.done((response) => .done((response) =>
@next = response.next @next = response.next
@searching = false
@results.push.apply(@results, response.entries) @results.push.apply(@results, response.entries)
@changed() @changed()
) )
.fail((jqXHR, textStatus, errorMessage) => .fail((jqXHR, textStatus, errorMessage) =>
@searching = false
@changed()
@app.ajaxError(jqXHR, textStatus, errorMessage) @app.ajaxError(jqXHR, textStatus, errorMessage)
) )
@ -42,18 +46,22 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions
query = @createQuery() query = @createQuery()
@searching = true
rest.searchTeachers(query) rest.searchTeachers(query)
.done((response) => .done((response) =>
@next = response.next @next = response.next
@results.push.apply(@results, response.entries) @results.push.apply(@results, response.entries)
@searching = false
@changed() @changed()
) )
.fail((jqXHR, textStatus, errorMessage) => .fail((jqXHR, textStatus, errorMessage) =>
@searching = false
@app.ajaxError(jqXHR, textStatus, errorMessage) @app.ajaxError(jqXHR, textStatus, errorMessage)
@changed()
) )
getState: () -> getState: () ->
({results: @results, next: @next, currentPage: @page}) ({results: @results, next: @next, currentPage: @page, searching: @searching})
changed:() -> changed:() ->
@trigger(@getState()) @trigger(@getState())

View File

@ -76,7 +76,8 @@
$('.like-link').click(function() { $('.like-link').click(function() {
var like_site = $(this).data('site'); var like_site = $(this).data('site');
JK.GA.trackJKSocial(JK.GA.Categories.jkLike, like_site, JK.clientType()); // removed because we are juggling 20 events max in GA
//JK.GA.trackJKSocial(JK.GA.Categories.jkLike, like_site, JK.clientType());
window.open("/endorse/0/"+like_site, '_blank'); window.open("/endorse/0/"+like_site, '_blank');
}); });
} }

View File

@ -190,7 +190,7 @@
logger.debug("marking all unassigned inputs length=(" + $allInputs.length + ")") logger.debug("marking all unassigned inputs length=(" + $allInputs.length + ")")
var maxTries = 20; var maxTries = 12; // we only allow up to 6 tracks, 12 inputs
for(var i = 0; i < maxTries; i++) { for(var i = 0; i < maxTries; i++) {
$unassignedInputs = $inputChannels.find('input[type="checkbox"]:not(:checked)'); $unassignedInputs = $inputChannels.find('input[type="checkbox"]:not(:checked)');
@ -1105,6 +1105,11 @@
return false; return false;
} }
if (!passedOnce) {
passedOnce = true;
autoAssignToSingleInput();
}
if(!savedProfile) { if(!savedProfile) {
context.jamClient.FTUESetMusicProfileName(gearUtils.createProfileName(selectedDeviceInfo)); context.jamClient.FTUESetMusicProfileName(gearUtils.createProfileName(selectedDeviceInfo));
var result = context.jamClient.FTUESave(true); var result = context.jamClient.FTUESave(true);
@ -1122,10 +1127,6 @@
} }
} }
if (!passedOnce) {
passedOnce = true;
autoAssignToSingleInput();
}
// keep the shared state between step 2 and step 3 up-to-date // keep the shared state between step 2 and step 3 up-to-date
wizard.setChosenInputs(context._.map($assignedInputs, function(input) { return $(input).attr('data-id') })); wizard.setChosenInputs(context._.map($assignedInputs, function(input) { return $(input).attr('data-id') }));
@ -1147,12 +1148,12 @@
} }
var $unassignedInputs = $inputChannels.find('input[type="checkbox"]:not(:checked)') var $unassignedInputs = $inputChannels.find('input[type="checkbox"]:not(:checked)')
$allInputs.eq(0).iCheck('check').attr('checked', 'checked') $allInputs.eq(0).iCheck('check').attr('checked', 'checked')
var firstInputDomNode = $allInputs.get(0)
var maxTries = 20; var maxTries = 36;
for(var i = 0; i < maxTries; i++) { for(var i = 0; i < maxTries; i++) {
var $assignedInputs = $inputChannels.find('input[type="checkbox"]:checked') var $assignedInputs = $inputChannels.find('input[type="checkbox"]:checked')
var $allInputs = $inputChannels.find('input[type="checkbox"]')
var firstInputDomNode = $allInputs.get(0)
if ($assignedInputs.length == 1) { if ($assignedInputs.length == 1) {
break; break;
} }
@ -1169,9 +1170,7 @@
} }
}) })
} }
} }
} }
function onFocus() { function onFocus() {
@ -1182,6 +1181,7 @@
} }
function newSession() { function newSession() {
passedOnce = false;
savedProfile = false; savedProfile = false;
initialScan = false; initialScan = false;
deviceInformation = gearUtils.loadDeviceInfo(); deviceInformation = gearUtils.loadDeviceInfo();

View File

@ -79,6 +79,15 @@
} }
} }
.onlyOption {
input[type="radio"], .iradio_minimal {
display:none;
}
.slot-alt-prompt {
padding-left:0;
}
}
.slot-detail { .slot-detail {
color: $ColorTextTypical; color: $ColorTextTypical;
display: inline-block; display: inline-block;
@ -160,6 +169,14 @@
.user-name { .user-name {
line-height:48px; line-height:48px;
vertical-align:middle; vertical-align:middle;
color:white;
}
.request-sent {
color:$ColorTextTypical;
.generic-time-stmt, .proposing-new-time {
margin-top:10px !important;
}
} }
.avatar { .avatar {
position:absolute; position:absolute;

View File

@ -194,7 +194,22 @@
.years {float:right} .years {float:right}
} }
.profileNavActions { .profileNavActions {
margin-right: -3px; right:20px;
top:10px;
position:absolute;
}
.spinner-large {
width:200px;
height:200px;
position:relative;
margin:0 auto;
}
.loading-profile {
text-align:center;
color:white;
} }
.ratings-block { .ratings-block {

View File

@ -1,5 +1,41 @@
@import "client/common"; @import "client/common";
.jamclass-search-header {
a {
font-size:16px;
text-decoration:underline;
margin-bottom:5px;
}
.search-results-options {
font-size:16px;
color:$ColorTextTypical;
}
.search-summary {
font-size:11px;
}
.search-description {
white-space: nowrap;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
vertical-align:middle;
}
.search-summary {
line-height:16px;
vertical-align: middle;
}
.results-text {
&.link {
}
}
}
#teacherSearch { #teacherSearch {
div[data-react-class="TeacherSearchScreen"] { div[data-react-class="TeacherSearchScreen"] {
height:100%; height:100%;
@ -9,29 +45,21 @@
padding:20px; padding:20px;
} }
.header { .jamclass-search-header {
margin-bottom:10px; margin-bottom:10px;
}
.spinner-large {
width:200px;
a { height:200px;
font-size:16px; position:relative;
text-decoration:underline; margin:0 auto;
margin-bottom:5px;
}
.search-results-options {
font-size:16px;
color:$ColorTextTypical;
}
} }
a.readmore { a.readmore {
display:none; display:none;
} }
.search-summary {
font-size:11px;
}
.teacher-search-result { .teacher-search-result {
@include border_box_sizing; @include border_box_sizing;
clear:both; clear:both;
@ -108,17 +136,7 @@
padding-right: 31px; padding-right: 31px;
margin-bottom: 20px; margin-bottom: 20px;
} }
.search-description {
white-space: nowrap;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
vertical-align:middle;
}
.search-summary {
line-height:16px;
vertical-align: middle;
}
.result-text { .result-text {
line-height:16px; line-height:16px;
} }

View File

@ -9,6 +9,29 @@
.checkbox-scroller { .checkbox-scroller {
height:100px; height:100px;
margin-top:0; margin-top:0;
.checkItem {
clear: both;
margin-bottom:4px;
label {
color: black;
display: inline;
float: left;
font-size: 1em;
}
.icheckbox_minimal {
color: black;
display: inline;
float: left;
font-size: 1em;
margin-right:5px;
}
input {
width: auto;
text-align: left;
float: left;
display: inline;
}
}
} }
min-width:200px; min-width:200px;
width:25%; width:25%;
@ -72,6 +95,10 @@
} }
.student-levels-taught { .student-levels-taught {
.icheckbox_minimal {
top:3px;
margin-right:5px;
}
.teaching-level { .teaching-level {
margin-bottom:10px; margin-bottom:10px;
} }

View File

@ -0,0 +1,35 @@
@import "client/common";
#music-notation-upload-dialog {
width: 500px;
max-height:500px;
h3 {
color:white;
margin-bottom:20px;
}
.dialog-inner {
width: auto;
height:100%;
@include border_box_sizing;
margin-top: -29px;
padding: 50px 25px 25px;
}
div[data-react-class="MusicNotationUploadDialog"] {
}
.MusicNotationUploadDialog {
height:100%;
}
.spinner-large {
width:200px;
height:200px;
line-height: 200px;
position:relative;
margin:25px auto;
}
.actions {
text-align:right;
}
}

View File

@ -16,6 +16,11 @@
overflow-y:auto; overflow-y:auto;
} }
.title-artist {
margin-left:25px;
}
.action-buttons { .action-buttons {
margin-bottom:10px; margin-bottom:10px;
} }

View File

@ -13,7 +13,6 @@
height: 36px; height: 36px;
} }
.recording-controls { .recording-controls {
margin-top: 15px; margin-top: 15px;
padding: 3px 5px 3px 10px; padding: 3px 5px 3px 10px;

View File

@ -52,7 +52,7 @@ class ApiLessonBookingsController < ApiController
specified_slot.timezone = params[:timezone] specified_slot.timezone = params[:timezone]
slots << specified_slot slots << specified_slot
end end
@lesson_booking = LessonBooking.book_free(current_user, teacher, slots, params[:description]) @lesson_booking = LessonBooking.book_free(current_user, teacher, slots, params[:description])
if @lesson_booking.errors.any? if @lesson_booking.errors.any?
@ -129,10 +129,10 @@ class ApiLessonBookingsController < ApiController
def accept def accept
next_lesson = @lesson_booking.next_lesson next_lesson = @lesson_booking.next_lesson
result = next_lesson.accept({ result = next_lesson.accept({
message: params[:message], message: params[:message],
slot: params[:slot], slot: params[:slot],
accepter: current_user accepter: current_user
}) })
if result.errors.any? if result.errors.any?
if result.is_a?(JamRuby::LessonBooking) if result.is_a?(JamRuby::LessonBooking)
@ -159,16 +159,20 @@ class ApiLessonBookingsController < ApiController
slot = LessonBookingSlot.new slot = LessonBookingSlot.new
if @lesson_booking.recurring if @lesson_booking.recurring
slot.slot_type = LessonBookingSlot::SLOT_TYPE_RECURRING if params[:update_all]
slot.day_of_week = params[:day_of_week] slot.slot_type = LessonBookingSlot::SLOT_TYPE_RECURRING
else
slot.slot_type = LessonBookingSlot::SLOT_TYPE_SINGLE
end
else else
slot.slot_type = LessonBookingSlot::SLOT_TYPE_SINGLE slot.slot_type = LessonBookingSlot::SLOT_TYPE_SINGLE
end
if params[:date].present? slot.day_of_week = params[:day_of_week]
day = params[:date] if params[:date].present?
day = Date.parse(day) if day && !day.include?('NaN') day = params[:date]
slot.preferred_day = day day = Date.parse(day) if day && !day.include?('NaN')
end slot.preferred_day = day
end end
slot.hour = params[:hour] slot.hour = params[:hour]
slot.minute = params[:minute] slot.minute = params[:minute]
@ -176,10 +180,10 @@ class ApiLessonBookingsController < ApiController
slot.update_all = params[:update_all] slot.update_all = params[:update_all]
result = target_lesson.counter({ result = target_lesson.counter({
proposer: current_user, proposer: current_user,
message: params[:message], message: params[:message],
slot: slot slot: slot
}) })
if result.errors.any? if result.errors.any?
if result.is_a?(JamRuby::LessonBooking) if result.is_a?(JamRuby::LessonBooking)
@ -188,7 +192,7 @@ class ApiLessonBookingsController < ApiController
recursive_errors(result, [:lesson_booking_slots]) recursive_errors(result, [:lesson_booking_slots])
else else
raise "unknown response type in counter #{result.class}" raise "unknown response type in counter #{result.class}"
end end
return return
end end
@lesson_booking.reload @lesson_booking.reload
@ -203,10 +207,10 @@ class ApiLessonBookingsController < ApiController
@lesson_session = target_lesson @lesson_session = target_lesson
result = target_lesson.cancel({ result = target_lesson.cancel({
canceler: current_user, canceler: current_user,
message: params[:message], message: params[:message],
update_all: true update_all: params[:update_all]
}) })
if result.errors.any? if result.errors.any?
if result.is_a?(JamRuby::LessonBooking) if result.is_a?(JamRuby::LessonBooking)

View File

@ -94,7 +94,7 @@ class ApiLessonSessionsController < ApiController
if params[:update_all] if params[:update_all]
# check if the next scheduled lesson is doable # check if the next scheduled lesson is doable
if 24.hours.from_now > @lesson_session.booking.next_lesson.music_session.scheduled_start if 24.hours.from_now > @lesson_session.lesson_booking.next_lesson.music_session.scheduled_start
response = {message: 'time_limit'} response = {message: 'time_limit'}
render :json => response, :status => 422 render :json => response, :status => 422
return return
@ -135,6 +135,7 @@ class ApiLessonSessionsController < ApiController
end end
msg = ChatMessage.create(me, nil, '', ChatMessage::CHANNEL_LESSON, nil, other, @lesson_session, 'JamKazam Recording', nil, claimed_recording) msg = ChatMessage.create(me, nil, '', ChatMessage::CHANNEL_LESSON, nil, other, @lesson_session, 'JamKazam Recording', nil, claimed_recording)
UserMailer.lesson_attachment(me, other, @lesson_session, claimed_recording)
end end
render :json => {}, :status => 200 render :json => {}, :status => 200

View File

@ -40,6 +40,8 @@ class ApiMusicNotationsController < ApiController
end end
msg = ChatMessage.create(me, nil, '', ChatMessage::CHANNEL_LESSON, nil, other, lesson_session, purpose, music_notation) msg = ChatMessage.create(me, nil, '', ChatMessage::CHANNEL_LESSON, nil, other, lesson_session, purpose, music_notation)
UserMailer.lesson_attachment(me, other, lesson_session, music_notation)
end end
end end

View File

@ -955,10 +955,13 @@ class ApiUsersController < ApiController
end end
def lookup_user def lookup_user
User.includes([{musician_instruments: :instrument}, User.includes([{musician_instruments: :instrument},
{band_musicians: :user}, {band_musicians: :user},
{genre_players: :genre}, {genre_players: :genre},
:bands, :instruments, :genres, :jam_track_rights, :affiliate_partner]) :bands, :instruments, :genres, :jam_track_rights,
:affiliate_partner, :reviews, :review_summary, :recordings,
:teacher => [:subjects, :instruments, :languages, :genres, :teachers_languages, :experiences_teaching, :experiences_award, :experiences_education, :reviews, :review_summary]])
.find(params[:id]) .find(params[:id])
end end

View File

@ -380,7 +380,8 @@ class UsersController < ApplicationController
js =<<JS js =<<JS
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
JK.GA.trackJKSocial(JK.GA.Categories.jkLike, '#{service}', 'email'); // // removed because we are juggling 20 events max in GA
// JK.GA.trackJKSocial(JK.GA.Categories.jkLike, '#{service}', 'email');
window.location = "#{url}"; window.location = "#{url}";
}); });
</script> </script>

View File

@ -77,4 +77,8 @@ module MusicSessionHelper
def pretty_scheduled_start(music_session, with_timezone, shorter = false) def pretty_scheduled_start(music_session, with_timezone, shorter = false)
music_session.pretty_scheduled_start(with_timezone, shorter) music_session.pretty_scheduled_start(with_timezone, shorter)
end end
def pretty_scheduled_start_slot(slot, with_timezone)
slot.pretty_scheduled_start(with_timezone)
end
end end

View File

@ -3,7 +3,8 @@ object @lesson_session
attributes :id, :lesson_booking_id, :lesson_type, :duration, :price, :teacher_complete, :student_complete, attributes :id, :lesson_booking_id, :lesson_type, :duration, :price, :teacher_complete, :student_complete,
:status, :student_canceled, :teacher_canceled, :student_canceled_at, :teacher_canceled_at, :student_canceled_reason, :status, :student_canceled, :teacher_canceled, :student_canceled_at, :teacher_canceled_at, :student_canceled_reason,
:teacher_canceled_reason, :status, :success, :teacher_unread_messages, :student_unread_messages, :is_active?, :recurring, :teacher_canceled_reason, :status, :success, :teacher_unread_messages, :student_unread_messages, :is_active?, :recurring,
:analysed, :school_on_school?, :teacher_id, :student_id, :pretty_scheduled_start, :scheduled_start, :teacher_short_canceled :analysed, :school_on_school?, :teacher_id, :student_id, :pretty_scheduled_start, :scheduled_start, :teacher_short_canceled,
:best_display_time
node do |lesson_session| node do |lesson_session|
{ {
@ -17,6 +18,15 @@ child(:lesson_booking => :lesson_booking) {
} }
child(:counter_slot => :counter_slot) {
attributes :id, :preferred_day, :day_of_week, :hour, :minute, :slot_type, :pretty_scheduled_start, :message, :pretty_start_time, :proposer_id, :is_student_created?, :is_teacher_created?, :timezone, :pretty_timezone
node :pretty_scheduled_start_with_timezone do |slot|
pretty_scheduled_start_slot(slot, true)
end
}
child(:music_session => :music_session) { child(:music_session => :music_session) {
attributes :id, :music_session_id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat, attributes :id, :music_session_id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat,
:band_id, :user_id, :genre_id, :created_at, :like_count, :comment_count, :play_count, :scheduled_duration, :band_id, :user_id, :genre_id, :created_at, :like_count, :comment_count, :play_count, :scheduled_duration,

View File

@ -3,7 +3,7 @@ node :next do |page|
end end
node :entries do |page| node :entries do |page|
partial "api_users/show", object: @users partial "api_users/show_teacher_index", object: @users
end end
node :total_entries do |page| node :total_entries do |page|

View File

@ -0,0 +1,8 @@
object @user
attributes :id, :first_name, :last_name, :name, :photo_url
child :teacher do |teacher|
attributes :id, :biography, :school_id
end

View File

@ -14,25 +14,7 @@ script type='text/template' id='template-lesson-session-actions'
li data-lesson-option="attach-audio" li data-lesson-option="attach-audio"
a href='#' Attach Audio File a href='#' Attach Audio File
= '{% } else if (data.isRequested) { %}' = '{% } else if (data.isActive) { %}'
ul
li data-lesson-option="status"
a href='#' View Status
li data-lesson-option="messages"
a href='#' View Messages
li data-lesson-option="messages"
a href='#' Attach Message
li data-lesson-option="cancel"
= '{% if (data.isStudent) { %}'
a href='#' Cancel Request
= '{% } else { %}'
a href='#' Decline Request
= '{% } %}'
= '{% } else if (data.isScheduled) { %}'
ul ul
li data-lesson-option="status" li data-lesson-option="status"
a href='#' View Status a href='#' View Status
@ -67,6 +49,23 @@ script type='text/template' id='template-lesson-session-actions'
li data-lesson-option="start-65-ago" li data-lesson-option="start-65-ago"
a href='#' Set Start 65 Min Ago a href='#' Set Start 65 Min Ago
= '{% } %}' = '{% } %}'
= '{% } else if (data.isRequested) { %}'
ul
li data-lesson-option="status"
a href='#' View Status
li data-lesson-option="messages"
a href='#' View Messages
li data-lesson-option="messages"
a href='#' Attach Message
li data-lesson-option="cancel"
= '{% if (data.isStudent) { %}'
a href='#' Cancel Request
= '{% } else { %}'
a href='#' Decline Request
= '{% } %}'
= '{% } else { %}' = '{% } else { %}'
ul ul

View File

@ -16,14 +16,13 @@
.wizard-step{ 'layout-wizard-step' => "1", 'dialog-title' => "Select & Test Audio Gear", 'dialog-purpose' => "SelectAudioGear" } .wizard-step{ 'layout-wizard-step' => "1", 'dialog-title' => "Select & Test Audio Gear", 'dialog-purpose' => "SelectAudioGear" }
.ftuesteps .ftuesteps
.clearall .clearall
.help-text In this step, you will select, configure, and test your audio gear. Please watch the video for best instructions. .help-text In this step, you will select, configure, and test your audio gear. Please click Instructions button for guidance.
.wizard-step-content .wizard-step-content
.wizard-step-column .wizard-step-column
%h2 Instructions %h2 Instructions
.ftue-box.instructions .ftue-box.instructions
%ul %ul
%li Select audio interface for inputs and outputs. %li Select audio interface for inputs and outputs.
%li Check input ports to which you will connect instruments or mics.
%li Check output ports you will use to monitor. %li Check output ports you will use to monitor.
%li Configure interface settings. %li Configure interface settings.
%li View test results. %li View test results.

View File

@ -54,3 +54,4 @@
= render 'dialogs/cancelLessonDialog' = render 'dialogs/cancelLessonDialog'
= render 'dialogs/rescheduleLessonDialog' = render 'dialogs/rescheduleLessonDialog'
= render 'dialogs/rateUserDialog' = render 'dialogs/rateUserDialog'
= render 'dialogs/musicNotationUploadDialog'

View File

@ -0,0 +1,2 @@
.dialog.dialog-overlay-sm.top-parent layout='dialog' layout-id='music-notation-upload-dialog' id='music-notation-upload-dialog'
= react_component 'MusicNotationUploadDialog', {}

View File

@ -8,7 +8,7 @@
%a{:hoveraction => "{{data.feed_item.helpers.artist_hoveraction}}", :profileaction => "{{data.feed_item.helpers.artist_hoveraction}}", :"{{data.feed_item.helpers.artist_datakey}}" => "{{data.feed_item.helpers.artist_id}}"} %a{:hoveraction => "{{data.feed_item.helpers.artist_hoveraction}}", :profileaction => "{{data.feed_item.helpers.artist_hoveraction}}", :"{{data.feed_item.helpers.artist_datakey}}" => "{{data.feed_item.helpers.artist_id}}"}
%img{ src: '{{data.feed_item.helpers.avatar}}' } %img{ src: '{{data.feed_item.helpers.avatar}}' }
/ type and artist / type and artist
.left.ml20.w15 .left.ml20.w15.title-artist
.title .title
%a.title-text{:href => "/recordings/{{data.candidate_claimed_recording.id}}", :rel => "external", :hoveraction => "recording", :'recording-id' => '{{data.candidate_claimed_recording.id}}'} RECORDING %a.title-text{:href => "/recordings/{{data.candidate_claimed_recording.id}}", :rel => "external", :hoveraction => "recording", :'recording-id' => '{{data.candidate_claimed_recording.id}}'} RECORDING
%a.edit-recording-dialog{href: "#"} (edit) %a.edit-recording-dialog{href: "#"} (edit)

View File

@ -1,7 +1,11 @@
require 'factory_girl' require 'factory_girl'
require 'timecop' require 'timecop'
require 'rspec-rails' require 'rspec-rails'
begin
require Rails.root.join('spec', 'support', 'lessons.rb') require Rails.root.join('spec', 'support', 'lessons.rb')
rescue LoadError
puts "for production; we ignore LoadError"
end
namespace :lessons do namespace :lessons do
@ -77,8 +81,8 @@ namespace :lessons do
end end
task book_test_drive: :environment do |task, args| task book_test_drive: :environment do |task, args|
user = User.find_by_email(ENV['STUDENT_EMAIL']) user = User.find_by_email(ENV['STUDENT'])
teacher = User.find_by_email(ENV['TEACHER_EMAIL']) teacher = User.find_by_email(ENV['TEACHER'])
slots = [] slots = []
@ -91,7 +95,6 @@ namespace :lessons do
user.save! user.save!
end end
booking = LessonBooking.book_test_drive(user, teacher, slots, "Hey I've heard of you before.") booking = LessonBooking.book_test_drive(user, teacher, slots, "Hey I've heard of you before.")
if booking.errors.any? if booking.errors.any?
puts booking.errors.inspect puts booking.errors.inspect
@ -100,7 +103,7 @@ namespace :lessons do
lesson = booking.lesson_sessions[0] lesson = booking.lesson_sessions[0]
if user.most_recent_test_drive_purchase.nil? if user.most_recent_test_drive_purchase.nil?
LessonPackagePurchase.create(user, lesson.booking, LessonPackageType.test_drive_4) LessonPackagePurchase.create(user, lesson.lesson_booking, LessonPackageType.test_drive_4)
end end
#lesson.accept({message: 'Yeah I got this', slot: slots[0]}) #lesson.accept({message: 'Yeah I got this', slot: slots[0]})
@ -109,6 +112,6 @@ namespace :lessons do
#lesson.slot.should eql slots[0] #lesson.slot.should eql slots[0]
#lesson.status.should eql LessonSession::STATUS_APPROVED #lesson.status.should eql LessonSession::STATUS_APPROVED
puts "http://localhost:3000/client#/jamclass/lesson-booking/#{lesson.booking.id}" puts "http://localhost:3000/client#/jamclass/lesson-booking/#{lesson.lesson_booking.id}"
end end
end end

View File

@ -23,6 +23,21 @@ describe ApiTeachersController do
Teacher.destroy_all Teacher.destroy_all
end end
describe "index" do
it "simple" do
@teacher = Teacher.save_teacher(
user,
years_teaching: 21,
biography: BIO,
genres: [genre1, genre2],
instruments: [instrument1, instrument2],
languages: [language1, language2],
subjects: [subject1, subject2]
)
get :index
end
end
describe "creates" do describe "creates" do
it "simple" do it "simple" do
post :create, biography: BIO, format: 'json' post :create, biography: BIO, format: 'json'

View File

@ -49,7 +49,7 @@ describe "Test Drive", :js => true, :type => :feature, :capybara_feature => true
# we tell user they have test drive purchased, and take them to the teacher screen # we tell user they have test drive purchased, and take them to the teacher screen
find('#banner h1', text: 'Test Drive Purchased') find('#banner h1', text: 'Test Drive Purchased')
find('#banner .dialog-inner', text: "You have purchased #{4} TestDrive credits and have used 1 credit it to request a JamClass with #{teacher_user.name}") find('#banner .dialog-inner', text: "You have purchased #{4} TestDrive credits and have used 1 credit to request a JamClass with #{teacher_user.name}")
# dismiss banner # dismiss banner
find('a.button-orange', text:'CLOSE').trigger(:click) find('a.button-orange', text:'CLOSE').trigger(:click)

View File

@ -118,4 +118,95 @@ describe "JamClassScreen", :js => true, :type => :feature, :capybara_feature =>
find('#jam-class-student-screen td.displayStatusColumn', text: 'Canceled (Teacher)') find('#jam-class-student-screen td.displayStatusColumn', text: 'Canceled (Teacher)')
end end
it "student cancels one of recurring" do
lesson = monthly_lesson(user, teacher_user, {accept: true})
lesson1 = lesson.lesson_booking.lesson_sessions[0]
lesson2 = lesson.lesson_booking.lesson_sessions[1]
fast_signin(user, "/client#/jamclass")
find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Scheduled')
find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Scheduled')
# open up hover
find('tr[data-lesson-session-id="' + lesson1.id + '"] .lesson-session-actions-btn').trigger(:click)
find('li[data-lesson-option="cancel"] a', visible: false, text: 'Cancel Lesson').trigger(:click)
# confirm cancelation -
find('#banner a', text: 'CANCEL THIS LESSON').trigger(:click)
find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Canceled (Student)')
find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Scheduled')
switch_user(teacher_user, "/client#/jamclass")
find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Canceled (Student)')
find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Scheduled')
end
it "student cancels all recurring" do
lesson = monthly_lesson(user, teacher_user, {accept: true})
lesson1 = lesson.lesson_booking.lesson_sessions[0]
lesson2 = lesson.lesson_booking.lesson_sessions[1]
lesson1.recurring.should be_true
lesson.lesson_booking.recurring.should be_true
fast_signin(user, "/client#/jamclass")
find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Scheduled')
find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Scheduled')
# open up hover
find('tr[data-lesson-session-id="' + lesson1.id + '"] .lesson-session-actions-btn').trigger(:click)
find('li[data-lesson-option="cancel"] a', visible: false, text: 'Cancel Lesson').trigger(:click)
# confirm cancelation -
find('#banner a', text: 'CANCEL ALL LESSONS').trigger(:click)
find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Canceled (Student)')
lesson1.reload
lesson2.reload
lesson1.status.should eql LessonSession::STATUS_CANCELED
lesson2.status.should eql LessonSession::STATUS_CANCELED
find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Canceled (Student)')
switch_user(teacher_user, "/client#/jamclass")
find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Canceled (Student)')
find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Canceled (Student)')
end
describe "counter" do
it "test drive" do
lesson = testdrive_lesson(user, teacher_user, {accept: false})
fast_signin(teacher_user, "/client#/jamclass")
validate_status(lesson, 'Requested')
jamclass_hover_option('reschedule', 'Reschedule Lesson')
# no popup should show in this case, because it's not yet scheduled
# we should be at lesson status page
find('h2', text: 'respond to lesson request')
screenshot
counter_day
# switch to student
switch_user(user, "/client#/jamclass")
validate_status(lesson, 'Requested')
jamclass_hover_option('status', 'View Status')
find('h2', text: 'this lesson is coming up soon')
screenshot
approve_lesson(lesson)
jamclass_hover_option('reschedule', 'Reschedule Lesson')
find('#banner h1', text: 'Lesson Change Requested')
find('#banner .close-btn').trigger(:click)
counter_day
end
end
end end

View File

@ -25,6 +25,7 @@ describe "Lesson Booking Status page", :js => true, :type => :feature, :capybara
find('h2', text: 'your lesson has been requested') find('h2', text: 'your lesson has been requested')
find('p.proposing-new-time')
screenshot screenshot
end end
@ -32,11 +33,14 @@ describe "Lesson Booking Status page", :js => true, :type => :feature, :capybara
it "approved" do it "approved" do
lesson = testdrive_lesson(user, teacher, {accept:true, finish:false}) lesson = testdrive_lesson(user, teacher, {accept:true, finish:false})
fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.id) lesson.reload
lesson.status.should
lesson.lesson_booking.accepter_id.should_not be_nil
fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.id )
find('h2', text: 'this lesson is coming up soon') find('h2', text: 'this lesson is coming up soon')
find('p.lesson-time', "will take place each") find('p.generic-time-stmt')
screenshot screenshot
end end
@ -185,4 +189,90 @@ describe "Lesson Booking Status page", :js => true, :type => :feature, :capybara
find('#lesson-booking', text: 'US Central Time') find('#lesson-booking', text: 'US Central Time')
end end
it "requested recurring with focused lesson" do
lesson = monthly_lesson(user, teacher, {accept: false})
fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.id)
find('.request-sent', text: 'Your request has been sent.')
page.should_not have_selector('p.proposing-new-time')
# the teacher can either accept or propose a new time, so they see both
switch_user(teacher, "/client#/jamclass/lesson-booking/" + lesson.id)
find('p.action', text: 'Has requested')
page.should_not have_selector('p.generic-time-stmt')
find(".slot-decision-field[data-slot-id=\"#{lesson.lesson_booking.default_slot.id}\"] ins", visible: false).trigger(:click)
find('.schedule.button-orange').trigger(:click)
find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Scheduled')
end
it "requested recurring with unfocused lesson" do
lesson = monthly_lesson(user, teacher, {accept: false})
fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.lesson_booking.id)
find('.request-sent', text: 'Your request has been sent.')
page.should_not have_selector('p.proposing-new-time')
switch_user(teacher, "/client#/jamclass/lesson-booking/" + lesson.id)
find('p.action', text: 'Has requested')
page.should_not have_selector('p.generic-time-stmt')
find(".slot-decision-field[data-slot-id=\"#{lesson.lesson_booking.default_slot.id}\"] ins", visible: false).trigger(:click)
find('.schedule.button-orange').trigger(:click)
find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Scheduled')
end
it "accepted recurring with focused lesson" do
lesson = monthly_lesson(user, teacher, {accept: true})
fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.id + "_rescheduling")
find('p.proposing-new-time')
# the teacher can either accept or propose a new time, so they see both
switch_user(teacher, "/client#/jamclass/lesson-booking/" + lesson.id + "_rescheduling")
find('p.proposing-new-time')
# change the lesson time
fill_in "alt-date-input", with: date_picker_format(Date.new(Date.today.year, Date.today.month + 1, 17))
find('td a', text: '17').trigger(:click)
sleep 3
find('.schedule.button-orange').trigger(:click)
find('#banner h1', text: 'Lesson Change Requested')
find('#banner .close-btn').trigger(:click)
find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Requested')
switch_user(user, "/client#/jamclass")
end
it "accepted recurring with unfocused lesson" do
lesson = monthly_lesson(user, teacher, {accept: true})
fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.lesson_booking.id + "_rescheduling")
find('.request-sent', text: 'Your request has been sent.')
page.should_not have_selector('p.proposing-new-time')
switch_user(teacher, "/client#/jamclass/lesson-booking/" + lesson.lesson_booking.id + "_rescheduling")
find('p.proposing-new-time')
fill_in "alt-date-input", with: date_picker_format(Date.new(Date.today.year, Date.today.month + 1, 17))
find('td a', text: '17').trigger(:click)
sleep 3
find(".slot-decision-field[data-slot-id=\"#{lesson.lesson_booking.default_slot.id}\"] ins", visible: false).trigger(:click)
find('.schedule.button-orange').trigger(:click)
find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Scheduled')
end
end end

View File

@ -29,7 +29,7 @@ describe "Student Landing", :js => true, :type => :feature, :capybara_feature =>
fill_in "email", with: 'student_123@jamkazam.com' fill_in "email", with: 'student_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)

View File

@ -25,12 +25,16 @@ def teacher_approve(lesson_session)
sign_out_poltergeist(validate: true) sign_out_poltergeist(validate: true)
sign_in_poltergeist(lesson_session.teacher, password: 'foobar') sign_in_poltergeist(lesson_session.teacher, password: 'foobar')
visit "/client#/jamclass/lesson-booking/" + lesson_session.id visit "/client#/jamclass/lesson-booking/" + lesson_session.id
find(".slot-decision-field[data-slot-id=\"#{lesson_session.lesson_booking.default_slot.id}\"] ins", visible: false).trigger(:click)
find('.schedule.button-orange').trigger(:click) find('.schedule.button-orange').trigger(:click)
# dismiss banner # dismiss banner
#find('a.button-orange', text:'CLOSE').trigger(:click) #find('a.button-orange', text:'CLOSE').trigger(:click)
find('tr[data-lesson-session-id="' + lesson_session.id + '"] .displayStatusColumn', text: 'Scheduled') find('tr[data-lesson-session-id="' + lesson_session.id + '"] .displayStatusColumn', text: 'Scheduled')
end end
def date_picker_format(date)
date.strftime('%a %b %d %Y')
end
def fill_out_single_lesson def fill_out_single_lesson
find('h2', text: 'book testdrive lesson') find('h2', text: 'book testdrive lesson')
@ -56,6 +60,33 @@ def fill_out_single_lesson
end end
def validate_status(lesson, expectedStatus)
find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: expectedStatus)
end
def jamclass_hover_option(lesson, option, text)
# open up hover
find('tr[data-lesson-session-id="' + lesson.id + '"] .lesson-session-actions-btn').trigger(:click)
find('li[data-lesson-option="' + option + '"] a', visible: false, text: text).trigger(:click)
end
def counter_day
fill_in "alt-date-input", with: date_picker_format(Date.new(Date.today.year, Date.today.month + 1, 17))
find('td a', text: '17').trigger(:click)
sleep 3
find('.schedule.button-orange').trigger(:click)
find('#banner h1', text: 'Lesson Change Requested')
find('#banner .close-btn').trigger(:click)
find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Requested')
end
def approve_lesson(lesson, slot = lesson.lesson_booking.default_slot)
find(".slot-decision-field[data-slot-id=\"#{slot.id}\"] ins", visible: false).trigger(:click)
find('.schedule.button-orange').trigger(:click)
find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Scheduled')
end
def fill_out_payment(expected = nil) def fill_out_payment(expected = nil)
fill_in 'card-number', with: '4111111111111111' fill_in 'card-number', with: '4111111111111111'
@ -159,7 +190,7 @@ def book_lesson(user, teacher, options)
end end
if options[:accept] if options[:accept]
lesson.accept({message: 'Yeah I got this', slot: slots[0]}) lesson.accept({message: 'Yeah I got this', slot: slots[0], accepter: teacher})
lesson.errors.any?.should be_false unless options[:no_validate] lesson.errors.any?.should be_false unless options[:no_validate]
lesson.reload lesson.reload
lesson.slot.should eql slots[0] unless options[:no_validate] lesson.slot.should eql slots[0] unless options[:no_validate]

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB