VRFS-4142 - test drive packaging feature

This commit is contained in:
Seth Call 2016-06-02 23:32:09 -05:00
parent ece81a2c9b
commit 88ebada41c
49 changed files with 750 additions and 123 deletions

View File

@ -3,6 +3,6 @@
ol.nested-fields
//= f.input :test_drive_package, :required=>true, value: @test_drive_package, include_blank: true
= f.input :user, :required=>true, collection: User.where(is_a_teacher: true, phantom: false), include_blank: true
= f.input :short_bio
= f.input :short_bio_temp
= link_to_remove_association "Delete Teacher", f, class: 'button', style: 'margin-left:10px'

View File

@ -356,4 +356,5 @@ remove_stripe_acct_id.sql
track_user_on_lesson.sql
audio_in_music_notations.sql
lesson_time_tracking.sql
packaged_test_drive.sql
packaged_test_drive.sql
packaged_test_drive2.sql

View File

@ -0,0 +1,2 @@
ALTER TABLE lesson_booking_slots ADD COLUMN from_package BOOL DEFAULT FALSE;
ALTER TABLE lesson_bookings ADD COLUMN test_drive_package_choice_id VARCHAR(64) REFERENCES test_drive_package_choices(id);

View File

@ -45,6 +45,7 @@ module JamRuby
belongs_to :default_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :default_slot_id, inverse_of: :defaulted_booking, :dependent => :destroy
belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_booking, :dependent => :destroy
belongs_to :school, class_name: "JamRuby::School"
belongs_to :test_drive_package_choice, class_name: "JamRuby::TestDrivePackageChoice"
has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot", :dependent => :destroy
has_many :lesson_sessions, class_name: "JamRuby::LessonSession", :dependent => :destroy
has_many :lesson_package_purchases, class_name: "JamRuby::LessonPackagePurchase", :dependent => :destroy
@ -139,6 +140,9 @@ module JamRuby
booked_price
end
def no_slots
default_slot.from_package
end
def alt_slot
found = nil
@ -193,12 +197,16 @@ module JamRuby
self.counterer = proposer
self.countered_at = Time.now
self.sent_counter_reminder = false
if self.default_slot.from_package
self.default_slot = slot
end
#self.status = STATUS_COUNTERED
self.save
end
def automatically_default_slot
if is_requested?
if is_requested? && default_slot.nil?
if lesson_booking_slots.length > 0
self.default_slot = lesson_booking_slots[0]
end
@ -680,8 +688,10 @@ module JamRuby
end
def validate_lesson_booking_slots
if lesson_booking_slots.length == 0 || lesson_booking_slots.length == 1
errors.add(:lesson_booking_slots, "must have two times specified")
if test_drive_package_choice.nil?
if lesson_booking_slots.length == 0 || lesson_booking_slots.length == 1
errors.add(:lesson_booking_slots, "must have two times specified")
end
end
end
@ -711,19 +721,22 @@ module JamRuby
!!school
end
def self.book_packaged_test_drive(user, teacher, description, test_drive_package_choice)
book_test_drive(user, teacher, LessonBookingSlot.packaged_slots, description, test_drive_package_choice)
end
def self.book_free(user, teacher, lesson_booking_slots, description)
self.book(user, teacher, LessonBooking::LESSON_TYPE_FREE, lesson_booking_slots, false, 30, PAYMENT_STYLE_ELSEWHERE, description)
end
def self.book_test_drive(user, teacher, lesson_booking_slots, description)
self.book(user, teacher, LessonBooking::LESSON_TYPE_TEST_DRIVE, lesson_booking_slots, false, 30, PAYMENT_STYLE_ELSEWHERE, description)
def self.book_test_drive(user, teacher, lesson_booking_slots, description, test_drive_package_choice = nil)
self.book(user, teacher, LessonBooking::LESSON_TYPE_TEST_DRIVE, lesson_booking_slots, false, 30, PAYMENT_STYLE_ELSEWHERE, description, test_drive_package_choice)
end
def self.book_normal(user, teacher, lesson_booking_slots, description, recurring, payment_style, lesson_length)
self.book(user, teacher, LessonBooking::LESSON_TYPE_PAID, lesson_booking_slots, recurring, lesson_length, payment_style, description)
end
def self.book(user, teacher, lesson_type, lesson_booking_slots, recurring, lesson_length, payment_style, description)
def self.book(user, teacher, lesson_type, lesson_booking_slots, recurring, lesson_length, payment_style, description, test_drive_package_choice = nil)
lesson_booking = nil
LessonBooking.transaction do
@ -739,6 +752,7 @@ module JamRuby
lesson_booking.payment_style = payment_style
lesson_booking.description = description
lesson_booking.status = STATUS_REQUESTED
lesson_booking.test_drive_package_choice = test_drive_package_choice
if lesson_booking.teacher && lesson_booking.teacher.teacher.school
lesson_booking.school = lesson_booking.teacher.teacher.school
end

View File

@ -74,6 +74,18 @@ module JamRuby
(Time.now + APP_CONFIG.minimum_lesson_booking_hrs * 60 * 60)
end
# create a canned slot for a TestDrivePackage. The most important thing here is that it expires in 30 days
def self.packaged_slots
slot = LessonBookingSlot.new
slot.from_package = true
slot.preferred_day = Date.today + 30
slot.slot_type = LessonBookingSlot::SLOT_TYPE_SINGLE
slot.hour = 1
slot.minute = 0
slot.timezone = 'America/Chicago'
[slot]
end
def scheduled_times(needed_sessions, minimum_start_time)
#puts "NEEDED SESSIONS #{needed_sessions} #{minimum_start_time}"

View File

@ -55,7 +55,7 @@ module JamRuby
def to_s
"#{name} (#{amount_charged})"
"#{name}"
end
def name

View File

@ -66,7 +66,6 @@ module JamRuby
validate :validate_canceled, :if => :canceling
validate :validate_autocancel, :if => :autocanceling
after_save :after_counter, :if => :countering
after_save :manage_slot_changes
after_create :create_charge
@ -444,10 +443,6 @@ module JamRuby
self.lesson_booking.save(:validate => false)
end
def after_counter
send_counter(@countered_lesson, @countered_slot)
end
def scheduled_start
if music_session
music_session.scheduled_start
@ -458,10 +453,12 @@ module JamRuby
end
def send_counter(countered_lesson, countered_slot)
if countered_slot.is_teacher_created?
UserMailer.student_lesson_counter(countered_lesson, countered_slot).deliver
else
UserMailer.teacher_lesson_counter(countered_lesson, countered_slot).deliver
if !lesson_booking.errors.any?
if countered_slot.is_teacher_created?
UserMailer.student_lesson_counter(countered_lesson, countered_slot).deliver
else
UserMailer.teacher_lesson_counter(countered_lesson, countered_slot).deliver
end
end
self.countering = false
end
@ -522,6 +519,7 @@ module JamRuby
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}.")
@ -632,7 +630,7 @@ module JamRuby
# if the school owner is a teacher, show his bookings too
extra_teacher = " OR lesson_sessions.teacher_id = '#{user.teacher.id}'"
end
query = query.where('lesson_sessions.teacher_id in (?)' + extra_teacher, user.owned_school.teachers.map {|t| t.user.id})
query = query.where('lesson_sessions.teacher_id in (?)' + extra_teacher, user.owned_school.teachers.map { |t| t.user.id })
query = query.where('lesson_sessions.status = ? OR lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED)
else
# this is a normal teacher (not a school owner)
@ -819,7 +817,7 @@ module JamRuby
self.countered_lesson = self
self.status = STATUS_COUNTERED
#if !update_all
self.counter_slot = slot
self.counter_slot = slot
#end
if self.save
#if update_all && !lesson_booking.counter(self, proposer, slot)
@ -832,6 +830,7 @@ module JamRuby
raise ActiveRecord::Rollback
end
send_counter(@countered_lesson, @countered_slot)
message = '' if message.nil?
msg = ChatMessage.create(slot.proposer, music_session, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, self, "New Time Proposed")
Notification.send_lesson_message('counter', self, slot.is_teacher_created?)

View File

@ -13,6 +13,9 @@ module JamRuby
#validate :teacher_count
def lesson_package_type
LessonPackageType.package_for_test_drive_count(package_type.to_i)
end
def teacher_count
if package_type != test_drive_package_teachers.length
self.errors.add(:test_drive_package_teachers, "wrong number of teachers specified for the given package type #{package_type}")

View File

@ -5,9 +5,9 @@ module JamRuby
@@log = Logging.logger[TestDrivePackageChoice]
belongs_to :test_drive_package, class_name: "JamRuby::TestDrivePackage"
belongs_to :user, class_name: "JamRuby::User"
has_many :test_drive_package_choice_teachers, class_name: "JamRuby::TestDrivePackageChoiceTeacher", foreign_key: :teacher_id
belongs_to :user, class_name: "JamRuby::User", foreign_key: :user_id, inverse_of: :test_drive_package_choices
has_many :test_drive_package_choice_teachers, class_name: "JamRuby::TestDrivePackageChoiceTeacher", inverse_of: :test_drive_package_choice
has_many :lesson_bookings, class_name: "JamRuby::LessonBooking"
end
end

View File

@ -4,7 +4,7 @@ module JamRuby
@@log = Logging.logger[TestDrivePackageChoiceTeacher]
belongs_to :test_drive_package_choice, class_name: "JamRuby::TestDrivePackageChoice"
belongs_to :test_drive_package_choice, class_name: "JamRuby::TestDrivePackageChoice", inverse_of: :test_drive_package_choice_teachers
belongs_to :teacher, class_name: "JamRuby::User", foreign_key: :teacher_id
end

View File

@ -4,8 +4,8 @@ module JamRuby
@@log = Logging.logger[TestDrivePackageTeacher]
attr_accessor :short_bio_temp
attr_accessible :user_id, :test_drive_package_id, :short_bio, as: :admin
attr_writer :short_bio_temp
attr_accessible :user_id, :test_drive_package_id, :short_bio, :short_bio_temp, as: :admin
belongs_to :test_drive_package, class_name: "JamRuby::TestDrivePackage"
belongs_to :user, class_name: "JamRuby::User"
@ -18,12 +18,19 @@ module JamRuby
# silly pass through for activeadmin. We pass short_bio set here on to teacher
def after_save
if user && user.teacher
user.teacher.short_bio = short_bio
user.teacher.save!
if @another_bio.present?
user.teacher.short_bio = @another_bio
user.teacher.save!
end
end
end
def short_bio
def short_bio_temp=(short_bio)
self.updated_at = Time.now
self.short_bio = short_bio
@another_bio = short_bio
end
def short_bio_temp
if user && user.teacher
user.teacher.short_bio
end

View File

@ -41,7 +41,7 @@ module JamRuby
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection
# updating_password corresponds to a lost_password
attr_accessor :validate_instruments, :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json, :expecting_gift_card
attr_accessor :test_drive_packaging, :validate_instruments, :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json, :expecting_gift_card
belongs_to :icecast_server_group, class_name: "JamRuby::IcecastServerGroup", inverse_of: :users, foreign_key: 'icecast_server_group_id'
@ -176,6 +176,8 @@ module JamRuby
has_many :teacher_lesson_bookings, :class_name => "JamRuby::LessonBooking", :foreign_key => "teacher_id", inverse_of: :teacher
has_many :teacher_distributions, :class_name => "JamRuby::TeacherDistribution", :foreign_key => "teacher_id", inverse_of: :teacher
has_many :teacher_payments, :class_name => "JamRuby::TeacherPayment", :foreign_key => "teacher_id", inverse_of: :teacher
has_many :test_drive_package_choice_teachers, :class_name => "JamRuby::TestDrivePackageChoiceTeacher", :foreign_key => "teacher_id"
has_many :test_drive_package_choices, :class_name => "JamRuby::TestDrivePackageChoice", :foreign_key => "user_id", inverse_of: :user
belongs_to :desired_package, :class_name => "JamRuby::LessonPackageType", :foreign_key => "lesson_package_type_id", inverse_of: :user_desired_packages # used to hold whether user last wanted test drive 4/2/1
# Shopping carts
@ -203,7 +205,7 @@ module JamRuby
has_many :taught_lessons, :class_name => "JamRuby::LessonSession", inverse_of: :teacher, foreign_key: :teacher_id
belongs_to :school, :class_name => "JamRuby::School", inverse_of: :students
has_one :owned_school, :class_name => "JamRuby::School", inverse_of: :user
has_many :test_drive_package_choices, :class_name =>"JamRuby::TestDrivePackageChoice"
has_many :jamblasters_users, class_name: "JamRuby::JamblasterUser"
has_many :jamblasters, class_name: 'JamRuby::Jamblaster', through: :jamblasters_users
@ -1076,6 +1078,27 @@ module JamRuby
end
end
def handle_test_drive_package(package, details)
self.test_drive_packaging = true
choice = TestDrivePackageChoice.new
choice.user = self
choice.test_drive_package = package
details[:teachers].each do |teacher|
teacher_choice = TestDrivePackageChoiceTeacher.new
teacher_choice.teacher = User.find(teacher[:id])
choice.test_drive_package_choice_teachers << teacher_choice
end
choice.save!
choice.test_drive_package_choice_teachers.each do |teacher_choice|
booking = LessonBooking.book_packaged_test_drive(self, teacher_choice.teacher, "Please suggest a time that works for you.", choice)
if booking.errors.any?
raise "unable to create booking in package user:#{self.email}"
end
end
end
# throws ActiveRecord::RecordNotFound if instrument is invalid
# throws an email delivery error if unable to connect out to SMTP
def self.signup(options)
@ -1107,6 +1130,9 @@ module JamRuby
school_id = options[:school_id]
school_interest = options[:school_interest]
origin = options[:origin]
test_drive_package_details = options[:test_drive_package]
test_drive_package = TestDrivePackage.find_by_name(test_drive_package_details[:name])
school = School.find(school_id) if school_id
user = User.new
@ -1351,6 +1377,7 @@ module JamRuby
user.save
end if affiliate_referral_id.present?
user.handle_test_drive_package(test_drive_package, test_drive_package_details) if test_drive_package
if user.is_a_student
UserMailer.student_welcome_message(user).deliver
@ -1981,9 +2008,10 @@ module JamRuby
customer
end
def card_approved(token, zip, booking_id)
def card_approved(token, zip, booking_id, test_drive_package_choice_id = nil)
approved_booking = nil
choice = nil
found_uncollectables = nil
User.transaction do
self.stripe_token = token if token
@ -1999,6 +2027,13 @@ module JamRuby
end
end
if test_drive_package_choice_id
choice = TestDrivePackageChoice.find(test_drive_package_choice_id)
choice.lesson_bookings.each do|booking|
booking.card_approved
end
end
if uncollectables.count > 0
found_uncollectables = uncollectables
uncollectables.update_all(billing_should_retry: true)
@ -2007,7 +2042,7 @@ module JamRuby
end
end
end
[approved_booking, found_uncollectables]
[approved_booking, found_uncollectables, choice]
end
def update_name(name)
@ -2038,6 +2073,7 @@ module JamRuby
purchase = nil
lesson_package_type = nil
uncollectables = nil
choice = nil
User.transaction do
if params[:name].present?
@ -2046,12 +2082,15 @@ module JamRuby
end
end
booking, uncollectables = card_approved(params[:token], params[:zip], params[:booking_id])
booking, uncollectables, choice = card_approved(params[:token], params[:zip], params[:booking_id], params[:test_drive_package_choice_id])
if params[:test_drive]
self.reload
if booking
lesson_package_type = booking.resolved_test_drive_package
elsif choice
choice.test_drive_package.lesson_package_type
end
if lesson_package_type.nil?
lesson_package_type = LessonPackageType.test_drive_4
end
@ -2071,7 +2110,7 @@ module JamRuby
end
{lesson: booking, test_drive: test_drive, purchase: purchase, lesson_package_type: lesson_package_type, uncollectables: uncollectables}
{lesson: booking, test_drive: test_drive, purchase: purchase, lesson_package_type: lesson_package_type, uncollectables: uncollectables, package: choice}
end
def requested_test_drive(teacher = nil)

View File

@ -108,6 +108,7 @@ FactoryGirl.define do
association :user, factory: :user
price_per_lesson_60_cents 3000
price_per_month_60_cents 3000
short_bio 'abc def uueue doc neck'
end
factory :musician_instrument, :class => JamRuby::MusicianInstrument do

View File

@ -905,6 +905,30 @@ describe User do
uncollectable.is_card_declined?.should be_false
end
end
describe "handle_test_drive_package" do
let(:user) {FactoryGirl.create(:user)}
it "4-count" do
package_size = 4
package = FactoryGirl.create(:test_drive_package, :four_pack)
detail = {}
teachers = []
detail[:teachers] = teachers
package.test_drive_package_teachers.each do |package_teacher|
teachers << {id: package_teacher.user.id}
end
user.handle_test_drive_package(package, detail)
user.errors.any?.should be_false
LessonSession.where(user_id: user.id).count.should eql package_size
user.student_lesson_bookings.count.should eql package_size
user.student_lesson_bookings.each do |booking|
booking.status.should eql LessonBooking::STATUS_REQUESTED
booking.card_presumed_ok.should be_false
end
end
end
=begin
describe "update avatar" do

View File

@ -271,7 +271,6 @@
var show = false;
console.log("data.newScreen", data.newScreen)
if (data.newScreen && activate.indexOf(data.newScreen) > -1) {
show = true;
}

View File

@ -237,6 +237,8 @@
}
helpBubble.testDrivePackageGo = function($element, $offsetParent, package_type) {
return context.JK.prodBubble($element, 'test-drive-package-go', {plural: package_type != '1'}, bigHelpDarkOptions({offsetParent:$offsetParent, width:260, positions:['bottom']}))
return context.JK.prodBubble($element, 'test-drive-package-go', {plural: package_type != '1'}, bigHelpDarkOptions({offsetParent:$offsetParent, width:300, duration:13000, positions:['bottom'], postShow: function(container) {
subtlePulse(container)
}}))
}
})(window, jQuery);

View File

@ -2133,6 +2133,15 @@
})
}
function getTestDrivePackageChoice(options) {
options = options || {}
return $.ajax({
type: "GET",
url: '/api/test_drive_package_choice/' + options.id,
dataType: "json",
contentType: 'application/json'
})
}
function getLessonBooking(options) {
options = options || {}
@ -2717,6 +2726,7 @@
this.portOverCarts = portOverCarts;
this.bookLesson = bookLesson;
this.attachRecordingToLesson = attachRecordingToLesson;
this.getTestDrivePackageChoice = getTestDrivePackageChoice;
this.getLessonBooking = getLessonBooking;
this.getUnprocessedLesson = getUnprocessedLesson;
this.getUnprocessedLessonOrIntent = getUnprocessedLessonOrIntent;

View File

@ -20,6 +20,9 @@
//= require jquery.ba-bbq
//= require jquery.icheck
//= require jquery.exists
//= require jquery.manageVsts
//= require jquery.lessonSessionActions
//= require ResizeSensor
//= require AAA_Log
//= require AAC_underscore
//= require alert
@ -35,6 +38,7 @@
//= require ui_helper
//= require jam_rest
//= require ga
//= require recordingModel
//= require web/signup_helper
//= require web/signin_helper
//= require web/signin

View File

@ -114,7 +114,10 @@ LessonTimerActions = context.LessonTimerActions
text: "Start time for session set to 65 mins ago"
})))
else if data.lessonAction == 'enter-payment'
window.location.href = "/client#/jamclass/lesson-payment/lesson-booking_#{lessonId}"
if lesson.lesson_booking.test_drive_package_choice_id?
window.location.href = "/client#/jamclass/lesson-payment/package_#{lesson.lesson_booking.test_drive_package_choice_id}"
else
window.location.href = "/client#/jamclass/lesson-payment/lesson-booking_#{lessonId}"
else
context.JK.showAlert('unknown lesson action', 'The option in the menu is unknown')
@ -527,7 +530,10 @@ LessonTimerActions = context.LessonTimerActions
else
unreadMessages = null
if lessonData.status == 'countered'
if lessonData.lesson_booking.no_slots
timeStmt = 'No time has been scheduled yet'
else if lessonData.status == 'countered'
timeStmt = lessonData.counter_slot.pretty_scheduled_start_with_timezone
else
timeStmt = lessonData.music_session.pretty_scheduled_start_with_timezone

View File

@ -50,6 +50,10 @@ UserStore = context.UserStore
slot.creatorRoleRelative = "your"
slot.mySlot = @mySlot(slot)
# for a test drive packaged delai, while we do create a slot to satisfy certain aspects of the backend, it's not 'real'. So we say 'noSlots' until someone has proposed something
noSlots: () ->
@state.booking?.no_slots
processBooking:(booking) ->
booking.neverAccepted = booking.accepter_id?
booking.isCounter = booking.counter_slot? && booking.status != 'canceled' && booking.status != 'suspended'
@ -58,6 +62,7 @@ UserStore = context.UserStore
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
@ -472,7 +477,10 @@ UserStore = context.UserStore
selfLastToAct: () ->
if @isRequested()
@studentViewing()
if @isCounter()
@counterer().id == @myself().id
else
@studentViewing()
else if @isCounter()
@counterer().id == @myself().id
else if @isCanceled()
@ -647,7 +655,8 @@ UserStore = context.UserStore
selfLastToAct: this.selfLastToAct(),
counterErrors: this.state.counterErrors,
cancelErrors: this.state.cancelErrors,
focusedLesson: this.focusedLesson()
focusedLesson: this.focusedLesson(),
noSlots: this.noSlots()
}
render: () ->
@ -813,9 +822,15 @@ UserStore = context.UserStore
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>`
if @noSlots()
detail = `<p className="proposing-new-time no-slot">You are proposing a date/time for this lesson.</p>`
else
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>`
if @noSlots()
detail = `<p className="generic-time-stmt no-slot">Your lesson has no scheduled time yet.</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>`
@ -916,13 +931,20 @@ UserStore = context.UserStore
else
action = `<p className="action">Has requested a {this.lessonDesc()} lesson, for which you will be paid {this.lessonPaymentAmt()}.</p>`
if @noSlots()
slots = []
else
slots = [this.state.booking.default_slot]
if this.state.booking.alt_slot?
slots.push(this.state.booking.alt_slot)
`<div className="contents">
<div className="row">
{this.userHeader(this.other())}
{action}
{this.slotMessage(this.state.booking.default_slot)}
</div>
<LessonBookingDecision {...this.decisionProps([this.state.booking.default_slot, this.state.booking.alt_slot])} />
<LessonBookingDecision {...this.decisionProps(slots)} />
</div>`
renderTeacherApproved: () ->

View File

@ -145,7 +145,10 @@
multipleOptions: () ->
if this.props.initial
!(!this.props.counter && this.props.selfLastToAct)
if this.props.noSlots
false
else
!(!this.props.counter && this.props.selfLastToAct)
else if this.props.counter
!this.props.selfLastToAct
else
@ -156,16 +159,19 @@
#showUpdateAll = !this.props.initial
if (!this.props.initial && !this.props.counter) || this.props.selfLastToAct
userPromptHeader = `<h3>Would you like to change this lesson?</h3>`
userPromptHeader = `<h3>Would you like to update this lesson?</h3>`
messagePromptHeader = `<h3>Send message to {this.props.otherRole} with your update.</h3>`
else
userPromptHeader = `<h3>How do you want to handle this request?</h3>`
messagePromptHeader = `<h3>Send message to {this.props.otherRole} with your response.</h3>`
if this.props.slot_decision == 'counter'
if this.props.update_all
actionBtnText = "PROPOSE ALTERNATE TIME FOR ALL LESSONS"
if this.props.is_recurring && this.props.update_all
actionBtnText = "PROPOSE TIME FOR ALL LESSONS"
else
actionBtnText = "PROPOSE ALTERNATE TIME"
if @props.noSlots
actionBtnText = 'PROPOSE TIME'
else
actionBtnText = "PROPOSE ALTERNATE TIME"
else if this.props.slot_decision == 'decline'
@ -199,7 +205,7 @@
<span className="alt-time-block">
<span className="alt-time">Time:</span>
<select className="hour">{this.hours}</select> : <select disabled={this.props.disabled} className="minute">{this.minutes}</select>
<select className="am_pm">{this.am_pm}</select>
<select disabled={this.props.disabled} className="am_pm">{this.am_pm}</select>
<br/>
<span>* Time will be local to {window.JK.currentTimezone()}</span>
{errorText}
@ -239,7 +245,10 @@
slots = []
if !(this.props.counter && this.props.selfLastToAct)
proposeAltLabelText = "Propose alternate day/time"
if this.props.noSlots
proposeAltLabelText = 'Propose a day/time'
else
proposeAltLabelText = "Propose alternate day/time"
for slot, i in @props.slots
if this.props.is_recurring
@ -255,7 +264,11 @@
{slotDetail}
</div>`)
else
proposeAltLabelText = "Propose new alternate day/time"
if this.props.noSlots
proposeAltLabelText = 'Propose a day/time'
else
proposeAltLabelText = "Propose new alternate day/time"
# if you have issued a counter, you should be able to withdraw it
# TODO

View File

@ -53,7 +53,8 @@ UserStore = context.UserStore
billingInUS: true,
userWantsUpdateCC: false,
"test-drive": false,
teacher: null
teacher: null,
package: null
}
beforeHide: (e) ->
@ -75,6 +76,9 @@ UserStore = context.UserStore
rest.getUserDetail({id: parsed.teacher_id}).done((response) => @teacherLoaded(response)).fail((jqXHR) => @failedTeacher(jqXHR))
else if parsed['test-drive']
logger.debug("test-drive lesson payment; no teacher/booking in context")
else if parsed['package-choice']
logger.debug("TestDrive package selected " + parsed.package_id)
rest.getTestDrivePackageChoice({id: parsed.package_id}).done((response) => @packageLoaded(response)).fail((jqXHR) => @failedPackage(jqXHR))
else
logger.error("unknown state for lesson-payment")
window.location.href = '/client#/jamclass'
@ -92,6 +96,7 @@ UserStore = context.UserStore
result['test-drive'] = false
result['lesson-booking'] = false
result['teacher-intent'] = false
result['package-choice'] = false
bits = id.split('_')
if bits.length == 1
@ -105,6 +110,9 @@ UserStore = context.UserStore
else if type == 'teacher'
result['teacher-intent'] = true
result.teacher_id = bits[1]
else if type == 'package'
result['package-choice'] = true
result.package_id = bits[1]
logger.debug("LessonPayment: parseId " + JSON.stringify(result))
@ -119,7 +127,7 @@ UserStore = context.UserStore
@setState({billingInUS: checked})
resetState: () ->
@setState({updating: false, lesson: null, teacher: null, "test-drive": false, "lesson-booking" : false, "teacher-intent": false})
@setState({updating: false, lesson: null, teacher: null, "test-drive": false, "lesson-booking" : false, "teacher-intent": false, package: null, "package-choice": null})
lessonBookingLoaded: (response) ->
@setState({updating: false})
@ -149,6 +157,18 @@ UserStore = context.UserStore
text: 'Something has gone wrong. Please try refreshing the page.'
})
packageLoaded: (response) ->
@setState({updating: false})
logger.debug("package loaded", response)
@setState({package: response})
failedPackage: (jqXHR) ->
@setState({updating: false})
@app.layout.notify({
title: 'unable to load package info',
text: 'Something has gone wrong. Please try refreshing the page.'
})
onBack: (e) ->
e.preventDefault()
window.location.href = '/client#/teachers/search'
@ -253,7 +273,7 @@ UserStore = context.UserStore
@state.lesson?.lesson_type == 'paid'
isTestDrive: () ->
@state['test-drive'] == true || @state.lesson?.lesson_type == 'test-drive' || @state['teacher-intent']
@state['test-drive'] == true || @state.lesson?.lesson_type == 'test-drive' || @state['teacher-intent'] || @state['package-choice'] == true
attemptPurchase: (token) ->
if this.state.billingInUS
@ -264,6 +284,7 @@ UserStore = context.UserStore
zip: zip,
test_drive: @isTestDrive(),
booking_id: @state.lesson?.id,
test_drive_package_choice_id: @state.package?.id
normal: @isNormal()
}
@ -304,6 +325,8 @@ UserStore = context.UserStore
teacher_id = response.test_drive.teacher_id
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 if response.package?
text = "Each teacher has received your request and should respond shortly."
else
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"
@ -326,7 +349,7 @@ UserStore = context.UserStore
text = "You now have #{testDriveCount} TestDrive credits that you can take with #{testDriveCount} different teachers.<br/><br/>We've taken you to the Teacher Search screen, so you can search for teachers right for you."
location = "/client#/teachers/search"
context.JK.Banner.showNotice("Test Drive Purchased",text)
context.JK.Banner.showNotice("TestDrive Purchased",text)
window.location = location
else
context.JK.Banner.showNotice("Something Went Wrong", "Please email support@jamkazam.com and indicate that your attempt to buy a TestDrive failed")
@ -392,7 +415,7 @@ UserStore = context.UserStore
{name}
</div>`
else
if @state.lesson? || @state['test-drive'] || @state.teacher?
if @state.lesson? || @state['test-drive'] || @state.teacher? || @state['package-choice'] == true
if @state.teacher?
photo_url = @state.teacher.photo_url
name = @state.teacher.name
@ -406,6 +429,42 @@ UserStore = context.UserStore
</div>
{name}
</div>`
else if @state.package?
teachers = []
teachersHolder = []
count = 0
for teacher_choice in @state.package.teachers
if count == 2
teachersHolder.push(
`<div className="teacher-holder">
{teachers}
</div>`)
teachers = []
teacher = teacher_choice.user
photo_url = teacher.photo_url
name = teacher.name
if !photo_url?
photo_url = '/assets/shared/avatar_generic.png'
teachers.push(
`<div key={teacher.id} className="teacher-subheader">
<div className="avatar">
<img src={photo_url}/>
</div>
<div className="teacher-name-packaged">{teacher.first_name}<br/>{teacher.last_name}</div>
</div>`)
count++
teachersHolder.push(
`<div className="teacher-holder">
{teachers}
</div>`)
teacherDetails = `<div className="teacher-header packaged">
{teachersHolder}
<br className="clearall" />
</div>`
if @state.lesson?
lesson_length = @state.lesson.lesson_length
@ -415,6 +474,7 @@ UserStore = context.UserStore
lesson_type = 'test-drive'
if @isTestDrive()
if @reuseStoredCard()
@ -423,14 +483,26 @@ UserStore = context.UserStore
header = `<div><h2>enter payment info for test drive</h2></div>`
bookingInfo = `<p></p>`
if this.state.user.lesson_package_type_id == 'test-drive'
explanation = `<span>You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 4 private online music lessons - 1 each from 4 different instructors in the JamClass instructor community. The price of this TestDrive package is $49.99.</span>`
else if this.state.user.lesson_package_type_id == 'test-drive-1'
explanation =`<span>You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 1 private online music lesson from an instructor in the JamClass instructor community. The price of this TestDrive package is $14.99.</span>`
else if this.state.user.lesson_package_type_id == 'test-drive-2'
explanation =`<span>You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 2 private online music lessons - 1 each from 2 different instructors in the JamClass instructor community. The price of this TestDrive package is $29.99.</span>`
if this.state['package-choice']
if this.state.package?
if @state.package.teachers.length == 1
explanation = `<span>You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entities you to take a private online music lesson from this instructor. The price of this TestDrive is $14.99. If you have scheduling conflicts with this instructors, we will help you choose another teacher as a replacement.</span>`
else if @state.package.teachers.length == 2
explanation = `<span className="explanation">You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entities you to take 2 private online music lessons - 1 each from these 2 instructors. The price of this TestDrive is $29.99. If you have scheduling conflicts with any of these instructors, we will help you choose another teacher as a replacement.</span>`
else if @state.package.teachers.length == 4
explanation = `<span>You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entities you to take 4 private online music lessons - 1 each from these 4 instructors. The price of this TestDrive is $49.99. If you have scheduling conflicts with any of these instructors, we will help you choose another teacher as a replacement.</span>`
else
alert("unknown package type")
else
alert("You do not have a test drive package selected")
if this.state.user.lesson_package_type_id == 'test-drive'
explanation = `<span>You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 4 private online music lessons - 1 each from 4 different instructors in the JamClass instructor community. The price of this TestDrive package is $49.99.</span>`
else if this.state.user.lesson_package_type_id == 'test-drive-1'
explanation =`<span>You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 1 private online music lesson from an instructor in the JamClass instructor community. The price of this TestDrive package is $14.99.</span>`
else if this.state.user.lesson_package_type_id == 'test-drive-2'
explanation =`<span>You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 2 private online music lessons - 1 each from 2 different instructors in the JamClass instructor community. The price of this TestDrive package is $29.99.</span>`
else
alert("You do not have a test drive package selected")
bookingDetail = `<p>{explanation}

View File

@ -22,13 +22,33 @@ context = window
getInitialState: () ->
{
target: null,
page_data: null
target: null,
page_data: null
}
componentDidMount: () ->
@root = $(@getDOMNode())
@dialog = @root.closest('.dialog')
renderTeacher: (teacher) ->
photo_url = '/assets/shared/avatar_generic.png'
if teacher.photo_url?
photo_url = teacher.photo_url
`<div className="teacher-select">
<div className="avatar">
<img src={photo_url}/>
</div>
<div className="username">
{teacher.first_name}
<br/>
{teacher.last_name}
</div>
<div className="checkbox-wrapper">
<input type="checkbox" name="teacher-select" data-teacher-id={teacher.id}/>
</div>
</div>`
doCancel: (e) ->
e.preventDefault()
@ -43,55 +63,66 @@ context = window
checked = @root.find('input[type="checkbox"]:checked')
if checked.length != 2
context.JK.Banner.showNotice('hold on there', 'Please select 2 teachers.')
window.JK.Banner.showAlert('hold on there', 'Please select 2 teachers.')
return
if @state.target == '1'
checked = @root.find('input[type="checkbox"]:checked')
if checked.length != 1
context.JK.Banner.showNotice('hold on there', 'Please select a teacher.')
window.JK.Banner.showAlert('hold on there', 'Please select 1 teacher.')
return
teachers = []
console.log("STATE TIME", @state)
if @state.page_data?.package?.teachers?
for teacher in @state.page_data.package.teachers
teachers.push(@teacher(teacher.name, teacher.photo_url, teacher.id))
$.each(checked, (i, node) => (
$node = $(node)
teachers.push($node.data('teacher'))
teacherId = $node.attr('data-teacher-id')
for teacher in @state.page_data.package.teachers
if teacher.id == teacherId
teachers.push(teacher)
break
))
@root.data('result', { package_type: @state.target, teachers: teachers })
@dialog.data('result', {package_type: @state.target, teachers: teachers})
@app.layout.closeDialog('test-drive-package-dialog')
render: () ->
if @state.target == '2'
help =
`<p>
Check the boxes under the 2 instructors you want to select for your TestDrive package. Then click the Select button below.
Check the boxes under the 2 instructors you want to select for your TestDrive package. Then click the Select
button below.
</p>`
else
help =
help =
`<p>
Check the box under the instructor you want to select for your TestDrive package. Then click the Select button below.
Check the box under the instructor you want to select for your TestDrive package. Then click the Select button
below.
</p>`
teachers = []
if @state.page_data?.package?.teachers?
for teacher in @state.page_data.package.teachers
teachers.push(@renderTeacher(teacher))
teacherHolderClasses = {"teacher-holder" : true, "two": teachers.length == 2, "four": teachers.length == 4}
dialogInnerClasses = {"dialog-inner": true, "two": teachers.length == 2, "four": teachers.length == 4}
`<div>
<div className="content-head">
<img className="content-icon" src="/assets/content/icon_add.png" height={19} width={19}/>
<h1>select instructors</h1>
</div>
<div className="dialog-inner">
<div className={classNames(dialogInnerClasses)}>
{help}
{teachers}
<div className={classNames(teacherHolderClasses)}>
{teachers}
<br className="clearall"/>
</div>
<div className="actions">
<a onClick={this.doCancel} className="button-grey">CANCEL</a>

View File

@ -20,15 +20,23 @@
selector: (count, e) ->
e.preventDefault()
context.JK.app.layout.showDialog('test-drive-package-dialog').one(contexnt.JK.EVENTS.DIALOG_CLOSED, (e, data) =>
# cheesy way to pass the page data to the dialog
window.page_data = @props
window.JK.app.layout.showDialog('test-drive-package-dialog', {d1: count.toString()}).one(window.JK.EVENTS.DIALOG_CLOSED, (e, data) =>
#... code
if !data.canceled
console.log("dialog closed. result", data.result)
console.log("dialog closed. result", data)
# dialog wasn't cancelled, so let's check the value of our result:
@setState(data.result)
@setState({modified: true})
window.teacherModifications = data.result.teachers
setTimeout((() => context.JK.prodBubble($('.preview-area.jam-class'), 'body', data.result.package_type)), 1500)
$ctaBox = $('.preview-area.jam-class')
$.scrollTo($ctaBox, {duration: 500, offset: 0})
setTimeout((() => window.JK.HelpBubbleHelper.testDrivePackageGo($ctaBox, 'body', data.result.package_type)), 2500)
)
componentDidMount: () ->
@ -59,7 +67,7 @@
`<div className="other-options">
<p>Like the TestDrive concept, but 4 teachers is too many for you?</p>
<ul>
<li>Get a special offer of <a onClick={this.selector.bind(this, 2)}>2 of these teachers for a total of $29.99</a>.</li>
<li>Get a special offer of <a className="pick-two" onClick={this.selector.bind(this, 2)}>2 of these teachers for a total of $29.99</a>.</li>
<li>Or <a onClick={this.selector.bind(this, 1)}>1 teacher for $14.99</a>.</li>
<li>Or you can <a href="/client#/jamclass/searchOptions">search all of our teachers</a> and then book a TestDrive package.</li>
</ul>

View File

@ -46,7 +46,34 @@ rest = context.JK.Rest()
onClick={this.ctaClick}>{ctaButtonText}</button>
</form>
</div>`
if @props.package?
ctaBoxContents = `<div className={classNames({'preview-area': true, 'jam-class': true})}>
<p>Sign up for this amazing TestDrive offer now!</p>
<p>When you sign up below, we will ask you to pay for your TestDrive package, and then we'll forward
your lesson requests to these teachers for scheduling.</p>
<p>We will not share your email. See our <a href="/corp/privacy" onClick={this.privacyPolicy}>privacy
policy</a></p>
{register}
<p>We'll give you 1:1 help to get set up and ready to go with our free app.</p>
</div>`
else
ctaBoxContents = `<div className={classNames({'preview-area': true, 'jam-class': true})}>
<p>Sign up now. You have no obligation to buy anything. Signing up makes you eligible for our TestDrive
offers.</p>
<p>After signing up, you can search our community of world-class instructors. If you book a TestDrive lesson
you can choose to TestDrive 4, 2, or 1 teachers at that time.</p>
<p>We will not share your email. See our <a href="/corp/privacy" onClick={this.privacyPolicy}>privacy
policy</a></p>
{register}
<p>And pick your teachers now!</p>
<p>We'll give you 1:1 help to get set up and ready to go with our free app.</p>
</div>`
`<div className="top-container">
<div className="full-row name-and-artist">
@ -71,20 +98,7 @@ rest = context.JK.Rest()
<div className="preview-jamtrack-header">
Sign Up for TestDrive
</div>
<div className={classNames({'preview-area': true, 'jam-class': true})}>
<p>Sign up now. You have no obligation to buy anything. Signing up makes you eligible for our TestDrive
offers.</p>
<p>After signing up, you can search our community of world-class instructors. If you book a TestDrive lesson
you can choose to TestDrive 4, 2, or 1 teachers at that time.</p>
<p>We will not share your email. See our <a href="/corp/privacy" onClick={this.privacyPolicy}>privacy
policy</a></p>
{register}
<p>And pick your teachers now!</p>
<p>We'll give you 1:1 help to get set up and ready to go with our free app.</p>
</div>
{ctaBoxContents}
</div>
</div>
<div className="row summary-text">
@ -156,10 +170,28 @@ rest = context.JK.Rest()
password = $form.find('input[name="password"]').val()
terms = $form.find('input[name="terms"]').is(':checked')
rest.signup({email: email, password: password, first_name: null, last_name: null, terms: terms, student: true})
test_drive_package = null
if @props.package
test_drive_package ={}
if window.teacherModifications?
teachers = window.window.teacherModifications
else
teachers = @props.package.teachers
test_drive_package.name = @props.package.name
test_drive_package.teachers = teachers
rest.signup({email: email, password: password, first_name: null, last_name: null, terms: terms, student: true, test_drive_package: test_drive_package})
.done((response) =>
@setState({done: true})
context.location = '/client#/jamclass/searchOptions'
if test_drive_package?
choice = response.test_drive_package_choices?[0]
if choice?
context.location = '/client#/jamclass/lesson-payment/package_' + choice.id
else
context.location = '/client#/jamclass/searchOptions'
else
context.location = '/client#/jamclass/searchOptions'
).fail((jqXHR) =>
@setState({processing: false})
if jqXHR.status == 422

View File

@ -23,7 +23,11 @@
//= require jquery.exists
//= require jquery.visible
//= require jquery.lessonSessionActions
//= require jquery.manageVsts
//= require jquery.scrollTo
//= require jquery.pulse
//= require howler.core.js
//= require ResizeSensor
//= require AAA_Log
//= require AAC_underscore
//= require alert
@ -59,6 +63,7 @@
//= require jam_track_preview
//= require landing/init
//= require landing/signup
//= require recordingModel
//= require web/downloads
//= require web/congratulations
//= require web/sessions

View File

@ -1,7 +1,7 @@
@import "client/common";
//body.jam, body.web, .dialog{
html {
html body {
.bt-wrapper {
font-size: 14px;
font-family: 'Raleway', Arial, Helvetica, sans-serif;
@ -15,6 +15,8 @@ html {
margin:1em;
line-height:150%;
font-size:14px;
width:auto;
@include border_box_sizing;
}
ul {
font-size:14px;
@ -153,6 +155,9 @@ html {
width:180px;
}
.test-drive-package-go {
width:300px;
}
.help-hover-recorded-tracks, .help-hover-stream-mix, .help-hover-recorded-backing-tracks {

View File

@ -95,6 +95,27 @@
-moz-border-radius:24px;
border-radius:24px;
}
.teacher-subheader {
.avatar {
margin:10px 0 0 0;
}
float:left;
width:100px;
text-align:center;
}
.teacher-holder {
display:inline-block;
}
.teacher-header {
&.packaged {
margin:40px 0 20px 0;
text-align:center;
}
}
.teacher-name-packaged {
color:$ColorTextTypical;
overflow: hidden;
}
.teacher-name {
font-size:16px;
display:inline-block;

View File

@ -0,0 +1,85 @@
@import "client/common";
#test-drive-package-dialog {
//width: 700px;
min-width:400px;
width:auto;
h3 {
color:white;
margin-bottom:20px;
}
.dialog-inner {
width: auto;
&.two {
width:400px;
}
&.four {
width:600px;
}
height:100%;
@include border_box_sizing;
margin-top: -29px;
padding: 50px 25px 25px;
}
div[data-react-class="TestDrivePackageDialog"] {
}
.TestDrivePackageDialog {
height:100%;
}
.teacher-select {
float:left;
}
.actions {
text-align:center;
}
.teacher-select {
width: 120px;
text-align:center;
margin-bottom:20px;
}
.avatar {
display: inline-block;
text-align:center;
padding: 1px;
width: 48px;
height: 48px;
background-color: #ed4818;
margin: 4px 0px 7px 0;
-webkit-border-radius: 24px;
-moz-border-radius: 24px;
border-radius: 24px;
float: none;
}
.avatar img {
width: 48px;
height: 48px;
-webkit-border-radius: 24px;
-moz-border-radius: 24px;
border-radius: 24px;
}
.username {
margin-bottom:10px;
}
.teacher-holder {
text-align:center;
margin:auto;
margin-top:20px;
&.two {
width:242px;
}
&.four {
width:482px;
}
}
}

View File

@ -512,7 +512,7 @@ body.web.individual_jamtrack {
p {
line-height: 150%;
width: 100% !important;
width: 100% ;
}
.bottom-banner {
@ -1056,6 +1056,14 @@ body.web.individual_jamtrack {
}
}
.other-options {
margin-top:20px;
border:1px solid $copy-color-on-dark;
padding:20px;
ul {
margin-bottom:0;
}
}
.teacher-option {
margin-bottom:20px;
height: 98px;
@ -1078,6 +1086,7 @@ body.web.individual_jamtrack {
display: inline-block;
width: 140px;
margin-right: 20px;
text-align:center;
}
.avatar {
@ -1087,7 +1096,7 @@ body.web.individual_jamtrack {
width: 64px;
height: 64px;
background-color: #ed4818;
margin: 4px 8px 7px 0;
margin: 4px 0px 7px 0;
-webkit-border-radius: 32px;
-moz-border-radius: 32px;
border-radius: 32px;

View File

@ -8,6 +8,9 @@ p, div {
white-space: normal;
}
.dialog {
position:fixed !important;
}
body.web {
background-repeat: repeat-x;

View File

@ -29,7 +29,6 @@
*= require web/downloads
*= require users/signinCommon
*= require dialogs/dialog
*= require client/help
*= require landings/partner_agreement_v1
*= require web/affiliate_links
*= require_directory ../landings

View File

@ -18,6 +18,12 @@ class ApiLessonBookingsController < ApiController
end
def show_choice
@choice = TestDrivePackageChoice.find(params[:id])
raise JamPermissionError, "You do not have permission to this data" if @choice.user != current_user
end
def create
if params[:lesson_type] == LessonBooking::LESSON_TYPE_FREE

View File

@ -16,6 +16,7 @@ class ApiStripeController < ApiController
@normal = data[:normal]
@lesson_package_type = data[:lesson_package_type]
@uncollectables = data[:uncollectables]
@package = data[:package]
end
end

View File

@ -93,7 +93,8 @@ class ApiUsersController < ApiController
school_id: params[:school_id],
school_interest: params[:school_interest],
affiliate_referral_id: cookies[:affiliate_visitor],
origin: origin_cookie
origin: origin_cookie,
test_drive_package: params[:test_drive_package]
}
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)

View File

@ -87,7 +87,7 @@ class LandingsController < ApplicationController
teachers = []
package.test_drive_package_teachers.each do |package_teacher|
teacher = package_teacher.user
teachers.push({id: teacher.id, name: teacher.name, biography: teacher.teacher.biography, photo_url: teacher.photo_url})
teachers.push({id: teacher.id, name: teacher.name, first_name: teacher.first_name,last_name: teacher.last_name, biography: teacher.teacher.short_bio, photo_url: teacher.photo_url})
end
package_data[:teachers] = teachers

View File

@ -1,21 +1,21 @@
object @lesson_booking
attributes :id, :status, :lesson_type, :payment_style, :recurring, :teacher_id, :description, :lesson_length, :created_at, :user_id, :active, :accepter_id, :canceler_id, :cancel_message, :booked_price, :card_presumed_ok
attributes :id, :status, :lesson_type, :payment_style, :recurring, :teacher_id, :description, :lesson_length, :created_at, :user_id, :active, :accepter_id, :canceler_id, :cancel_message, :booked_price, :card_presumed_ok, :no_slots
child(:lesson_booking_slots => :slots) {
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
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, :from_package
}
child(:default_slot => :default_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
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, :from_package
}
child(:alt_slot => :alt_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
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, :from_package
}
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
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, :from_package
}

View File

@ -0,0 +1,13 @@
object @choice
attributes :id
child(:test_drive_package => :package) {
attributes :package_type
}
child(:test_drive_package_choice_teachers => :teachers) {
child(:teacher) {
attributes :id, :first_name, :last_name, :name, :photo_url
}
}

View File

@ -14,13 +14,13 @@ end
child(:lesson_booking => :lesson_booking) {
attributes :card_presumed_ok
attributes :card_presumed_ok, :test_drive_package_choice_id, :no_slots
}
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
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, :from_package
node :pretty_scheduled_start_with_timezone do |slot|
pretty_scheduled_start_slot(slot, true)
end

View File

@ -14,6 +14,13 @@ if @test_drive
end
if @package
node :package do |lesson|
{id: @package.id}
end
end
if @normal
node :normal do |lesson|
{teacher_id: @normal.teacher_id}

View File

@ -38,6 +38,10 @@ if current_user && @user == current_user
attributes :uid, :provider, :token_expiration
end
child :test_drive_package_choices do |choice|
attributes :id
end
node :geoiplocation do |user|
geoiplocation = current_user.geoiplocation
geoiplocation.info if geoiplocation

View File

@ -41,6 +41,8 @@
<%= render "clients/footer" %>
</div>
<%= render "clients/lessonSessionActions" %>
<%= render "clients/manageVsts" %>
<%= render 'dialogs/dialogs' %>
<script type="text/javascript">

View File

@ -103,6 +103,7 @@
<%= render "clients/jam_track_preview" %>
<%= render "clients/help" %>
<%= render "clients/lessonSessionActions" %>
<%= render "clients/manageVsts" %>
<%= render 'dialogs/dialogs' %>
<script type="text/javascript">
@ -147,6 +148,8 @@
JK.genres = genres;
});
AppActions.appInit.trigger(JK.app)
JK.JamServer.connect() // singleton here defined in JamServer.js
.done(function() {
console.log("websocket connected")

View File

@ -707,6 +707,7 @@ SampleApp::Application.routes.draw do
match '/lesson_bookings/:id/counter' => 'api_lesson_bookings#counter', :via => :post
match '/lesson_bookings/:id/cancel' => 'api_lesson_bookings#cancel', :via => :post
match '/lesson_bookings/:id' => 'api_lesson_bookings#show', :via => :get
match '/test_drive_package_choice/:id' => 'api_lesson_bookings#show_choice', :via => :get
match '/schools/:id' => 'api_schools#show', :via => :get

View File

@ -36,6 +36,7 @@ class UserManager < BaseManager
school_id = options[:school_id]
school_interest = options[:school_interest]
origin = options[:origin]
test_drive_package = options[:test_drive_package]
recaptcha_failed = false
unless options[:skip_recaptcha] # allow callers to opt-of recaptcha
@ -86,7 +87,8 @@ class UserManager < BaseManager
school_invitation_code: school_invitation_code,
school_id: school_id,
school_interest: school_interest,
origin: origin)
origin: origin,
test_drive_package: test_drive_package)
user
end

View File

@ -123,6 +123,7 @@ FactoryGirl.define do
association :user, factory: :user
price_per_lesson_60_cents 3000
price_per_month_60_cents 3000
short_bio "It's gonna be a blast!"
end
factory :musician_instrument, :class=> JamRuby::MusicianInstrument do
@ -1004,4 +1005,53 @@ FactoryGirl.define do
ready false
amount_in_cents 1000
end
factory :test_drive_package, class: "JamRuby::TestDrivePackage" do
sequence(:name) { |n| "package-#{n}" }
trait :one_pack do
package_type 1
after(:create) do |package, evaluator|
1.times.each do
FactoryGirl.create(:test_drive_package_teachers, test_drive_package: package)
end
end
end
trait :two_pack do
package_type 2
after(:create) do |package, evaluator|
2.times.each do
FactoryGirl.create(:test_drive_package_teachers, test_drive_package: package)
end
end
end
trait :four_pack do
package_type 4
after(:create) do |package, evaluator|
4.times.each do
FactoryGirl.create(:test_drive_package_teachers, test_drive_package: package)
end
end
end
end
factory :test_drive_package_teachers, class: "JamRuby::TestDrivePackageTeacher" do
association :user, factory: :teacher_user
association :test_drive_package, factory: [:test_drive_package, :four_pack]
end
factory :test_drive_package_choice, class: "JamRuby::TestDrivePackageChoice" do
association :user, factory: :user
association :test_drive_package, factory: [:test_drive_package, :four_pack]
end
factory :test_drive_package_choice_teacher, class: "JamRuby::TestDrivePackageChoiceTeacher" do
association :teacher, factory: :teacher_user
association :test_drive_package_choice, factory: :test_drive_package_choice
end
end

View File

@ -27,8 +27,8 @@ describe "Student Landing", :js => true, :type => :feature, :capybara_feature =>
# should fail because we haven't filled out email/password/terms
find('.register-area .errors', text: "Email can't be blank")
fill_in "email", with: 'student_123@jamkazam.com'
fill_in "password", with: 'jam123'
fill_in "email", with: 'student_123@jamkazam.com'
fill_in "password", with: 'jam123'
find('.register-area ins', visible: false).trigger(:click)
find('button.cta-button', text: 'SIGN UP').trigger(:click)
@ -55,9 +55,9 @@ describe "Student Landing", :js => true, :type => :feature, :capybara_feature =>
# should fail because we haven't filled out email/password/terms
find('.register-area .errors', text: "Email can't be blank")
fill_in "email", with: 'student_125@jamkazam.com'
fill_in "password", with: 'jam123'
find('.register-area ins', visible: false) .trigger(:click)
fill_in "email", with: 'student_125@jamkazam.com'
fill_in "password", with: 'jam123'
find('.register-area ins', visible: false).trigger(:click)
find('button.cta-button', text: 'SIGN UP').trigger(:click)
find('h3', text: 'Student Levels Taught:')
@ -74,7 +74,7 @@ describe "Student Landing", :js => true, :type => :feature, :capybara_feature =>
it "logged in" do
fast_signin(user,"/landing/jamclass/students")
fast_signin(user, "/landing/jamclass/students")
find('h1.jamclass-h1', 'Let Us Find You The Perfect Music Teacher')
find('h2.jamclass-h2', 'And Connect You Online With Our Patented, Unique Technology')
@ -89,4 +89,109 @@ describe "Student Landing", :js => true, :type => :feature, :capybara_feature =>
user.musician.should be true
end
it "package 4 count but picks 2" do
package = FactoryGirl.create(:test_drive_package, :four_pack)
visit "/landing/jamclass/students?utm-teachers=#{package.name}"
find('h1.jamclass-h1', 'Let Us Find You The Perfect Music Teacher')
find('h2.jamclass-h2', 'And Connect You Online With Our Patented, Unique Technology')
teacher1 = package.test_drive_package_teachers[0].user
teacher2 = package.test_drive_package_teachers[1].user
teacher3 = package.test_drive_package_teachers[2].user
teacher4 = package.test_drive_package_teachers[3].user
find('p', text: 'Like the TestDrive concept, but 4 teachers is too many for you?')
find('a.pick-two').trigger(:click)
find('input[data-teacher-id="' + teacher1.id + '"]').trigger(:click)
find('input[data-teacher-id="' + teacher2.id + '"]').trigger(:click)
find('a.select-teachers').trigger(:click)
fill_in "email", with: 'student_package2@jamkazam.com'
fill_in "password", with: 'jam123'
find('.register-area ins', visible: false).trigger(:click)
find('button.cta-button', text: 'SIGN UP').trigger(:click)
find('.teacher-name-packaged', text: teacher1.last_name)
find('.teacher-name-packaged', text: teacher2.last_name)
page.should_not have_selector('.teacher-name-packaged', text: teacher3.last_name)
page.should_not have_selector('.teacher-name-packaged', text: teacher4.last_name)
find('.explanation', text: '2 private online music lessons - 1 each from these 2 instructors')
end
it "package 4 count" do
package = FactoryGirl.create(:test_drive_package, :four_pack)
visit "/landing/jamclass/students?utm-teachers=#{package.name}"
find('h1.jamclass-h1', 'Let Us Find You The Perfect Music Teacher')
find('h2.jamclass-h2', 'And Connect You Online With Our Patented, Unique Technology')
find('p', text: 'Like the TestDrive concept, but 4 teachers is too many for you?')
fill_in "email", with: 'student_package1@jamkazam.com'
fill_in "password", with: 'jam123'
find('.register-area ins', visible: false).trigger(:click)
find('button.cta-button', text: 'SIGN UP').trigger(:click)
package.test_drive_package_teachers.each do |package_teacher|
teacher = package_teacher.user
find('.teacher-name-packaged', text: teacher.last_name)
end
user = User.find_by_email('student_package1@jamkazam.com')
user.is_a_student.should be true
user.is_a_teacher.should be false
user.musician.should be true
user.origin_utm_source.should eql "organic"
user.origin_utm_campaign.should eql "127.0.0.1"
user.origin_utm_medium.should eql "organic"
user.origin_referrer.should_not be_nil
user.test_drive_package_choices.count.should eql 1
choice = user.test_drive_package_choices[0]
choice.test_drive_package_choice_teachers.count.should eql 4
fill_out_payment(nil, "Super HahaGuy")
find('#banner h1', text: 'TestDrive Purchased')
find('#banner .dialog-inner', text: 'Each teacher has received your request and should respond shortly')
find('#banner .close-btn').trigger(:click)
user.reload
user.first_name.should eql 'Super'
# find each of teh 4 lessons requested
user.student_lesson_bookings.each do |booking|
lesson = booking.lesson_sessions[0]
lesson.status.should eql LessonSession::STATUS_REQUESTED
booking.card_presumed_ok.should be_true
find('tr[data-lesson-session-id="' + lesson.id + '"] td.startTimeColumn', text: 'No time has been scheduled yet')
end
booking = user.student_lesson_bookings[0]
lesson = booking.lesson_sessions[0]
teacher = lesson.teacher
jamclass_hover_option(lesson, 'status', 'View Status')
find('h2', text: 'your lesson has been requested')
# verify that the lesson stats screen looks right
find('a.button-orange.schedule', text: 'PROPOSE TIME')
find('label', text: 'Propose a day/time')
find('.generic-time-stmt.no-slot', text: 'Your lesson has no scheduled time yet.')
switch_user(teacher, '/client#/jamclass')
jamclass_hover_option(lesson, 'status', 'View Status')
find('.description', text: 'Message from ' + user.first_name)
counter_day(lesson)
switch_user(user, '/client#/jamclass')
jamclass_hover_option(lesson, 'status', 'View Status')
find('.generic-time-stmt', text: 'will take place this')
approve_lesson(lesson)
end
end

View File

@ -71,7 +71,7 @@ def jamclass_hover_option(lesson, option, text)
find('li[data-lesson-option="' + option + '"] a', visible: false, text: text).trigger(:click)
end
def counter_day
def counter_day(lesson)
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
@ -87,8 +87,11 @@ def approve_lesson(lesson, slot = lesson.lesson_booking.default_slot)
find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Scheduled')
end
def fill_out_payment(expected = nil)
def fill_out_payment(expected = nil, name = nil )
if name
fill_in 'name', with: name
end
fill_in 'card-number', with: '4111111111111111'
fill_in 'expiration', with: '11/2016'
fill_in 'cvv', with: '111'

View File

@ -212,7 +212,8 @@ def sign_out_poltergeist(options = {})
end
def open_user_dropdown
find('.userinfo').hover()
#find('.userinfo').hover()
find('.userinfo').mousemove()
end