Merge branch 'develop' into feature/packages
This commit is contained in:
commit
1abacf0ec6
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
#!/usr/bin/env 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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -297,7 +297,10 @@ require "jam_ruby/models/affiliate_distribution"
|
|||
require "jam_ruby/models/teacher_intent"
|
||||
require "jam_ruby/models/school"
|
||||
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
|
||||
|
||||
module JamRuby
|
||||
|
|
|
|||
|
|
@ -1770,5 +1770,28 @@ module JamRuby
|
|||
format.html { render :layout => "from_user_mailer" }
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
|
||||
|
||||
|
|
@ -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 %>)
|
||||
|
||||
|
||||
|
|
@ -16,7 +16,8 @@ module JamRuby
|
|||
has_and_belongs_to_many :recordings, :class_name => "JamRuby::Recording", :join_table => "recordings_genres"
|
||||
|
||||
# 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
|
||||
has_many :genres_jam_tracks, :class_name => "JamRuby::GenreJamTrack", :foreign_key => "genre_id"
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ module JamRuby
|
|||
has_and_belongs_to_many :music_sessions, :class_name => "JamRuby::ActiveMusicSession", :join_table => "genres_music_sessions"
|
||||
|
||||
# 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
|
||||
return Instrument.where('instruments.popularity > 0').order('instruments.description ASC')
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ module JamRuby
|
|||
class Language < ActiveRecord::Base
|
||||
include HtmlSanitize
|
||||
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
|
||||
languages = Language.order(:description)
|
||||
|
|
|
|||
|
|
@ -243,7 +243,12 @@ module JamRuby
|
|||
minimum_start_time = create_minimum_booking_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
|
||||
# # adjust all session times
|
||||
|
|
@ -421,6 +426,9 @@ module JamRuby
|
|||
self.errors.add(:status, "This lesson is already #{self.status}.")
|
||||
end
|
||||
|
||||
if self.accepter.nil?
|
||||
self.errors.add(:accepter, "No one has been indicated as accepting the lesson")
|
||||
end
|
||||
self.accepting = false
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ module JamRuby
|
|||
validate :validate_proposer
|
||||
before_validation :before_validation
|
||||
|
||||
def is_recurring?
|
||||
slot_type == SLOT_TYPE_RECURRING
|
||||
end
|
||||
def before_validation
|
||||
if proposer.nil?
|
||||
self.proposer = container.student
|
||||
|
|
@ -81,6 +84,7 @@ module JamRuby
|
|||
candidate = scheduled_time(i + 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
|
||||
# move it up a week
|
||||
week_offset += 1
|
||||
|
|
@ -173,7 +177,7 @@ module JamRuby
|
|||
duration = lesson_length * 60 # convert from minutes to seconds
|
||||
end_time = start_time + duration
|
||||
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
|
||||
"#{start_time.strftime("%A, %B %e")} - #{start_time.strftime("%l:%M%P").strip}"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module JamRuby
|
|||
include HtmlSanitize
|
||||
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]
|
||||
|
|
@ -61,6 +61,7 @@ module JamRuby
|
|||
validates :post_processed, inclusion: {in: [true, false]}
|
||||
|
||||
validate :validate_creating, :if => :creating
|
||||
validate :validate_countering, :if => :countering_flag
|
||||
validate :validate_accepted, :if => :accepting
|
||||
validate :validate_canceled, :if => :canceling
|
||||
validate :validate_autocancel, :if => :autocanceling
|
||||
|
|
@ -165,7 +166,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
# 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.session_completed
|
||||
end
|
||||
|
|
@ -366,7 +367,9 @@ module JamRuby
|
|||
end
|
||||
|
||||
def recurring_completed
|
||||
puts "RECURRING COMPLETED #{success}"
|
||||
if success
|
||||
|
||||
if lesson_booking.is_monthly_payment?
|
||||
# monthly payments are handled at beginning of month; just poke with email, and move on
|
||||
|
||||
|
|
@ -399,6 +402,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
else
|
||||
puts "STUDENT NO BILL SENT #{self.id}"
|
||||
if !sent_notices
|
||||
if !school_on_school?
|
||||
# bad session; just poke user
|
||||
|
|
@ -422,6 +426,7 @@ module JamRuby
|
|||
else
|
||||
if !sent_notices
|
||||
if !school_on_school?
|
||||
puts "STUDENT NO BILL SENT #{success}"
|
||||
UserMailer.student_lesson_normal_no_bill(self).deliver
|
||||
UserMailer.teacher_lesson_normal_no_bill(self).deliver
|
||||
end
|
||||
|
|
@ -501,6 +506,22 @@ module JamRuby
|
|||
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
|
||||
if self.status_was != STATUS_REQUESTED && self.status_was != STATUS_COUNTERED
|
||||
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
|
||||
self.countering = true
|
||||
self.countering_flag = true
|
||||
slot.proposer = proposer
|
||||
slot.lesson_session = self
|
||||
slot.message = message
|
||||
|
|
@ -796,11 +818,12 @@ module JamRuby
|
|||
self.countered_slot = slot
|
||||
self.countered_lesson = self
|
||||
self.status = STATUS_COUNTERED
|
||||
if !update_all
|
||||
#if !update_all
|
||||
self.counter_slot = slot
|
||||
end
|
||||
#end
|
||||
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
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ module JamRuby
|
|||
s3_manager.sign_url(self[:file_url], {:expires => expiration_time, :secure => true})
|
||||
end
|
||||
|
||||
def is_notation?
|
||||
self.attachment_type == TYPE_NOTATION
|
||||
end
|
||||
private
|
||||
|
||||
def self.construct_filename(notation)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ module JamRuby
|
|||
class Subject < ActiveRecord::Base
|
||||
include HtmlSanitize
|
||||
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
|
||||
|
|
|
|||
|
|
@ -4,10 +4,14 @@ module JamRuby
|
|||
html_sanitize strict: [:biography, :website]
|
||||
attr_accessor :validate_introduction, :validate_basics, :validate_pricing
|
||||
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_and_belongs_to_many :instruments, :class_name => "JamRuby::Instrument", :join_table => "teachers_instruments", :order => "description"
|
||||
has_and_belongs_to_many :subjects, :class_name => "JamRuby::Subject", :join_table => "teachers_subjects", :order => "description"
|
||||
has_and_belongs_to_many :languages, :class_name => "JamRuby::Language", :join_table => "teachers_languages", :order => "description"
|
||||
has_many :genres, :class_name => "JamRuby::Genre", :through => :teachers_genres # , :order => "description"
|
||||
has_many :teachers_genres, :class_name => "JamRuby::TeacherGenre"
|
||||
has_many :instruments, :class_name => "JamRuby::Instrument", through: :teachers_instruments # , :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 :experiences_teaching, :class_name => "JamRuby::TeacherExperience", conditions: {experience_type: 'teaching'}
|
||||
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 :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
|
||||
|
||||
|
|
@ -53,7 +57,7 @@ module JamRuby
|
|||
limit ||= 20
|
||||
limit = limit.to_i
|
||||
|
||||
query = User.joins(:teacher)
|
||||
query = User.unscoped.joins(:teacher)
|
||||
|
||||
# only show teachers with ready for session set to true
|
||||
query = query.where('teachers.ready_for_session_at IS NOT NULL')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -51,7 +51,7 @@ module JamRuby
|
|||
has_many :user_authorizations, :class_name => "JamRuby::UserAuthorization"
|
||||
|
||||
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)
|
||||
has_many :calendars, :class_name => "JamRuby::Calendar"
|
||||
|
|
@ -2090,7 +2090,7 @@ module JamRuby
|
|||
LessonBooking.unprocessed(self).where(lesson_type: LessonBooking::LESSON_TYPE_PAID).first
|
||||
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
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1057,5 +1057,13 @@ FactoryGirl.define do
|
|||
factory :email_blacklist, class: "JamRuby::EmailBlacklist" do
|
||||
sequence(:email) { |n| "person_#{n}@example.com"}
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ describe "Monthly Recurring Lesson Flow" do
|
|||
########## Need validate their credit card
|
||||
token = create_stripe_token
|
||||
result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id})
|
||||
puts "result #{result.inspect}"
|
||||
booking.reload
|
||||
booking.card_presumed_ok.should be_true
|
||||
booking.errors.any?.should be_false
|
||||
|
|
@ -118,7 +117,7 @@ describe "Monthly Recurring Lesson Flow" do
|
|||
|
||||
######## Teacher accepts slot
|
||||
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|
|
||||
# puts del.inspect
|
||||
end
|
||||
|
|
@ -203,7 +202,7 @@ describe "Monthly Recurring Lesson Flow" do
|
|||
payment = TeacherPayment.first
|
||||
payment.amount_in_cents.should eql 3000
|
||||
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.should eql teacher_user
|
||||
payment.teacher_distribution.should eql teacher_distribution
|
||||
|
|
@ -320,7 +319,7 @@ describe "Monthly Recurring Lesson Flow" do
|
|||
|
||||
######## Teacher accepts slot
|
||||
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|
|
||||
# puts del.inspect
|
||||
end
|
||||
|
|
@ -419,6 +418,7 @@ describe "Monthly Recurring Lesson Flow" do
|
|||
|
||||
|
||||
it "affiliate gets their cut" do
|
||||
Timecop.travel(2016, 05, 15)
|
||||
user.affiliate_referral = affiliate_partner
|
||||
user.save!
|
||||
teacher_user.affiliate_referral = affiliate_partner2
|
||||
|
|
@ -429,7 +429,6 @@ describe "Monthly Recurring Lesson Flow" do
|
|||
|
||||
user.reload
|
||||
|
||||
puts "user.lesson_purchases #{user.lesson_purchases}"
|
||||
user.lesson_purchases.count.should eql 1
|
||||
lesson_package_purchase = user.lesson_purchases.first
|
||||
teacher_distribution = lesson_package_purchase.teacher_distribution
|
||||
|
|
@ -447,6 +446,7 @@ describe "Monthly Recurring Lesson Flow" do
|
|||
end
|
||||
|
||||
it "school affiliate gets nothing when teacher school is involved" do
|
||||
Timecop.travel(2016, 05, 15)
|
||||
teacher.school = school
|
||||
teacher.save!
|
||||
|
||||
|
|
@ -469,6 +469,8 @@ describe "Monthly Recurring Lesson Flow" do
|
|||
end
|
||||
|
||||
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.save!
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ describe "Normal Lesson Flow" do
|
|||
######## Teacher accepts slot
|
||||
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.reload
|
||||
lesson_session.slot.should eql booking.default_slot
|
||||
|
|
@ -251,7 +251,7 @@ describe "Normal Lesson Flow" do
|
|||
payment = TeacherPayment.first
|
||||
payment.amount_in_cents.should eql 3000
|
||||
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.should eql teacher_user
|
||||
payment.teacher_distribution.should eql teacher_distribution
|
||||
|
|
@ -352,7 +352,7 @@ describe "Normal Lesson Flow" do
|
|||
######## Teacher accepts slot
|
||||
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.reload
|
||||
lesson_session.slot.should eql student_counter
|
||||
|
|
@ -508,7 +508,7 @@ describe "Normal Lesson Flow" do
|
|||
######## Teacher accepts slot
|
||||
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.reload
|
||||
lesson_session.slot.should eql student_counter
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ describe "Recurring Lesson Flow" do
|
|||
booking.status.should eql LessonBooking::STATUS_REQUESTED
|
||||
|
||||
######### 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
|
||||
lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'})
|
||||
booking.reload
|
||||
|
|
@ -81,7 +81,7 @@ describe "Recurring Lesson Flow" do
|
|||
#notification.message.should eql "Instructor has proposed a different time for your lesson."
|
||||
|
||||
######### 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
|
||||
lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'})
|
||||
lesson_session.errors.any?.should be false
|
||||
|
|
@ -105,7 +105,7 @@ describe "Recurring Lesson Flow" do
|
|||
|
||||
######## Teacher accepts slot
|
||||
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|
|
||||
# puts del.inspect
|
||||
end
|
||||
|
|
@ -132,7 +132,11 @@ describe "Recurring Lesson Flow" do
|
|||
notification.student_directed.should eql true
|
||||
notification.purpose.should eql 'accept'
|
||||
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
|
||||
start = lesson_session.scheduled_start
|
||||
|
|
@ -181,6 +185,9 @@ describe "Recurring Lesson Flow" do
|
|||
lesson_session.sent_billing_notices.should be true
|
||||
user.reload
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ describe "TestDrive Lesson Flow" do
|
|||
######## Teacher accepts slot
|
||||
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.reload
|
||||
lesson_session.slot.should eql student_counter
|
||||
|
|
@ -237,7 +237,7 @@ describe "TestDrive Lesson Flow" do
|
|||
teacher_distribution.ready.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
|
||||
|
||||
user.sales.count.should eql 1
|
||||
|
|
|
|||
|
|
@ -741,6 +741,14 @@ describe LessonBooking do
|
|||
|
||||
UserMailer.deliveries.clear
|
||||
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.errors.any?.should be_false
|
||||
lesson_session.reload
|
||||
|
|
@ -748,14 +756,32 @@ describe LessonBooking do
|
|||
booking.reload
|
||||
booking.status.should eql LessonSession::STATUS_CANCELED
|
||||
booking.canceler.should eql user
|
||||
UserMailer.deliveries.length.should eql 2
|
||||
end
|
||||
end
|
||||
describe "rescheduling" do
|
||||
|
||||
after do
|
||||
Timecop.return
|
||||
it "initial slot is in the past" do
|
||||
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
|
||||
|
||||
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)
|
||||
lesson_session = booking.lesson_sessions[0]
|
||||
|
|
|
|||
|
|
@ -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_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
|
||||
it "can't autocancel in the past" do
|
||||
lesson_session.status.should eql LessonSession::STATUS_REQUESTED
|
||||
|
|
|
|||
|
|
@ -45,18 +45,20 @@ describe Teacher do
|
|||
|
||||
it "instruments" do
|
||||
teacher = FactoryGirl.create(:teacher, ready_for_session_at: Time.now)
|
||||
teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query]
|
||||
teachers.length.should eq 0
|
||||
#teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query]
|
||||
#teachers.length.should eq 0
|
||||
|
||||
teacher.instruments << Instrument.find('acoustic guitar')
|
||||
teacher.save!
|
||||
teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query]
|
||||
teachers.length.should eq 1
|
||||
teachers[0].should eq(teacher.user)
|
||||
#teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query]
|
||||
#teachers.length.should eq 1
|
||||
#teachers[0].should eq(teacher.user)
|
||||
|
||||
teacher.instruments << Instrument.find('electric guitar')
|
||||
teacher.save!
|
||||
#teacher.instruments << Instrument.find('electric guitar')
|
||||
#teacher.save!
|
||||
puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||
teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query]
|
||||
puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!---"
|
||||
teachers.length.should eq 1
|
||||
teachers[0].should eq(teacher.user)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ describe GoogleAnalyticsEvent do
|
|||
end
|
||||
|
||||
describe "track band analytics" do
|
||||
pending "job is commented out"
|
||||
it 'reports first recording' do
|
||||
ResqueSpec.reset!
|
||||
user = FactoryGirl.create(:user)
|
||||
|
|
@ -26,6 +27,7 @@ describe GoogleAnalyticsEvent do
|
|||
end
|
||||
|
||||
it 'reports first real session' do
|
||||
pending "job is commented out"
|
||||
ResqueSpec.reset!
|
||||
JamRuby::GoogleAnalyticsEvent::BandSessionTracker.should have_schedule_size_of(0)
|
||||
user = FactoryGirl.create(:user)
|
||||
|
|
@ -72,6 +74,7 @@ describe GoogleAnalyticsEvent do
|
|||
ResqueSpec.reset!
|
||||
end
|
||||
it 'reports size increment' do
|
||||
pending "job is commented out"
|
||||
user = FactoryGirl.create(:user)
|
||||
music_session = FactoryGirl.create(:active_music_session,
|
||||
:creator => user,
|
||||
|
|
@ -86,6 +89,7 @@ describe GoogleAnalyticsEvent do
|
|||
end
|
||||
|
||||
it 'reports duration' do
|
||||
pending "job is commented out"
|
||||
user = FactoryGirl.create(:user)
|
||||
JamRuby::GoogleAnalyticsEvent::SessionDurationTracker.should have_schedule_size_of(0)
|
||||
music_session = FactoryGirl.create(:active_music_session,
|
||||
|
|
|
|||
|
|
@ -162,6 +162,26 @@ describe "RenderMailers", :slow => true do
|
|||
UserMailer.deliveries.clear
|
||||
UserMailer.lesson_starting_soon_student(lesson).deliver
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ require 'timecop'
|
|||
require 'resque_spec/scheduler'
|
||||
|
||||
# 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
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ def book_lesson(user, teacher, options)
|
|||
LessonPackagePurchase.create(user, booking, LessonPackageType.package_for_test_drive_count(options[:package_count]))
|
||||
end
|
||||
elsif options[:monthly]
|
||||
puts "did it"
|
||||
LessonPackagePurchase.create(user, booking, LessonPackageType.single, Date.today.year, Date.today.month)
|
||||
end
|
||||
|
||||
|
|
@ -83,7 +82,7 @@ def book_lesson(user, teacher, options)
|
|||
end
|
||||
|
||||
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.reload
|
||||
lesson.slot.should eql slots[0]
|
||||
|
|
|
|||
|
|
@ -581,6 +581,9 @@
|
|||
if(!defaults.show_checkbox) {
|
||||
$feedItem.find('.select-box').hide();
|
||||
}
|
||||
else {
|
||||
context.JK.checkbox($feedItem.find('.select-box'))
|
||||
}
|
||||
if(defaults.hide_avatar) {
|
||||
$feedItem.find('.avatar-small.ib').hide();
|
||||
}
|
||||
|
|
@ -598,6 +601,7 @@
|
|||
$feedItem.data('original-max-height', $feedItem.css('height'));
|
||||
context.JK.bindHoverEvents($feedItem);
|
||||
context.JK.bindProfileClickEvents($feedItem);
|
||||
context.JK.popExternalLinks($feedItem)
|
||||
}
|
||||
else {
|
||||
logger.warn("skipping feed type: " + feed.type);
|
||||
|
|
|
|||
|
|
@ -99,6 +99,10 @@
|
|||
ioTargetFail : 'ioTargetFail'
|
||||
}
|
||||
|
||||
var jamClassReasons = {
|
||||
testDrive: 'TestDrive'
|
||||
}
|
||||
|
||||
var networkTestFailReasons = {
|
||||
stun : 'STUN',
|
||||
bandwidth : 'Bandwidth',
|
||||
|
|
@ -129,7 +133,8 @@
|
|||
jkFollow : 'jkFollow',
|
||||
jkFavorite : 'jkFavorite',
|
||||
jkComment : 'jkComment',
|
||||
fileDownload: "DownloadFile"
|
||||
fileDownload: "DownloadFile",
|
||||
jamclass: 'JamClass'
|
||||
};
|
||||
|
||||
// JamTrack categories and actions:
|
||||
|
|
@ -204,6 +209,11 @@
|
|||
context.ga('send', 'event', categories.register, action, registrationType);
|
||||
}
|
||||
|
||||
function trackTestDrivePurchase(count) {
|
||||
|
||||
context.ga('send', 'event', categories.jamclass, jamClassReasons.testDrive, count);
|
||||
|
||||
}
|
||||
function trackDownload(platform) {
|
||||
var normalizedPlatform = translatePlatformForGA(platform);
|
||||
|
||||
|
|
@ -490,6 +500,7 @@
|
|||
GA.virtualPageView = virtualPageView;
|
||||
GA.trackTiming = trackTiming;
|
||||
GA.trackFileDownload = trackFileDownload;
|
||||
GA.trackTestDrivePurchase = trackTestDrivePurchase;
|
||||
|
||||
context.JK.GA = GA;
|
||||
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@
|
|||
"Electric Guitar": { "client_id": 50, "server_id": "electric guitar" },
|
||||
"Keyboard": { "client_id": 60, "server_id": "keyboard" },
|
||||
"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" },
|
||||
"Flute": { "client_id": 80, "server_id": "flute" },
|
||||
"Clarinet": { "client_id": 90, "server_id": "clarinet" },
|
||||
|
|
@ -250,6 +250,9 @@
|
|||
"Other": { "client_id": 250, "server_id": "other" }
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
context.JK.client_to_server_instrument_map = {
|
||||
10: { "server_id": "acoustic guitar" },
|
||||
20: { "server_id": "bass guitar" },
|
||||
|
|
@ -259,7 +262,7 @@
|
|||
50: { "server_id": "electric guitar" },
|
||||
60: { "server_id": "keyboard" },
|
||||
61: { "server_id": "piano"} ,
|
||||
62: { "server_id": "upright bass"} ,
|
||||
62: { "server_id": "double bass"} ,
|
||||
70: { "server_id": "voice" },
|
||||
80: { "server_id": "flute" },
|
||||
90: { "server_id": "clarinet" },
|
||||
|
|
@ -283,10 +286,21 @@
|
|||
|
||||
context.JK.instrument_id_to_instrument = {};
|
||||
|
||||
context.JK.server_to_client_instrument_alpha = [];
|
||||
|
||||
(function() {
|
||||
$.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.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;
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -203,7 +203,7 @@
|
|||
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) {
|
||||
subtlePulse(container)
|
||||
var $bookNow = container('a.book-now')
|
||||
var $bookNow = container.find('a.book-now')
|
||||
$bookNow.off('click').on('click', function(e) {
|
||||
e.preventDefault()
|
||||
callback()
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ AttachmentStore = context.AttachmentStore
|
|||
|
||||
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: () ->
|
||||
logger.debug("AttachmentStatus: notationUploadFail")
|
||||
|
|
@ -32,11 +32,11 @@ AttachmentStore = context.AttachmentStore
|
|||
audioSelected: (e) ->
|
||||
files = $(e.target).get(0).files
|
||||
logger.debug("audio files selected: ", files)
|
||||
window.AttachmentActions.uploadAudio.trigger(files, @notationUploadDone, @notationUploadFail)
|
||||
window.AttachmentActions.uploadAudios.trigger(files, @notationUploadDone, @notationUploadFail)
|
||||
|
||||
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: () ->
|
||||
logger.debug("AttachmentStatus: audioUploadFail")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
context = window
|
||||
rest = context.JK.Rest()
|
||||
logger = context.JK.logger
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ ChatActions = @ChatActions
|
|||
else
|
||||
purpose = null
|
||||
|
||||
additional = null
|
||||
if msg.purpose == 'Notation File'
|
||||
additional = `<a className="additional" onClick={this.notationClicked.bind(this, msg.music_notation)}>{msg.music_notation.file_name}</a>`
|
||||
else if msg.purpose == 'Audio File'
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ logger = context.JK.logger
|
|||
for object in this.props.sourceObjects
|
||||
nm = "check_#{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="checkbox-scroller left">
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ ConfigureTracksStore = @ConfigureTracksStore
|
|||
|
||||
instruments = []
|
||||
instruments.push(`<option key="" value="">Select the instrument for this track</option>`)
|
||||
for displayName, value of context.JK.server_to_client_instrument_map
|
||||
instruments.push(`<option key={value.server_id} value={value.server_id}>{displayName}</option>`)
|
||||
for instrument in context.JK.server_to_client_instrument_alpha
|
||||
instruments.push(`<option key={instrument.server_id} value={instrument.server_id}>{instrument.display}</option>`)
|
||||
|
||||
vsts = []
|
||||
|
||||
|
|
@ -134,8 +134,8 @@ ConfigureTracksStore = @ConfigureTracksStore
|
|||
midiInstruments = []
|
||||
|
||||
instruments = []
|
||||
for displayName, value of context.JK.server_to_client_instrument_map
|
||||
instruments.push(`<option key={value.server_id} value={value.server_id}>{displayName}</option>`)
|
||||
for instrument in context.JK.server_to_client_instrument_alpha
|
||||
instruments.push(`<option key={instrument.server_id} value={instrument.server_id}>{instrument.display}</option>`)
|
||||
|
||||
selectedMidiInterface = ''
|
||||
selectedInstrument = context.JK.client_to_server_instrument_map[50].server_id # default to electric guitar
|
||||
|
|
|
|||
|
|
@ -129,9 +129,9 @@ LessonTimerActions = context.LessonTimerActions
|
|||
rest.checkLessonReschedule({id: lesson.id, update_all: recurring})
|
||||
.done((response) => (
|
||||
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
|
||||
window.location.href = '/client#/jamclass/lesson-booking/' + lesson.id
|
||||
window.location.href = '/client#/jamclass/lesson-booking/' + lesson.id + "_rescheduling"
|
||||
))
|
||||
.fail((jqXHR) => (
|
||||
if jqXHR.status == 422
|
||||
|
|
@ -179,7 +179,13 @@ LessonTimerActions = context.LessonTimerActions
|
|||
context.JK.Banner.showAlert('late cancellation warning',
|
||||
'Cancelling a lesson less than 24 hours before it’s scheduled to start should be avoided, as it’s 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) ->
|
||||
@app.ajaxError(jqXHR)
|
||||
|
|
@ -224,12 +230,12 @@ LessonTimerActions = context.LessonTimerActions
|
|||
if lesson.recurring
|
||||
buttons = []
|
||||
buttons.push({
|
||||
name: 'THIS SESSION',
|
||||
name: 'THIS LESSON',
|
||||
buttonStyle: 'button-orange',
|
||||
click: (() => (@rescheduleSelected(lesson, false)))
|
||||
})
|
||||
buttons.push({
|
||||
name: 'ALL SESSIONS',
|
||||
name: 'ALL LESSONS',
|
||||
buttonStyle: 'button-orange',
|
||||
click: (() => (@rescheduleSelected(lesson, true)))
|
||||
})
|
||||
|
|
@ -251,13 +257,13 @@ LessonTimerActions = context.LessonTimerActions
|
|||
verbLower = 'cancel'
|
||||
if !lesson.isRequested || lesson.recurring
|
||||
buttons = []
|
||||
buttons.push({name: 'CANCEL', buttonStyle: 'button-grey'})
|
||||
buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'})
|
||||
buttons.push({
|
||||
name: 'THIS SESSION',
|
||||
name: 'CANCEL THIS LESSON',
|
||||
buttonStyle: 'button-orange',
|
||||
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({
|
||||
title: 'Select One',
|
||||
html: "Do you wish to all #{verbLower} all lessons or just the one selected?",
|
||||
|
|
@ -521,7 +527,10 @@ LessonTimerActions = context.LessonTimerActions
|
|||
else
|
||||
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.startingSoon
|
||||
|
|
|
|||
|
|
@ -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> :
|
||||
<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> :
|
||||
<a href="/client#/jamclass">JamClass Home</a> :
|
||||
<a className="teacher-search-options" href="/client#/jamclass/searchOptions">Teachers Search</a><span
|
||||
className="teacher-quote"> : </span>
|
||||
{complete}
|
||||
</div>`
|
||||
})
|
||||
|
|
@ -50,6 +50,44 @@ UserStore = context.UserStore
|
|||
slot.creatorRoleRelative = "your"
|
||||
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) ->
|
||||
if nextState.booking?
|
||||
booking = nextState.booking
|
||||
|
|
@ -61,6 +99,35 @@ UserStore = context.UserStore
|
|||
@processSlot(booking.default_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: () ->
|
||||
{
|
||||
user: null,
|
||||
|
|
@ -73,14 +140,28 @@ UserStore = context.UserStore
|
|||
|
||||
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) ->
|
||||
@setState({updating: true, counterErrors: null, cancelErrors: null})
|
||||
parsed = @parseId(e.id)
|
||||
@setState({updating: true, counterErrors: null, cancelErrors: null, purpose: parsed.purpose})
|
||||
rest.getLessonBooking({
|
||||
id: e.id,
|
||||
id: parsed.id,
|
||||
}).done((response) => @getLessonBookingDone(response)).fail(@app.ajaxError)
|
||||
|
||||
hasFocusedLesson: () ->
|
||||
this.state.booking.focused_lesson?.id?
|
||||
@focusedLesson()?
|
||||
|
||||
focusedLesson: () ->
|
||||
this.state?.booking?.focused_lesson
|
||||
|
|
@ -105,6 +186,14 @@ UserStore = context.UserStore
|
|||
#booking.next_lesson.lesson_booking = booking
|
||||
@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})
|
||||
|
||||
getLessonBookingDone: (response) ->
|
||||
|
|
@ -213,7 +302,8 @@ UserStore = context.UserStore
|
|||
minute = $slot.find('.minute').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 != ''
|
||||
hour = new Number(hour)
|
||||
|
|
@ -227,7 +317,7 @@ UserStore = context.UserStore
|
|||
else
|
||||
minute = null
|
||||
|
||||
if !@isRecurring()
|
||||
if !update_all
|
||||
date = picker.datepicker("getDate")
|
||||
if 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)}."
|
||||
|
||||
slotTime: (slot, booking = this.state.booking) ->
|
||||
if @isRecurring(booking)
|
||||
"#{this.dayOfWeek(slot)} at #{this.dayTime(slot)}"
|
||||
else
|
||||
if @hasFocusedLesson() || !@isRecurring(booking)
|
||||
slot.pretty_start_time
|
||||
else
|
||||
"#{this.dayOfWeek(slot)} at #{this.dayTime(slot)}"
|
||||
|
||||
slotTimePhrase: (slot) ->
|
||||
if @isRecurring()
|
||||
|
|
@ -704,9 +794,10 @@ UserStore = context.UserStore
|
|||
|
||||
renderStudentRequested: () ->
|
||||
`<div className="contents">
|
||||
<div className="row">
|
||||
<div className="row request-sent">
|
||||
{this.userHeader(this.myself())}
|
||||
Your request has been sent. You will receive an email when {this.teacher().name} responds.
|
||||
{this.createDetail()}
|
||||
</div>
|
||||
<LessonBookingDecision {...this.decisionProps([])} />
|
||||
</div>`
|
||||
|
|
@ -719,6 +810,22 @@ UserStore = context.UserStore
|
|||
updateCreditCard: (e) ->
|
||||
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: () ->
|
||||
@renderStudentApproved()
|
||||
|
||||
|
|
@ -746,10 +853,7 @@ UserStore = context.UserStore
|
|||
if @studentMadeDefaultSlot()
|
||||
message = this.slotMessage(this.state.booking.default_slot, 'accept')
|
||||
|
||||
if @isRecurring()
|
||||
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>`
|
||||
detail = @createDetail()
|
||||
summary = `<div className="row">
|
||||
{this.userHeader(this.teacher())}
|
||||
<p>Has accepted your lesson request.</p>
|
||||
|
|
@ -766,10 +870,7 @@ UserStore = context.UserStore
|
|||
if @studentMadeDefaultSlot()
|
||||
message = this.slotMessage(this.state.booking.default_slot, 'accept')
|
||||
|
||||
if @isRecurring()
|
||||
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>`
|
||||
detail = @createDetail()
|
||||
summary = `<div className="row">
|
||||
{this.userHeader(this.teacher())}
|
||||
<p>Has accepted your lesson request.</p>
|
||||
|
|
@ -780,10 +881,7 @@ UserStore = context.UserStore
|
|||
if @studentMadeDefaultSlot()
|
||||
message = this.slotMessage(this.state.booking.default_slot, 'accept')
|
||||
|
||||
if @isRecurring()
|
||||
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>`
|
||||
detail = @createDetail()
|
||||
|
||||
summary = `<div className="row">
|
||||
{this.userHeader(this.teacher())}
|
||||
|
|
@ -847,10 +945,7 @@ UserStore = context.UserStore
|
|||
if @studentMadeDefaultSlot()
|
||||
message = this.slotMessage(this.state.booking.default_slot, 'accept')
|
||||
|
||||
if @isRecurring()
|
||||
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>`
|
||||
detail = @createDetail()
|
||||
summary = `<div className="row">
|
||||
{this.userHeader(this.student())}
|
||||
<p>Is ready to take the lesson.</p>
|
||||
|
|
@ -869,10 +964,7 @@ UserStore = context.UserStore
|
|||
if @studentMadeDefaultSlot()
|
||||
message = this.slotMessage(this.state.booking.default_slot, 'accept')
|
||||
|
||||
if @isRecurring()
|
||||
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>`
|
||||
detail = @createDetail()
|
||||
|
||||
summary = `<div className="row">
|
||||
{this.userHeader(this.teacher())}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@
|
|||
@checkboxes = [{selector: 'input.slot-decision', stateKey: 'slot-decision'}, {selector: 'input.update-all', propsKey: 'update_all'}]
|
||||
@root = $(@getDOMNode())
|
||||
@iCheckify()
|
||||
@slotDate = @root.find('.date-picker')
|
||||
@slotDate.datepicker({
|
||||
dateFormat: "D M d yy",
|
||||
onSelect: ((e) => @toggleDate(e))
|
||||
})
|
||||
|
||||
componentDidUpdate: () ->
|
||||
@iCheckify()
|
||||
|
|
@ -66,6 +71,14 @@
|
|||
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) ->
|
||||
|
||||
|
||||
|
|
@ -126,10 +139,21 @@
|
|||
|
||||
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: () ->
|
||||
|
||||
|
||||
showUpdateAll = !this.props.initial
|
||||
#showUpdateAll = !this.props.initial
|
||||
|
||||
if (!this.props.initial && !this.props.counter) || this.props.selfLastToAct
|
||||
userPromptHeader = `<h3>Would you like to change this lesson?</h3>`
|
||||
|
|
@ -150,7 +174,7 @@
|
|||
else
|
||||
verb = "CANCEL"
|
||||
|
||||
if this.props.update_all && showUpdateAll
|
||||
if this.props.update_all
|
||||
actionBtnText = "#{verb} ALL LESSONS"
|
||||
else
|
||||
actionBtnText = "#{verb} LESSON"
|
||||
|
|
@ -159,13 +183,13 @@
|
|||
else
|
||||
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?
|
||||
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">
|
||||
<span className="alt-date-block">
|
||||
|
|
@ -182,17 +206,18 @@
|
|||
</span>
|
||||
|
||||
</div>`
|
||||
if showUpdateAll
|
||||
updateAllField =
|
||||
`<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>
|
||||
</div>`
|
||||
#if @props.update_all
|
||||
# updateAllField =
|
||||
# `<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>
|
||||
# </div>`
|
||||
updateAllField = null
|
||||
|
||||
else
|
||||
slotAltPrompt = `<div className="slot-alt-prompt">
|
||||
<span className="alt-date-block">
|
||||
<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 className="alt-time-block">
|
||||
<span className="alt-time">Time:</span>
|
||||
|
|
@ -222,7 +247,7 @@
|
|||
else
|
||||
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">
|
||||
<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>
|
||||
|
|
@ -234,6 +259,12 @@
|
|||
# if you have issued a counter, you should be able to withdraw it
|
||||
# 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="column column-left">
|
||||
{userPromptHeader}
|
||||
|
|
@ -247,10 +278,7 @@
|
|||
</div>
|
||||
{slotAltPrompt}
|
||||
</div>
|
||||
<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}
|
||||
{updateAllField}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -294,18 +294,24 @@ UserStore = context.UserStore
|
|||
|
||||
logger.debug("testDriveCount: " + testDriveCount)
|
||||
|
||||
testDriveCountInt = parseInt(testDriveCount);
|
||||
if context._.isNaN(testDriveCountInt)
|
||||
testDriveCountInt = 3
|
||||
|
||||
context.JK.GA.trackTestDrivePurchase(testDriveCountInt);
|
||||
|
||||
if response.test_drive?.teacher_id
|
||||
teacher_id = response.test_drive.teacher_id
|
||||
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."
|
||||
if testDriveCount == '1'
|
||||
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
|
||||
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"
|
||||
else
|
||||
if @state.teacher?.id
|
||||
|
||||
# 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."
|
||||
location = "/client#/jamclass/book-lesson/test-drive_" + teacher_id
|
||||
else
|
||||
|
|
@ -313,7 +319,7 @@ UserStore = context.UserStore
|
|||
location = "/client#/jamclass/book-lesson/test-drive_" + teacher_id
|
||||
else
|
||||
# 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."
|
||||
location = "/client#/teachers/search"
|
||||
else
|
||||
|
|
|
|||
|
|
@ -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>`
|
||||
})
|
||||
|
||||
|
|
@ -704,14 +704,21 @@ proficiencyDescriptionMap = {
|
|||
render: () ->
|
||||
if @state.user?
|
||||
avatar = context.JK.resolveAvatarUrl(@state.user.photo_url);
|
||||
if @state.user?.teacher?
|
||||
mainContent = @mainContent()
|
||||
profileLeft = @profileLeft()
|
||||
else
|
||||
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>`
|
||||
if @state.user?
|
||||
if @state.user.teacher?
|
||||
mainContent = @mainContent()
|
||||
profileLeft = @profileLeft()
|
||||
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="">
|
||||
{noTeacherProfile}
|
||||
</div>`
|
||||
|
|
@ -730,10 +737,7 @@ proficiencyDescriptionMap = {
|
|||
</div>`
|
||||
`<div className="content-body-scroller">
|
||||
<div className="profile-header profile-head">
|
||||
|
||||
<div className="user-header">
|
||||
<h2 id="username"></h2>
|
||||
</div>
|
||||
<JamClassSearchHeader teacher={this.state.user}/>
|
||||
|
||||
{actionButtons}
|
||||
<br clear="all"/><br />
|
||||
|
|
|
|||
|
|
@ -5,11 +5,25 @@ LocationActions = @LocationActions
|
|||
|
||||
@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")]
|
||||
|
||||
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: () ->
|
||||
{options: {onlyMySchool: true}}
|
||||
|
||||
|
|
@ -71,6 +85,20 @@ LocationActions = @LocationActions
|
|||
options.onlyMySchool = checked
|
||||
@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: () ->
|
||||
if @state.user?.school_id?
|
||||
onlySchoolOption =
|
||||
|
|
@ -114,15 +142,15 @@ LocationActions = @LocationActions
|
|||
<h3>Student Levels Taught:</h3>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ ProfileActions = @ProfileActions
|
|||
refreshing: false
|
||||
|
||||
getInitialState: () ->
|
||||
{searchOptions: {}, results: [], user: null}
|
||||
{searchOptions: {}, results: [], user: null, searching: false}
|
||||
|
||||
onAppInit: (@app) ->
|
||||
@app.bindScreen('teachers/search', {beforeShow: @beforeShow, afterShow: @afterShow, afterHide: @afterHide})
|
||||
|
|
@ -58,7 +58,7 @@ ProfileActions = @ProfileActions
|
|||
@needToSearch = true
|
||||
|
||||
onTeacherSearchResultsStore: (results) ->
|
||||
results.searching = false
|
||||
#results.searching = false
|
||||
@refreshing = false
|
||||
@contentBodyScroller.find('.infinite-scroll-loader-2').remove()
|
||||
@setState(results)
|
||||
|
|
@ -89,7 +89,7 @@ ProfileActions = @ProfileActions
|
|||
if $scroller.scrollTop() + $scroller.innerHeight() + 100 >= $scroller[0].scrollHeight
|
||||
$scroller.append('<div class="infinite-scroll-loader-2">... Loading more Teachers ...</div>')
|
||||
@refreshing = true
|
||||
@setState({searching: true})
|
||||
#@setState({searching: true})
|
||||
logger.debug("refreshing more teachers for infinite scroll")
|
||||
TeacherSearchResultsActions.nextPage()
|
||||
)
|
||||
|
|
@ -149,7 +149,9 @@ ProfileActions = @ProfileActions
|
|||
moreAboutTeacher: (user, e) ->
|
||||
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) ->
|
||||
e.preventDefault()
|
||||
|
|
@ -210,162 +212,60 @@ ProfileActions = @ProfileActions
|
|||
target.trigger( 'destroy.dot' );
|
||||
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: () ->
|
||||
|
||||
searchDesc = @createSearchDescription()
|
||||
|
||||
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
|
||||
if !photo_url?
|
||||
photo_url = '/assets/shared/avatar_generic.png'
|
||||
photo_url = user.photo_url
|
||||
if !photo_url?
|
||||
photo_url = '/assets/shared/avatar_generic.png'
|
||||
|
||||
bio = user.teacher.biography
|
||||
if !bio?
|
||||
bio = 'No bio'
|
||||
bio = user.teacher.biography
|
||||
if !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
|
||||
bookTestDriveBtn = null
|
||||
bookSingleBtn = null
|
||||
bookTestDriveBtn = null
|
||||
|
||||
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>`
|
||||
else
|
||||
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}>
|
||||
<div className="user-avatar">
|
||||
<div className="avatar small">
|
||||
<img src={photo_url} />
|
||||
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>`
|
||||
else
|
||||
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}>
|
||||
<div className="user-avatar">
|
||||
<div className="avatar small">
|
||||
<img src={photo_url} />
|
||||
</div>
|
||||
<div className="user-name">
|
||||
{user.name}
|
||||
</div>
|
||||
</div>
|
||||
<div className="user-name">
|
||||
{user.name}
|
||||
<div className="user-info">
|
||||
<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 className="user-info">
|
||||
<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>`)
|
||||
<br className="clearall" />
|
||||
</div>`)
|
||||
|
||||
`<div className="content-body-scroller">
|
||||
<div className="screen-content">
|
||||
<div className="header">
|
||||
<a href="/client#/home">JamKazam Home</a> :
|
||||
<a href="/client#/jamclass">JamClass Home</a> :
|
||||
<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>
|
||||
<JamClassSearchHeader/>
|
||||
</div>
|
||||
<div className="results">
|
||||
{resultsJsx}
|
||||
|
|
|
|||
|
|
@ -5,4 +5,5 @@ context = window
|
|||
startAttachNotation: {}
|
||||
startAttachAudio: {}
|
||||
uploadNotations: {}
|
||||
uploadAudios: {}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ rest = context.JK.Rest()
|
|||
</li>
|
||||
</ol>
|
||||
<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"
|
||||
onClick={alert.bind('not yet')}>JamClass
|
||||
JamClass and in JamKazam in general, you can check out our online <a target="_blank"
|
||||
href="https://jamkazam.desk.com/customer/en/portal/topics/926073-jamclass-online-music-lessons---for-students/articles">JamClass
|
||||
User Guide</a>.</p>
|
||||
</div>`
|
||||
`<div className="top-container">
|
||||
|
|
|
|||
|
|
@ -40,14 +40,18 @@ teacherActions = window.JK.Actions.Teacher
|
|||
enableICheck: (e) ->
|
||||
if !@root?
|
||||
return
|
||||
checkBoxes = @root.find('input[type="checkbox"]')
|
||||
if checkBoxes.length > 0
|
||||
context.JK.checkbox(checkBoxes)
|
||||
checkBoxes.on('ifChanged', (e) => @checkIfCanFire(e))
|
||||
radioBoxes = @root.find('input[type="radio"]')
|
||||
if radioBoxes.length > 0
|
||||
context.JK.checkbox(radioBoxes)
|
||||
radioBoxes.on('ifChanged', (e) => @checkIfCanFire(e))
|
||||
|
||||
for checkbox in @checkboxes
|
||||
selector = checkbox.selector
|
||||
|
||||
checkBoxes = @root.find(selector + '[type="checkbox"]')
|
||||
if checkBoxes.length > 0
|
||||
context.JK.checkbox(checkBoxes)
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ teacherActions = window.JK.Actions.Teacher
|
|||
lesson.isAdmin = context.JK.currentUserAdmin
|
||||
lesson.schoolOnSchool = lesson['school_on_school?']
|
||||
lesson.cardNotOk = !lesson.schoolOnSchool && !lesson.lesson_booking.card_presumed_ok
|
||||
|
||||
lesson.isActive = lesson['is_active?']
|
||||
if (lesson.status == 'requested' || lesson.status == 'countered')
|
||||
lesson.isRequested = true
|
||||
if lesson.cardNotOk
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ AttachmentActions = @AttachmentActions
|
|||
formData.append('lesson_session_id', @lessonId);
|
||||
formData.append('attachment_type', 'notation')
|
||||
|
||||
@app.layout.showDialog('music-notation-upload-dialog')
|
||||
|
||||
rest.uploadMusicNotations(formData)
|
||||
.done((response) => @doneUploadingNotatations(notations, response, doneCallback, failCallback))
|
||||
.fail((jqXHR) => @failUploadingNotations(jqXHR, failCallback))
|
||||
|
|
@ -137,6 +139,71 @@ AttachmentActions = @AttachmentActions
|
|||
else
|
||||
@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: () ->
|
||||
this.trigger({lessonId: @lessonId, uploading: @uploading})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions
|
|||
results: []
|
||||
page: 1
|
||||
limit: 20
|
||||
searching: false
|
||||
|
||||
init: ->
|
||||
# Register with the app store to get @app
|
||||
|
|
@ -22,6 +23,7 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions
|
|||
onReset: () ->
|
||||
@results = []
|
||||
@page = 1
|
||||
@searching = true
|
||||
@changed()
|
||||
|
||||
query = @createQuery()
|
||||
|
|
@ -29,11 +31,13 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions
|
|||
rest.searchTeachers(query)
|
||||
.done((response) =>
|
||||
@next = response.next
|
||||
|
||||
@searching = false
|
||||
@results.push.apply(@results, response.entries)
|
||||
@changed()
|
||||
)
|
||||
.fail((jqXHR, textStatus, errorMessage) =>
|
||||
@searching = false
|
||||
@changed()
|
||||
@app.ajaxError(jqXHR, textStatus, errorMessage)
|
||||
)
|
||||
|
||||
|
|
@ -42,18 +46,22 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions
|
|||
|
||||
query = @createQuery()
|
||||
|
||||
@searching = true
|
||||
rest.searchTeachers(query)
|
||||
.done((response) =>
|
||||
@next = response.next
|
||||
@results.push.apply(@results, response.entries)
|
||||
@searching = false
|
||||
@changed()
|
||||
)
|
||||
.fail((jqXHR, textStatus, errorMessage) =>
|
||||
@searching = false
|
||||
@app.ajaxError(jqXHR, textStatus, errorMessage)
|
||||
@changed()
|
||||
)
|
||||
|
||||
getState: () ->
|
||||
({results: @results, next: @next, currentPage: @page})
|
||||
({results: @results, next: @next, currentPage: @page, searching: @searching})
|
||||
|
||||
changed:() ->
|
||||
@trigger(@getState())
|
||||
|
|
|
|||
|
|
@ -76,7 +76,8 @@
|
|||
|
||||
$('.like-link').click(function() {
|
||||
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');
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@
|
|||
|
||||
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++) {
|
||||
$unassignedInputs = $inputChannels.find('input[type="checkbox"]:not(:checked)');
|
||||
|
|
@ -1105,6 +1105,11 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!passedOnce) {
|
||||
passedOnce = true;
|
||||
autoAssignToSingleInput();
|
||||
}
|
||||
|
||||
if(!savedProfile) {
|
||||
context.jamClient.FTUESetMusicProfileName(gearUtils.createProfileName(selectedDeviceInfo));
|
||||
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
|
||||
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)')
|
||||
$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++) {
|
||||
var $assignedInputs = $inputChannels.find('input[type="checkbox"]:checked')
|
||||
|
||||
var $allInputs = $inputChannels.find('input[type="checkbox"]')
|
||||
var firstInputDomNode = $allInputs.get(0)
|
||||
if ($assignedInputs.length == 1) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -1169,9 +1170,7 @@
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
|
|
@ -1182,6 +1181,7 @@
|
|||
}
|
||||
|
||||
function newSession() {
|
||||
passedOnce = false;
|
||||
savedProfile = false;
|
||||
initialScan = false;
|
||||
deviceInformation = gearUtils.loadDeviceInfo();
|
||||
|
|
|
|||
|
|
@ -79,6 +79,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
.onlyOption {
|
||||
input[type="radio"], .iradio_minimal {
|
||||
display:none;
|
||||
}
|
||||
.slot-alt-prompt {
|
||||
padding-left:0;
|
||||
}
|
||||
}
|
||||
|
||||
.slot-detail {
|
||||
color: $ColorTextTypical;
|
||||
display: inline-block;
|
||||
|
|
@ -160,6 +169,14 @@
|
|||
.user-name {
|
||||
line-height:48px;
|
||||
vertical-align:middle;
|
||||
color:white;
|
||||
}
|
||||
.request-sent {
|
||||
color:$ColorTextTypical;
|
||||
|
||||
.generic-time-stmt, .proposing-new-time {
|
||||
margin-top:10px !important;
|
||||
}
|
||||
}
|
||||
.avatar {
|
||||
position:absolute;
|
||||
|
|
|
|||
|
|
@ -194,7 +194,22 @@
|
|||
.years {float:right}
|
||||
}
|
||||
.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 {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,41 @@
|
|||
@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 {
|
||||
div[data-react-class="TeacherSearchScreen"] {
|
||||
height:100%;
|
||||
|
|
@ -9,29 +45,21 @@
|
|||
padding:20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
.jamclass-search-header {
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
a {
|
||||
font-size:16px;
|
||||
text-decoration:underline;
|
||||
margin-bottom:5px;
|
||||
}
|
||||
.search-results-options {
|
||||
font-size:16px;
|
||||
color:$ColorTextTypical;
|
||||
}
|
||||
.spinner-large {
|
||||
width:200px;
|
||||
height:200px;
|
||||
position:relative;
|
||||
margin:0 auto;
|
||||
}
|
||||
|
||||
a.readmore {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.search-summary {
|
||||
font-size:11px;
|
||||
}
|
||||
.teacher-search-result {
|
||||
@include border_box_sizing;
|
||||
clear:both;
|
||||
|
|
@ -108,17 +136,7 @@
|
|||
padding-right: 31px;
|
||||
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 {
|
||||
line-height:16px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,29 @@
|
|||
.checkbox-scroller {
|
||||
height:100px;
|
||||
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;
|
||||
width:25%;
|
||||
|
|
@ -72,6 +95,10 @@
|
|||
}
|
||||
|
||||
.student-levels-taught {
|
||||
.icheckbox_minimal {
|
||||
top:3px;
|
||||
margin-right:5px;
|
||||
}
|
||||
.teaching-level {
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,11 @@
|
|||
overflow-y:auto;
|
||||
}
|
||||
|
||||
|
||||
.title-artist {
|
||||
margin-left:25px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
height: 36px;
|
||||
}
|
||||
|
||||
|
||||
.recording-controls {
|
||||
margin-top: 15px;
|
||||
padding: 3px 5px 3px 10px;
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class ApiLessonBookingsController < ApiController
|
|||
specified_slot.timezone = params[:timezone]
|
||||
slots << specified_slot
|
||||
end
|
||||
|
||||
|
||||
@lesson_booking = LessonBooking.book_free(current_user, teacher, slots, params[:description])
|
||||
|
||||
if @lesson_booking.errors.any?
|
||||
|
|
@ -129,10 +129,10 @@ class ApiLessonBookingsController < ApiController
|
|||
def accept
|
||||
next_lesson = @lesson_booking.next_lesson
|
||||
result = next_lesson.accept({
|
||||
message: params[:message],
|
||||
slot: params[:slot],
|
||||
accepter: current_user
|
||||
})
|
||||
message: params[:message],
|
||||
slot: params[:slot],
|
||||
accepter: current_user
|
||||
})
|
||||
|
||||
if result.errors.any?
|
||||
if result.is_a?(JamRuby::LessonBooking)
|
||||
|
|
@ -159,16 +159,20 @@ class ApiLessonBookingsController < ApiController
|
|||
|
||||
slot = LessonBookingSlot.new
|
||||
if @lesson_booking.recurring
|
||||
slot.slot_type = LessonBookingSlot::SLOT_TYPE_RECURRING
|
||||
slot.day_of_week = params[:day_of_week]
|
||||
if params[:update_all]
|
||||
slot.slot_type = LessonBookingSlot::SLOT_TYPE_RECURRING
|
||||
else
|
||||
slot.slot_type = LessonBookingSlot::SLOT_TYPE_SINGLE
|
||||
end
|
||||
else
|
||||
slot.slot_type = LessonBookingSlot::SLOT_TYPE_SINGLE
|
||||
end
|
||||
|
||||
if params[:date].present?
|
||||
day = params[:date]
|
||||
day = Date.parse(day) if day && !day.include?('NaN')
|
||||
slot.preferred_day = day
|
||||
end
|
||||
slot.day_of_week = params[:day_of_week]
|
||||
if params[:date].present?
|
||||
day = params[:date]
|
||||
day = Date.parse(day) if day && !day.include?('NaN')
|
||||
slot.preferred_day = day
|
||||
end
|
||||
slot.hour = params[:hour]
|
||||
slot.minute = params[:minute]
|
||||
|
|
@ -176,10 +180,10 @@ class ApiLessonBookingsController < ApiController
|
|||
slot.update_all = params[:update_all]
|
||||
|
||||
result = target_lesson.counter({
|
||||
proposer: current_user,
|
||||
message: params[:message],
|
||||
slot: slot
|
||||
})
|
||||
proposer: current_user,
|
||||
message: params[:message],
|
||||
slot: slot
|
||||
})
|
||||
|
||||
if result.errors.any?
|
||||
if result.is_a?(JamRuby::LessonBooking)
|
||||
|
|
@ -188,7 +192,7 @@ class ApiLessonBookingsController < ApiController
|
|||
recursive_errors(result, [:lesson_booking_slots])
|
||||
else
|
||||
raise "unknown response type in counter #{result.class}"
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
@lesson_booking.reload
|
||||
|
|
@ -203,10 +207,10 @@ class ApiLessonBookingsController < ApiController
|
|||
@lesson_session = target_lesson
|
||||
|
||||
result = target_lesson.cancel({
|
||||
canceler: current_user,
|
||||
message: params[:message],
|
||||
update_all: true
|
||||
})
|
||||
canceler: current_user,
|
||||
message: params[:message],
|
||||
update_all: params[:update_all]
|
||||
})
|
||||
|
||||
if result.errors.any?
|
||||
if result.is_a?(JamRuby::LessonBooking)
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class ApiLessonSessionsController < ApiController
|
|||
|
||||
if params[:update_all]
|
||||
# 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'}
|
||||
render :json => response, :status => 422
|
||||
return
|
||||
|
|
@ -135,6 +135,7 @@ class ApiLessonSessionsController < ApiController
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
render :json => {}, :status => 200
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ class ApiMusicNotationsController < ApiController
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -955,10 +955,13 @@ class ApiUsersController < ApiController
|
|||
end
|
||||
|
||||
def lookup_user
|
||||
|
||||
User.includes([{musician_instruments: :instrument},
|
||||
{band_musicians: :user},
|
||||
{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])
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -380,7 +380,8 @@ class UsersController < ApplicationController
|
|||
js =<<JS
|
||||
<script type="text/javascript">
|
||||
$(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}";
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -77,4 +77,8 @@ module MusicSessionHelper
|
|||
def pretty_scheduled_start(music_session, with_timezone, shorter = false)
|
||||
music_session.pretty_scheduled_start(with_timezone, shorter)
|
||||
end
|
||||
|
||||
def pretty_scheduled_start_slot(slot, with_timezone)
|
||||
slot.pretty_scheduled_start(with_timezone)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ object @lesson_session
|
|||
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,
|
||||
: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|
|
||||
{
|
||||
|
|
@ -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) {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ node :next do |page|
|
|||
end
|
||||
|
||||
node :entries do |page|
|
||||
partial "api_users/show", object: @users
|
||||
partial "api_users/show_teacher_index", object: @users
|
||||
end
|
||||
|
||||
node :total_entries do |page|
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -14,25 +14,7 @@ script type='text/template' id='template-lesson-session-actions'
|
|||
|
||||
li data-lesson-option="attach-audio"
|
||||
a href='#' Attach Audio File
|
||||
= '{% } 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 if (data.isScheduled) { %}'
|
||||
= '{% } else if (data.isActive) { %}'
|
||||
ul
|
||||
li data-lesson-option="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"
|
||||
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 { %}'
|
||||
ul
|
||||
|
|
|
|||
|
|
@ -16,14 +16,13 @@
|
|||
.wizard-step{ 'layout-wizard-step' => "1", 'dialog-title' => "Select & Test Audio Gear", 'dialog-purpose' => "SelectAudioGear" }
|
||||
.ftuesteps
|
||||
.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-column
|
||||
%h2 Instructions
|
||||
.ftue-box.instructions
|
||||
%ul
|
||||
%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 Configure interface settings.
|
||||
%li View test results.
|
||||
|
|
|
|||
|
|
@ -54,3 +54,4 @@
|
|||
= render 'dialogs/cancelLessonDialog'
|
||||
= render 'dialogs/rescheduleLessonDialog'
|
||||
= render 'dialogs/rateUserDialog'
|
||||
= render 'dialogs/musicNotationUploadDialog'
|
||||
|
|
@ -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', {}
|
||||
|
|
@ -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}}"}
|
||||
%img{ src: '{{data.feed_item.helpers.avatar}}' }
|
||||
/ type and artist
|
||||
.left.ml20.w15
|
||||
.left.ml20.w15.title-artist
|
||||
.title
|
||||
%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)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
require 'factory_girl'
|
||||
require 'timecop'
|
||||
require 'rspec-rails'
|
||||
begin
|
||||
require Rails.root.join('spec', 'support', 'lessons.rb')
|
||||
rescue LoadError
|
||||
puts "for production; we ignore LoadError"
|
||||
end
|
||||
|
||||
namespace :lessons do
|
||||
|
||||
|
|
@ -77,8 +81,8 @@ namespace :lessons do
|
|||
end
|
||||
|
||||
task book_test_drive: :environment do |task, args|
|
||||
user = User.find_by_email(ENV['STUDENT_EMAIL'])
|
||||
teacher = User.find_by_email(ENV['TEACHER_EMAIL'])
|
||||
user = User.find_by_email(ENV['STUDENT'])
|
||||
teacher = User.find_by_email(ENV['TEACHER'])
|
||||
|
||||
|
||||
slots = []
|
||||
|
|
@ -91,7 +95,6 @@ namespace :lessons do
|
|||
user.save!
|
||||
end
|
||||
|
||||
|
||||
booking = LessonBooking.book_test_drive(user, teacher, slots, "Hey I've heard of you before.")
|
||||
if booking.errors.any?
|
||||
puts booking.errors.inspect
|
||||
|
|
@ -100,7 +103,7 @@ namespace :lessons do
|
|||
lesson = booking.lesson_sessions[0]
|
||||
|
||||
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
|
||||
|
||||
#lesson.accept({message: 'Yeah I got this', slot: slots[0]})
|
||||
|
|
@ -109,6 +112,6 @@ namespace :lessons do
|
|||
#lesson.slot.should eql slots[0]
|
||||
#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
|
||||
|
|
|
|||
|
|
@ -23,6 +23,21 @@ describe ApiTeachersController do
|
|||
Teacher.destroy_all
|
||||
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
|
||||
it "simple" do
|
||||
post :create, biography: BIO, format: 'json'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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
|
||||
find('a.button-orange', text:'CLOSE').trigger(:click)
|
||||
|
|
|
|||
|
|
@ -118,4 +118,95 @@ describe "JamClassScreen", :js => true, :type => :feature, :capybara_feature =>
|
|||
find('#jam-class-student-screen td.displayStatusColumn', text: 'Canceled (Teacher)')
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ describe "Lesson Booking Status page", :js => true, :type => :feature, :capybara
|
|||
|
||||
find('h2', text: 'your lesson has been requested')
|
||||
|
||||
find('p.proposing-new-time')
|
||||
screenshot
|
||||
end
|
||||
|
||||
|
|
@ -32,11 +33,14 @@ describe "Lesson Booking Status page", :js => true, :type => :feature, :capybara
|
|||
it "approved" do
|
||||
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('p.lesson-time', "will take place each")
|
||||
find('p.generic-time-stmt')
|
||||
screenshot
|
||||
end
|
||||
|
||||
|
|
@ -185,4 +189,90 @@ describe "Lesson Booking Status page", :js => true, :type => :feature, :capybara
|
|||
find('#lesson-booking', text: 'US Central Time')
|
||||
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
|
||||
|
|
@ -29,7 +29,7 @@ describe "Student Landing", :js => true, :type => :feature, :capybara_feature =>
|
|||
|
||||
fill_in "email", with: 'student_123@jamkazam.com'
|
||||
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)
|
||||
|
||||
# this should show on the /client#/home page (WILL CHANGE)
|
||||
|
|
|
|||
|
|
@ -25,12 +25,16 @@ def teacher_approve(lesson_session)
|
|||
sign_out_poltergeist(validate: true)
|
||||
sign_in_poltergeist(lesson_session.teacher, password: 'foobar')
|
||||
visit "/client#/jamclass/lesson-booking/" + lesson_session.id
|
||||
find(".slot-decision-field[data-slot-id=\"#{lesson_session.lesson_booking.default_slot.id}\"] ins", visible: false).trigger(:click)
|
||||
find('.schedule.button-orange').trigger(:click)
|
||||
# dismiss banner
|
||||
#find('a.button-orange', text:'CLOSE').trigger(:click)
|
||||
find('tr[data-lesson-session-id="' + lesson_session.id + '"] .displayStatusColumn', text: 'Scheduled')
|
||||
end
|
||||
|
||||
def date_picker_format(date)
|
||||
date.strftime('%a %b %d %Y')
|
||||
end
|
||||
def fill_out_single_lesson
|
||||
|
||||
find('h2', text: 'book testdrive lesson')
|
||||
|
|
@ -56,6 +60,33 @@ def fill_out_single_lesson
|
|||
|
||||
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)
|
||||
|
||||
fill_in 'card-number', with: '4111111111111111'
|
||||
|
|
@ -159,7 +190,7 @@ def book_lesson(user, teacher, options)
|
|||
end
|
||||
|
||||
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.reload
|
||||
lesson.slot.should eql slots[0] unless options[:no_validate]
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Loading…
Reference in New Issue