school education working

This commit is contained in:
Seth Call 2016-09-08 05:59:58 -05:00
parent 5353b75c2e
commit fd4d21ae78
49 changed files with 2223 additions and 178 deletions

View File

@ -365,3 +365,4 @@ nullable_user_id_jamblaster.sql
rails4_migration.sql
non_free_jamtracks.sql
retailers.sql
second_ed.sql

4
db/up/second_ed.sql Normal file
View File

@ -0,0 +1,4 @@
ALTER TABLE schools ADD COLUMN education BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE teacher_distributions ADD COLUMN education BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE lesson_bookings ADD COLUMN same_school_free BOOLEAN NOT NULL DEFAULT FALSE;
UPDATE lesson_bookings SET same_school_free = true where same_school = true;

View File

@ -52,6 +52,7 @@ module JamRuby
def student_welcome_message(user)
@user = user
@subject = "Welcome to JamKazam and JamClass online lessons!"
@education = user.school && user.school.education
sendgrid_category "Welcome"
sendgrid_unique_args :type => "welcome_message"
@ -68,6 +69,9 @@ module JamRuby
def teacher_welcome_message(user)
@user = user
@subject= "Welcome to JamKazam and JamClass online lessons!"
@education = user.teacher && user.teacher.school && user.teacher.school.education
sendgrid_category "Welcome"
sendgrid_unique_args :type => "welcome_message"

View File

@ -6,6 +6,33 @@
</p>
<% end %>
<% if @education %>
<p>
Thank you for signing up to take online music lessons using the JamClass service by JamKazam. JamKazam technology was built from the ground up for playing music live in sync with studio quality audio from different locations over the Internet, and for delivering amazing online music lessons.
</p>
<p>
To get ready to take JamClass lessons online, here are the things you'll want to do:
</p>
<p><b style="color: white">1. Set Up Your Gear</b><br/>
When you sign up, someone from JamKazam will get in touch with you via email within a couple of business days to help you get set up. If you don't hear from us within a couple of days, please email us at <a style="color:#fc0" href="mailto:support@jamkazam.com">support@jamkazam.com</a> or call us at <a href="tel:+18773768742" style="color:#fc0">1-877-376-8742</a>. To play in online lessons, you will need at a minimum: (1) a Windows or Mac computer; (2) normal home Internet service; and (3) a pair of headphones or earbuds you can plug into the headphone minijack on your computer. If you would like to benefit from studio quality audio (recommended) in your lessons, JamKazam offers an amazing audio package for just $49.99 (less than our cost) that includes an audio interface (a little box that connects to your computer via USB cable), a microphone, a mic cable, and a mic stand. We'll discuss these options with you, and we're happy to support you whichever path you choose. We'll help step you through the setup process, and we'll even get into an online session with you to make sure everything is working properly, and to show you some of the key features you can use during online lessons.
</p>
<p><b style="color: white">2. Book Lessons</b><br/>
Once your gear is set up, you are ready to take lessons. Go to this web page: <a style="color:#fc0" href="<%= @user.school.teacher_list_url %>"><%= @user.school.teacher_list_url %></a>. If your school has preferred instructors, they will be listed on this page, and you can click a button to book a lesson with the teacher from whom you want to take lessons. If your school doesn't have preferred instructors, then there is a link on this page to use our teacher search feature to find a great instructor from our broader community of teachers. You'll need your parents to enter credit card information to pay for your lessons. We use one of the largest and most secure commerce platforms on the Internet called Stripe, so you can feel confident your financial information will be very secure.
</p>
<p><b style="color: white">3. Learn About JamClass Features</b><br/>
You can also review our <a style="color:#fc0" href="https://jamkazam.desk.com/customer/en/portal/topics/926073-jamclass-online-music-lessons---for-students/articles">JamClass user guide for students</a>
to familiarize yourself with the features and resources available to you through our JamClass lesson service. This includes how to join your teacher in online lessons, features you can use while in lessons, and more.
</p>
<p>
Again, welcome to JamKazam and our JamClass online music lesson service, and we look forward to helping you learn and grow as a musician!
</p>
<% else %>
<p>
Thank you for signing up to take online music lessons using the JamClass service by JamKazam. JamKazam technology was
@ -47,6 +74,7 @@
Again, welcome to JamKazam and our JamClass online music lesson service, and we look forward to helping you learn and grow as a musician!
</p>
<% end %>
<p>Best Regards,<br/>
Team JamKazam</p>

View File

@ -30,6 +30,10 @@
</p>
<p><b style="color: white">2. Set Up Your Gear</b><br/>
<% if @education %>
<a style="color:#fc0" href="https://jamkazam.desk.com/customer/en/portal/articles/1288274-computer-internet-audio-and-video-requirements">Click
here for information on the gear requirements to effectively teach using the JamClass service</a>. At a minimum, you'll need a Windows or Mac computer and home Internet service, but we also recommend using an audio interface for superior audio quality. If you already have an audio interface for home recording, you can very likely use the one you have. If not, JamKazam offers a high quality audio package of an audio interface (a small box you connect to your computer via USB cable), a microphone, a mic cable, and a mic stand for just $49.99 (less than our cost). After you have signed up, someone from JamKazam will contact you to schedule a 1:1 help session to help you get set up, to make sure your audio and video gear are working properly in an online session, and to make sure you feel comfortable with the key features you will be using in sessions with students.
<% else %>
<a style="color:#fc0" href="https://jamkazam.desk.com/customer/en/portal/articles/1288274-computer-internet-audio-and-video-requirements">Click
here for information on the gear requirements to effectively teach using the JamClass service</a>. When you have
everything you need,
@ -38,6 +42,7 @@
JamKazam application</a>. After you have signed up, someone from JamKazam will contact you to schedule a test online
session, in which we will make sure your audio and video gear are working properly in an online session, and to make
sure you feel comfortable with the key features you will be using in sessions with students.
<% end %>
</p>
<p><b style="color: white">3. Learn About JamClass Features</b><br/>

View File

@ -81,7 +81,7 @@ module JamRuby
subject = "Unable to charge user #{charged_user.email} for lesson #{self.id} (unhandled)"
body = "user=#{charged_user.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
AdminMailer.alerts({subject: subject, body: body}).deliver
AdminMailer.alerts({subject: subject, body: body}).deliver_now
return false
end

View File

@ -95,7 +95,7 @@ module JamRuby
end
def after_create
if (card_presumed_ok || school_on_school?) && !sent_notices
if (card_presumed_ok || !payment_if_school_on_school?) && !sent_notices
send_notices
end
end
@ -393,8 +393,8 @@ module JamRuby
end
def requires_teacher_distribution?(target)
if school_on_school?
false
if no_school_on_school_payment?
return false
elsif target.is_a?(JamRuby::LessonSession)
is_test_drive? || (is_normal? && !is_monthly_payment?)
elsif target.is_a?(JamRuby::LessonPackagePurchase)
@ -520,7 +520,17 @@ module JamRuby
end
end
def distribution_price_in_cents(target)
def distribution_price_in_cents(target, education)
distribution = teacher_distribution_price_in_cents(target)
if education
(distribution * 0.0625).round
else
distribution
end
end
def teacher_distribution_price_in_cents(target)
if is_single_free?
0
elsif is_test_drive?
@ -759,8 +769,10 @@ module JamRuby
if user
lesson_booking.same_school = !!(lesson_booking.school && user.school && (lesson_booking.school.id == user.school.id))
lesson_booking.same_school_free = lesson_booking.school_on_school_payment?
else
lesson_booking.same_school = false
lesson_booking.same_school_free = false
end
# two-way association slots, for before_validation loic in slot to work
@ -779,7 +791,7 @@ module JamRuby
end
def self.unprocessed(current_user)
LessonBooking.where(user_id: current_user.id).where(card_presumed_ok: false).where('school_id IS NULL')
LessonBooking.where(user_id: current_user.id).where(card_presumed_ok: false).where(same_school_free: false)
end
def self.requested(current_user)
@ -790,6 +802,19 @@ module JamRuby
same_school
end
def school_on_school_payment?
!!(same_school && school.education)
end
def no_school_on_school_payment?
!!(school_on_school? && !school_on_school_payment?)
end
# if this is school-on-school, is payment required?
def payment_if_school_on_school?
!!(!school_on_school? || school_on_school_payment?)
end
def school_and_teacher
if school && school.scheduling_comm?
[school.communication_email, teacher.email]
@ -862,7 +887,7 @@ module JamRuby
.joins("LEFT JOIN lesson_package_purchases ON (lesson_package_purchases.lesson_booking_id = lesson_bookings.id AND (lesson_package_purchases.year = #{current_month_first_day.year} AND lesson_package_purchases.month = #{current_month_first_day.month}))")
.where("lesson_package_purchases.id IS NULL OR (lesson_package_purchases.id IS NOT NULL AND lesson_package_purchases.post_processed = false)")
.where(payment_style: PAYMENT_STYLE_MONTHLY)
.where(same_school: false)
.where(same_school_free: false)
.active
.where('music_sessions.scheduled_start >= ?', current_month_first_day)
.where('music_sessions.scheduled_start <= ?', current_month_last_day).uniq
@ -932,8 +957,10 @@ module JamRuby
def bill_for_month(day_in_month)
# try to find lesson package purchase for this month, and last month, and see if they need processing
puts "bill_for_month"
current_month_purchase = lesson_package_purchases.where(lesson_booking_id: self.id, user_id: student.id, year: day_in_month.year, month: day_in_month.month).first
if current_month_purchase.nil?
puts "bill_for_month - day"
current_month_purchase = LessonPackagePurchase.create(user, self, lesson_package_type, day_in_month.year, day_in_month.month)
end
current_month_purchase.bill_monthly

View File

@ -4,7 +4,9 @@ module JamRuby
@@log = Logging.logger[LessonPackagePurchase]
delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed, :billed_at, :billing_error_detail, :billing_error_reason, :is_card_declined?, :is_card_expired?, :last_billed_at_date, :sent_billing_notices, to: :lesson_payment_charge
delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed, :billed_at,
:billing_error_detail, :billing_error_reason, :is_card_declined?, :is_card_expired?,
:last_billed_at_date, :sent_billing_notices, to: :lesson_payment_charge
delegate :test_drive_count, to: :lesson_package_type
# who purchased the lesson package?
@ -14,7 +16,7 @@ module JamRuby
belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking"
belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id
has_one :lesson_session, class_name: "JamRuby::LessonSession", dependent: :destroy
has_one :teacher_distribution, class_name: "JamRuby::TeacherDistribution"
has_many :teacher_distributions, class_name: "JamRuby::TeacherDistribution"
has_one :sale_line_item, class_name: "JamRuby::SaleLineItem", dependent: :destroy
@ -35,7 +37,7 @@ module JamRuby
end
def create_charge
if !school_on_school? && lesson_booking && lesson_booking.is_monthly_payment?
if payment_if_school_on_school? && lesson_booking && lesson_booking.is_monthly_payment?
self.lesson_payment_charge = LessonPaymentCharge.new
lesson_payment_charge.user = user
lesson_payment_charge.amount_in_cents = 0
@ -45,16 +47,22 @@ module JamRuby
end
end
def teacher_distribution
teacher_distributions.where(education:false).first
end
def education_distribution
teacher_distributions.where(education:true).first
end
def add_test_drives
if self.lesson_package_type.is_test_drive?
new_test_drives = user.remaining_test_drives + lesson_package_type.test_drive_count
User.where(id: user.id).update_all(remaining_test_drives: new_test_drives)
user.remaining_test_drives = new_test_drives
end
end
def to_s
"#{name}"
end
@ -79,9 +87,14 @@ module JamRuby
purchase.recurring = true
if lesson_booking && lesson_booking.requires_teacher_distribution?(purchase)
purchase.teacher_distribution = TeacherDistribution.create_for_lesson_package_purchase(purchase)
teacher_dist = TeacherDistribution.create_for_lesson_package_purchase(purchase, false)
purchase.teacher_distributions << teacher_dist
# price should always match the teacher_distribution, if there is one
purchase.price = purchase.teacher_distribution.amount_in_cents / 100
purchase.price = teacher_dist.amount_in_cents / 100
if lesson_booking.school_on_school_payment?
self.teacher_distributions << TeacherDistribution.create_for_lesson_package_purchase(purchase, true)
end
end
else
purchase.recurring = false
@ -136,10 +149,23 @@ module JamRuby
end
end
def school_on_school_payment?
!!(school_on_school? && teacher.teacher.school.education)
end
def no_school_on_school_payment?
!!(school_on_school? && !school_on_school_payment?)
end
# if this is school-on-school, is payment required?
def payment_if_school_on_school?
!!(!school_on_school? || school_on_school_payment?)
end
def bill_monthly(force = false)
if school_on_school?
if school_on_school_payment?
puts "SCHOOL ON SCHOOL PAYMENT OH NO"
raise "school-on-school: should not be here"
else
lesson_payment_charge.charge(force)

View File

@ -63,11 +63,8 @@ module JamRuby
post_sale_test_failure
distribution = target.teacher_distribution
if distribution # not all lessons/payment charges have a distribution
distribution.ready = true
distribution.save(validate: false)
end
target.teacher_distributions.update_all(ready:true) # possibly there are 0 distributions on this lesson
stripe_charge
end
@ -103,7 +100,9 @@ module JamRuby
end
def expected_price_in_cents
target.lesson_booking.distribution_price_in_cents(target)
distribution = target.teacher_distribution
for_education = distribution && distribution.education
target.lesson_booking.distribution_price_in_cents(target, for_education)
end
end
end

View File

@ -11,7 +11,7 @@ module JamRuby
@@log = Logging.logger[LessonSession]
delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed_at, :billing_error_detail, :billing_error_reason, :is_card_declined?, :is_card_expired?, :last_billed_at_date, :sent_billing_notices, to: :lesson_payment_charge, allow_nil: true
delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, :recurring, :is_monthly_payment?, :school_on_school?, :scheduling_email, :teacher_school_emails, :school_and_teacher, :school_over_teacher, :school_and_teacher_ids, :school_over_teacher_ids, to: :lesson_booking
delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, :recurring, :is_monthly_payment?, :school_on_school?, :school_on_school_payment?, :no_school_on_school_payment?, :payment_if_school_on_school?, :scheduling_email, :teacher_school_emails, :school_and_teacher, :school_over_teacher, :school_and_teacher_ids, :school_over_teacher_ids, to: :lesson_booking
delegate :pretty_scheduled_start, to: :music_session
@ -41,7 +41,7 @@ module JamRuby
belongs_to :slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :slot_id, :dependent => :destroy
belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id
belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_lesson, :dependent => :destroy
has_one :teacher_distribution, class_name: "JamRuby::TeacherDistribution", dependent: :destroy
has_many :teacher_distributions, class_name: "JamRuby::TeacherDistribution", dependent: :destroy
has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot"
has_many :notifications, :class_name => "JamRuby::Notification", :foreign_key => "lesson_session_id"
has_many :chat_messages, :class_name => "JamRuby::ChatMessage", :foreign_key => "lesson_session_id"
@ -86,7 +86,7 @@ module JamRuby
.order('music_sessions.scheduled_start DESC') }
def create_charge
if !school_on_school? && !is_test_drive? && !is_monthly_payment?
if payment_if_school_on_school? && !is_test_drive? && !is_monthly_payment?
self.lesson_payment_charge = LessonPaymentCharge.new
lesson_payment_charge.user = @assigned_student
lesson_payment_charge.amount_in_cents = 0
@ -96,6 +96,14 @@ module JamRuby
end
end
def teacher_distribution
teacher_distributions.where(education:false).first
end
def education_distribution
teacher_distributions.where(education:true).first
end
def manage_slot_changes
# if this slot changed, we need to update the time. But LessonBooking does this for us, for requested/accepted .
# TODO: what to do, what to do.
@ -209,7 +217,10 @@ module JamRuby
self.status = STATUS_COMPLETED
if success && lesson_booking.requires_teacher_distribution?(self)
self.teacher_distribution = TeacherDistribution.create_for_lesson(self)
self.teacher_distributions << TeacherDistribution.create_for_lesson(self, false)
if lesson_booking.school_on_school_payment?
self.teacher_distributions << TeacherDistribution.create_for_lesson(self, true)
end
end
if self.save
@ -292,7 +303,7 @@ module JamRuby
end
def bill_lesson
if school_on_school?
if no_school_on_school_payment?
success = true
else
lesson_payment_charge.charge
@ -341,14 +352,9 @@ module JamRuby
def test_drive_completed
distribution = teacher_distribution
if !sent_notices
if success
if distribution # not all lessons/payment charges have a distribution
distribution.ready = true
distribution.save(validate: false)
end
teacher_distributions.update_all(ready:true) # possibly there are 0 distributions on this lesson
student.test_drive_succeeded(self)
else
student.test_drive_failed(self)
@ -387,7 +393,7 @@ module JamRuby
else
if lesson_booking.is_monthly_payment?
if !sent_notices
if !school_on_school?
if payment_if_school_on_school?
# bad session; just poke user
UserMailer.monthly_recurring_no_bill(self).deliver_now
end
@ -401,7 +407,7 @@ module JamRuby
else
if !sent_notices
if !school_on_school?
if payment_if_school_on_school?
# bad session; just poke user
UserMailer.student_lesson_normal_no_bill(self).deliver_now
end
@ -422,7 +428,7 @@ module JamRuby
bill_lesson
else
if !sent_notices
if !school_on_school?
if payment_if_school_on_school?
UserMailer.student_lesson_normal_no_bill(self).deliver_now
UserMailer.teacher_lesson_normal_no_bill(self).deliver_now
end

View File

@ -24,6 +24,7 @@ module JamRuby
validates :user, presence: true
validates :enabled, inclusion: {in: [true, false]}
validates :education, inclusion: {in: [true, false]}
validates :scheduling_communication, inclusion: {in: SCHEDULING_COMMS}
validates :correspondence_email, email: true, allow_blank: true
validate :validate_avatar_info
@ -31,6 +32,10 @@ module JamRuby
after_create :create_affiliate
before_save :stringify_avatar_info, :if => :updating_avatar
def is_education?
education
end
def scheduling_comm?
scheduling_communication == SCHEDULING_COMM_SCHOOL
end
@ -39,6 +44,10 @@ module JamRuby
correspondence_email.blank? ? owner.email : correspondence_email
end
def approved_teachers
teachers.where('teachers.ready_for_session_at is not null')
end
def create_affiliate
AffiliatePartner.create_from_school(self)
end
@ -119,4 +128,8 @@ module JamRuby
self.crop_selection = crop_selection.to_json if !crop_selection.nil?
end
end
def teacher_list_url
"#{APP_CONFIG.external_root_url}/school/#{id}/teachers"
end
end

View File

@ -416,21 +416,7 @@ module JamRuby
## !!!! this is only valid for tests
def stripe_account_id=(new_acct_id)
existing = user.stripe_auth
existing.destroy if existing
user_auth_hash = {
:provider => 'stripe_connect',
:uid => new_acct_id,
:token => 'bogus',
:refresh_token => 'refresh_bogus',
:token_expiration => Date.new(2050, 1, 1),
:secret => "secret"
}
authorization = user.user_authorizations.build(user_auth_hash)
authorization.save!
user.stripe_account_id = new_acct_id
end
# how complete is their profile?

View File

@ -43,24 +43,26 @@ module JamRuby
end
end
def self.create_for_lesson(lesson_session)
distribution = create(lesson_session)
def self.create_for_lesson(lesson_session, for_education)
distribution = create(lesson_session, for_education)
distribution.lesson_session = lesson_session
distribution.education = for_education
distribution
end
def self.create_for_lesson_package_purchase(lesson_package_purchase)
distribution = create(lesson_package_purchase)
def self.create_for_lesson_package_purchase(lesson_package_purchase, for_education)
distribution = create(lesson_package_purchase, for_education)
distribution.lesson_package_purchase = lesson_package_purchase
distribution.education = for_education
distribution
end
def self.create(target)
def self.create(target, education)
distribution = TeacherDistribution.new
distribution.teacher = target.teacher
distribution.ready = false
distribution.distributed = false
distribution.amount_in_cents = target.lesson_booking.distribution_price_in_cents(target)
distribution.amount_in_cents = target.lesson_booking.distribution_price_in_cents(target, education)
distribution.school = target.lesson_booking.school
distribution
end
@ -82,6 +84,7 @@ module JamRuby
end
def real_distribution
(real_distribution_in_cents / 100.0)
end
@ -109,6 +112,9 @@ module JamRuby
end
def calculate_teacher_fee
if education
0
else
if is_test_drive?
0
else
@ -122,6 +128,7 @@ module JamRuby
(amount_in_cents * (rate + 0.03)).round
end
end
end
def student
if lesson_session

View File

@ -15,7 +15,11 @@ module JamRuby
# pay the school if the payment owns the school; otherwise default to the teacher
def payable_teacher
if school
if school.education
teacher
else
school.owner
end
else
teacher
end

View File

@ -21,8 +21,6 @@ module JamRuby
def do_charge(force)
# source will let you supply a token. But... how to get a token in this case?
@stripe_charge = Stripe::Charge.create(
:amount => amount_in_cents,
:currency => "usd",

View File

@ -2033,6 +2033,24 @@ module JamRuby
customer
end
## !!!! this is only valid for tests
def stripe_account_id=(new_acct_id)
existing = stripe_auth
existing.destroy if existing
user_auth_hash = {
:provider => 'stripe_connect',
:uid => new_acct_id,
:token => 'bogus',
:refresh_token => 'refresh_bogus',
:token_expiration => Date.new(2050, 1, 1),
:secret => "secret"
}
authorization = user_authorizations.build(user_auth_hash)
authorization.save!
end
def card_approved(token, zip, booking_id, test_drive_package_choice_id = nil)
approved_booking = nil
@ -2261,6 +2279,10 @@ module JamRuby
LessonBooking.engaged_bookings(student, self, since_at).test_drive.count > 0
end
def same_school_with_student?(student)
student.school && self.teacher && self.teacher.school && student.school.id == self.teacher.school.id
end
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64

View File

@ -35,6 +35,8 @@ describe "Monthly Recurring Lesson Flow" do
booking.card_presumed_ok.should be_false
booking.user.should eql user
booking.card_presumed_ok.should be_false
booking.same_school.should be_false
booking.same_school_free.should be_false
booking.should eql user.unprocessed_normal_lesson
booking.sent_notices.should be_false
booking.booked_price.should eql 30.00
@ -213,6 +215,252 @@ describe "Monthly Recurring Lesson Flow" do
# teacher & student get into session
start = lesson_session.scheduled_start
end_time = lesson_session.scheduled_start + (60 * lesson_session.duration)
uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time)
# artificially end the session, which is covered by other background jobs
lesson_session.music_session.session_removed_at = end_time
lesson_session.music_session.save!
UserMailer.deliveries.clear
# background code comes around and analyses the session
LessonSession.hourly_check
lesson_session.reload
lesson_session.analysed.should be_true
analysis = lesson_session.analysis
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
if lesson_session.billing_error_detail
puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests
end
lesson.amount_charged.should eql 0.0
lesson_session.billing_error_reason.should be_nil
lesson_session.sent_billing_notices.should be nil
user.reload
user.remaining_test_drives.should eql 0
UserMailer.deliveries.length.should eql 0 # one for student
end
it "works (school on school education)" do
# make sure teacher can get payments
teacher.stripe_account_id = stripe_account1_id
school.user.stripe_account_id = stripe_account2_id
# get user and teacher into same school
school.education = true
school.save!
user.school = school
user.save!
teacher.school = school
teacher.save!
# if it's later in the month, we'll make 2 lesson_package_purchases (prorated one, and next month's), which can throw off some assertions later on
Timecop.travel(Date.new(2016, 3, 20))
# user has no test drives, no credit card on file, but attempts to book a lesson
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60)
booking.errors.any?.should be_false
booking.card_presumed_ok.should be_false
booking.user.should eql user
booking.card_presumed_ok.should be_false
booking.should eql user.unprocessed_normal_lesson
booking.sent_notices.should be_false
booking.booked_price.should eql 30.00
########## Need validate their credit card
token = create_stripe_token
result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id})
booking.reload
booking.card_presumed_ok.should be_true
booking.errors.any?.should be_false
booking = result[:lesson]
lesson = booking.lesson_sessions[0]
lesson.errors.any?.should be_false
booking.sent_notices.should be_true
lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
lesson.amount_charged.should be 0.0
lesson.reload
user.reload
user.stripe_customer_id.should_not be nil
user.remaining_test_drives.should eql 0
user.lesson_purchases.length.should eql 0
customer = Stripe::Customer.retrieve(user.stripe_customer_id)
customer.email.should eql user.email
booking.lesson_sessions.length.should eql 1
lesson_session = booking.lesson_sessions[0]
lesson_session.status.should eql LessonBooking::STATUS_REQUESTED
booking.status.should eql LessonBooking::STATUS_REQUESTED
######### Teacher counters with new slot
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
booking.errors.any?.should be false
lesson_session.lesson_booking.errors.any?.should be false
lesson_session.lesson_booking_slots.length.should eql 1
lesson_session.lesson_booking_slots[0].proposer.should eql teacher_user
teacher_counter = lesson_session.lesson_booking_slots.order(:created_at).last
teacher_counter.should eql teacher_countered_slot
teacher_counter.proposer.should eql teacher_user
booking.lesson_booking_slots.length.should eql 3
UserMailer.deliveries.length.should eql 1
chat = ChatMessage.unscoped.order(:created_at).last
chat.channel.should eql ChatMessage::CHANNEL_LESSON
chat.message.should eql 'Does this work?'
chat.user.should eql teacher_user
chat.target_user.should eql user
notification = Notification.unscoped.order(:created_at).last
notification.session_id.should eql lesson_session.music_session.id
notification.student_directed.should eql true
notification.purpose.should eql 'counter'
notification.description.should eql NotificationTypes::LESSON_MESSAGE
######### Student counters with new slot
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
lesson_session.lesson_booking.errors.any?.should be false
lesson_session.lesson_booking_slots.length.should eql 2
student_counter = booking.lesson_booking_slots.order(:created_at).last
student_counter.proposer.should eql user
booking.reload
booking.lesson_booking_slots.length.should eql 4
UserMailer.deliveries.length.should eql 1
chat = ChatMessage.unscoped.order(:created_at).last
chat.message.should eql 'Does this work better?'
chat.channel.should eql ChatMessage::CHANNEL_LESSON
chat.user.should eql user
chat.target_user.should eql teacher_user
notification = Notification.unscoped.order(:created_at).last
notification.session_id.should eql lesson_session.music_session.id
notification.student_directed.should eql false
notification.purpose.should eql 'counter'
notification.description.should eql NotificationTypes::LESSON_MESSAGE
######## Teacher accepts slot
UserMailer.deliveries.clear
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
# get acceptance emails, as well as 'your stuff is accepted'
UserMailer.deliveries.length.should eql 2
lesson_session.errors.any?.should be_false
lesson_session.reload
lesson_session.slot.should eql student_counter
lesson_session.status.should eql LessonSession::STATUS_APPROVED
booking.reload
booking.default_slot.should eql student_counter
lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
booking.status.should eql LessonBooking::STATUS_APPROVED
UserMailer.deliveries.length.should eql 2
chat = ChatMessage.unscoped.order(:created_at).last
chat.message.should eql 'Yeah I got this'
chat.purpose.should eql 'Lesson Approved'
chat.channel.should eql ChatMessage::CHANNEL_LESSON
chat.user.should eql teacher_user
chat.target_user.should eql user
notification = Notification.unscoped.order(:created_at).last
notification.session_id.should eql lesson_session.music_session.id
notification.student_directed.should eql true
notification.purpose.should eql 'accept'
notification.description.should eql NotificationTypes::LESSON_MESSAGE
# teacher & student get into session
start = lesson_session.scheduled_start
end_time = lesson_session.scheduled_start + (60 * lesson_session.duration)
uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time)
# artificially end the session, which is covered by other background jobs
lesson_session.music_session.session_removed_at = end_time
lesson_session.music_session.save!
Timecop.travel(end_time + 1)
LessonSession.hourly_check
lesson_session.reload
lesson_session.analysed.should be_true
analysis = lesson_session.analysis
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
if lesson_session.billing_error_detail
puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests
end
# let user pay for it
LessonBooking.hourly_check
booked_price = booking.booked_price
prorated = booked_price / 2
prorated_cents = (booked_price * 100).to_i
user.reload
user.lesson_purchases.length.should eql 1
lesson_purchase = user.lesson_purchases[0]
puts "LESSON_PURCHASE PRICE #{lesson_purchase.price}"
lesson_purchase.price.should eql prorated
lesson_purchase.lesson_package_type.is_normal?.should eql true
lesson_purchase.price_in_cents.should eql prorated_cents
teacher_distribution = lesson_purchase.teacher_distribution
teacher_distribution.amount_in_cents.should eql prorated_cents
teacher_distribution.ready.should be_true
teacher_distribution.distributed.should be_false
education_distribution = lesson_purchase.education_distribution
education_distribution.amount_in_cents.should eql (prorated_cents * 0.0625).round
education_distribution.ready.should be_true
education_distribution.distributed.should be_false
user.sales.length.should eql 1
sale = user.sales.first
sale.stripe_charge_id.should_not be_nil
sale.recurly_tax_in_cents.should eql (100 * prorated * 0.0825).round.to_i
sale.recurly_total_in_cents.should eql ((prorated * 100 * 0.0825).round + 100 * prorated).to_i
sale.recurly_subtotal_in_cents.should eql prorated_cents
sale.recurly_currency.should eql 'USD'
sale.stripe_charge_id.should_not be_nil
line_item = sale.sale_line_items[0]
line_item.quantity.should eql 1
line_item.product_type.should eql SaleLineItem::LESSON
line_item.product_id.should eq LessonPackageType.single.id
line_item.lesson_package_purchase.should eql lesson_purchase
lesson_purchase.sale_line_item.should eql line_item
TeacherPayment.count.should eql 0
TeacherPayment.hourly_check
teacher_distribution.reload
teacher_distribution.distributed.should be_true
TeacherPayment.count.should eql 2
payment = teacher_distribution.teacher_payment
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 + 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
education_distribution.reload
education_distribution.distributed.should be_true
education_amt = (3000 * 0.0625).round
payment = education_distribution.teacher_payment
payment.amount_in_cents.should eql education_amt
payment.fee_in_cents.should eql 0
payment.teacher_payment_charge.amount_in_cents.should eql (education_amt + education_amt * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 0
payment.teacher.should eql teacher_user
payment.teacher_distribution.should eql education_distribution
# teacher & student get into session
start = lesson_session.scheduled_start
end_time = lesson_session.scheduled_start + (60 * lesson_session.duration)
@ -422,6 +670,7 @@ describe "Monthly Recurring Lesson Flow" do
end
it "affiliate gets their cut" do
Timecop.travel(2016, 05, 15)
user.affiliate_referral = affiliate_partner

View File

@ -266,12 +266,16 @@ describe "Normal Lesson Flow" do
it "works" do
# set up teacher stripe acct
teacher.stripe_account_id = stripe_account1_id
# user has no test drives, no credit card on file, but attempts to book a lesson
booking = LessonBooking.book_normal(user, teacher_user, valid_single_slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60)
booking.errors.any?.should be_false
booking.card_presumed_ok.should be_false
booking.user.should eql user
booking.card_presumed_ok.should be_false
booking.same_school_free.should be_false
booking.should eql user.unprocessed_normal_lesson
booking.sent_notices.should be_false
booking.booked_price.should eql 30.00
@ -293,6 +297,7 @@ describe "Normal Lesson Flow" do
user.stripe_customer_id.should_not be nil
user.remaining_test_drives.should eql 0
user.lesson_purchases.length.should eql 0
teacher_user.stripe_auth.should_not be_nil
customer = Stripe::Customer.retrieve(user.stripe_customer_id)
customer.email.should eql user.email
@ -389,6 +394,7 @@ describe "Normal Lesson Flow" do
UserMailer.deliveries.clear
# background code comes around and analyses the session
LessonSession.hourly_check
lesson_session.reload
lesson_session.analysed.should be_true
analysis = lesson_session.analysis
@ -397,6 +403,17 @@ describe "Normal Lesson Flow" do
if lesson_session.billing_error_detail
puts "testdrive flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests
end
TeacherPayment.count.should eql 0
TeacherPayment.hourly_check
TeacherPayment.count.should eql 1
teacher_distribution = lesson_session.teacher_distribution
teacher_distribution.ready.should be_true
teacher_distribution.distributed.should be_true
education_distribution = lesson_session.education_distribution
education_distribution.should be_nil
lesson_session.billed.should be true
user.reload
user.lesson_purchases.length.should eql 1
@ -423,7 +440,7 @@ describe "Normal Lesson Flow" do
lesson_session.sent_billing_notices.should be true
user.reload
user.remaining_test_drives.should eql 0
UserMailer.deliveries.length.should eql 2 # one for student, one for teacher
UserMailer.deliveries.length.should eql 3 # one for student, one for teacher
end
@ -568,6 +585,224 @@ describe "Normal Lesson Flow" do
TeacherDistribution.count.should eql 0
end
it "works (school on school education)" do
# make sure teacher can get payments
teacher.stripe_account_id = stripe_account1_id
school.user.stripe_account_id = stripe_account2_id
# make sure can get stripe payments
# get user and teacher into same school
school.education = true
school.save!
user.school = school
user.save!
teacher.school = school
teacher.save!
# user has no test drives, no credit card on file, but attempts to book a lesson
booking = LessonBooking.book_normal(user, teacher_user, valid_single_slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60)
booking.errors.any?.should be_false
booking.school.should be_true
booking.card_presumed_ok.should be_false
booking.user.should eql user
user.unprocessed_normal_lesson.should be_nil
booking.sent_notices.should be_false
booking.booked_price.should eql 30.00
booking.is_requested?.should be_true
booking.lesson_sessions[0].music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
LessonPaymentCharge.count.should eql 1
########## Need validate their credit card
token = create_stripe_token
result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id})
booking = result[:lesson]
lesson = booking.lesson_sessions[0]
booking.errors.any?.should be_false
lesson.errors.any?.should be_false
booking.card_presumed_ok.should be_true
booking.sent_notices.should be_true
lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
lesson.amount_charged.should eql 0.0
lesson.reload
user.reload
user.stripe_customer_id.should_not be nil
user.remaining_test_drives.should eql 0
user.lesson_purchases.length.should eql 0
customer = Stripe::Customer.retrieve(user.stripe_customer_id)
customer.email.should eql user.email
booking.lesson_sessions.length.should eql 1
lesson_session = booking.lesson_sessions[0]
lesson_session.status.should eql LessonBooking::STATUS_REQUESTED
booking.status.should eql LessonBooking::STATUS_REQUESTED
######### Teacher counters with new slot
teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_single, hour: 14)
UserMailer.deliveries.clear
lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'})
booking.reload
booking.errors.any?.should be false
lesson_session.lesson_booking.errors.any?.should be false
lesson_session.lesson_booking_slots.length.should eql 1
lesson_session.lesson_booking_slots[0].proposer.should eql teacher_user
teacher_counter = lesson_session.lesson_booking_slots.order(:created_at).last
teacher_counter.should eql teacher_countered_slot
teacher_counter.proposer.should eql teacher_user
booking.lesson_booking_slots.length.should eql 3
UserMailer.deliveries.length.should eql 1
chat = ChatMessage.unscoped.order(:created_at).last
chat.channel.should eql ChatMessage::CHANNEL_LESSON
chat.message.should eql 'Does this work?'
chat.user.should eql teacher_user
chat.target_user.should eql user
notification = Notification.unscoped.order(:created_at).last
notification.session_id.should eql lesson_session.music_session.id
notification.student_directed.should eql true
notification.purpose.should eql 'counter'
notification.description.should eql NotificationTypes::LESSON_MESSAGE
######### Student counters with new slot
student_countered_slot = FactoryGirl.build(:lesson_booking_slot_single, hour: 16)
UserMailer.deliveries.clear
lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'})
lesson_session.errors.any?.should be false
lesson_session.lesson_booking.errors.any?.should be false
lesson_session.lesson_booking_slots.length.should eql 2
student_counter = booking.lesson_booking_slots.order(:created_at).last
student_counter.proposer.should eql user
booking.reload
booking.lesson_booking_slots.length.should eql 4
UserMailer.deliveries.length.should eql 1
chat = ChatMessage.unscoped.order(:created_at).last
chat.message.should eql 'Does this work better?'
chat.channel.should eql ChatMessage::CHANNEL_LESSON
chat.user.should eql user
chat.target_user.should eql teacher_user
notification = Notification.unscoped.order(:created_at).last
notification.session_id.should eql lesson_session.music_session.id
notification.student_directed.should eql false
notification.purpose.should eql 'counter'
notification.description.should eql NotificationTypes::LESSON_MESSAGE
######## Teacher accepts slot
UserMailer.deliveries.clear
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
lesson_session.status.should eql LessonSession::STATUS_APPROVED
booking.reload
booking.default_slot.should eql student_counter
lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
booking.status.should eql LessonBooking::STATUS_APPROVED
UserMailer.deliveries.length.should eql 2
chat = ChatMessage.unscoped.order(:created_at).last
chat.message.should eql 'Yeah I got this'
chat.purpose.should eql 'Lesson Approved'
chat.channel.should eql ChatMessage::CHANNEL_LESSON
chat.user.should eql teacher_user
chat.target_user.should eql user
notification = Notification.unscoped.order(:created_at).last
notification.session_id.should eql lesson_session.music_session.id
notification.student_directed.should eql true
notification.purpose.should eql 'accept'
notification.description.should eql NotificationTypes::LESSON_MESSAGE
# teacher & student get into session
start = lesson_session.scheduled_start
end_time = lesson_session.scheduled_start + (60 * lesson_session.duration)
uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time)
# artificially end the session, which is covered by other background jobs
lesson_session.music_session.session_removed_at = end_time
lesson_session.music_session.save!
Timecop.travel(end_time + 1)
UserMailer.deliveries.clear
# background code comes around and analyses the session
LessonSession.hourly_check
lesson_session.reload
lesson_session.analysed.should be_true
analysis = lesson_session.analysis
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
lesson_session.billed.should be_true
if lesson_session.billing_error_detail
puts "testdrive flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests
end
lesson_session.billing_attempts.should eql 1
user.reload
user.lesson_purchases.length.should eql 1
LessonBooking.hourly_check
lesson_session.reload
teacher_distribution = lesson_session.teacher_distribution
teacher_distribution.amount_in_cents.should eql 3000
teacher_distribution.ready.should be_true
teacher_distribution.distributed.should be_false
lesson_session.teacher_distributions.count.should eql 2
education_distribution = lesson_session.education_distribution
education_distribution.amount_in_cents.should eql (3000 * 0.0625).round
education_distribution.ready.should be_true
education_distribution.distributed.should be_false
lesson_session.billed.should be true
user.reload
user.lesson_purchases.length.should eql 1
user.sales.length.should eql 1
lesson_session.amount_charged.should eql 32.48
lesson_session.billing_error_reason.should be_nil
lesson_session.sent_billing_notices.should be_true
user.reload
user.remaining_test_drives.should eql 0
UserMailer.deliveries.length.should eql 2 # one for student, one for teacher
TeacherPayment.count.should eql 0
TeacherPayment.hourly_check
TeacherPayment.count.should eql 2
LessonPaymentCharge.count.should eql 1
TeacherDistribution.count.should eql 2
teacher_distribution.reload
teacher_distribution.distributed.should be_true
education_distribution.reload
education_distribution.distributed.should be_true
education_amt = (3000 * 0.0625).round
payment = education_distribution.teacher_payment
payment.amount_in_cents.should eql education_amt
payment.fee_in_cents.should eql 0
payment.teacher_payment_charge.amount_in_cents.should eql (education_amt + education_amt * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 0
payment.teacher.should eql teacher_user
payment.teacher_distribution.should eql education_distribution
payment = teacher_distribution.teacher_payment
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 + 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
lesson_session.lesson_booking.status.should eql LessonBooking::STATUS_COMPLETED
lesson_session.lesson_booking.success.should be_true
end
it "affiliate gets their cut" do
user.affiliate_referral = affiliate_partner
user.save!

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -92,7 +92,8 @@ profileUtils = context.JK.ProfileUtils
schoolName: null,
studentInvitations: null,
teacherInvitations: null,
updating: false
updating: false,
distributions: []
}
isSchoolManaged: () ->
@ -187,9 +188,15 @@ profileUtils = context.JK.ProfileUtils
removeFromSchool: (id, isTeacher, e) ->
if isTeacher
rest.deleteSchoolTeacher({id: this.state.school.id, teacher_id: id}).done((response) => @removeFromSchoolDone(response)).fail((jqXHR) => @removeFromSchoolFail(jqXHR))
rest.deleteSchoolTeacher({
id: this.state.school.id,
teacher_id: id
}).done((response) => @removeFromSchoolDone(response)).fail((jqXHR) => @removeFromSchoolFail(jqXHR))
else
rest.deleteSchoolStudent({id: this.state.school.id, student_id: id}).done((response) => @removeFromSchoolDone(response)).fail((jqXHR) => @removeFromSchoolFail(jqXHR))
rest.deleteSchoolStudent({
id: this.state.school.id,
student_id: id
}).done((response) => @removeFromSchoolDone(response)).fail((jqXHR) => @removeFromSchoolFail(jqXHR))
removeFromSchoolDone: (school) ->
context.JK.Banner.showNotice("User removed", "User was removed from your school.")
@ -237,6 +244,7 @@ profileUtils = context.JK.ProfileUtils
if this.state.school.teachers? && this.state.school.teachers.length > 0
for teacher in this.state.school.teachers
if teacher.user
teachers.push(@renderUser(teacher.user, true))
else
teachers = `<p>No teachers</p>`
@ -305,23 +313,17 @@ profileUtils = context.JK.ProfileUtils
cancelClasses = {"button-grey": true, "cancel": true, disabled: this.state.updating}
updateClasses = {"button-orange": true, "update": true, disabled: this.state.updating}
`<div className="account-block info-block">
<div className={nameClasses}>
<label>School Name:</label>
<input type="text" name="name" value={this.nameValue()} onChange={this.nameChanged}/>
{nameErrors}
</div>
<div className="field logo">
<label>School Logo:</label>
<AvatarEditLink target={this.state.school} target_type="school"/>
</div>
if this.state.school.education
management = null
else
management = `<div>
<h4>Management Preference</h4>
<div className="field scheduling_communication">
<div className="scheduling_communication school">
<input type="radio" name="scheduling_communication" readOnly={true} value="school"
checked={this.isSchoolManaged()}/><label>School owner will manage scheduling of student lessons sourced
checked={this.isSchoolManaged()}/><label>School owner will manage scheduling of student lessons
sourced
by JamKazam</label>
</div>
<div className="scheduling_communication teacher">
@ -339,6 +341,21 @@ profileUtils = context.JK.ProfileUtils
</div>
{correspondenceEmailErrors}
</div>
</div>`
`<div className="account-block info-block">
<div className={nameClasses}>
<label>School Name:</label>
<input type="text" name="name" value={this.nameValue()} onChange={this.nameChanged}/>
{nameErrors}
</div>
<div className="field logo">
<label>School Logo:</label>
<AvatarEditLink target={this.state.school} target_type="school"/>
</div>
{management}
<h4>Payments</h4>
@ -397,6 +414,73 @@ profileUtils = context.JK.ProfileUtils
<p>Coming soon</p>
</div>`
paymentsToYou: () ->
rows = []
for paymentHistory in this.state.distributions
paymentMethod = 'Stripe'
if paymentHistory.distributed
date = paymentHistory.teacher_payment.teacher_payment_charge.last_billing_attempt_at
status = 'Paid'
else
date = paymentHistory.created_at
if paymentHistory.not_collectable
status = 'Uncollectible'
else if !paymentHistory.teacher?.teacher?.stripe_account_id?
status = 'No Stripe Acct'
else
status = 'Collecting'
date = context.JK.formatDate(date, true)
description = paymentHistory.description
if paymentHistory.teacher_payment?
amt = paymentHistory.teacher_payment.real_distribution_in_cents
else
amt = paymentHistory.real_distribution_in_cents
displayAmount = ' $' + (amt / 100).toFixed(2)
amountClasses = {status: status}
row =
`<tr>
<td>{date}</td>
<td className="capitalize">{paymentMethod}</td>
<td>{description}</td>
<td className="capitalize">{status}</td>
<td className={classNames(amountClasses)}>{displayAmount}</td>
</tr>`
rows.push(row)
`<div>
<table className="payment-table">
<thead>
<tr>
<th>DATE</th>
<th>METHOD</th>
<th>DESCRIPTION</th>
<th>STATUS</th>
<th>AMOUNT</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
<a className="btn-next-pager" href="/api/sales?page=1">Next</a>
<div className="end-of-payments-list end-of-list">No more payment history</div>
<div className="input-aligner">
<a className="back button-grey" onClick={this.onBack}>BACK</a>
</div>
<br className="clearall"/>
</div>`
agreement: () ->
`<div className="agreement-block info-block">
<p>The agreement between your music school and JamKazam is part of JamKazam's terms of service. You can find the

View File

@ -99,7 +99,7 @@ UserStore = context.UserStore
userDetailDone: (response) ->
if response.id == @state.teacherId
school_on_school = response.teacher.school_id? && @state.user?.school_id? && response.teacher.school_id == @state.user.school_id
school_on_school = response.teacher.school_id? && @state.user?.school_id? && response.teacher.school_id == @state.user.school_id && !response.teacher.school.education
@setState({teacher: response, isSelf: response.id == context.JK.currentUserId, school_on_school: school_on_school})
else
logger.debug("BookLesson: ignoring teacher details", response.id, @state.teacherId)

View File

@ -1,8 +1,9 @@
context = window
SchoolStore = context.SchoolStore
@InviteSchoolUserDialog = React.createClass({
mixins: [Reflux.listenTo(@AppStore, "onAppInit")]
mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(SchoolStore, "onSchoolChanged")]
teacher: false
beforeShow: (args) ->
@ -14,6 +15,9 @@ context = window
@setState({inviteErrors: null, teacher: args.d1})
afterHide: () ->
onSchoolChanged: (schoolState) ->
@setState(schoolState)
onAppInit: (@app) ->
dialogBindings = {
'beforeShow': @beforeShow,
@ -22,12 +26,11 @@ context = window
@app.bindDialog('invite-school-user', dialogBindings);
componentDidMount: () ->
@root = $(@getDOMNode())
getInitialState: () ->
{inviteErrors: null}
{inviteErrors: null, school: null}
doCancel: (e) ->
e.preventDefault()
@ -41,7 +44,13 @@ context = window
firstName = @root.find('input[name="first_name"]').val()
school = context.SchoolStore.getState().school
@setState({inviteErrors: null})
rest.createSchoolInvitation({id: school.id, as_teacher: this.state.teacher, email: email, last_name: lastName, first_name: firstName }).done((response) => @createDone(response)).fail((jqXHR) => @createFail(jqXHR))
rest.createSchoolInvitation({
id: school.id,
as_teacher: this.state.teacher,
email: email,
last_name: lastName,
first_name: firstName
}).done((response) => @createDone(response)).fail((jqXHR) => @createFail(jqXHR))
createDone: (response) ->
context.SchoolActions.addInvitation(response)
@ -49,7 +58,6 @@ context = window
@app.layout.closeDialog('invite-retailer-user')
createFail: (jqXHR) ->
handled = false
if jqXHR.status == 422
@ -60,8 +68,50 @@ context = window
if !handled
@app.ajaxError(jqXHR, null, null)
render: () ->
renderEducation: () ->
`<div>
<div className="content-head">
<img className="content-icon" src="/assets/content/icon_add.png" height={19} width={19}/>
<h1>How to Invite Your Students</h1>
</div>
<div className="dialog-inner">
<p>
Please copy and paste the text below into the email application you use to communicate with students and
parents in your music program. This is a suggested starting point, but you may edit the message as you prefer.
Please make sure the web page link in this message is included in the email you send and is unchanged because
students must use this specific link to sign up so that they will be properly associated with your school.
</p>
<textarea readonly="true" value={this.educationCopyEmailText()}></textarea>
<div className="actions">
<a onClick={this.close} className="button-orange">DONE</a>
</div>
</div>
</div>`
close: (e) ->
e.preventDefault()
@app.layout.closeDialog('invite-school-user');
educationCopyEmailText: () ->
path = context.JK.makeAbsolute("/school/#{this.state.school.id}/student")
msg = "Hello Students & Parents -
I'm writing to make you aware of a very interesting new option for private music lessons. A company called JamKazam has built remarkable new technology that lets musicians play together live in sync with studio quality audio from different locations over the Internet. Here's an example: https://www.youtube.com/watch?v=I2reeNKtRjg. Now they have built an online music lesson service that uses this technology: https://www.youtube.com/watch?v=wdMN1fQyD9k.
\n\nThis means that students can now take lessons online and much more conveniently from home. Parents don't have to leave work early to drive students to and from lessons during rush hour. A 30-minute lesson is just a 30-minute lesson at home, not a 90-minute expedition across town. And students can record lessons to refer back to them later.
\n\nIf the convenience of online lessons is attractive to your family, then you can use this link to sign up for online lessons: #{path}. After you sign up, someone from JamKazam will reach out to answer your questions and help you get set up and ready to go. Your student can continue to take lessons from the same instructor through this service if desired. The student will need access to a Windows or Mac computer, and you'll need basic Internet service at home. The service uses the built-in microphone and headphone jack on the computer for audio. You can also purchase a pro audio upgrade package from JamKazam for $49.99 that includes an audio interface (a small box that connects to the computer via a USB cable), a microphone, a microphone cable, and a microphone stand. This is optional, but will deliver superior audio quality in lessons.
\n\nThe music program directors are primarily concerned with giving our students the highest quality music education possible, so we encourage you to make whatever decision you feel is best for the student. That said, for students who take lessons through the JamKazam service, a portion of the lesson fees are distributed back into our music program booster fund, which helps to fund the program's expenses, and is a nice additional benefit. If you have more questions, you can send an email to support@jamkazam.com."
return msg
renderSchool: () ->
firstNameErrors = context.JK.reactSingleFieldErrors('first_name', @state.inviteErrors)
lastNameErrors = context.JK.reactSingleFieldErrors('last_name', @state.inviteErrors)
emailErrors = context.JK.reactSingleFieldErrors('email', @state.inviteErrors)
@ -121,4 +171,16 @@ context = window
</div>
</div>`
render: () ->
school = this.state.school
if !school?
return `<div>no school</div>`
if school.education
@renderEducation()
else
@renderSchool()
})

View File

@ -145,7 +145,9 @@ proficiencyDescriptionMap = {
if @state.user?['has_booked_test_drive_with_student']
@showBuyNormalLessonBubble()
else
if @user['remaining_test_drives'] > 0
if @state.user?['same_school_with_student']
@showBuyNormalLessonBubble()
else if @user['remaining_test_drives'] > 0
@showUseRemainingTestDrivesBubble()
else if @user['can_buy_test_drive?']
@showBuyTestDriveBubble()

View File

@ -0,0 +1,378 @@
context = window
rest = context.JK.Rest()
@JamClassEducationLandingBottomPage = React.createClass({
render: () ->
`<div className="top-container">
<div className="row awesome jam-class teachers">
<h2 className="awesome">How JamClass by JamKazam Can Help Your Music School</h2>
<p>Online music lessons offer major advantages to your students, private lesson teachers, and your school's
booster program.</p>
<p>
Students can take lessons much more conveniently from home, while enjoying studio quality audio and while
retaining the ability to play live in sync with their instructor. Students can take lessons from the best
teacher vs. settling for someone who lives close by. Parents don't have to leave work early to drive students
to and from lessons during rush hour, while carting siblings along to lessons. A 30-minute lesson is just a
30-minute lesson, not a 90-minute expedition. And students can record lessons to refer back to them later.
</p>
<p>
Teachers can now provide lessons to students nearly anywhere, rather than being constrained to students who
live within a 30-minute drive. Teachers don't have to spend as much time driving to schools and to students'
homes as they do teaching, so they can travel less, teach more, and earn more. And teachers can provide
instruction to students from underserved schools that are located in areas that are more difficult to reach.
</p>
<p>
Even the booster program benefits, as JamKazam funnels a portion of lesson fees back into the music program
booster fund, helping to pay for trips, instrument repairs, and other music program expenses - all without
students selling things, and without additional time or effort expended by the music program director.
</p>
<p>Some teachers and students have historically tried using Skype to power online lessons, but have found that
the lesson experience is significantly diminished. Why? Because Skype and similar apps were built for voice
chat not to deliver online music lessons. This is a major problem. Voice technology processes all audio as
if it were a spoken human voice, which makes music sound awful in online sessions so bad that teachers cant
assess the students tone and sometimes even the pitch of what they are playing. These apps also have very
high latency a technical term that means that the student and teacher cannot play together, another
important requirement for productive lessons. Since Skype wasnt built for music, it also lacks many other
basic features to support effective lessons, like a metronome, mixers, backing tracks, etc.
</p>
<p>
At JamKazam, weve spent years designing, patenting, and building technology specifically to enable musicians
to play online live in sync with studio quality audio. Weve built a wide variety of critical online music
performance features into this platform. And now weve built a lesson marketplace on top of this foundation,
and crafted a partner program specifically to meet the needs of secondary education music programs. The bottom
line is that your students, private lesson teachers, and your music program's booster fund can now all "win"
by adopting this amazing new Internet service. And you don't have to do it all at once. You can simply make
this available as an option to students and parents who decide this is a good fit for them and will help them.
</p>
<p>
If this sounds interesting to you, read on to learn more about some of the top features of JamClass by
JamKazam.
</p>
<div className="testimonials jam-class teachers">
<h3>JamClass Kudos</h3>
<div className="testimonial">
<img src="/assets/landing/Scott Himel - Speech Bubble.png" className="testimonial-speech-bubble"/>
<img src="/assets/landing/Scott Himel - Avatar.png" className="testimonial-avatar"/>
<h4><strong>Scott Himel</strong></h4>
<div className="testiminal-background">
Texas high school band director
</div>
</div>
<div className="testimonial">
<img src="/assets/landing/Justin Pierce - Jam Class - Speech Bubble.png"
className="testimonial-speech-bubble"/>
<img src="/assets/landing/Justin Pierce - Avatar.png" className="testimonial-avatar"/>
<h4><strong>Justin Pierce</strong></h4>
<div className="testiminal-background">
Masters degree in jazz studies, performer in multiple bands, saxophone instructor
</div>
</div>
<div className="testimonial">
<img src="/assets/landing/Dave Sebree - Jam Class - Speech Bubble.png"
className="testimonial-speech-bubble"/>
<img src="/assets/landing/Dave Sebree - Avatar.png" className="testimonial-avatar"/>
<h4><strong>Dave Sebree</strong></h4>
<div className="testiminal-background">
Founder of Austin School of Music, Gibson-endorsed guitarist, touring musician
</div>
</div>
<div className="testimonial">
<img src="/assets/landing/Sara Nelson - Jam Class - Speech Bubble.png"
className="testimonial-speech-bubble"/>
<img src="/assets/landing/Sara Nelson - Avatar.png" className="testimonial-avatar"/>
<h4><strong>Sara Nelson</strong></h4>
<div className="testiminal-background">
Cellist for Austin Lyric Opera, frequently recorded with major artists
</div>
</div>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">1</div>
Play Live In Sync From Different Locations
</h3>
<p>
<div className="video-wrapper right">
<div className="video-container">
<iframe src="//www.youtube.com/embed/I2reeNKtRjg" frameborder="0" allowfullscreen="allowfullscreen"/>
</div>
</div>
<p>Teacher and student need to be able to play together to enable effective lessons. As any teacher who has
attempted to teach using Skype will tell you, Skype doesn't let you play together. JamKazam's patented
technologies deliver on this requirement at an amazing level. Click the video above to watch 6 bands play
together from different locations to see our tech in action. And for an even more impressive feat, <a
href="https://www.youtube.com/watch?v=2Zk7-04IAx4" target="_blank">watch this video</a> with a band
playing together from Austin, Atlanta, Chicago, and Brooklyn using JamKazam tech.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">2</div>
Studio Quality Audio
</h3>
<p>
<div className="audio-wrapper">
<a href="https://www.jamkazam.com/recordings/94c5d5aa-2c61-440a-93a4-c661bf77d4a8" target="_blank">Sample
Session Audio #1</a>
<div className="sample-audio-text">Electric Guitars & Drum</div>
<a href="https://www.jamkazam.com/recordings/4916dbfe-0eeb-4bfb-b08a-4085dfecedcb" target="_blank">Sample
Session Audio #2</a>
<div className="sample-audio-text">Acoustic Guitar, Bass & Voice</div>
<a href="https://www.jamkazam.com/recordings/5875be7e-2cc3-4555-825c-046bd2f849e7" target="_blank">Sample
Session Audio #3</a>
<div className="sample-audio-text">Trumpet & Keys</div>
<p className="listening-note">These audio links will open a new tab in your browser. When done listening,
close the tab and return to this page.</p>
</div>
<p>Skype was built for voice - for people talking with each other. It uses something called a "voice codec".
This just means it processes all audio as a spoken human voice, and the result is that music, whether
instrumental or vocal, sounds very bad in Skype, as it has been processed through tech built for talking.
JamKazam delivers very high quality audio. You will be amazed at how good it sounds. It sounds like you're
sitting next to each other playing. This is also critical for a good lesson. Poor audio is hard to endure
in lessons.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">3</div>
Record Lessons & Student Performances
</h3>
<p>
<div className="video-wrapper left">
<div className="video-container">
<iframe src="//www.youtube.com/embed/KMIDnUlRiPs" frameborder="0" allowfullscreen="allowfullscreen"/>
</div>
<div className="cta-text">watch this sample video recording from a lesson</div>
</div>
<p>Many times a student thinks they've got it during a lesson, but they get home and realize "I don't got
it", and then they've wasted a week. In JamClass, you can easily record lessons to refer back to them
later. Students can also use our app to record their performances, upload them to YouTube, and share with
them the music program director.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">4</div>
Use JamTracks to Motivate Students
</h3>
<p>
<div className="video-wrapper right">
<div className="video-container">
<iframe src="//www.youtube.com/embed/07zJC7C2ICA" frameborder="0" allowfullscreen="allowfullscreen"/>
</div>
</div>
<p>Teachers can also use JamTracks to further motivate the student by letting them play with songs they
love. JamKazam offers a catalog of 3,700+ popular songs. Each song is is a complete multi-track recording,
with fully isolated tracks for each instrument and part of the music. So a student can listen to just the
part they're learning in isolation, turn around and mute that one part to play along with the rest of the
band, slow down playback for practice, record and share their performances, and more. It's really fun! And
a great way to keep your students motivated and engaged.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">5</div>
Broadcast Recitals
</h3>
<p>
<img src="/assets/landing/YouTube Logo.png" className="awesome-image right" width="264" height="117"/>
<p>When your music program adopts JamClass, you can easily live broadcast video and audio of student
recitals and even full band performances through YouTube - FREE. This enables other students, parents,
grandparents, and friends to "tune in" for these performances even if they cannot attend in person. This
can also be a great way to inspire and attract younger students in feeder schools without transporting
your entire band or orchestra for remote performances.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">6</div>
Apply VST & AU Audio Plug-In Effects
</h3>
<p>
<img src="/assets/landing/Top 10 Image - Number 6.png" className="awesome-image left" width="350"
height="240"/>
<p>The free JamKazam app lets you easily apply VST & AU plugin effects to your live performance in lessons.
For example, guitarists can apply popular amp sims like AmpliTube to get any kind of guitar tone without
pedal boards or amps, and vocalists can apply effects like reverb, pitch correction, etc.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class">
<div className="awesome-item">
<h3>
<div className="awesome-number">7</div>
Use MIDI Instruments
</h3>
<p>
<img src="/assets/landing/Top 10 Image - Number 7.png" className="awesome-image" width="320" height="257"/>
<p>The free JamKazam app also lets you use MIDI instruments in online lesson sessions. For example, keys
players can use MIDI keyboard controllers with VST & AU plugins to generate traditional piano sounds,
Rhodes electric piano, Hammond organ, and other classic keys tones. And drummers who use electronic kits
can use their favorite plugins to power their percussive audio.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome-thing jam-class multi-para">
<div className="awesome-item">
<h3>
<div className="awesome-number">8</div>
And So Much More...
</h3>
<p>
<p>There are many other features that are specifically useful for online lessons built into JamClass by
JamKazam, including a metronome feature, the ability for either teacher or student to open any audio file
and use it as a backing track for session acccompaniment, and too many more to list.</p>
<p>In addition to the lesson features, an awesome bonus is that once your students are set up to
play with your teachers in online lessons, they can also play completely FREE with anyone else
in the JamKazam community any time to use the skills theyre learning in lessons to play with
others, which again reinforces and motivates students to stay engaged, as its more fun to play
with others than alone. If you teach ensembles and rock bands, your students can practice in
groups between lessons without having to find rehearsal space, pack gear, and travel. Plus
there are thousands of online sessions played every month on the JamKazam service, including
open jam sessions set up by our user community, and students can hop into these sessions,
create their own improptu sessions, etc. It's a vibrant and welcoming community of fellow
musicians.</p>
<div className="clearall"/>
</p>
</div>
</div>
<div className="row awesome jam-class">
<h2 className="awesome">How Does My Music Program Start Using JamClass By JamKazam?</h2>
<p>
One of the great things about this program is that it's extremely easy to get started, and very flexible to
your preferences. To get started the music program director simply enters his or her email address and a
password at the top of this page to express an interest in signing up. This is not a commitment, just an
expression of interest. Someone from JamKazam will follow up with you to answer all your questions. And then
if you decide this is a good fit for your music program, it takes only an hour of your time (or less) to set
up the program, and we're happy to help you do it step by step. JamKazam does everything else, so this won't
be a drain on your time or energy, and you can stay completely focused on your students and your program.
Additionally, this is not an all-or-nothing service. It's very flexible. You can offer this service to your
music program's parents and students as a helpful option, and anyone who wants to use it can use it, and no
one else needs to use it.
</p>
</div>
<div className="row awesome jam-class">
<h2 className="awesome">What Equipment Does The Student Need?</h2>
<p>
A student's family needs to have either a Windows or Mac computer at home that the student can use, and basic
Internet service. Nothing fancy at all. The JamKazam can use the built-in microphone and built-in webcam on
these computers to capture audio and video, and the student can plug a pair of headphones or earbuds into the
computer to hear the high-quality audio.
</p>
<p>
For parents and students who want the best possible audio quality, JamKazam offers a truly amazing package
deal (below our cost), so that a parent/student may purchase a pro quality audio gear package for just $49.99.
With this gear, the student can enjoy studio quality audio that is superior to what a built-in microphone can
capture. This package includes an audio interface (a small box that plugs into the computer using a USB
cable), a microphone, a mic cable, and a mic stand.
</p>
<p>
JamKazam provides 1:1 support to both teachers and students to help them get everything set up and working
properly, and our staff get into an online session to verify that everything is working great, and to show the
student around the key features they can use in online sessions.
</p>
</div>
<div className="row awesome jam-class">
<h2 className="awesome">How Do the Business Aspects of JamClass Work?</h2>
<p>JamKazam handles all student billing for lessons, so you and the instructors don't have to worry about this.
Parents pay online using a highly secure credit card processing system powered by Stripe, one of the largest
and most secure Internet commerce processing platforms in the world.</p>
<p>When a parent/student pays for a lesson, or for a month of lessons, JamKazam immediately distributes 75%
(less 3% for Stripe transaction processing fees) of the lesson fees to the lesson instructor. The instructor
gives up some income on each lesson, but our service enables instructors to travel less, reach more students,
and spend more time teaching (and earning). So instructors actually earn more and win with this program
too.</p>
<p>And finally, JamKazam distributes 6.25% of the lesson fees into the music program's booster fund to help your
program pay for trips, instrument repairs, or whatever other expenses your program needs to fund. This is
processed as a direct deposit, so even this takes no time or effort from you to manage.</p>
</div>
<div id="what-now" className="row awesome jam-class">
<h2 className="awesome">What Now?</h2>
<p>
If you're ready to sign up your school, or you think this might be good for your school but are not sure yet,
scroll back up to the top of this page, and enter your email address and a password. Once you've done this,
we'll reach out to you to answer any and all questions you have. If you find you want to move forward, well
work with you directly to help you get your school ready to go. We're excited that you are considering this
JamKazam service, and we look forward to hearing from you!
</p>
</div>
</div>`
})

View File

@ -0,0 +1,175 @@
context = window
rest = context.JK.Rest()
@JamClassEducationLandingPage = React.createClass({
render: () ->
loggedIn = context.JK.currentUserId?
if this.state.done
ctaButtonText = 'sending you in...'
else if this.state.processing
ctaButtonText = 'hold on...'
else
if loggedIn
ctaButtonText = "SIGN UP"
else
ctaButtonText = "SIGN UP"
if loggedIn
register = `<button className={classNames({'cta-button' : true, 'processing': this.state.processing})}
onClick={this.ctaClick}>{ctaButtonText}</button>`
else
if this.state.loginErrors?
for key, value of this.state.loginErrors
break
errorText = context.JK.getFullFirstError(key, this.state.loginErrors,
{email: 'Email', password: 'Password', 'terms_of_service': 'The terms of service'})
register = `<div className="register-area jam-class">
<div className={classNames({'errors': true, 'active': this.state.loginErrors})}>
{errorText}
</div>
<form className="jamtrack-signup-form">
<label>Email: </label><input type="text" name="email"/>
<label>Password: </label><input type="password" name="password"/>
<div className="clearall"/>
<input className="terms-checkbox" type="checkbox" name="terms"/><label className="terms-help">I have read and
agree to the JamKazam <a href="/corp/terms" onClick={this.termsClicked}>terms of service</a></label>
<div className="clearall"/>
<button className={classNames({'cta-button' : true, 'processing': this.state.processing})}
onClick={this.ctaClick}>{ctaButtonText}</button>
</form>
</div>`
`<div className="top-container">
<div className="full-row name-and-artist">
<div>
<div className="jam-class-ed-video">
<iframe src="//www.youtube.com/embed/wdMN1fQyD9k" frameborder="0" allowfullscreen="allowfullscreen"/>
</div>
<h1 className="jam-track-name">MAKE LESSONS MORE CONVENIENT</h1>
<h2 className="original-artist">And give your booster fund a boost!</h2>
<div className="clearall"/>
</div>
<JamClassPhone customClass="school"/>
<div className="preview-and-action-box jamclass school">
<img src="/assets/landing/arrow-1-student.png" className="arrow1-jamclass"/>
<div className="preview-jamtrack-header">
Sign Up Your School
</div>
<div className={classNames({'preview-area': true, 'jam-class': true})}>
<p>Sign up to let us know youre interested in partnering, and well follow up to answer your
questions.</p>
<p>If this is a good fit for your school, well give you all the 1:1 help you need to get your school
and staff up and running.</p>
<p>We will not share your email. See our <a href="/corp/privacy" onClick={this.privacyPolicy}>privacy
policy</a></p>
{register}
<p>It takes less than 1 hour of your time to set up this program for your school! We do everything else.</p>
</div>
</div>
</div>
<div className="row summary-text">
<p className="top-summary">
JamKazam has developed remarkable new technology that lets musicians play together live in sync with studio
quality audio from different locations over the Internet. Now JamKazam has launched an online music lesson
marketplace, and weve set up a program specifically to partner with secondary education music programs to
make lessons more convenient for students and parents, to help instructors teach more, and to simultaneously
contribute to your music program's booster fund.
</p>
</div>
</div>`
getInitialState: () ->
{loginErrors: null, processing: false}
privacyPolicy: (e) ->
e.preventDefault()
context.JK.popExternalLink('/corp/privacy')
termsClicked: (e) ->
e.preventDefault()
context.JK.popExternalLink('/corp/terms')
componentDidMount: () ->
$root = $(this.getDOMNode())
$checkbox = $root.find('.terms-checkbox')
context.JK.checkbox($checkbox)
# add item to cart, create the user if necessary, and then place the order to get the free JamTrack.
ctaClick: (e) ->
e.preventDefault()
return if @state.processing
@setState({loginErrors: null})
loggedIn = context.JK.currentUserId?
if loggedIn
@markTeacher()
else
@createUser()
@setState({processing: true})
markTeacher: () ->
rest.updateUser({school_interest: true})
.done((response) =>
this.setState({done: true})
context.location = '/client#/home'
)
.fail((jqXHR) =>
this.setState({processing: false})
context.JK.app.notifyServerError(jqXHR, "Unable to Mark As Interested in School")
)
createUser: () ->
$form = $('.jamtrack-signup-form')
email = $form.find('input[name="email"]').val()
password = $form.find('input[name="password"]').val()
terms = $form.find('input[name="terms"]').is(':checked')
rest.signup({
email: email,
password: password,
first_name: null,
last_name: null,
terms: terms,
school_interest: true,
education: true
})
.done((response) =>
context.location = '/client#/home'
).fail((jqXHR) =>
@setState({processing: false})
if jqXHR.status == 422
response = JSON.parse(jqXHR.responseText)
if response.errors
@setState({loginErrors: response.errors})
else
context.JK.app.notify({title: 'Unknown Signup Error', text: jqXHR.responseText})
else
context.JK.app.notifyServerError(jqXHR, "Unable to Sign Up")
)
@setState({processing: true})
})

View File

@ -4,8 +4,6 @@ rest = context.JK.Rest()
@JamClassSchoolLandingPage = React.createClass({
render: () ->
loggedIn = context.JK.currentUserId?
if this.state.done
@ -19,14 +17,15 @@ rest = context.JK.Rest()
ctaButtonText = "SIGN UP"
if loggedIn
register = `<button className={classNames({'cta-button' : true, 'processing': this.state.processing})} onClick={this.ctaClick}>{ctaButtonText}</button>`
register = `<button className={classNames({'cta-button' : true, 'processing': this.state.processing})}
onClick={this.ctaClick}>{ctaButtonText}</button>`
else
if this.state.loginErrors?
for key, value of this.state.loginErrors
break
errorText = context.JK.getFullFirstError(key, this.state.loginErrors, {email: 'Email', password: 'Password', 'terms_of_service' : 'The terms of service'})
errorText = context.JK.getFullFirstError(key, this.state.loginErrors,
{email: 'Email', password: 'Password', 'terms_of_service': 'The terms of service'})
register = `<div className="register-area jam-class">
<div className={classNames({'errors': true, 'active': this.state.loginErrors})}>
@ -35,10 +34,14 @@ rest = context.JK.Rest()
<form className="jamtrack-signup-form">
<label>Email: </label><input type="text" name="email"/>
<label>Password: </label><input type="password" name="password"/>
<div className="clearall"/>
<input className="terms-checkbox" type="checkbox" name="terms" /><label className="terms-help">I have read and agree to the JamKazam <a href="/corp/terms" onClick={this.termsClicked}>terms of service</a></label>
<input className="terms-checkbox" type="checkbox" name="terms"/><label className="terms-help">I have read and
agree to the JamKazam <a href="/corp/terms" onClick={this.termsClicked}>terms of service</a></label>
<div className="clearall"/>
<button className={classNames({'cta-button' : true, 'processing': this.state.processing})} onClick={this.ctaClick}>{ctaButtonText}</button>
<button className={classNames({'cta-button' : true, 'processing': this.state.processing})}
onClick={this.ctaClick}>{ctaButtonText}</button>
</form>
</div>`
@ -46,14 +49,20 @@ rest = context.JK.Rest()
`<div className="top-container">
<div className="full-row name-and-artist">
<div>
<img className="jam-class-teacher" width="375" height="215" src="/assets/landing/jam_class.png" alt="teacher instructing a jam class"/>
<img className="jam-class-teacher" width="375" height="215" src="/assets/landing/jam_class.png"
alt="teacher instructing a jam class"/>
<h1 className="jam-track-name">GROW YOUR SCHOOLS REACH & INCOME</h1>
<h2 className="original-artist">Do you own/operate a music school?</h2>
<div className="clearall"/>
</div>
<JamClassPhone customClass="school"/>
<div className="preview-and-action-box jamclass school">
<img src="/assets/landing/arrow-1-student.png" className="arrow1-jamclass"/>
<div className="preview-jamtrack-header">
Sign Up Your School
</div>
@ -61,30 +70,25 @@ rest = context.JK.Rest()
<p>Sign up to let us know youre interested in partnering, and well follow up to answer your
questions.</p>
<p>If this is a good fit for your school, well give you all the 1:1 help you need to get your school
and staff up and running.</p>
<p>We will not share your email. See our <a href="/corp/privacy" onClick={this.privacyPolicy}>privacy policy</a></p>
{register}
<p>Learn how we can help you greatly extend your reach to new markets while increasing your
revenues.</p>
<p>We will not share your email. See our <a href="/corp/privacy" onClick={this.privacyPolicy}>privacy
policy</a></p>
{register}
<p>Learn how our program helps you, students, teachers, and your booster fund all win!</p>
</div>
</div>
</div>
<div className="row summary-text">
<p className="top-summary">
Founded by a team that has built and sold companies to Google, eBay, GameStop and more,
JamKazam has developed incredibly unique technology that lets musicians play together live in
sync with studio quality audio from different locations over the Internet. Now JamKazam has
launched an online music lesson marketplace, and weve set up a program specifically to
partner with music schools to help you attract and engage students across the country,
extending your schools reach and generating more income.
Founded by a team that has built and sold companies to Google, eBay, GameStop and more, JamKazam has developed
incredibly unique technology that lets musicians play together live in sync with studio quality audio from
different locations over the Internet. Now JamKazam has launched an online music lesson marketplace, and weve
set up a program specifically to partner with music schools to help you attract and engage students across the
country, extending your schools reach and generating more income.
</p>
</div>
</div>`
@ -142,7 +146,14 @@ 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, school_interest: true})
rest.signup({
email: email,
password: password,
first_name: null,
last_name: null,
terms: terms,
school_interest: true
})
.done((response) =>
context.location = '/client#/home'
).fail((jqXHR) =>

View File

@ -16,7 +16,7 @@ rest = context.JK.Rest()
ctaButtonText = 'hold on...'
else
if loggedIn
ctaButtonText = "GO TO JAMKAZAM"
ctaButtonText = "ALREADY A JAMKAZAM USER"
else
ctaButtonText = "SIGN UP"

View File

@ -16,7 +16,7 @@ rest = context.JK.Rest()
ctaButtonText = 'hold on...'
else
if loggedIn
ctaButtonText = "GO TO JAMKAZAM"
ctaButtonText = "ALREADY A JAMKAZAM USER"
else
ctaButtonText = "SIGN UP"
@ -131,8 +131,16 @@ rest = context.JK.Rest()
})
.done((response) =>
@setState({done: true})
#window.location.href = "/client#/jamclass"
redirectTo = $.QueryString['redirect-to'];
if redirectTo
logger.debug("redirectTo:" + redirectTo);
window.location.href = redirectTo;
else
logger.debug("default post-login path");
window.location.href = "/client#/profile/#{response.id}"
).fail((jqXHR) =>
@setState({processing: false})
if jqXHR.status == 422

View File

@ -16,7 +16,7 @@ rest = context.JK.Rest()
ctaButtonText = 'hold on...'
else
if loggedIn
ctaButtonText = "GO TO JAMKAZAM"
ctaButtonText = "ALREADY A JAMKAZAM USER"
else
ctaButtonText = "SIGN UP"

View File

@ -0,0 +1,233 @@
context = window
rest = context.JK.Rest()
@SchoolTeacherListPage = React.createClass({
signupUrl: () ->
"/school/#{this.props.school.id}/student?redirect-to=#{encodeURIComponent(window.location.href)}"
render: () ->
loggedIn = context.JK.currentUserId?
if this.props.school.large_photo_url?
logo = `<div className="school-logo">
<img src={this.props.school.large_photo_url}/>
</div>`
if this.state.done
ctaButtonText = 'reloading page...'
else if this.state.processing
ctaButtonText = 'hold on...'
else
if loggedIn
ctaButtonText = "SIGN UP"
else
ctaButtonText = "SIGN UP"
if loggedIn
register = `<div className={classNames({'cta-button' : true})}>ALREADY SIGNED UP</div>`
else
if this.state.loginErrors?
for key, value of this.state.loginErrors
break
errorText = context.JK.getFullFirstError(key, this.state.loginErrors,
{email: 'Email', password: 'Password', 'terms_of_service': 'The terms of service'})
register = `<div className="register-area jam-class">
<div className={classNames({'errors': true, 'active': this.state.loginErrors})}>
{errorText}
</div>
<form className="school-signup-form">
<label>Email: </label><input type="text" name="email"/>
<label>Password: </label><input type="password" name="password"/>
<div className="clearall"/>
<input className="terms-checkbox" type="checkbox" name="terms"/><label className="terms-help">I have read and
agree to the JamKazam <a href="/corp/terms" onClick={this.termsClicked}>terms of service</a></label>
<div className="clearall"/>
<button className={classNames({'cta-button' : true, 'processing': this.state.processing})}
onClick={this.ctaClick}>{ctaButtonText}</button>
</form>
</div>`
`<div className="container">
<div className="header-area">
<div className="header-content">
{logo}
<div className="headers">
<h1>OUR TEACHERS</h1>
<h2>at {this.props.school.name}</h2>
</div>
<div className="explain">
<p>
If you have not signed up to take private music lessons online using JamKazam, you can sign up using the form
on the right side of the page.
</p>
<p>
If you have already signed up and have set up your gear with help from the people at JamKazam, then you are
ready to book your lessons with a teacher.
You may book a lesson with one of your school's preferred instructors from the list below by clicking the BOOK
LESSON button next to your preferred instructor.
</p>
<p>
If your school does not have preferred instructors, or if your music program director has indicated that you
should find
and select your instructor from our broader community of teachers, then <a href='/client#/'>click this link</a> to use
our instructor search feature to find
a great instructor for you. If you need help, email us at <a href='mailto:support@jamkazam.com'>support@jamkazam.com</a>.
</p>
</div>
<br className="clearall"/>
</div>
</div>
<div className="preview-and-action-box jamclass school">
<div className="preview-jamtrack-header">
Sign Up For Lessons
</div>
<div className={classNames({'preview-area': true, 'jam-class': true})}>
<p>Sign up to let us know youre interested taking lessons online using JamKazam.</p>
<p>We'll follow up to answer all your questions, and to help you get set up and ready to go.</p>
<p>We will not share your email. See our <a href="/corp/privacy" onClick={this.privacyPolicy}>privacy
policy</a></p>
{register}
</div>
</div>
<div className="teacher-lister">
{this.list()}
</div>
<br className="clearall"/>
<br/>
</div>`
teaches: (teacher) ->
if teacher.instruments.length == 0
return ''
else if teacher.instruments.length == 2
return 'teaches ' + teacher.instruments[0].description + ' and ' + teacher.instruments[1].description
else
return 'teaches ' + teacher.instruments.map((i) -> i.description).join(', ')
list: () ->
teachers = []
teachersList = this.rabl.teachers
for teacher in teachersList
continue if !teacher.user?
teachers.push(`
<div className="school-teacher">
<div className="school-top-row">
<div className="school-left">
<div className="avatar">
<span className="vertalign">
<img src={teacher.user.resolved_photo_url}/>
</span>
</div>
<div className="book-lesson">
<span className="vertalign">
<a className="button-orange" onClick={this.bookLessonClicked.bind(this, teacher)} href={this.bookLessonUrl(teacher)}>BOOK LESSON</a>
</span>
</div>
</div>
<div className="school-right">
<div className="username">
<span className="vertalign">
<span className="teacher-descr">
{teacher.user.name} {this.teaches(teacher)}
<a className="profile-link" href={teacher.user.teacher_profile_url}>see {teacher.user.first_name}'s detailed instructor profile</a>
</span>
</span>
</div>
</div>
<br className="clearall"/>
</div>
</div>`)
teachers
bookLessonClicked: (teacher, e) ->
loggedIn = context.JK.currentUserId?
if loggedIn
# do nothing
else
e.preventDefault()
context.JK.Banner.showNotice('Please Sign Up', 'Before booking a lesson with a teacher, please sign up by filling out the sign up form on the right. Thank you!')
bookLessonUrl: (teacher) ->
'/client#/jamclass/book-lesson/normal_' + teacher.user.id
getInitialState: () ->
{loginErrors: null, processing: false}
componentWillMount: () ->
this.rabl = JSON.parse(this.props.rabl)
componentDidMount: () ->
$root = $(this.getDOMNode())
$checkbox = $root.find('.terms-checkbox')
context.JK.checkbox($checkbox)
# add item to cart, create the user if necessary, and then place the order to get the free JamTrack.
ctaClick: (e) ->
e.preventDefault()
return if @state.processing
@setState({loginErrors: null})
loggedIn = context.JK.currentUserId?
if loggedIn
#window.location.href = "/client#/jamclass"
window.location.href = "/client#/profile/#{context.JK.currentUserId}"
else
@createUser()
@setState({processing: true})
createUser: () ->
$form = $('.school-signup-form')
email = $form.find('input[name="email"]').val()
password = $form.find('input[name="password"]').val()
terms = $form.find('input[name="terms"]').is(':checked')
rest.signup({
email: email,
password: password,
first_name: null,
last_name: null,
terms: terms,
student: true,
school_id: this.props.school.id
})
.done((response) =>
@setState({done: true})
#window.location.href = "/client#/jamclass"
window.location.reload()
).fail((jqXHR) =>
@setState({processing: false})
if jqXHR.status == 422
response = JSON.parse(jqXHR.responseText)
if response.errors
@setState({loginErrors: response.errors})
else
context.JK.app.notify({title: 'Unknown Signup Error', text: jqXHR.responseText})
else
context.JK.app.notifyServerError(jqXHR, "Unable to Sign Up")
)
})

View File

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

View File

@ -81,7 +81,7 @@ SessionStore = context.SessionStore
query.start = next
return query
onInitializeLesson: (lessonSessionId) ->
c: (lessonSessionId) ->
@lessonSessionId = lessonSessionId
@channelType = 'lesson'

View File

@ -781,3 +781,18 @@ button.stripe-connect {
font-size:20px;
}
}
.vertalign {
text-align: center;
display: inline-block;
height: 100%;
vertical-align: middle;
&:before {
content: ' ';
display: inline-block;
vertical-align: middle;
height: 100%;
}
}

View File

@ -30,4 +30,10 @@
.field {
margin-bottom:20px;
}
textarea {
height:500px;
width:100%;
margin-bottom:20px;
}
}

View File

@ -1,6 +1,11 @@
@import "client/common";
$fluid-break: 1100px;
$copy-color-on-dark: #b9b9b9;
$copy-color-on-white: #575757;
$cta-color: #e03d04;
$chunkyBorderWidth: 6px;
@mixin layout-small {
@media (max-width: #{$fluid-break - 1px}) {
@content;
@ -25,10 +30,14 @@ body.web.individual_jamtrack {
width: auto;
}
}
$copy-color-on-dark: #b9b9b9;
$copy-color-on-white: #575757;
$cta-color: #e03d04;
$chunkyBorderWidth: 6px;
&.education {
.row.awesome.jam-class.teachers:nth-of-type(1) {
padding-top:180px;
}
.testimonials.jam-class.teachers {
top:1081px;
}
}
[data-react-class="JamClassStudentLandingPage"] {
h2.future-header {
@ -465,6 +474,17 @@ body.web.individual_jamtrack {
margin-right: 20px;
margin-bottom: 20px;
}
.jam-class-ed-video {
width: 375px;
height: 215px;
float: left;
margin-right: 20px;
margin-bottom: 20px;
iframe {
width:100%;
height:100%;
}
}
p.gift-getter {
margin-top: 20px;
line-height: 125%;

View File

@ -3,6 +3,8 @@
$fluid-break: 1100px;
$copy-color-on-dark: #b9b9b9;
$cta-color: #e03d04;
$copy-color-on-white: #575757;
$chunkyBorderWidth: 6px;
@mixin layout-small {
@ -25,6 +27,11 @@ body.web.school_register {
display: none;
}
}
.wrapper {
@include layout-small {
width: auto;
}
}
.header-area {
padding-top:30px;
@ -53,6 +60,7 @@ body.web.school_register {
.school-logo {
margin-right:60px;
float:left;
margin-bottom:20px;
img {
max-width:225px;
@ -63,7 +71,8 @@ body.web.school_register {
.headers {
float:left;
text-align:left;
padding-top:64px;
margin-bottom:20px;
h1 {
margin-bottom:10px;
}
@ -74,7 +83,7 @@ body.web.school_register {
.register-area {
text-align:center;
width:400px;
width:100%;
margin:0 auto;
input {
@ -189,7 +198,360 @@ body.web.school_register {
}
}
input {
width: 206px;
width: 195px;
height: 36px;
float: right;
margin-bottom: 15px;
@include border-box_sizing;
@include layout-small {
height:40pt;
font-size:30pt;
}
}
}
&.teacher_list {
.headers {
//padding-top:140px;
clear:right;
width:50%;
}
.header-area {
text-align:left;
}
.explain {
width:700px;
float:left;
text-align:left;
p {
line-height: 125%;
margin-bottom: 20px;
width:auto;
}
@include layout-small {
width:90%;
padding:0 20px;
}
}
.school-teacher {
margin-bottom:20px;
.verthelper {
}
}
.school-top-row {
height:70px;
margin-bottom:20px;
}
.school-bottom-row {
}
.signup-btn {
margin-bottom:40px;
}
.school-left {
width:260px;
float:left;
height:70px;
}
.school-right {
float:left;
height:70px;
}
.teacher-descr {
position:relative;
}
.profile-link {
position:absolute;
top:20px;
white-space: nowrap;
left:0;
}
.avatar {
float:right;
padding:1px;
width:64px;
height:64px;
background-color:#ed4818;
margin:0;
-webkit-border-radius:32px;
-moz-border-radius:32px;
border-radius:32px;
margin-right:20px;
}
.avatar img {
width: 64px;
height: 64px;
-webkit-border-radius:32px;
-moz-border-radius:32px;
border-radius:32px;
}
.username {
float:left;
margin-right:20px;
height:100%;
}
.book-lesson {
float:right;
margin-right:20px;
height:100%;
}
.school-teacher{
}
.teacher-lister {
float:left;
}
.preview-and-action-box {
background-color: black;
float:right;
max-width: 330px;
right:0;
@include border_box_sizing;
border-width: $chunkyBorderWidth;
border-style: solid;
border-color: $copy-color-on-dark;
z-index: 1;
@include layout-small {
margin: 20px 20px;
position: static;
width: calc(100% - 40px);
max-width: calc(100% - 40px);
}
.preview-jamtrack-header {
background-color: $cta-color;
color: white;
font-size: 24px;
text-align: center;
padding: 20px 0;
border-width: 0 0 $chunkyBorderWidth;
border-style: solid;
border-color: $copy-color-on-dark;
@include layout-small {
font-size: 40pt;
}
}
.preview-area {
padding: 10px;
border-width: 0 0 $chunkyBorderWidth;
@include layout-small {
font-size: 20px;
}
&.jam-class {
border-width: 0 0 2px;
p {
line-height:125%;
padding: 8px 10px;
text-align: center;
@include border_box_sizing;
}
}
border-style: solid;
border-color: $copy-color-on-dark;
&.logged-in {
border-width: 0;
.tracks {
height: 258px;
}
}
.cta-buttons {
text-align: center;
}
.cta-button {
@include border_box_sizing;
font-size: 24px;
color: white;
background-color: $cta-color;
text-align: center;
padding: 10px;
display: block;
width: 100%;
border-radius: 8px;
border: 1px outset buttonface;
font-family: Raleway, Arial, Helvetica, sans-serif;
@include layout-small {
font-size: 30pt;
}
&.gift-card {
font-size: 16px;
width: 138px;
margin: 15px 5px;
display: inline-block;
}
}
}
.terms-help {
float: left;
margin-top: 2px;
font-size: 12px;
}
.register-area {
@include layout-small {
label {
width: 40% !important;
text-align: right;
padding-right: 20px;
&.terms-help {
height: auto !important;
}
@include layout-small {
height: 40pt;
font-size: 30pt;
}
}
input {
width: 60% !important;
@include layout-small {
height: 40pt;
font-size: 30pt;
}
}
}
&.jam-class {
padding: 0;
}
&.ios {
background-color: $cta-color;
font-size: 40pt;
.ios-appstore-badge {
display: inline-block;
text-align: center;
margin-left: 20px;
margin-top: 30px;
width: 100%;
img {
width: 50%;
}
}
}
padding: 10px;
input {
background-color: $copy-color-on-dark;
color: black;
font-size: 16px;
@include layout-small {
font-size: 30pt;
}
&[name="terms"] {
width: auto;
line-height: 24px;
vertical-align: middle;
@include layout-small {
line-height: 125%;
}
}
}
.checkbox-wrap {
float: left;
margin-top: 6px;
margin-left: 64px;
@include border_box_sizing;
text-align: right;
input {
height: auto;
@include layout-small {
height: 30pt !important;
width: 30pt !important;
}
}
@include layout-small {
width: 40%;
margin-left: 0;
.icheckbox_minimal {
right: -18px;
}
}
.icheckbox_minimal {
}
}
.privacy-policy {
text-decoration: underline;
}
form {
margin: 0 0 10px;
}
.errors {
font-size: 12px;
height: 20px;
margin: 0;
visibility: hidden;
text-align: center;
color: red;
font-weight: bold;
&.active {
visibility: visible;
}
@include layout-small {
font-size: 20pt;
height: 32pt;
}
}
label {
display: inline-block;
height: 36px;
vertical-align: middle;
line-height: 36px;
margin-bottom: 15px;
@include border-box_sizing;
&.terms-help {
width: 205px;
height: 28px;
line-height: 14px;
float: right;
@include layout-small {
line-height: 125%;
}
}
@include layout-small {
height: 40pt;
font-size: 30pt;
}
}
input {
width: 195px;
height: 36px;
float: right;
margin-bottom: 15px;
@ -202,3 +564,5 @@ body.web.school_register {
}
}
}
}
}

View File

@ -11,7 +11,6 @@ class ApiTeacherDistributionsController < ApiController
render "api_teacher_distributions/index", :layout => nil
end
private
end

View File

@ -131,7 +131,6 @@ class LandingsController < ApplicationController
render 'jam_class_affiliates', layout: 'web'
end
def jam_class_schools
enable_olark
@no_landing_tag = true
@ -140,6 +139,14 @@ class LandingsController < ApplicationController
render 'jam_class_schools', layout: 'web'
end
def jam_class_education
enable_olark
@no_landing_tag = true
@landing_tag_play_learn_earn = true
@show_after_black_bar_border = true
render 'jam_class_education', layout: 'web'
end
def jam_class_retailers
enable_olark
@no_landing_tag = true
@ -378,6 +385,28 @@ class LandingsController < ApplicationController
render 'school_teacher_register', layout: 'web'
end
def school_teacher_list
@no_landing_tag = true
@landing_tag_play_learn_earn = true
@school = School.find_by_id(params[:id])
if @school.nil?
redirect_to '/signup'
return
end
@title = 'Our teachers at ' + @school.name
@description = "View the profile and pick a teacher right for you from " + @school.name
@preview = !params[:preview].nil?
schoolRabl = Rabl::Renderer.json(@school, 'api_schools/show')
@page_data = {school: @school, preview: @preview, rabl: schoolRabl}
render 'school_teacher_list', layout: 'web'
end
def retailer_teacher_register
@no_landing_tag = true
@landing_tag_play_learn_earn = true

View File

@ -3,7 +3,7 @@ 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?, :no_school_on_school_payment?, :payment_if_school_on_school?, :teacher_id, :student_id, :pretty_scheduled_start, :scheduled_start, :teacher_short_canceled,
:best_display_time
node do |lesson_session|

View File

@ -1,6 +1,6 @@
object @school
attributes :id, :user_id, :name, :enabled, :scheduling_communication, :original_fpfile, :cropped_fpfile, :crop_selection, :photo_url, :correspondence_email
attributes :id, :user_id, :name, :enabled, :scheduling_communication, :original_fpfile, :cropped_fpfile, :crop_selection, :photo_url, :correspondence_email, :education
child :owner => :owner do
attributes :id, :email, :photo_url, :name, :first_name, :last_name
@ -10,10 +10,14 @@ child :students => :students do
attributes :id, :name, :first_name, :last_name, :photo_url
end
child :teachers => :teachers do |teacher|
child :approved_teachers => :teachers do |teacher|
attributes :id
child :instruments do |teacher|
attributes :id, :description
end
child :user => :user do
attributes :id, :name, :first_name, :last_name, :photo_url
attributes :id, :name, :first_name, :last_name, :photo_url, :teacher_profile_url, :resolved_photo_url
end
end

View File

@ -3,11 +3,15 @@ object @teacher_distribution
attributes :id, :description, :ready, :distributed, :created_at, :amount_in_cents, :real_distribution_in_cents, :not_collectable
child(:teacher => :teacher) {
attributes :id, :teacher_profile_url
child(:teacher => :teacher) {
attributes :stripe_account_id
}
}
child(:student => :student) {
attributes :id, :first_name, :last_name, :profile_url
}
child(:teacher_payment => :teacher_payment) {
attributes :real_distribution_in_cents, :created_at

View File

@ -41,7 +41,10 @@ attributes :id,
child :review_summary => :review_summary do
attributes :avg_rating, :wilson_score, :review_count
end
child :school => :school do
attributes :id, :education
end
child :recent_reviews => :recent_reviews do

View File

@ -111,6 +111,10 @@ elsif current_user
node :has_booked_test_drive_with_student do |user|
user.has_booked_test_drive_with_student?(current_user)
end
node :same_school_with_student do |user|
user.same_school_with_student?(current_user)
end
end
child :friends => :friends do

View File

@ -5,4 +5,9 @@ attributes :id, :first_name, :last_name, :name, :photo_url
child :teacher do |teacher|
attributes :id, :biography, :school_id
child :school do |school|
attributes :id, :education
end
end

View File

@ -0,0 +1,14 @@
- provide(:page_name, 'landing_page full individual_jamtrack education')
- provide(:description, @description)
- provide(:title, @title)
= react_component 'JamClassEducationLandingPage', @page_data.to_json
- content_for :after_black_bar do
.row.cta-row
h2 SIGN UP YOUR SCHOOL NOW!
p And make better, more convenient lessons available to your students.
p.cta-text Not sure if our school partner program is for you? Scroll down to learn more.
- content_for :white_bar do
= react_component 'JamClassEducationLandingBottomPage', @page_data.to_json

View File

@ -0,0 +1,5 @@
- provide(:page_name, 'landing_page full school_register teacher_list')
- provide(:description, @description)
- provide(:title, @title)
= react_component 'SchoolTeacherListPage', @page_data.to_json

View File

@ -48,8 +48,9 @@ Rails.application.routes.draw do
get '/landing/jamclass/free/students', to: 'landings#jam_class_students_free'
get '/landing/jamclass/teachers', to: 'landings#jam_class_teachers', as: 'jamclass_teacher_signup'
get '/landing/jamclass/affiliates', to: 'landings#jam_class_affiliates'
get '/landing/jamclass/schools', to: 'landings#jam_class_schools'
get '/landing/jamclass/retailers', to: 'landings#jam_class_retailers'
get '/landing/jamclass/schools', to: 'landings#jam_class_schools', as: 'jam_class_schools'
get '/landing/jamclass/education', to: 'landings#jam_class_education', as: 'jam_class_education'
get '/landing/jamclass/retailers', to: 'landings#jam_class_retailers', as: 'jam_class_retailers'
get '/affiliateProgram', to: 'landings#affiliate_program', as: 'affiliate_program'
@ -57,6 +58,7 @@ Rails.application.routes.draw do
match '/school/:id/student', to: 'landings#school_student_register', via: :get, as: 'school_student_register'
match '/school/:id/teacher', to: 'landings#school_teacher_register', via: :get, as: 'school_teacher_register'
match '/school/:id/teachers', to: 'landings#school_teacher_list', via: :get, as: 'school_teacher_list'
match '/retailer/:id/teacher', to: 'landings#retailer_teacher_register', via: :get, as: 'retailer_teacher_register'
match '/posa/:slug', to: 'landings#posa_activation', via: :get, as: 'posa_activation'

View File

@ -35,6 +35,9 @@ SitemapGenerator::Sitemap.create do
add(buy_gift_card_path, priority: 0.9)
add(jamclass_student_signup_path, priority:0.9)
add(jamclass_teacher_signup_path, priority:0.9)
add(jam_class_schools_path, priority:0.9)
add(jam_class_retailers_path, priority:0.9)
add(jam_class_education_path, priority:0.9)
JamTrack.all.each do |jam_track|
slug = jam_track.slug