merging origin
This commit is contained in:
commit
c23944607c
|
|
@ -28,7 +28,7 @@ ActiveAdmin.register_page "Giftcarduploads" do
|
|||
end
|
||||
|
||||
content do
|
||||
semantic_form_for GiftCard.new, :url => admin_giftcarduploads_upload_giftcards_path, :builder => ActiveAdmin::FormBuilder do |f|
|
||||
active_admin_form_for GiftCard.new, :url => admin_giftcarduploads_upload_giftcards_path, :builder => ActiveAdmin::FormBuilder do |f|
|
||||
f.inputs "Upload Gift Cards" do
|
||||
f.input :csv, as: :file, required: true, :label => "A single column CSV that contains ONE type of gift card (5 JamTrack, 10 JamTrack, etc)"
|
||||
f.input :card_type, required:true, as: :select, :collection => JamRuby::GiftCard::CARD_TYPES
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
ActiveAdmin.register JamRuby::User, :as => 'EducationInterest' do
|
||||
|
||||
menu :label => 'Interested in Education', :parent => 'JamClass'
|
||||
|
||||
config.sort_order = 'created_at desc'
|
||||
config.batch_actions = false
|
||||
config.per_page = 100
|
||||
config.paginate = true
|
||||
config.filters = false
|
||||
|
||||
scope("All", default: true) { |scope| scope.where(education_interest: true) }
|
||||
|
||||
index do
|
||||
column "Name" do |user|
|
||||
span do
|
||||
link_to "#{user.name} (#{user.email})", "#{Rails.application.config.external_root_url}/client#/profile/#{user.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
ActiveAdmin.register JamRuby::User, :as => 'RetailerInterest' do
|
||||
|
||||
menu :label => 'Interested in Retailers', :parent => 'JamClass'
|
||||
|
||||
config.sort_order = 'created_at desc'
|
||||
config.batch_actions = false
|
||||
config.per_page = 100
|
||||
config.paginate = true
|
||||
config.filters = false
|
||||
|
||||
scope("All", default: true) { |scope| scope.where(retailer_interest: true) }
|
||||
|
||||
index do
|
||||
column "Name" do |user|
|
||||
span do
|
||||
link_to "#{user.name} (#{user.email})", "#{Rails.application.config.external_root_url}/client#/profile/#{user.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -5,31 +5,31 @@ ActiveAdmin.register_page "Monthly Stats" do
|
|||
content :title => "Monthly Stats" do
|
||||
h2 "Distinct Users Playing in Sessions"
|
||||
table_for MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', msuh.created_at)::date as month, count(distinct(user_id)) from music_sessions_user_history msuh group by month order by month desc;") do
|
||||
column "Month", Proc.new { |row| Date.parse(row.month).strftime('%B %Y') }
|
||||
column "Month", Proc.new { |row| row.month.strftime('%B %Y') }
|
||||
column "Users", :count
|
||||
end
|
||||
|
||||
h2 "Music Sessions"
|
||||
table_for MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', ms.created_at)::date as month, count(id) from music_sessions ms where started_at is not null group by month order by month desc;") do
|
||||
column "Month", Proc.new { |row| Date.parse(row.month).strftime('%B %Y') }
|
||||
column "Month", Proc.new { |row| row.month.strftime('%B %Y') }
|
||||
column "Sessions", :count
|
||||
end
|
||||
|
||||
h2 "Distinct Users Who Played with a JamTrack"
|
||||
table_for MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', jts.created_at)::date as month, count(distinct(user_id)) from jam_track_sessions jts group by month order by month desc;") do
|
||||
column "Month", Proc.new { |row| Date.parse(row.month).strftime('%B %Y') }
|
||||
column "Month", Proc.new { |row| row.month.strftime('%B %Y') }
|
||||
column "Users", :count
|
||||
end
|
||||
|
||||
h2 "Music Sessions with JamTracks Played"
|
||||
table_for MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', jts.created_at)::date as month, count(distinct(music_session_id)) from jam_track_sessions jts where session_type = 'session' group by month order by month desc;") do
|
||||
column "Month", Proc.new { |row| Date.parse(row.month).strftime('%B %Y') }
|
||||
column "Month", Proc.new { |row| row.month.strftime('%B %Y') }
|
||||
column "Sessions", :count
|
||||
end
|
||||
|
||||
h2 "JamTrack Web Player Sessions"
|
||||
table_for MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', jts.created_at)::date as month, count(id) from jam_track_sessions jts where session_type = 'browser' group by month order by month desc;") do
|
||||
column "Month", Proc.new { |row| Date.parse(row.month).strftime('%B %Y') }
|
||||
column "Month", Proc.new { |row| row.month.strftime('%B %Y') }
|
||||
column "Sessions", :count
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
ActiveAdmin.register_page "POSA Card Uploads" do
|
||||
|
||||
menu :label => 'Posa Cards Upload', :parent => 'JamClass'
|
||||
|
||||
page_action :upload_posacards, :method => :post do
|
||||
PosaCard.transaction do
|
||||
|
||||
puts params
|
||||
|
||||
file = params[:jam_ruby_posa_card][:csv]
|
||||
array_of_arrays = CSV.read(file.tempfile.path)
|
||||
array_of_arrays.each do |row|
|
||||
if row.length != 1
|
||||
raise "UKNONWN CSV FORMAT! Must be 1 column"
|
||||
end
|
||||
|
||||
code = row[0]
|
||||
|
||||
posa_card = PosaCard.new
|
||||
posa_card.code = code
|
||||
posa_card.card_type = params[:jam_ruby_posa_card][:card_type]
|
||||
posa_card.origin = file .original_filename
|
||||
posa_card.save!
|
||||
end
|
||||
|
||||
redirect_to admin_posa_card_uploads_path, :notice => "Created #{array_of_arrays.length} POSA cards!"
|
||||
end
|
||||
end
|
||||
|
||||
=begin
|
||||
form :html => {:multipart => true} do |f|
|
||||
f.inputs "Details" do
|
||||
f.input :version, :hint => "Should match Jenkins build number of artifact"
|
||||
f.input :environment, :hint => "Typically just 'public'"
|
||||
f.input :product, :as => :select, :collection => JamRuby::ArtifactUpdate::PRODUCTS
|
||||
end
|
||||
f.inputs "Artifact Upload" do
|
||||
f.input :uri, :as => :file, :hint => "Upload the artifact from Jenkins"
|
||||
end
|
||||
|
||||
f.actions
|
||||
|
||||
end
|
||||
=end
|
||||
|
||||
content do
|
||||
active_admin_form_for PosaCard.new, :url => admin_posa_card_uploads_upload_posacards_path, :builder => ActiveAdmin::FormBuilder do |f|
|
||||
f.inputs "Upload POSA Cards" do
|
||||
f.input :csv, as: :file, required: true, :label => "A single column CSV that contains ONE type of gift card (5 JamTrack, 10 JamTrack, 4 JamClass etc)"
|
||||
f.input :card_type, required:true, as: :select, :collection => JamRuby::PosaCard::CARD_TYPES
|
||||
|
||||
end
|
||||
f.actions
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -25,7 +25,9 @@ ActiveAdmin.register JamRuby::SaleLineItem, :as => 'Sale Line Items' do
|
|||
link_to("#{oo.affiliate_referral.display_name} #{oo.affiliate_referral_fee_in_cents ? "#{oo.affiliate_referral_fee_in_cents}\u00A2" : ''}", oo.affiliate_referral.admin_url, {:title => oo.affiliate_referral.display_name}) if oo.affiliate_referral
|
||||
end
|
||||
column 'User' do |oo|
|
||||
link_to(oo.sale.user.name, admin_user_path(oo.sale.user.id), {:title => oo.sale.user.name})
|
||||
if oo.sale.user
|
||||
link_to(oo.sale.user.name, admin_user_path(oo.sale.user.id), {:title => oo.sale.user.name})
|
||||
end
|
||||
end
|
||||
column 'Source' do |oo|
|
||||
oo.sale.source
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class ArtifactsController < ApplicationController
|
|||
ArtifactUpdate.transaction do
|
||||
# VRFS-1071: Postpone client update notification until installer is available for download
|
||||
ArtifactUpdate.connection.execute('SET TRANSACTION ISOLATION LEVEL READ COMMITTED')
|
||||
@artifact = ArtifactUpdate.find_or_create_by({product: product, environement: environment})
|
||||
@artifact = ArtifactUpdate.find_or_create_by({product: product, environment: environment})
|
||||
|
||||
@artifact.version = version
|
||||
@artifact.uri = file
|
||||
|
|
|
|||
|
|
@ -363,4 +363,9 @@ jamblasters_network.sql
|
|||
immediate_recordings.sql
|
||||
nullable_user_id_jamblaster.sql
|
||||
rails4_migration.sql
|
||||
non_free_jamtracks.sql
|
||||
non_free_jamtracks.sql
|
||||
retailers.sql
|
||||
second_ed.sql
|
||||
second_ed_v2.sql
|
||||
retailers_v2.sql
|
||||
retailer_interest.sql
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE users ADD COLUMN retailer_interest BOOLEAN DEFAULT FALSE NOT NULL;
|
||||
alter table retailers alter column slug drop not null;
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
CREATE TABLE retailers (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id VARCHAR(64) REFERENCES users(id) NOT NULL,
|
||||
name VARCHAR,
|
||||
enabled BOOLEAN DEFAULT TRUE,
|
||||
city VARCHAR,
|
||||
state VARCHAR,
|
||||
slug VARCHAR NOT NULL,
|
||||
encrypted_password VARCHAR NOT NULL DEFAULT uuid_generate_v4(),
|
||||
photo_url VARCHAR(2048),
|
||||
original_fpfile VARCHAR(8000),
|
||||
cropped_fpfile VARCHAR(8000),
|
||||
cropped_s3_path VARCHAR(8000),
|
||||
crop_selection VARCHAR(256),
|
||||
large_photo_url VARCHAR(512),
|
||||
cropped_large_s3_path VARCHAR(512),
|
||||
cropped_large_fpfile VARCHAR(8000),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE SEQUENCE retailer_key_sequence;
|
||||
ALTER SEQUENCE retailer_key_sequence RESTART WITH 10000;
|
||||
ALTER TABLE retailers ALTER COLUMN id SET DEFAULT nextval('retailer_key_sequence');
|
||||
|
||||
|
||||
CREATE TABLE retailer_invitations (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id VARCHAR(64) REFERENCES users(id),
|
||||
retailer_id INTEGER REFERENCES retailers(id) NOT NULL,
|
||||
invitation_code VARCHAR(256) NOT NULL UNIQUE,
|
||||
note VARCHAR,
|
||||
email VARCHAR NOT NULL,
|
||||
first_name VARCHAR,
|
||||
last_name VARCHAR,
|
||||
accepted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE posa_cards (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
code VARCHAR(64) UNIQUE NOT NULL,
|
||||
user_id VARCHAR (64) REFERENCES users(id) ON DELETE SET NULL,
|
||||
card_type VARCHAR(64) NOT NULL,
|
||||
origin VARCHAR(200),
|
||||
activated_at TIMESTAMP,
|
||||
claimed_at TIMESTAMP,
|
||||
retailer_id INTEGER REFERENCES retailers(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX posa_card_user_id_idx ON posa_cards(user_id);
|
||||
|
||||
ALTER TABLE users ADD COLUMN jamclass_credits INTEGER DEFAULT 0;
|
||||
|
||||
|
||||
CREATE TABLE posa_card_types (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
card_type VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO posa_card_types (id, card_type) VALUES ('jam_tracks_5', 'jam_tracks_5');
|
||||
INSERT INTO posa_card_types (id, card_type) VALUES ('jam_tracks_10', 'jam_tracks_10');
|
||||
INSERT INTO posa_card_types (id, card_type) VALUES ('jam_class_10', 'jam_class_10');
|
||||
|
||||
CREATE TABLE posa_card_purchases (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE SET NULL,
|
||||
posa_card_type_id VARCHAR(64) REFERENCES posa_card_types(id) ON DELETE SET NULL,
|
||||
posa_card_id VARCHAR(64) REFERENCES posa_cards(id) ON DELETE SET NULL,
|
||||
recurly_adjustment_uuid VARCHAR(500),
|
||||
recurly_adjustment_credit_uuid VARCHAR(500),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE sale_line_items ADD COLUMN posa_card_purchase_id VARCHAR(64) REFERENCES posa_card_purchases(id);
|
||||
|
||||
|
||||
ALTER TABLE teachers ADD COLUMN retailer_id INTEGER REFERENCES retailers(id);
|
||||
ALTER TABLE teachers ADD COLUMN joined_retailer_at TIMESTAMP;
|
||||
ALTER TABLE retailers ADD jamkazam_rate NUMERIC (8, 2) DEFAULT 0.25;
|
||||
ALTER TABLE retailers ADD COLUMN affiliate_partner_id INTEGER REFERENCES affiliate_partners(id);
|
||||
ALTER TABLE lesson_bookings ADD COLUMN retailer_id INTEGER REFERENCES retailers(id);
|
||||
ALTER TABLE teacher_payments ADD COLUMN retailer_id INTEGER REFERENCES retailers(id);
|
||||
ALTER TABLE teacher_distributions ADD COLUMN retailer_id INTEGER REFERENCES retailers(id);
|
||||
|
||||
|
||||
ALTER TABLE sales ALTER COLUMN user_id DROP NOT NULL;
|
||||
ALTER TABLE sales ADD COLUMN retailer_id INTEGER REFERENCES retailers(id);
|
||||
ALTER TABLE sale_line_items ADD COLUMN retailer_id INTEGER REFERENCES retailers(id);
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE lesson_bookings ADD COLUMN posa_card_id VARCHAR(64);
|
||||
ALTER TABLE jam_track_rights ADD COLUMN posa_card_id VARCHAR(64);
|
||||
ALTER TABLE lesson_package_purchases ADD COLUMN posa_card_id VARCHAR(64);
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE users ADD COLUMN education_interest BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
|
@ -280,6 +280,9 @@ require "jam_ruby/models/subject"
|
|||
require "jam_ruby/models/band_search"
|
||||
require "jam_ruby/import/tency_stem_mapping"
|
||||
require "jam_ruby/models/jam_track_search"
|
||||
require "jam_ruby/models/posa_card"
|
||||
require "jam_ruby/models/posa_card_type"
|
||||
require "jam_ruby/models/posa_card_purchase"
|
||||
require "jam_ruby/models/gift_card"
|
||||
require "jam_ruby/models/gift_card_purchase"
|
||||
require "jam_ruby/models/gift_card_type"
|
||||
|
|
@ -305,6 +308,8 @@ require "jam_ruby/models/affiliate_distribution"
|
|||
require "jam_ruby/models/teacher_intent"
|
||||
require "jam_ruby/models/school"
|
||||
require "jam_ruby/models/school_invitation"
|
||||
require "jam_ruby/models/retailer"
|
||||
require "jam_ruby/models/retailer_invitation"
|
||||
require "jam_ruby/models/teacher_instrument"
|
||||
require "jam_ruby/models/teacher_subject"
|
||||
require "jam_ruby/models/teacher_language"
|
||||
|
|
|
|||
|
|
@ -49,9 +49,27 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def student_education_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"
|
||||
|
||||
sendgrid_recipients([user.email])
|
||||
sendgrid_substitute('@USERID', [user.id])
|
||||
sendgrid_substitute(EmailBatchProgression::VAR_FIRST_NAME, [user.first_name])
|
||||
|
||||
mail(:to => user.email, :subject => @subject) do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
|
||||
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 +86,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"
|
||||
|
||||
|
|
@ -97,6 +118,38 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def retailer_owner_welcome_message(user)
|
||||
@user = user
|
||||
@subject= "Welcome to JamKazam!"
|
||||
sendgrid_category "Welcome"
|
||||
sendgrid_unique_args :type => "welcome_message"
|
||||
|
||||
sendgrid_recipients([user.email])
|
||||
sendgrid_substitute('@USERID', [user.id])
|
||||
sendgrid_substitute(EmailBatchProgression::VAR_FIRST_NAME, [user.first_name])
|
||||
|
||||
mail(:to => user.email, :subject => @subject) do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
|
||||
def education_owner_welcome_message(user)
|
||||
@user = user
|
||||
@subject= "Welcome to JamKazam and JamClass online lessons!"
|
||||
sendgrid_category "Welcome"
|
||||
sendgrid_unique_args :type => "welcome_message"
|
||||
|
||||
sendgrid_recipients([user.email])
|
||||
sendgrid_substitute('@USERID', [user.id])
|
||||
sendgrid_substitute(EmailBatchProgression::VAR_FIRST_NAME, [user.first_name])
|
||||
|
||||
mail(:to => user.email, :subject => @subject) do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
|
||||
def password_changed(user)
|
||||
@user = user
|
||||
|
||||
|
|
@ -1002,7 +1055,20 @@ module JamRuby
|
|||
|
||||
@user = lesson_session.student
|
||||
email = @student.email
|
||||
subject = "You have used #{@student.used_test_drives} of #{@student.total_test_drives} TestDrive lesson credits"
|
||||
|
||||
|
||||
if lesson_session.posa_card
|
||||
@total_credits = @student.total_posa_credits
|
||||
@used_credits = @student.used_posa_credits
|
||||
@remaining_credits = @student.jamclass_credits
|
||||
else
|
||||
@total_credits = @student.total_test_drives
|
||||
@used_credits = @student.used_test_drives
|
||||
@remaining_credits = @student.remaining_test_drives
|
||||
end
|
||||
|
||||
subject = "You have used #{@used_credits} of #{@total_credits} TestDrive lesson credits"
|
||||
|
||||
unique_args = {:type => "student_test_drive_success"}
|
||||
|
||||
sendgrid_category "Notification"
|
||||
|
|
@ -1698,6 +1764,26 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def invite_retailer_teacher(retailer_invitations)
|
||||
@retailer_invitation = retailer_invitations
|
||||
@retailer = retailer_invitations.retailer
|
||||
|
||||
email = retailer_invitations.email
|
||||
@subject = "#{@retailer.owner.name} has sent you an invitation to join #{@retailer.name} on JamKazam"
|
||||
unique_args = {:type => "invite_retailer_teacher"}
|
||||
|
||||
sendgrid_category "Notification"
|
||||
sendgrid_unique_args :type => unique_args[:type]
|
||||
sendgrid_recipients([email])
|
||||
|
||||
@suppress_user_has_account_footer = true
|
||||
|
||||
mail(:to => email, :subject => @subject) do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
|
||||
def invite_school_teacher(school_invitation)
|
||||
@school_invitation = school_invitation
|
||||
@school = school_invitation.school
|
||||
|
|
@ -1882,7 +1968,22 @@ module JamRuby
|
|||
format.text
|
||||
format.html { render :layout => "from_user_mailer" }
|
||||
end
|
||||
end
|
||||
|
||||
def retailer_customer_blast(email, retailer)
|
||||
@retailer = retailer
|
||||
@subject = "Check out our teachers at #{@retailer.name}"
|
||||
unique_args = {:type => "retailer_customer_email"}
|
||||
|
||||
sendgrid_category "Notification"
|
||||
sendgrid_unique_args :type => unique_args[:type]
|
||||
sendgrid_recipients([email])
|
||||
|
||||
@suppress_user_has_account_footer = true
|
||||
mail(:to => email, :subject => @subject) do |format|
|
||||
format.text
|
||||
format.html
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
<% provide(:title, @subject) %>
|
||||
|
||||
|
||||
<% if !@user.anonymous? %>
|
||||
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<p>
|
||||
Thank you for expressing an interest in exploring our secondary education partner program! A member of our staff will
|
||||
reach out to you shortly to chat with you and answer any questions you have about our partner program, our
|
||||
technologies, and how we can help you continue to deliver the best possible music education to your students.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It takes less than 1 hour of your time to set up your music program to partner with JamKazam. And we are happy to walk
|
||||
you through the process step by step, so you don't have to worry about figuring out how to do this. But if you're
|
||||
curious, then you can check out our
|
||||
<a href="https://jamkazam.desk.com/customer/en/portal/topics/985544-jamclass-online-music-lessons---for-secondary-education-music-program-directors/articles" style="color:#fc0">help
|
||||
articles for music program directors</a>. These help articles explain things from
|
||||
the perspective of the school program director - e.g. how to set up your school, how to invite teachers and students
|
||||
to sign up if they wish, how distributions are made into your booster fund, and so on.
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
JamKazam handles all the technical support needed to help your students, as well as any preferred teachers associated
|
||||
with your music program, to get set up and ready to go. We even get into a sample online session with each individual
|
||||
to make sure everything is working, and to show them around the features they'll use in online lessons. But if you are
|
||||
curious about how it all works, 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">help guide for students</a> and our <a style="color:#fc0" href="https://jamkazam.desk.com/customer/en/portal/topics/926076-jamclass-online-music-lessons---for-teachers/articles">help guide for teachers</a>.
|
||||
</p>
|
||||
<p>
|
||||
Thanks again for connecting with us, and we look forward to speaking with you soon!
|
||||
</p>
|
||||
<p>Best Regards,<br/>
|
||||
Team JamKazam</p>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<% if !@user.anonymous? %>
|
||||
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %>
|
||||
<% end %>
|
||||
|
||||
Thank you for expressing an interest in exploring our secondary education partner program! A member of our staff will
|
||||
reach out to you shortly to chat with you and answer any questions you have about our partner program, our
|
||||
technologies, and how we can help you continue to deliver the best possible music education to your students.
|
||||
|
||||
It takes less than 1 hour of your time to set up your music program to partner with JamKazam. And we are happy to walk
|
||||
you through the process step by step, so you don't have to worry about figuring out how to do this. But if you're
|
||||
curious, then you can check out our help articles for music program directors -- https://jamkazam.desk.com/customer/en/portal/topics/985544-jamclass-online-music-lessons---for-secondary-education-music-program-directors/articles. These help articles explain things from
|
||||
the perspective of the school program director - e.g. how to set up your school, how to invite teachers and students
|
||||
to sign up if they wish, how distributions are made into your booster fund, and so on.
|
||||
|
||||
JamKazam handles all the technical support needed to help your students, as well as any preferred teachers associated
|
||||
with your music program, to get set up and ready to go. We even get into a sample online session with each individual
|
||||
to make sure everything is working, and to show them around the features they'll use in online lessons. But if you are
|
||||
curious about how it all works, you can also review our help guide for students -- https://jamkazam.desk.com/customer/en/portal/topics/926073-jamclass-online-music-lessons---for-students/articles and our help
|
||||
guide for teachers -- https://jamkazam.desk.com/customer/en/portal/topics/926076-jamclass-online-music-lessons---for-teachers/articles.
|
||||
Best Regards,
|
||||
Team JamKazam
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<% provide(:title, @subject) %>
|
||||
|
||||
Hello <%= @retailer_invitation.first_name %> -
|
||||
<p><%= @retailer.owner.first_name %> is using JamKazam to deliver online music lessons, and has sent you this invitation so that you can
|
||||
register to take online music lessons with <%= @retailer.name %>. To accept this invitation, please click the SIGN UP NOW
|
||||
button below, and follow the instructions on the web page to which you are taken. Thanks, and on behalf of
|
||||
<%= @retailer.name %>, welcome to JamKazam!</p>
|
||||
<br/>
|
||||
<p>
|
||||
<a href="<%= @retailer_invitation.generate_signup_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">SIGN
|
||||
UP NOW</a>
|
||||
</p>
|
||||
<br/>
|
||||
<br/>
|
||||
Best Regards,<br>
|
||||
Team JamKazam
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<% provide(:title, @subject) %>
|
||||
|
||||
Hello <%= @retailer_invitation.first_name %> -
|
||||
<br/>
|
||||
<p>
|
||||
<%= @retailer.owner.first_name %> has set up <%= @retailer.name %> on JamKazam, enabling you to deliver online music
|
||||
lessons in an amazing new way that really works. To accept this invitation, please click the SIGN UP NOW button below,
|
||||
and follow the instructions on the web page to which you are taken. Thanks, and welcome to JamKazam!</p>
|
||||
<br/>
|
||||
<p>
|
||||
<a href="<%= @retailer_invitation.generate_signup_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">SIGN
|
||||
UP NOW</a>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
Best Regards,<br>
|
||||
Team JamKazam
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<% provide(:title, @subject) %>
|
||||
|
||||
Hello <%= @retailer_invitation.first_name %> -
|
||||
<%= @retailer.owner.first_name %> has set up <%= @retailer.name %> on JamKazam, enabling you to deliver online music
|
||||
lessons in an amazing new way that really works. To accept this invitation, please click the link below,
|
||||
and follow the instructions on the web page to which you are taken. Thanks, and welcome to JamKazam!
|
||||
|
||||
<%= @retailer_invitation.generate_signup_url %>
|
||||
|
||||
Best Regards,
|
||||
Team JamKazam
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<% provide(:title, @subject) %>
|
||||
|
||||
<p>Click the link of each teacher's profile at <%= @retailer.name %> to find the best fit for you:</p>
|
||||
<ul>
|
||||
<% @retailer.teachers.each do |teacher| %>
|
||||
<li><a href="<%= teacher.user.teacher_profile_url%>" style="color:#fc0"><%= teacher.user.name %></a><br/><%= teacher.teaches %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
Check out each teacher's profile at <%= @retailer.name %> to find the best fit for you:
|
||||
|
||||
<% @retailer.teachers.each do |teacher| %>
|
||||
<%= teacher.user.name %>: <%= teacher.user.teacher_profile_url%> (<%= teacher.teaches %>)
|
||||
<% end %>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<% provide(:title, @subject) %>
|
||||
|
||||
|
||||
<% if !@user.anonymous? %>
|
||||
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<p>
|
||||
Thank you for expressing an interest in exploring our retailer partner program! A member of our staff will reach out to you shortly to chat with you and answer any/all questions you may have about our partner program and our technologies.
|
||||
</p>
|
||||
|
||||
<p>Best Regards,<br/>
|
||||
Team JamKazam</p>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<% if !@user.anonymous? %>
|
||||
Hello <%= EmailBatchProgression::VAR_FIRST_NAME %>
|
||||
<% end %>
|
||||
|
||||
Thank you for expressing an interest in exploring our retailer partner program! A member of our staff will reach out to you shortly to chat with you and answer any/all questions you may have about our partner program and our technologies.
|
||||
|
||||
Best Regards,
|
||||
Team JamKazam
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<% provide(:title, @subject) %>
|
||||
|
||||
|
||||
<% if !@user.anonymous? %>
|
||||
<p>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
|
||||
</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
|
||||
built from the ground up for playing music live in sync with high quality audio from different locations over the
|
||||
Internet. Unlike other lesson services, this means we can deliver a massively better online music lesson experience
|
||||
than voice/chat apps like Skype, etc. Our rapidly growing community of <%= APP_CONFIG.musician_count %> musicians will attest to this.
|
||||
</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. Find a Teacher & Book Lessons</b><br/>
|
||||
|
||||
If you haven't done so already, <a href="https://www.jamkazam.com/client#/jamclass/searchOptions" style="color:#fc0">use this link to search our teachers</a>, and click to book a TestDrive with a teacher who looks good for you. When you do this, you'll be given the option to take full 30-minute TestDrive lessons:
|
||||
|
||||
<ul>
|
||||
<li>With 4 different teachers for just $12.50 each</li>
|
||||
<li>With 2 different teachers for just $14.99 each</li>
|
||||
<li>Or with 1 teacher for just $14.99</li>
|
||||
</ul>
|
||||
<p>
|
||||
Pick whichever option you prefer. TestDrive lets you safely and easily try multiple teachers to find the one who is best specifically for you, which is a great way to maximize the benefit from your lessons. And TestDrive lessons are heavily discounted to give you a risk-free way to get started. We'd suggest scheduling your first lesson for about a week in the future to give you plenty of time to get up and running with our free app.
|
||||
</p>
|
||||
</p>
|
||||
|
||||
<p><b style="color: white">2. Set Up Your Gear</b><br/>
|
||||
Please review our <a href="https://jamkazam.desk.com/customer/en/portal/topics/564807-gear-recommendations/articles" style="color:#fc0">help articles on gear recommendations</a>
|
||||
to make sure you have everything you need to get set up properly for best results in your online lessons.
|
||||
If you have everything you need, then you can follow the instructions on our <a href="https://jamkazam.desk.com/customer/en/portal/topics/930331-setting-up-your-gear-to-play-in-online-sessions/articles" style="color:#fc0">setup help articles</a> to download and install our free app and set it up with your audio gear and webcam. Please email us at <a href="mailto:support@jamkaazm.com" style="color:#fc0">support@jamkazam.com</a> or call us at <a href="tel:+18773768742" style="color:#fc0">1-877-376-8742</a> any time so that we can help you with these steps. We are very happy to help, and we also strongly suggest that you let one of our staff get into an online session with you to make sure everything is working properly and to make sure you're comfortable with the app and ready for your first lesson.
|
||||
</p>
|
||||
|
||||
<p><b style="color: white">3. Learn About JamClass Features</b><br/>
|
||||
Please review our <a href="https://jamkazam.desk.com/customer/en/portal/topics/926073-jamclass-online-music-lessons---for-students/articles" style="color:#fc0">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 search for the best teacher for you, how to request/book lessons, how to join your teacher in online lessons, features you can use while in lessons, and much 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>
|
||||
|
||||
<% end %>
|
||||
|
||||
<p>Best Regards,<br/>
|
||||
Team JamKazam</p>
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<% if !@user.anonymous? %>Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --<% end %>
|
||||
|
||||
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 high quality audio from different locations over the
|
||||
Internet. Unlike other lesson services, this means we can deliver a massively better online music lesson experience
|
||||
than voice/chat apps like Skype, etc. Our rapidly growing community of <%= APP_CONFIG.musician_count %> musicians will attest to this.
|
||||
|
||||
|
||||
To get ready to take JamClass lessons online, here are the things you'll want to do:
|
||||
|
||||
1. Find a Teacher & Book Lessons
|
||||
If you already know the teacher from whom you want to learn, then you can simply
|
||||
use this link to search for them (https://www.jamkazam.com/client#/jamclass/searchOptions), and
|
||||
click the Book Lesson button to get started. But if you're like most of us, you don't know. In this case, we strongly
|
||||
advise signing up for our unique TestDrive service.
|
||||
|
||||
TestDrive lets you take 4 full lessons (30 minutes each) from 4 different teachers for just $49.99 to find the best
|
||||
teacher for you. Finding the right teacher is the single most important determinant of success in your lessons. Would
|
||||
you marry the first person you ever dated? No? Same here. Pick 4 teachers who look great, and then see who you click
|
||||
with. It's a phenomenal value, and then you can stick with the best teacher for you.
|
||||
Click this link to sign up now for TestDrive (https://www.jamkazam.com/client#/jamclass/test-drive-selection).
|
||||
Then you can book 4 TestDrive lessons to get rolling.
|
||||
|
||||
2. Set Up Your Gear
|
||||
Use this link to a set of
|
||||
help articles on how to set up your gear (https://jamkazam.desk.com/customer/en/portal/topics/673197-first-time-setup/articles)
|
||||
to be ready to teach online. 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 teachers.
|
||||
|
||||
3. Learn About JamClass Features
|
||||
Use this link to a set of help articles for students on JamClass (https://jamkazam.desk.com/customer/en/portal/topics/926073-jamclass-online-music-lessons---for-students/articles)
|
||||
to familiarize yourself with the most useful features
|
||||
for online lessons. This includes how to search for the best teacher for you, how to request/book lessons, how to join
|
||||
your teacher in online lessons, features you can use while in lessons, and much more. There is very important basic
|
||||
information, plus some really nifty stuff here, so be sure to look through it at least briefly to see how we can
|
||||
turbocharge your online lessons!
|
||||
|
||||
4. Play With Other Musicians Online - It's Free!
|
||||
With JamKazam, you can use the things you're learning in lessons to play with other amateur musicians in online
|
||||
sessions, free! Or just play for fun. Once you've set up your gear for lessons, you can
|
||||
create online music sessions (https://jamkazam.desk.com/customer/en/portal/articles/1599977-creating-a-session)
|
||||
that others can join, or find other musicians' online music sessions (https://jamkazam.desk.com/customer/en/portal/articles/1599978-finding-a-session)
|
||||
and hop into those to play with others. If you
|
||||
want to take advantage of this part of the JamKazam platform, we'd advise that you edit your musician profile (https://www.jamkazam.com/client#/account/profile) to make
|
||||
it easier to connect with other musicians (https://jamkazam.desk.com/customer/en/portal/articles/1707418-connecting-with-other-musicians) in our community to expand your set of musician friends. It's a ton of fun,
|
||||
so give it a try!
|
||||
|
||||
As you work through these things, if you ever get stuck or have questions, please don't hesitate to reach out for
|
||||
help. You can email us any time at support@jamkazam.com. We are happy to
|
||||
help you, and we look forward to helping you
|
||||
learn and grow as a musician, and expand your musical universe!
|
||||
|
||||
Best Regards,
|
||||
Team JamKazam
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<% provide(:title, "You have used #{@student.used_test_drives} of #{@student.total_test_drives} TestDrive lesson credits") %>
|
||||
<% provide(:title, "You have used #{@used_credits} of #{@total_credits} TestDrive lesson credits") %>
|
||||
<% provide(:photo_url, @teacher.resolved_photo_url) %>
|
||||
|
||||
<% content_for :note do %>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
</p>
|
||||
|
||||
<p>We hope you enjoyed your JamClass lesson today with <%= @teacher.name %>. You have
|
||||
used <%= @student.used_test_drives %> TestDrive credits, and you have <%= @student.remaining_test_drives %>
|
||||
used <%= @used_credits %> TestDrive credits, and you have <%= @remaining_credits %>
|
||||
remaining TestDrive lesson(s) available. If you haven’t booked your next TestDrive lesson,
|
||||
<a href="<%= User.search_url %>" style="color:#fc0">click here</a> to search our teachers and get your next
|
||||
lesson lined up today!</p>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
You have used <%= @student.used_test_drives %> of <%= @student.total_test_drives %> TestDrive lesson credits.
|
||||
You have used <%= @used_credits %> of <%= @total_credits %> TestDrive lesson credits.
|
||||
|
||||
<% if @student.has_rated_teacher(@teacher) %>
|
||||
Also, please rate your teacher at <%= @teacher.ratings_url %> now for today’s lesson to help other students in the community find the best instructors.
|
||||
|
|
|
|||
|
|
@ -6,21 +6,62 @@
|
|||
</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 high quality audio from different locations over the
|
||||
Internet. Unlike other lesson services, this means we can deliver a massively better online music lesson experience
|
||||
than voice/chat apps like Skype, etc. Our rapidly growing community of <%= APP_CONFIG.musician_count %> musicians will attest to this.
|
||||
</p>
|
||||
<p>
|
||||
Thank you for expressing an interest in taking private music lessons online using JamKazam! A member of our staff
|
||||
will reach out to you shortly to chat with you and answer any questions you have about how our online music lesson
|
||||
service works, to help you determine if this is a good option for you.
|
||||
</p>
|
||||
<p>
|
||||
If you decide online lessons look good, then we'll help you get your gear set up properly, and we'll even get into a
|
||||
sample online session with you to make sure everything is working properly, and to ensure you are comfortable using
|
||||
our app's features in a lesson.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To get ready to take JamClass lessons online, here are the things you'll want to do:
|
||||
</p>
|
||||
<p>To take online lessons on JamKazam, you'll need the following things at home:</p>
|
||||
|
||||
<p><b style="color: white">1. Find a Teacher & Book Lessons</b><br/>
|
||||
<ul>
|
||||
<li>A Windows or Mac computer, with a built-in or external webcam for video, and a built-in microphone and headphone jack for audio</li>
|
||||
<li>Home internet service</li>
|
||||
</ul>
|
||||
|
||||
If you haven't done so already, <a href="https://www.jamkazam.com/client#/jamclass/searchOptions" style="color:#fc0">use this link to search our teachers</a>, and click to book a TestDrive with a teacher who looks good for you. When you do this, you'll be given the option to take full 30-minute TestDrive lessons:
|
||||
<p>
|
||||
For higher quality audio in online sessions, we recommend (this is an option, not a requirement) a premium audio
|
||||
gear bundle that includes an audio interface (a little box you connect to your computer with a USB cable), a
|
||||
microphone, a mic cable, and a mic stand. We offer this package for just $49.99, which is less than our cost for
|
||||
these products, but it makes a big difference in audio quality, so we think it's worth the upgrade.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you are curious to learn more about how everything works, you can also review our
|
||||
<a href="https://jamkazam.desk.com/customer/en/portal/topics/926073-jamclass-online-music-lessons---for-students/articles" style="color:#fc0">help
|
||||
guide for students</a>. Thanks
|
||||
again for connecting with us, and we look forward to speaking with you soon!
|
||||
</p>
|
||||
|
||||
<% else %>
|
||||
|
||||
|
||||
<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 high quality audio from different locations over the
|
||||
Internet. Unlike other lesson services, this means we can deliver a massively better online music lesson experience
|
||||
than voice/chat apps like Skype, etc. Our rapidly growing community of <%= APP_CONFIG.musician_count %> musicians
|
||||
will attest to this.
|
||||
</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. Find a Teacher & Book Lessons</b><br/>
|
||||
|
||||
If you haven't done so already,
|
||||
<a href="https://www.jamkazam.com/client#/jamclass/searchOptions" style="color:#fc0">use this link to search our
|
||||
teachers</a>, and click to book a TestDrive with a teacher who looks good for you. When you do this, you'll be
|
||||
given the option to take full 30-minute TestDrive lessons:
|
||||
|
||||
<ul>
|
||||
<li>With 4 different teachers for just $12.50 each</li>
|
||||
|
|
@ -28,25 +69,43 @@
|
|||
<li>Or with 1 teacher for just $14.99</li>
|
||||
</ul>
|
||||
<p>
|
||||
Pick whichever option you prefer. TestDrive lets you safely and easily try multiple teachers to find the one who is best specifically for you, which is a great way to maximize the benefit from your lessons. And TestDrive lessons are heavily discounted to give you a risk-free way to get started. We'd suggest scheduling your first lesson for about a week in the future to give you plenty of time to get up and running with our free app.
|
||||
Pick whichever option you prefer. TestDrive lets you safely and easily try multiple teachers to find the one who is
|
||||
best specifically for you, which is a great way to maximize the benefit from your lessons. And TestDrive lessons are
|
||||
heavily discounted to give you a risk-free way to get started. We'd suggest scheduling your first lesson for about a
|
||||
week in the future to give you plenty of time to get up and running with our free app.
|
||||
</p>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
<p><b style="color: white">2. Set Up Your Gear</b><br/>
|
||||
Please review our <a href="https://jamkazam.desk.com/customer/en/portal/topics/564807-gear-recommendations/articles" style="color:#fc0">help articles on gear recommendations</a>
|
||||
to make sure you have everything you need to get set up properly for best results in your online lessons.
|
||||
If you have everything you need, then you can follow the instructions on our <a href="https://jamkazam.desk.com/customer/en/portal/topics/930331-setting-up-your-gear-to-play-in-online-sessions/articles" style="color:#fc0">setup help articles</a> to download and install our free app and set it up with your audio gear and webcam. Please email us at <a href="mailto:support@jamkaazm.com" style="color:#fc0">support@jamkazam.com</a> or call us at <a href="tel:+18773768742" style="color:#fc0">1-877-376-8742</a> any time so that we can help you with these steps. We are very happy to help, and we also strongly suggest that you let one of our staff get into an online session with you to make sure everything is working properly and to make sure you're comfortable with the app and ready for your first lesson.
|
||||
</p>
|
||||
<p><b style="color: white">2. Set Up Your Gear</b><br/>
|
||||
Please review our
|
||||
<a href="https://jamkazam.desk.com/customer/en/portal/topics/564807-gear-recommendations/articles" style="color:#fc0">help
|
||||
articles on gear recommendations</a>
|
||||
to make sure you have everything you need to get set up properly for best results in your online lessons.
|
||||
If you have everything you need, then you can follow the instructions on our
|
||||
<a href="https://jamkazam.desk.com/customer/en/portal/topics/930331-setting-up-your-gear-to-play-in-online-sessions/articles" style="color:#fc0">setup
|
||||
help articles</a> to download and install our free app and set it up with your audio gear and webcam. Please email
|
||||
us at <a href="mailto:support@jamkaazm.com" style="color:#fc0">support@jamkazam.com</a> or call us at
|
||||
<a href="tel:+18773768742" style="color:#fc0">1-877-376-8742</a> any time so that we can help you with these steps.
|
||||
We are very happy to help, and we also strongly suggest that you let one of our staff get into an online session
|
||||
with you to make sure everything is working properly and to make sure you're comfortable with the app and ready for
|
||||
your first lesson.
|
||||
</p>
|
||||
|
||||
<p><b style="color: white">3. Learn About JamClass Features</b><br/>
|
||||
Please review our <a href="https://jamkazam.desk.com/customer/en/portal/topics/926073-jamclass-online-music-lessons---for-students/articles" style="color:#fc0">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 search for the best teacher for you, how to request/book lessons, how to join your teacher in online lessons, features you can use while in lessons, and much more.
|
||||
</p>
|
||||
<p><b style="color: white">3. Learn About JamClass Features</b><br/>
|
||||
Please review our
|
||||
<a href="https://jamkazam.desk.com/customer/en/portal/topics/926073-jamclass-online-music-lessons---for-students/articles" style="color:#fc0">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 search for the best teacher for you, how to request/book lessons, how
|
||||
to join your teacher in online lessons, features you can use while in lessons, and much more.
|
||||
</p>
|
||||
|
||||
<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>
|
||||
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>
|
||||
|
|
@ -6,56 +6,113 @@
|
|||
</p>
|
||||
<% end %>
|
||||
|
||||
<p>
|
||||
Thank you for signing up to teach online music lessons using the JamClass service by JamKazam. JamKazam technology was
|
||||
built from the ground up for playing music live in sync with high quality audio from different locations over the
|
||||
Internet, so you will find it delivers a massively better online music lesson platform than voice/chat apps like
|
||||
Skype, etc.
|
||||
</p>
|
||||
<% if @education %>
|
||||
|
||||
<p>
|
||||
To get ready to teach JamClass students online, here are the things you'll want to do:
|
||||
</p>
|
||||
<p>
|
||||
Thank you for expressing an interest in teaching private music lessons online using JamKazam! A member of our staff
|
||||
will reach out to you shortly to chat with you and answer any questions you have about how our online music lesson
|
||||
service works, to help you determine if this is a good option for you.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you decide teaching online lessons look good, then we'll help you get your gear set up properly, and we'll even
|
||||
get into a sample online session with you to make sure everything is working properly, and to ensure you are
|
||||
comfortable using our app's features in a lesson.
|
||||
</p>
|
||||
|
||||
<p>To take online lessons on JamKazam, you'll need the following things at home:</p>
|
||||
|
||||
<ul>
|
||||
<li>A Windows or Mac computer, with a built-in or external webcam for video, and a built-in microphone and headphone
|
||||
jack for audio
|
||||
</li>
|
||||
<li>Home internet service</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
For higher quality audio in online sessions, we recommend (this is an option, not a requirement) a premium audio
|
||||
gear bundle that includes an audio interface (a little box you connect to your computer with a USB cable), a
|
||||
microphone, a mic cable, and a mic stand. We offer this package for just $49.99, which is less than our cost for
|
||||
these products, but it makes a big difference in audio quality, so we think it's worth the upgrade. Also, if you
|
||||
already own an audio interface, it's highly likely you can use what you already have with our free app.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you are curious to learn more about how everything works, you can also review our <a href="https://jamkazam.desk.com/customer/en/portal/topics/926076-jamclass-online-music-lessons---for-teachers/articles" style="color:#fc0">help guide for teachers</a>. Thanks
|
||||
again for connecting with us, and we look forward to speaking with you soon!
|
||||
</p>
|
||||
|
||||
<% else %>
|
||||
<p>
|
||||
Thank you for signing up to teach online music lessons using the JamClass service by JamKazam. JamKazam technology
|
||||
was
|
||||
built from the ground up for playing music live in sync with high quality audio from different locations over the
|
||||
Internet, so you will find it delivers a massively better online music lesson platform than voice/chat apps like
|
||||
Skype, etc.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To get ready to teach JamClass students online, here are the things you'll want to do:
|
||||
</p>
|
||||
|
||||
|
||||
<p><b style="color: white">1. Set Up Your Teacher Profile</b><br/>
|
||||
As JamKazam brings students into the JamClass marketplace, these students search for teachers. The way they find
|
||||
teachers is by searching on their criteria (e.g. instruments, genres, etc.), and then by browsing through teacher
|
||||
profiles to get a feel for the teachers who match their search criteria. Your teacher profile is critical to being
|
||||
found in searches, and then presenting yourself in more depth to students who are interested in you. So you'll want to
|
||||
take a little time to fill in the information in your teacher profile to present yourself well.
|
||||
<a style="color:#fc0" href="https://jamkazam.desk.com/customer/en/portal/articles/2405835-creating-your-teacher-profile">Click
|
||||
here for
|
||||
instructions on filling out your teacher profile</a>.
|
||||
</p>
|
||||
<p><b style="color: white">1. Set Up Your Teacher Profile</b><br/>
|
||||
As JamKazam brings students into the JamClass marketplace, these students search for teachers. The way they find
|
||||
teachers is by searching on their criteria (e.g. instruments, genres, etc.), and then by browsing through teacher
|
||||
profiles to get a feel for the teachers who match their search criteria. Your teacher profile is critical to being
|
||||
found in searches, and then presenting yourself in more depth to students who are interested in you. So you'll want
|
||||
to
|
||||
take a little time to fill in the information in your teacher profile to present yourself well.
|
||||
<a style="color:#fc0" href="https://jamkazam.desk.com/customer/en/portal/articles/2405835-creating-your-teacher-profile">Click
|
||||
here for
|
||||
instructions on filling out your teacher profile</a>.
|
||||
</p>
|
||||
|
||||
<p><b style="color: white">2. Set Up Your Gear</b><br/>
|
||||
<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,
|
||||
<a style="color:#fc0" href="https://jamkazam.desk.com/customer/en/portal/topics/930331-setting-up-your-gear-to-play-in-online-sessions/articles">use
|
||||
this set of help articles as a good step-by-step guide to set up your gear for use with the
|
||||
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.
|
||||
</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,
|
||||
<a style="color:#fc0" href="https://jamkazam.desk.com/customer/en/portal/topics/930331-setting-up-your-gear-to-play-in-online-sessions/articles">use
|
||||
this set of help articles as a good step-by-step guide to set up your gear for use with the
|
||||
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/>
|
||||
<a style="color:#fc0" href="https://jamkazam.desk.com/customer/en/portal/topics/926076-jamclass-online-music-lessons---for-teachers/articles">Click
|
||||
this link for a set of help articles specifically for teachers</a> to learn how to respond to student lesson
|
||||
requests, how to join your lessons when they are scheduled to begin, how to get paid, and more. You can also
|
||||
<a style="color:#fc0" href="https://jamkazam.desk.com/customer/en/portal/topics/673198-key-features-to-use-in-online-sessions/articles">use
|
||||
this
|
||||
link for a set of help articles that explain how to use the key features available to you in online sessions</a>
|
||||
to
|
||||
effectively teach students.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
As you work through these things, if you ever get stuck or have questions, please don't hesitate to reach out for
|
||||
help. You can email us any time at <a href="mailto:support@jamkazam.com" style="color:#fc0">support@jamkazam.com</a>.
|
||||
We are happy to help you, and we look forward to helping you
|
||||
reach and teach more students!
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<p><b style="color: white">3. Learn About JamClass Features</b><br/>
|
||||
<a style="color:#fc0" href="https://jamkazam.desk.com/customer/en/portal/topics/926076-jamclass-online-music-lessons---for-teachers/articles">Click
|
||||
this link for a set of help articles specifically for teachers</a> to learn how to respond to student lesson
|
||||
requests, how to join your lessons when they are scheduled to begin, how to get paid, and more. You can also
|
||||
<a style="color:#fc0" href="https://jamkazam.desk.com/customer/en/portal/topics/673198-key-features-to-use-in-online-sessions/articles">use
|
||||
this
|
||||
link for a set of help articles that explain how to use the key features available to you in online sessions</a> to
|
||||
effectively teach students.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
As you work through these things, if you ever get stuck or have questions, please don't hesitate to reach out for
|
||||
help. You can email us any time at <a href="mailto:support@jamkazam.com" style="color:#fc0">support@jamkazam.com</a>.
|
||||
We are happy to help you, and we look forward to helping you
|
||||
reach and teach more students!
|
||||
</p>
|
||||
|
||||
<p>Best Regards,<br/>
|
||||
Team JamKazam</p>
|
||||
|
|
@ -3,6 +3,7 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
|
|||
|
||||
belongs_to :partner_user, :class_name => "JamRuby::User", :foreign_key => :partner_user_id, inverse_of: :affiliate_partner
|
||||
has_one :school, class_name: "JamRuby::School"
|
||||
has_one :retailer, class_name: "JamRuby::Retailer"
|
||||
has_many :user_referrals, :class_name => "JamRuby::User", :foreign_key => :affiliate_referral_id
|
||||
belongs_to :affiliate_legalese, :class_name => "JamRuby::AffiliateLegalese", :foreign_key => :legalese_id
|
||||
has_many :sale_line_items, :class_name => 'JamRuby::SaleLineItem', foreign_key: :affiliate_referral_id
|
||||
|
|
@ -94,6 +95,17 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
|
|||
oo.save
|
||||
end
|
||||
|
||||
def self.create_from_retailer(retailer)
|
||||
oo = AffiliatePartner.new
|
||||
oo.partner_name = "Affiliate from Retailer #{retailer.id}"
|
||||
oo.partner_user = retailer.owner
|
||||
oo.entity_type = 'Other'
|
||||
oo.retailer = retailer
|
||||
oo.signed_at = nil
|
||||
oo.save
|
||||
end
|
||||
|
||||
|
||||
def self.coded_id(code=nil)
|
||||
self.where(:partner_code => code).limit(1).pluck(:id).first if code.present?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ module JamRuby
|
|||
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
|
||||
belongs_to :last_mixdown, class_name: 'JamRuby::JamTrackMixdown', foreign_key: 'last_mixdown_id', inverse_of: :jam_track_right
|
||||
belongs_to :last_stem, class_name: 'JamRuby::JamTrackTrack', foreign_key: 'last_stem_id', inverse_of: :jam_track_right
|
||||
belongs_to :posa_card, class_name: 'JamRuby::PosaCard' #unused
|
||||
|
||||
validates :version, presence: true
|
||||
validates :user, presence: true
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ module JamRuby
|
|||
belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_booking, :dependent => :destroy
|
||||
belongs_to :school, class_name: "JamRuby::School"
|
||||
belongs_to :test_drive_package_choice, class_name: "JamRuby::TestDrivePackageChoice"
|
||||
belongs_to :posa_card, class_name: "JamRuby::PosaCard"
|
||||
has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot", :dependent => :destroy
|
||||
has_many :lesson_sessions, class_name: "JamRuby::LessonSession", :dependent => :destroy
|
||||
has_many :lesson_package_purchases, class_name: "JamRuby::LessonPackagePurchase", :dependent => :destroy
|
||||
|
|
@ -79,14 +80,14 @@ module JamRuby
|
|||
after_create :after_create
|
||||
around_save :around_update
|
||||
|
||||
scope :test_drive, -> { where(lesson_type: LESSON_TYPE_TEST_DRIVE) }
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :approved, -> { where(status: STATUS_APPROVED) }
|
||||
scope :requested, -> { where(status: STATUS_REQUESTED) }
|
||||
scope :canceled, -> { where(status: STATUS_CANCELED) }
|
||||
scope :suspended, -> { where(status: STATUS_SUSPENDED) }
|
||||
scope :engaged, -> { where(ENGAGED) }
|
||||
scope :engaged_or_successful, -> { where("(" + ENGAGED + ") OR (lesson_bookings.status = '#{STATUS_COMPLETED}' AND lesson_bookings.success = true)")}
|
||||
scope :test_drive, -> { where(lesson_type: LESSON_TYPE_TEST_DRIVE) }
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :approved, -> { where(status: STATUS_APPROVED) }
|
||||
scope :requested, -> { where(status: STATUS_REQUESTED) }
|
||||
scope :canceled, -> { where(status: STATUS_CANCELED) }
|
||||
scope :suspended, -> { where(status: STATUS_SUSPENDED) }
|
||||
scope :engaged, -> { where(ENGAGED) }
|
||||
scope :engaged_or_successful, -> { where("(" + ENGAGED + ") OR (lesson_bookings.status = '#{STATUS_COMPLETED}' AND lesson_bookings.success = true)") }
|
||||
|
||||
def before_validation
|
||||
if self.booked_price.nil?
|
||||
|
|
@ -95,7 +96,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
def after_create
|
||||
if (card_presumed_ok || school_on_school?) && !sent_notices
|
||||
if (posa_card || card_presumed_ok || !payment_if_school_on_school?) && !sent_notices
|
||||
send_notices
|
||||
end
|
||||
end
|
||||
|
|
@ -128,13 +129,14 @@ module JamRuby
|
|||
else
|
||||
if current_lesson.nil?
|
||||
puts "OHOHOMOOMG #{self.inspect}"
|
||||
raise "no purchase assigned to lesson booking for lesson!"
|
||||
raise "no purchase assigned to lesson booking for lesson!"
|
||||
end
|
||||
|
||||
real_price = self.current_lesson.teacher_distribution.jamkazam_margin
|
||||
end
|
||||
{price: real_price, real_price: real_price, total_price: real_price}
|
||||
end
|
||||
|
||||
# here for shopping_cart
|
||||
def price
|
||||
booked_price
|
||||
|
|
@ -215,13 +217,18 @@ module JamRuby
|
|||
|
||||
def sync_remaining_test_drives
|
||||
if is_test_drive? || is_single_free?
|
||||
if card_presumed_ok && !user_decremented
|
||||
if (posa_card || card_presumed_ok) && !user_decremented
|
||||
self.user_decremented = true
|
||||
self.save(validate: false)
|
||||
if is_single_free?
|
||||
user.remaining_free_lessons = user.remaining_free_lessons - 1
|
||||
elsif is_test_drive?
|
||||
user.remaining_test_drives = user.remaining_test_drives - 1
|
||||
if posa_card
|
||||
user.jamclass_credits = user.jamclass_credits - 1
|
||||
else
|
||||
user.remaining_test_drives = user.remaining_test_drives - 1
|
||||
end
|
||||
|
||||
end
|
||||
user.save(validate: false)
|
||||
end
|
||||
|
|
@ -313,7 +320,7 @@ module JamRuby
|
|||
times << time
|
||||
end
|
||||
end
|
||||
{ times: times, session: sessions.first }
|
||||
{times: times, session: sessions.first}
|
||||
end
|
||||
|
||||
def determine_needed_sessions(sessions)
|
||||
|
|
@ -393,8 +400,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 +527,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?
|
||||
|
|
@ -557,14 +574,21 @@ module JamRuby
|
|||
|
||||
def dayWeekDesc(slot = default_slot)
|
||||
day = case slot.day_of_week
|
||||
when 0 then "Sunday"
|
||||
when 1 then "Monday"
|
||||
when 2 then "Tuesday"
|
||||
when 3 then "Wednesday"
|
||||
when 4 then "Thursday"
|
||||
when 5 then "Friday"
|
||||
when 6 then "Saturday"
|
||||
end
|
||||
when 0 then
|
||||
"Sunday"
|
||||
when 1 then
|
||||
"Monday"
|
||||
when 2 then
|
||||
"Tuesday"
|
||||
when 3 then
|
||||
"Wednesday"
|
||||
when 4 then
|
||||
"Thursday"
|
||||
when 5 then
|
||||
"Friday"
|
||||
when 6 then
|
||||
"Saturday"
|
||||
end
|
||||
|
||||
|
||||
if slot.hour > 11
|
||||
|
|
@ -596,6 +620,7 @@ module JamRuby
|
|||
save
|
||||
self
|
||||
end
|
||||
|
||||
def cancel(canceler, other, message)
|
||||
|
||||
self.canceling = true
|
||||
|
|
@ -613,12 +638,12 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
if approved_before?
|
||||
# just tell both people it's cancelled, to act as confirmation
|
||||
Notification.send_lesson_message('canceled', next_lesson, false)
|
||||
Notification.send_lesson_message('canceled', next_lesson, true)
|
||||
UserMailer.student_lesson_booking_canceled(self, message).deliver_now
|
||||
UserMailer.teacher_lesson_booking_canceled(self, message).deliver_now
|
||||
purpose = "Lesson Canceled"
|
||||
# just tell both people it's cancelled, to act as confirmation
|
||||
Notification.send_lesson_message('canceled', next_lesson, false)
|
||||
Notification.send_lesson_message('canceled', next_lesson, true)
|
||||
UserMailer.student_lesson_booking_canceled(self, message).deliver_now
|
||||
UserMailer.teacher_lesson_booking_canceled(self, message).deliver_now
|
||||
purpose = "Lesson Canceled"
|
||||
else
|
||||
if canceler == student
|
||||
# if it's the first time acceptance student canceling, we call it a 'cancel'
|
||||
|
|
@ -659,10 +684,16 @@ module JamRuby
|
|||
# errors.add(:user, 'has no credit card stored')
|
||||
#end
|
||||
elsif is_test_drive?
|
||||
if !user.has_test_drives? && !user.can_buy_test_drive?
|
||||
errors.add(:user, "have no remaining test drives")
|
||||
elsif teacher.has_booked_test_drive_with_student?(user) && !user.admin
|
||||
errors.add(:user, "have an in-progress or successful TestDrive with this teacher already")
|
||||
if posa_card
|
||||
if !user.has_posa_credits?
|
||||
errors.add(:user, "have no remaining jamclass credits")
|
||||
end
|
||||
else
|
||||
if !user.has_test_drives? && !user.can_buy_test_drive?
|
||||
errors.add(:user, "have no remaining test drives")
|
||||
elsif teacher.has_booked_test_drive_with_student?(user) && !user.admin
|
||||
errors.add(:user, "have an in-progress or successful TestDrive with this teacher already")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -724,6 +755,7 @@ module JamRuby
|
|||
def self.book_packaged_test_drive(user, teacher, description, test_drive_package_choice)
|
||||
book_test_drive(user, teacher, LessonBookingSlot.packaged_slots, description, test_drive_package_choice)
|
||||
end
|
||||
|
||||
def self.book_free(user, teacher, lesson_booking_slots, description)
|
||||
self.book(user, teacher, LessonBooking::LESSON_TYPE_FREE, lesson_booking_slots, false, 30, PAYMENT_STYLE_ELSEWHERE, description)
|
||||
end
|
||||
|
|
@ -752,15 +784,29 @@ module JamRuby
|
|||
lesson_booking.payment_style = payment_style
|
||||
lesson_booking.description = description
|
||||
lesson_booking.status = STATUS_REQUESTED
|
||||
lesson_booking.test_drive_package_choice = test_drive_package_choice
|
||||
if lesson_type == LESSON_TYPE_TEST_DRIVE
|
||||
# if the user has any jamclass credits, then we should get their most recent posa purchase
|
||||
if user.jamclass_credits > 0
|
||||
lesson_booking.posa_card = user.most_recent_posa_purchase.posa_card
|
||||
else
|
||||
# otherwise, it's a normal test drive, and we should honor test_drive_package_choice if specified
|
||||
lesson_booking.test_drive_package_choice = test_drive_package_choice
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if lesson_booking.teacher && lesson_booking.teacher.teacher.school
|
||||
lesson_booking.school = lesson_booking.teacher.teacher.school
|
||||
end
|
||||
|
||||
if user
|
||||
lesson_booking.same_school = !!(lesson_booking.school && user.school && (lesson_booking.school.id == user.school.id))
|
||||
if lesson_booking.same_school
|
||||
lesson_booking.same_school_free = !user.school.education # non-education schools (music schools) are 'free' when school-on-school
|
||||
end
|
||||
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 +825,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).where(posa_card:nil)
|
||||
end
|
||||
|
||||
def self.requested(current_user)
|
||||
|
|
@ -790,6 +836,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 +921,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
|
||||
|
|
@ -908,6 +967,7 @@ module JamRuby
|
|||
def self.not_failed
|
||||
|
||||
end
|
||||
|
||||
def self.engaged_bookings(student, teacher, since_at = nil)
|
||||
bookings = bookings(student, teacher, since_at)
|
||||
bookings.engaged_or_successful
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
@ -13,8 +15,9 @@ module JamRuby
|
|||
belongs_to :teacher, class_name: "JamRuby::User"
|
||||
belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking"
|
||||
belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id
|
||||
belongs_to :posa_card, class_name: "JamRuby::PosaCard", foreign_key: :posa_card_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
|
||||
|
||||
|
|
@ -28,6 +31,10 @@ module JamRuby
|
|||
|
||||
def validate_test_drive
|
||||
if user
|
||||
# if this is a posa card purchase, we won't stop it from getting created
|
||||
if posa_card_id
|
||||
return
|
||||
end
|
||||
if lesson_package_type.is_test_drive? && !user.can_buy_test_drive?
|
||||
errors.add(:user, "can not buy test drive right now because you have already purchased it within the last year")
|
||||
end
|
||||
|
|
@ -35,7 +42,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 +52,27 @@ 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 posa_card_id
|
||||
#user.jamclass_credits incremented in posa_card.rb
|
||||
return
|
||||
end
|
||||
|
||||
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
|
||||
|
|
@ -67,21 +85,28 @@ module JamRuby
|
|||
lesson_payment_charge.amount_in_cents / 100.0
|
||||
end
|
||||
|
||||
def self.create(user, lesson_booking, lesson_package_type, year = nil, month = nil)
|
||||
def self.create(user, lesson_booking, lesson_package_type, year = nil, month = nil, posa_card = nil)
|
||||
purchase = LessonPackagePurchase.new
|
||||
purchase.user = user
|
||||
purchase.lesson_booking = lesson_booking
|
||||
purchase.teacher = lesson_booking.teacher if lesson_booking
|
||||
purchase.posa_card = posa_card
|
||||
|
||||
if year
|
||||
purchase.year = year
|
||||
purchase.month = month
|
||||
purchase.recurring = true
|
||||
|
||||
# this is for monthly
|
||||
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?
|
||||
purchase.teacher_distributions << TeacherDistribution.create_for_lesson_package_purchase(purchase, true)
|
||||
end
|
||||
end
|
||||
else
|
||||
purchase.recurring = false
|
||||
|
|
@ -136,10 +161,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 !payment_if_school_on_school?
|
||||
puts "SCHOOL ON SCHOOL PAYMENT OH NO"
|
||||
raise "school-on-school: should not be here"
|
||||
else
|
||||
lesson_payment_charge.charge(force)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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, :posa_card, 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
|
||||
|
|
@ -575,8 +581,12 @@ module JamRuby
|
|||
lesson_session.slot = booking.default_slot
|
||||
lesson_session.assigned_student = booking.student
|
||||
lesson_session.user = booking.student
|
||||
if booking.is_test_drive? && booking.student.remaining_test_drives > 0
|
||||
lesson_session.lesson_package_purchase = booking.student.most_recent_test_drive_purchase
|
||||
if booking.is_test_drive?
|
||||
if booking.student.jamclass_credits > 0
|
||||
lesson_session.lesson_package_purchase = booking.student.most_recent_posa_purchase
|
||||
elsif booking.student.remaining_test_drives > 0
|
||||
lesson_session.lesson_package_purchase = booking.student.most_recent_test_drive_purchase
|
||||
end
|
||||
end
|
||||
lesson_session.save
|
||||
|
||||
|
|
@ -659,7 +669,8 @@ module JamRuby
|
|||
query = query.where('(lesson_sessions.teacher_id = ? or music_sessions.user_id = ?)', user.id, user.id)
|
||||
end
|
||||
|
||||
query = query.where('lesson_bookings.card_presumed_ok = true OR (music_sessions.user_id = ?) ' + school_extra, user.id)
|
||||
# only show 'fully booked lessons'; not those they can not possibly be paid for
|
||||
query = query.where('lesson_bookings.posa_card_id IS NOT NULL OR lesson_bookings.card_presumed_ok = true OR (music_sessions.user_id = ?) ' + school_extra, user.id)
|
||||
|
||||
current_page = params[:page].nil? ? 1 : params[:page].to_i
|
||||
next_page = current_page + 1
|
||||
|
|
@ -733,7 +744,11 @@ module JamRuby
|
|||
# 1st time this has ever been approved; there are other things we need to do
|
||||
|
||||
if lesson_package_purchase.nil? && lesson_booking.is_test_drive?
|
||||
self.lesson_package_purchase = student.most_recent_test_drive_purchase
|
||||
if student.jamclass_credits > 0
|
||||
self.lesson_package_purchase = student.most_recent_posa_purchase
|
||||
elsif student.remaining_test_drives > 0
|
||||
self.lesson_package_purchase = student.most_recent_test_drive_purchase
|
||||
end
|
||||
end
|
||||
|
||||
if self.save
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
def can_download?(some_user)
|
||||
return false if some_user.nil?
|
||||
claimed_recording = ClaimedRecording.find_by_user_id_and_recording_id(some_user.id, recording.id)
|
||||
|
||||
if claimed_recording
|
||||
|
|
|
|||
|
|
@ -0,0 +1,182 @@
|
|||
# represents the gift card you hold in your hand
|
||||
module JamRuby
|
||||
class PosaCard < ActiveRecord::Base
|
||||
|
||||
@@log = Logging.logger[PosaCard]
|
||||
|
||||
JAM_TRACKS_5 = 'jam_tracks_5'
|
||||
JAM_TRACKS_10 = 'jam_tracks_10'
|
||||
JAM_CLASS_4 = 'jam_class_4'
|
||||
CARD_TYPES =
|
||||
[
|
||||
JAM_TRACKS_5,
|
||||
JAM_TRACKS_10,
|
||||
JAM_CLASS_4
|
||||
]
|
||||
|
||||
|
||||
belongs_to :user, class_name: "JamRuby::User"
|
||||
belongs_to :retailer, class_name: "JamRuby::Retailer"
|
||||
has_many :posa_card_purchases, class_name: 'JamRuby::PosaCardPurchase'
|
||||
has_one :lesson_package_purchase, class_name: 'JamRuby::LessonPackagePurchase'
|
||||
has_one :jam_track_right, class_name: "JamRuby::JamTrackRight"
|
||||
|
||||
validates :card_type, presence: true, inclusion: {in: CARD_TYPES}
|
||||
validates :code, presence: true, uniqueness: true
|
||||
|
||||
after_save :check_attributed
|
||||
|
||||
validate :already_activated
|
||||
validate :retailer_set
|
||||
validate :already_claimed
|
||||
validate :user_set
|
||||
validate :must_be_activated
|
||||
validate :within_one_year
|
||||
|
||||
def is_lesson_posa_card?
|
||||
card_type == JAM_CLASS_4
|
||||
end
|
||||
|
||||
def credits
|
||||
if card_type == JAM_TRACKS_5
|
||||
5
|
||||
elsif card_type == JAM_TRACKS_10
|
||||
10
|
||||
elsif card_type == JAM_CLASS_4
|
||||
4
|
||||
else
|
||||
raise "unknown card type #{card_type}"
|
||||
end
|
||||
end
|
||||
|
||||
def already_activated
|
||||
if activated_at && activated_at_was && activated_at_changed?
|
||||
if retailer && retailer_id == retailer_id_was
|
||||
self.errors.add(:activated_at, 'already activated. Please give card to customer. Thank you!')
|
||||
else
|
||||
self.errors.add(:activated_at, 'already activated by someone else. Please contact support@jamkaazm.com')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def within_one_year
|
||||
if user && claimed_at && claimed_at_was && claimed_at_changed?
|
||||
if !user.can_claim_posa_card
|
||||
self.errors.add(:claimed_at, 'was within 1 year')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def already_claimed
|
||||
if claimed_at && claimed_at_was && claimed_at_changed?
|
||||
self.errors.add(:claimed_at, 'already claimed')
|
||||
end
|
||||
end
|
||||
|
||||
def retailer_set
|
||||
if activated_at && !retailer
|
||||
self.errors.add(:retailer, 'must be specified')
|
||||
end
|
||||
end
|
||||
|
||||
def user_set
|
||||
if claimed_at && !user
|
||||
self.errors.add(:user, 'must be specified')
|
||||
end
|
||||
end
|
||||
|
||||
def must_be_activated
|
||||
if claimed_at && !activated_at
|
||||
self.errors.add(:activated_at, 'must already be set')
|
||||
end
|
||||
end
|
||||
|
||||
def check_attributed
|
||||
if user && user_id_changed?
|
||||
if card_type == JAM_TRACKS_5
|
||||
user.gifted_jamtracks += 5
|
||||
elsif card_type == JAM_TRACKS_10
|
||||
user.gifted_jamtracks += 10
|
||||
elsif card_type == JAM_CLASS_4
|
||||
user.jamclass_credits += 4
|
||||
else
|
||||
raise "unknown card type #{card_type}"
|
||||
end
|
||||
user.save!
|
||||
end
|
||||
end
|
||||
|
||||
def lesson_package_type
|
||||
if card_type == JAM_TRACKS_5
|
||||
raise 'not a lesson package: ' + card_type
|
||||
elsif card_type == JAM_TRACKS_10
|
||||
raise 'not a lesson package: ' + card_type
|
||||
elsif card_type == JAM_CLASS_4
|
||||
LessonPackageType.test_drive_4
|
||||
else
|
||||
raise "unknown card type #{card_type}"
|
||||
end
|
||||
end
|
||||
|
||||
def product_info
|
||||
price = nil
|
||||
plan_code = nil
|
||||
|
||||
if card_type == JAM_TRACKS_5
|
||||
price = 9.99
|
||||
plan_code = 'posa-jamtracks-5'
|
||||
elsif card_type == JAM_TRACKS_10
|
||||
price = 19.99
|
||||
plan_code = 'posa-jatracks-10'
|
||||
elsif card_type == JAM_CLASS_4
|
||||
price = 49.99
|
||||
plan_code = 'posa-jamclass-4'
|
||||
else
|
||||
raise "unknown card type #{card_type}"
|
||||
end
|
||||
{price: price, quantity: 1, marked_for_redeem: false, plan_code: plan_code}
|
||||
end
|
||||
|
||||
def self.activate(posa_card, retailer)
|
||||
Sale.posa_activate(posa_card, retailer)
|
||||
end
|
||||
|
||||
def activate(retailer)
|
||||
self.activated_at = Time.now
|
||||
self.retailer = retailer
|
||||
|
||||
self.save
|
||||
end
|
||||
|
||||
def claim(user)
|
||||
self.user = user
|
||||
self.claimed_at = Time.now
|
||||
|
||||
|
||||
if self.save
|
||||
UserWhitelist.card_create(user, 'posa')
|
||||
SaleLineItem.associate_user_for_posa(self, user)
|
||||
|
||||
# when you claim a POSA card, you are also making a LessonPackagePurchase
|
||||
if is_lesson_posa_card?
|
||||
purchase = LessonPackagePurchase.create(user, nil, lesson_package_type, nil, nil, self) if purchase.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def short_display
|
||||
if card_type == JAM_TRACKS_5
|
||||
'JT-5'
|
||||
elsif card_type == JAM_TRACKS_10
|
||||
'JT-10'
|
||||
elsif card_type == JAM_CLASS_4
|
||||
'JC-4'
|
||||
else
|
||||
raise "unknown card type #{card_type}"
|
||||
end
|
||||
end
|
||||
def to_s
|
||||
"POSA #{short_display} #{code}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# reperesents the gift card you buy from the site (but physical gift card is modeled by GiftCard)
|
||||
module JamRuby
|
||||
class PosaCardPurchase < ActiveRecord::Base
|
||||
|
||||
@@log = Logging.logger[PosaCardPurchase]
|
||||
|
||||
attr_accessible :user, :posa_card_type
|
||||
|
||||
def name
|
||||
posa_card_type.sale_display
|
||||
end
|
||||
|
||||
# who purchased the card?
|
||||
belongs_to :user, class_name: "JamRuby::User"
|
||||
belongs_to :posa_card_type, class_name: "JamRuby::PosaCardType"
|
||||
belongs_to :posa_card, class_name: 'JamRuby::PosaCard'
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# reperesents the posa card you buy from the site
|
||||
module JamRuby
|
||||
class PosaCardType < ActiveRecord::Base
|
||||
|
||||
@@log = Logging.logger[PosaCardType]
|
||||
|
||||
PRODUCT_TYPE = 'PosaCardType'
|
||||
|
||||
|
||||
JAM_TRACKS_5 = 'jam_tracks_5'
|
||||
JAM_TRACKS_10 = 'jam_tracks_10'
|
||||
JAM_CLASS_4 = 'jam_class_4'
|
||||
CARD_TYPES =
|
||||
[
|
||||
JAM_TRACKS_5,
|
||||
JAM_TRACKS_10,
|
||||
JAM_CLASS_4
|
||||
]
|
||||
|
||||
validates :card_type, presence: true, inclusion: {in: CARD_TYPES}
|
||||
|
||||
def self.jam_track_5
|
||||
PosaCardType.find(JAM_TRACKS_5)
|
||||
end
|
||||
|
||||
def self.jam_track_10
|
||||
PosaCardType.find(JAM_TRACKS_10)
|
||||
end
|
||||
|
||||
def self.jam_class_4
|
||||
PosaCardType.find(JAM_CLASS_4)
|
||||
end
|
||||
|
||||
|
||||
def name
|
||||
sale_display
|
||||
end
|
||||
|
||||
def price
|
||||
if card_type == JAM_TRACKS_5
|
||||
10.00
|
||||
elsif card_type == JAM_TRACKS_10
|
||||
20.00
|
||||
elsif card_type == JAM_CLASS_4
|
||||
49.99
|
||||
else
|
||||
raise "unknown card type #{card_type}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def sale_display
|
||||
if card_type == JAM_TRACKS_5
|
||||
'JamTracks Card (5)'
|
||||
elsif card_type == JAM_TRACKS_10
|
||||
'JamTracks Card (10)'
|
||||
elsif card_type == JAM_TRACKS_10
|
||||
'JamClass Card (4)'
|
||||
else
|
||||
raise "unknown card type #{card_type}"
|
||||
end
|
||||
end
|
||||
|
||||
def plan_code
|
||||
if card_type == JAM_TRACKS_5
|
||||
"jamtrack-posacard-5"
|
||||
elsif card_type == JAM_TRACKS_10
|
||||
"jamtrack-posacard-10"
|
||||
elsif card_type == JAM_CLASS_4
|
||||
"jamclass-posacard-4"
|
||||
else
|
||||
raise "unknown card type #{card_type}"
|
||||
end
|
||||
end
|
||||
|
||||
def sales_region
|
||||
'Worldwide'
|
||||
end
|
||||
|
||||
def to_s
|
||||
sale_display
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
module JamRuby
|
||||
class Retailer < ActiveRecord::Base
|
||||
|
||||
include HtmlSanitize
|
||||
html_sanitize strict: [:name]
|
||||
|
||||
attr_accessor :updating_avatar, :password, :should_validate_password
|
||||
attr_accessible :original_fpfile, :cropped_fpfile, :cropped_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection
|
||||
|
||||
belongs_to :posa_cards, class_name: 'JamRuby::PosaCard'
|
||||
belongs_to :user, class_name: ::JamRuby::User, inverse_of: :owned_retailer
|
||||
belongs_to :affiliate_partner, class_name: "JamRuby::AffiliatePartner"
|
||||
has_many :teachers, class_name: "JamRuby::Teacher"
|
||||
has_many :retailer_invitations, class_name: 'JamRuby::RetailerInvitation'
|
||||
has_many :teacher_payments, class_name: 'JamRuby::TeacherPayment'
|
||||
has_many :teacher_distributions, class_name: 'JamRuby::TeacherDistribution'
|
||||
has_many :sales, class_name: 'JamRuby::Sale'
|
||||
has_many :sale_line_items, class_name: 'JamRuby::SaleLineItem'
|
||||
|
||||
validates :user, presence: true
|
||||
#validates :slug, presence: true
|
||||
validates :enabled, inclusion: {in: [true, false]}
|
||||
validates_length_of :password, minimum: 6, maximum: 100, :if => :should_validate_password
|
||||
|
||||
after_create :create_affiliate
|
||||
after_create :create_slug
|
||||
# before_save :stringify_avatar_info, :if => :updating_avatar
|
||||
|
||||
def create_slug
|
||||
if self.slug.blank?
|
||||
puts "SELF ID #{self.id}"
|
||||
self.slug = self.id.to_s
|
||||
end
|
||||
|
||||
self.save!
|
||||
end
|
||||
|
||||
def create_affiliate
|
||||
AffiliatePartner.create_from_retailer(self)
|
||||
end
|
||||
|
||||
def encrypt(password)
|
||||
BCrypt::Password.create(password, cost: 12).to_s
|
||||
end
|
||||
|
||||
def matches_password(password)
|
||||
|
||||
if password.blank?
|
||||
return false
|
||||
end
|
||||
|
||||
puts "self.encrypted_password #{self.encrypted_password}"
|
||||
begin
|
||||
# we init passwordfield as a UUID, which is a bogus hash; so if we see UUId, we know retailer has no password yet
|
||||
UUIDTools::UUID.parse(self.encrypted_password)
|
||||
return false
|
||||
rescue ArgumentError
|
||||
end
|
||||
|
||||
BCrypt::Password.new(self.encrypted_password) == password
|
||||
end
|
||||
|
||||
def update_from_params(params)
|
||||
self.name = params[:name] if params[:name].present?
|
||||
self.city = params[:city]
|
||||
self.state = params[:state]
|
||||
self.slug = params[:slug] if params[:slug].present?
|
||||
|
||||
if params[:password].present?
|
||||
self.should_validate_password = true
|
||||
self.password = params[:password]
|
||||
self.encrypted_password = encrypt(params[:password])
|
||||
end
|
||||
self.save
|
||||
end
|
||||
|
||||
def owner
|
||||
user
|
||||
end
|
||||
|
||||
def validate_avatar_info
|
||||
if updating_avatar
|
||||
# we want to mak sure that original_fpfile and cropped_fpfile seems like real fpfile info objects (i.e, json objects from filepicker.io)
|
||||
errors.add(:original_fpfile, ValidationMessages::INVALID_FPFILE) if self.original_fpfile.nil? || self.original_fpfile["key"].nil? || self.original_fpfile["url"].nil?
|
||||
errors.add(:cropped_fpfile, ValidationMessages::INVALID_FPFILE) if self.cropped_fpfile.nil? || self.cropped_fpfile["key"].nil? || self.cropped_fpfile["url"].nil?
|
||||
errors.add(:cropped_large_fpfile, ValidationMessages::INVALID_FPFILE) if self.cropped_large_fpfile.nil? || self.cropped_large_fpfile["key"].nil? || self.cropped_large_fpfile["url"].nil?
|
||||
end
|
||||
end
|
||||
|
||||
def escape_filename(path)
|
||||
dir = File.dirname(path)
|
||||
file = File.basename(path)
|
||||
"#{dir}/#{ERB::Util.url_encode(file)}"
|
||||
end
|
||||
|
||||
def update_avatar(original_fpfile, cropped_fpfile, cropped_large_fpfile, crop_selection, aws_bucket)
|
||||
self.updating_avatar = true
|
||||
|
||||
cropped_s3_path = cropped_fpfile["key"]
|
||||
cropped_large_s3_path = cropped_large_fpfile["key"]
|
||||
|
||||
self.update_attributes(
|
||||
:original_fpfile => original_fpfile.to_json,
|
||||
:cropped_fpfile => cropped_fpfile.to_json,
|
||||
:cropped_large_fpfile => cropped_large_fpfile.to_json,
|
||||
:cropped_s3_path => cropped_s3_path,
|
||||
:cropped_large_s3_path => cropped_large_s3_path,
|
||||
:crop_selection => crop_selection.to_json,
|
||||
:photo_url => S3Util.url(aws_bucket, escape_filename(cropped_s3_path), :secure => true),
|
||||
:large_photo_url => S3Util.url(aws_bucket, escape_filename(cropped_large_s3_path), :secure => true)
|
||||
)
|
||||
end
|
||||
|
||||
def delete_avatar(aws_bucket)
|
||||
|
||||
User.transaction do
|
||||
|
||||
unless self.cropped_s3_path.nil?
|
||||
S3Util.delete(aws_bucket, File.dirname(self.cropped_s3_path) + '/cropped.jpg')
|
||||
S3Util.delete(aws_bucket, self.cropped_s3_path)
|
||||
S3Util.delete(aws_bucket, self.cropped_large_s3_path)
|
||||
end
|
||||
|
||||
return self.update_attributes(
|
||||
:original_fpfile => nil,
|
||||
:cropped_fpfile => nil,
|
||||
:cropped_large_fpfile => nil,
|
||||
:cropped_s3_path => nil,
|
||||
:cropped_large_s3_path => nil,
|
||||
:photo_url => nil,
|
||||
:crop_selection => nil,
|
||||
:large_photo_url => nil
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def stringify_avatar_info
|
||||
# fpfile comes in as a hash, which is a easy-to-use and validate form. However, we store it as a VARCHAR,
|
||||
# so we need t oconvert it to JSON before storing it (otherwise it gets serialized as a ruby object)
|
||||
# later, when serving this data out to the REST API, we currently just leave it as a string and make a JSON capable
|
||||
# client parse it, because it's very rare when it's needed at all
|
||||
self.original_fpfile = original_fpfile.to_json if !original_fpfile.nil? && original_fpfile.class != String
|
||||
self.cropped_fpfile = cropped_fpfile.to_json if !cropped_fpfile.nil? && cropped_fpfile.class != String
|
||||
self.crop_selection = crop_selection.to_json if !crop_selection.nil? && crop_selection.class != String
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
module JamRuby
|
||||
class RetailerInvitation < ActiveRecord::Base
|
||||
|
||||
include HtmlSanitize
|
||||
html_sanitize strict: [:note]
|
||||
|
||||
|
||||
belongs_to :user, class_name: ::JamRuby::User
|
||||
belongs_to :retailer, class_name: ::JamRuby::Retailer
|
||||
|
||||
validates :retailer, presence: true
|
||||
validates :email, email: true
|
||||
validates :invitation_code, presence: true
|
||||
validates :accepted, inclusion: {in: [true, false]}
|
||||
validates :first_name, presence: true
|
||||
validates :last_name, presence: true
|
||||
validate :retailer_has_name, on: :create
|
||||
|
||||
before_validation(on: :create) do
|
||||
self.invitation_code = SecureRandom.urlsafe_base64 if self.invitation_code.nil?
|
||||
end
|
||||
|
||||
def retailer_has_name
|
||||
if retailer && retailer.name.blank?
|
||||
errors.add(:retailer, "must have name")
|
||||
end
|
||||
end
|
||||
|
||||
def self.index(retailer, params)
|
||||
limit = params[:per_page]
|
||||
limit ||= 100
|
||||
limit = limit.to_i
|
||||
|
||||
query = RetailerInvitation.where(retailer_id: retailer.id)
|
||||
query = query.includes([:user, :retailer])
|
||||
query = query.order('created_at')
|
||||
query = query.where(accepted:false)
|
||||
|
||||
|
||||
current_page = params[:page].nil? ? 1 : params[:page].to_i
|
||||
next_page = current_page + 1
|
||||
|
||||
# will_paginate gem
|
||||
query = query.paginate(:page => current_page, :per_page => limit)
|
||||
|
||||
if query.length == 0 # no more results
|
||||
{query: query, next_page: nil}
|
||||
elsif query.length < limit # no more results
|
||||
{query: query, next_page: nil}
|
||||
else
|
||||
{query: query, next_page: next_page}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def self.create(current_user, specified_retailer, params)
|
||||
|
||||
invitation = RetailerInvitation.new
|
||||
invitation.retailer = specified_retailer
|
||||
invitation.email = params[:email]
|
||||
invitation.first_name = params[:first_name]
|
||||
invitation.last_name = params[:last_name]
|
||||
|
||||
if invitation.save
|
||||
invitation.send_invitation
|
||||
end
|
||||
invitation
|
||||
end
|
||||
|
||||
|
||||
def send_invitation
|
||||
UserMailer.invite_retailer_teacher(self).deliver_now
|
||||
end
|
||||
def generate_signup_url
|
||||
"#{APP_CONFIG.external_root_url}/retailer/#{retailer.id}/teacher?invitation_code=#{self.invitation_code}"
|
||||
end
|
||||
|
||||
def delete
|
||||
self.destroy
|
||||
end
|
||||
|
||||
def resend
|
||||
send_invitation
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -5,17 +5,20 @@ module JamRuby
|
|||
|
||||
JAMTRACK_SALE = 'jamtrack'
|
||||
LESSON_SALE = 'lesson'
|
||||
POSA_SALE = 'posacard'
|
||||
|
||||
SOURCE_RECURLY = 'recurly'
|
||||
SOURCE_IOS = 'ios'
|
||||
|
||||
belongs_to :retailer, class_name: 'JamRuby::Retailer'
|
||||
belongs_to :user, class_name: 'JamRuby::User'
|
||||
has_many :sale_line_items, class_name: 'JamRuby::SaleLineItem'
|
||||
|
||||
has_many :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook', inverse_of: :sale, foreign_key: 'invoice_id', primary_key: 'recurly_invoice_id'
|
||||
|
||||
validates :order_total, numericality: {only_integer: false}
|
||||
validates :user, presence: true
|
||||
#validates :user
|
||||
#validates :retailer
|
||||
|
||||
@@log = Logging.logger[Sale]
|
||||
|
||||
|
|
@ -215,6 +218,26 @@ module JamRuby
|
|||
def self.post_sale_test_failure
|
||||
return true
|
||||
end
|
||||
|
||||
def self.posa_activate(posa_card, retailer)
|
||||
sale = nil
|
||||
Sale.transaction(:requires_new => true) do
|
||||
|
||||
posa_card.activate(retailer)
|
||||
|
||||
if !posa_card.errors.any?
|
||||
|
||||
sale = create_posa_sale(retailer, posa_card)
|
||||
|
||||
SaleLineItem.create_from_posa_card(sale, retailer, posa_card)
|
||||
|
||||
sale.save
|
||||
end
|
||||
|
||||
end
|
||||
{sale: sale}
|
||||
end
|
||||
|
||||
# this is easy to make generic, but right now, it just purchases lessons
|
||||
def self.purchase_lesson(charge, current_user, lesson_booking, lesson_package_type, lesson_session = nil, lesson_package_purchase = nil, force = false)
|
||||
stripe_charge = nil
|
||||
|
|
@ -223,43 +246,43 @@ module JamRuby
|
|||
# everything needs to go into a transaction! If anything goes wrong, we need to raise an exception to break it
|
||||
Sale.transaction(:requires_new => true) do
|
||||
|
||||
sale = create_lesson_sale(current_user)
|
||||
sale = create_lesson_sale(current_user)
|
||||
|
||||
if sale.valid?
|
||||
if sale.valid?
|
||||
|
||||
if lesson_booking
|
||||
lesson_booking.current_lesson = lesson_session
|
||||
lesson_booking.current_purchase = lesson_package_purchase
|
||||
end
|
||||
|
||||
sale_line_item = SaleLineItem.create_from_lesson_package(current_user, sale, lesson_package_type, lesson_booking)
|
||||
|
||||
price_info = charge_stripe_for_lesson(charge, current_user, lesson_booking, lesson_package_type, sale_line_item, lesson_session, lesson_package_purchase, force)
|
||||
|
||||
post_sale_test_failure
|
||||
|
||||
if price_info[:purchase] && price_info[:purchase].errors.any?
|
||||
purchase = price_info[:purchase]
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
if !sale_line_item.valid?
|
||||
raise "invalid sale_line_item object for user #{current_user.email} and lesson_booking #{lesson_booking.id}"
|
||||
end
|
||||
# sale.source = 'stripe'
|
||||
sale.recurly_subtotal_in_cents = price_info[:subtotal_in_cents]
|
||||
sale.recurly_tax_in_cents = price_info[:tax_in_cents]
|
||||
sale.recurly_total_in_cents = price_info[:total_in_cents]
|
||||
sale.recurly_currency = price_info[:currency]
|
||||
sale.stripe_charge_id = price_info[:charge_id]
|
||||
sale.save
|
||||
stripe_charge = price_info[:charge]
|
||||
purchase = price_info[:purchase]
|
||||
else
|
||||
# should not get out of testing. This would be very rare (i.e., from a big regression). Sale is always valid at this point.
|
||||
puts "invalid sale object"
|
||||
raise "invalid sale object"
|
||||
if lesson_booking
|
||||
lesson_booking.current_lesson = lesson_session
|
||||
lesson_booking.current_purchase = lesson_package_purchase
|
||||
end
|
||||
|
||||
sale_line_item = SaleLineItem.create_from_lesson_package(current_user, sale, lesson_package_type, lesson_booking)
|
||||
|
||||
price_info = charge_stripe_for_lesson(charge, current_user, lesson_booking, lesson_package_type, sale_line_item, lesson_session, lesson_package_purchase, force)
|
||||
|
||||
post_sale_test_failure
|
||||
|
||||
if price_info[:purchase] && price_info[:purchase].errors.any?
|
||||
purchase = price_info[:purchase]
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
if !sale_line_item.valid?
|
||||
raise "invalid sale_line_item object for user #{current_user.email} and lesson_booking #{lesson_booking.id}"
|
||||
end
|
||||
# sale.source = 'stripe'
|
||||
sale.recurly_subtotal_in_cents = price_info[:subtotal_in_cents]
|
||||
sale.recurly_tax_in_cents = price_info[:tax_in_cents]
|
||||
sale.recurly_total_in_cents = price_info[:total_in_cents]
|
||||
sale.recurly_currency = price_info[:currency]
|
||||
sale.stripe_charge_id = price_info[:charge_id]
|
||||
sale.save
|
||||
stripe_charge = price_info[:charge]
|
||||
purchase = price_info[:purchase]
|
||||
else
|
||||
# should not get out of testing. This would be very rare (i.e., from a big regression). Sale is always valid at this point.
|
||||
puts "invalid sale object"
|
||||
raise "invalid sale object"
|
||||
end
|
||||
end
|
||||
|
||||
{sale: sale, stripe_charge: stripe_charge, purchase: purchase}
|
||||
|
|
@ -304,20 +327,34 @@ module JamRuby
|
|||
tax_in_cents = (subtotal_in_cents * tax_percent).round
|
||||
total_in_cents = subtotal_in_cents + tax_in_cents
|
||||
|
||||
lesson_id = lesson_session.id if lesson_session # not set if test drive
|
||||
if lesson_session # not set if test drive
|
||||
lesson_id = lesson_session.id
|
||||
teacher_id = lesson_session.teacher.id
|
||||
teacher_name = lesson_session.teacher.name
|
||||
end
|
||||
|
||||
charge_id = charge.id if charge # not set if test drive
|
||||
|
||||
begin
|
||||
metadata = {
|
||||
lesson_package: purchase.id,
|
||||
lesson_session: lesson_id,
|
||||
teacher_id: teacher_id,
|
||||
teacher_name: teacher_name,
|
||||
charge: charge_id,
|
||||
user: current_user.id,
|
||||
tax: tax_in_cents
|
||||
}
|
||||
rescue Exception => e
|
||||
metadata = {metaerror: true}
|
||||
end
|
||||
|
||||
stripe_charge = Stripe::Charge.create(
|
||||
:amount => total_in_cents,
|
||||
:currency => "usd",
|
||||
:customer => current_user.stripe_customer_id,
|
||||
:description => target.stripe_description(lesson_booking),
|
||||
:metadata => {
|
||||
lesson_package: purchase.id,
|
||||
lesson_session: lesson_id,
|
||||
charge: charge_id,
|
||||
user: current_user.id
|
||||
}
|
||||
:metadata => metadata
|
||||
)
|
||||
if charge
|
||||
charge.stripe_charge = stripe_charge
|
||||
|
|
@ -434,6 +471,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
unless sale.save
|
||||
puts "WTF"
|
||||
raise RecurlyClientError, "Invalid sale (at end)."
|
||||
end
|
||||
rescue Recurly::Resource::Invalid => e
|
||||
|
|
@ -441,6 +479,8 @@ module JamRuby
|
|||
sale.rollback_adjustments(current_user, created_adjustments)
|
||||
sale = nil
|
||||
raise ActiveRecord::Rollback # kill all db activity, but don't break outside logic
|
||||
rescue => e
|
||||
puts "UNKNOWN E #{e}"
|
||||
end
|
||||
else
|
||||
raise RecurlyClientError, "Could not find account to place order."
|
||||
|
|
@ -634,6 +674,15 @@ module JamRuby
|
|||
sale
|
||||
end
|
||||
|
||||
def self.create_posa_sale(retailer, posa_card)
|
||||
sale = Sale.new
|
||||
sale.retailer = retailer
|
||||
sale.sale_type = POSA_SALE # gift cards and jam tracks are sold with this type of sale
|
||||
sale.order_total = posa_card.product_info[:price]
|
||||
sale.save
|
||||
sale
|
||||
end
|
||||
|
||||
# this checks just jamtrack sales appropriately
|
||||
def self.check_integrity_of_jam_track_sales
|
||||
Sale.select([:total, :voided]).find_by_sql(
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ module JamRuby
|
|||
JAMTRACK = 'JamTrack'
|
||||
GIFTCARD = 'GiftCardType'
|
||||
LESSON = 'LessonPackageType'
|
||||
POSACARD = 'PosaCard'
|
||||
|
||||
belongs_to :sale, class_name: 'JamRuby::Sale'
|
||||
belongs_to :jam_track, class_name: 'JamRuby::JamTrack'
|
||||
belongs_to :jam_track_right, class_name: 'JamRuby::JamTrackRight'
|
||||
belongs_to :gift_card, class_name: 'JamRuby::GiftCard'
|
||||
belongs_to :lesson_package_purchase, class_name: 'JamRuby::LessonPackagePurchase'
|
||||
belongs_to :retailer, class_name: 'JamRuby::Retailer'
|
||||
|
||||
# deprecated; use affiliate_distribution !!
|
||||
belongs_to :affiliate_referral, class_name: 'JamRuby::AffiliatePartner', foreign_key: :affiliate_referral_id
|
||||
|
|
@ -20,7 +22,7 @@ module JamRuby
|
|||
|
||||
has_many :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook', inverse_of: :sale_line_item, foreign_key: 'subscription_id', primary_key: 'recurly_subscription_uuid'
|
||||
|
||||
validates :product_type, inclusion: {in: [JAMBLASTER, JAMCLOUD, JAMTRACK, GIFTCARD, LESSON]}
|
||||
validates :product_type, inclusion: {in: [JAMBLASTER, JAMCLOUD, JAMTRACK, GIFTCARD, LESSON, POSACARD]}
|
||||
validates :unit_price, numericality: {only_integer: false}
|
||||
validates :quantity, numericality: {only_integer: true}
|
||||
validates :free, numericality: {only_integer: true}
|
||||
|
|
@ -45,8 +47,11 @@ module JamRuby
|
|||
GiftCardType.find_by_id(product_id)
|
||||
elsif product_type == LESSON
|
||||
lesson_package_purchase
|
||||
elsif product_type == POSACARD
|
||||
PosaCard.find(product_id)
|
||||
else
|
||||
|
||||
|
||||
raise 'unsupported product type'
|
||||
end
|
||||
end
|
||||
|
|
@ -128,6 +133,41 @@ module JamRuby
|
|||
line_item
|
||||
end
|
||||
|
||||
def self.associate_user_for_posa(posa_card, user)
|
||||
sale_line_item = SaleLineItem.where(product_type: POSACARD).where(product_id: posa_card.id).first
|
||||
if sale_line_item
|
||||
sale_line_item.sale.user = user
|
||||
sale_line_item.sale.save!
|
||||
end
|
||||
end
|
||||
|
||||
def self.create_from_posa_card(sale, retailer, posa_card)
|
||||
product_info = posa_card.product_info
|
||||
sale_line_item = SaleLineItem.new
|
||||
sale_line_item.retailer = retailer
|
||||
sale_line_item.product_type = POSACARD
|
||||
sale_line_item.product_id = posa_card.id
|
||||
sale_line_item.unit_price = product_info[:price]
|
||||
sale_line_item.quantity = product_info[:quantity]
|
||||
sale_line_item.free = product_info[:marked_for_redeem]
|
||||
sale_line_item.sales_tax = nil
|
||||
sale_line_item.shipping_handling = 0
|
||||
sale_line_item.recurly_plan_code = product_info[:plan_code]
|
||||
|
||||
|
||||
#referral_info = retailer.referral_info
|
||||
|
||||
#if referral_info
|
||||
# sale_line_item.affiliate_distributions << AffiliateDistribution.create(retailer.affiliate_partner, referral_info[:fee_in_cents], sale_line_item)
|
||||
# sale_line_item.affiliate_referral = retailer.affiliate_partner
|
||||
# sale_line_item.affiliate_referral_fee_in_cents = referral_info[:fee_in_cents]
|
||||
#end
|
||||
|
||||
sale.sale_line_items << sale_line_item
|
||||
sale_line_item.save
|
||||
sale_line_item
|
||||
end
|
||||
|
||||
def self.create_from_shopping_cart(sale, shopping_cart, recurly_subscription_uuid, recurly_adjustment_uuid, recurly_adjustment_credit_uuid, instance = nil)
|
||||
product_info = shopping_cart.product_info(instance)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,12 +24,17 @@ 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
|
||||
|
||||
after_create :create_affiliate
|
||||
before_save :stringify_avatar_info, :if => :updating_avatar
|
||||
#before_save :stringify_avatar_info, :if => :updating_avatar
|
||||
|
||||
def is_education?
|
||||
education
|
||||
end
|
||||
|
||||
def scheduling_comm?
|
||||
scheduling_communication == SCHEDULING_COMM_SCHOOL
|
||||
|
|
@ -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
|
||||
|
|
@ -75,12 +84,12 @@ module JamRuby
|
|||
cropped_large_s3_path = cropped_large_fpfile["key"]
|
||||
|
||||
self.update_attributes(
|
||||
:original_fpfile => original_fpfile,
|
||||
:cropped_fpfile => cropped_fpfile,
|
||||
:cropped_large_fpfile => cropped_large_fpfile,
|
||||
:original_fpfile => original_fpfile.to_json,
|
||||
:cropped_fpfile => cropped_fpfile.to_json,
|
||||
:cropped_large_fpfile => cropped_large_fpfile.to_json,
|
||||
:cropped_s3_path => cropped_s3_path,
|
||||
:cropped_large_s3_path => cropped_large_s3_path,
|
||||
:crop_selection => crop_selection,
|
||||
:crop_selection => crop_selection.to_json,
|
||||
:photo_url => S3Util.url(aws_bucket, escape_filename(cropped_s3_path), :secure => true),
|
||||
:large_photo_url => S3Util.url(aws_bucket, escape_filename(cropped_large_s3_path), :secure => true)
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ module JamRuby
|
|||
has_one :review_summary, :class_name => "JamRuby::ReviewSummary", as: :target
|
||||
has_one :user, :class_name => 'JamRuby::User', foreign_key: :teacher_id
|
||||
belongs_to :school, :class_name => "JamRuby::School", inverse_of: :teachers
|
||||
belongs_to :retailer, :class_name => "JamRuby::Retailer", inverse_of: :teachers
|
||||
|
||||
validates :user, :presence => true
|
||||
validates :biography, length: {minimum: 5, maximum: 4096}, :if => :validate_introduction
|
||||
|
|
@ -218,7 +219,18 @@ module JamRuby
|
|||
teacher.teaches_test_drive = params[:teaches_test_drive] if params.key?(:teaches_test_drive)
|
||||
teacher.test_drives_per_week = params[:test_drives_per_week] if params.key?(:test_drives_per_week)
|
||||
teacher.test_drives_per_week = 10 if !params.key?(:test_drives_per_week) # default to 10 in absence of others
|
||||
teacher.school_id = params[:school_id] if params.key?(:school_id)
|
||||
if params.key?(:school_id)
|
||||
teacher.school_id = params[:school_id]
|
||||
if !teacher.joined_school_at
|
||||
teacher.joined_school_at = Time.now
|
||||
end
|
||||
end
|
||||
if params.key?(:retailer_id)
|
||||
teacher.retailer_id = params[:retailer_id]
|
||||
if !teacher.joined_retailer_at
|
||||
teacher.joined_retailer_at = Time.now
|
||||
end
|
||||
end
|
||||
|
||||
# How to validate:
|
||||
teacher.validate_introduction = !!params[:validate_introduction]
|
||||
|
|
@ -415,21 +427,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?
|
||||
|
|
@ -464,5 +462,15 @@ module JamRuby
|
|||
@part_complete[:pct] = complete.round
|
||||
@part_complete
|
||||
end
|
||||
|
||||
def teaches
|
||||
if instruments.length == 0
|
||||
return ''
|
||||
elsif instruments.length == 2
|
||||
return 'Teaches ' + instruments[0].description + ' and ' + instruments[1].description
|
||||
else
|
||||
return 'Teaches ' + instruments.map {|i| i.description}.join(', ')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ module JamRuby
|
|||
belongs_to :lesson_session, class_name: "JamRuby::LessonSession"
|
||||
belongs_to :lesson_package_purchase, class_name: "JamRuby::LessonPackagePurchase"
|
||||
belongs_to :school, class_name: "JamRuby::School"
|
||||
belongs_to :retailer, class_name: "JamRuby::Retailer"
|
||||
|
||||
validates :teacher, presence: true
|
||||
validates :amount_in_cents, presence: true
|
||||
|
|
@ -42,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
|
||||
|
|
@ -81,6 +84,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
def real_distribution
|
||||
|
||||
(real_distribution_in_cents / 100.0)
|
||||
end
|
||||
|
||||
|
|
@ -108,17 +112,21 @@ module JamRuby
|
|||
end
|
||||
|
||||
def calculate_teacher_fee
|
||||
if is_test_drive?
|
||||
if education
|
||||
0
|
||||
else
|
||||
if school
|
||||
# if school exists, use it's rate
|
||||
rate = school.jamkazam_rate
|
||||
if is_test_drive?
|
||||
0
|
||||
else
|
||||
# otherwise use the teacher's rate
|
||||
rate = teacher.teacher.jamkazam_rate
|
||||
if school
|
||||
# if school exists, use it's rate
|
||||
rate = school.jamkazam_rate
|
||||
else
|
||||
# otherwise use the teacher's rate
|
||||
rate = teacher.teacher.jamkazam_rate
|
||||
end
|
||||
(amount_in_cents * (rate + 0.03)).round # 0.03 is stripe fee that we include in cost of JK fee
|
||||
end
|
||||
(amount_in_cents * (rate + 0.03)).round
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ module JamRuby
|
|||
belongs_to :teacher_payment_charge, class_name: "JamRuby::TeacherPaymentCharge", foreign_key: :charge_id
|
||||
has_one :teacher_distribution, class_name: "JamRuby::TeacherDistribution"
|
||||
belongs_to :school, class_name: "JamRuby::School"
|
||||
belongs_to :retailer, class_name: "JamRuby::Retailer"
|
||||
|
||||
|
||||
def self.hourly_check
|
||||
|
|
@ -14,7 +15,11 @@ module JamRuby
|
|||
# pay the school if the payment owns the school; otherwise default to the teacher
|
||||
def payable_teacher
|
||||
if school
|
||||
school.owner
|
||||
if school.education
|
||||
teacher
|
||||
else
|
||||
school.owner
|
||||
end
|
||||
else
|
||||
teacher
|
||||
end
|
||||
|
|
@ -88,17 +93,19 @@ module JamRuby
|
|||
payment.amount_in_cents = payment.teacher_distribution.amount_in_cents
|
||||
payment.fee_in_cents = payment.teacher_distribution.calculate_teacher_fee
|
||||
|
||||
effective_in_cents = payment.amount_in_cents - payment.fee_in_cents
|
||||
|
||||
if payment.teacher_payment_charge.nil?
|
||||
charge = TeacherPaymentCharge.new
|
||||
charge.user = payment.payable_teacher
|
||||
charge.amount_in_cents = (payment.amount_in_cents / (1 - APP_CONFIG.stripe[:ach_pct])).round
|
||||
charge.amount_in_cents = (effective_in_cents / (1 - APP_CONFIG.stripe[:ach_pct])).round
|
||||
charge.fee_in_cents = payment.fee_in_cents
|
||||
charge.teacher_payment = payment
|
||||
payment.teacher_payment_charge = charge
|
||||
# charge.save!
|
||||
else
|
||||
charge = payment.teacher_payment_charge
|
||||
charge.amount_in_cents = (payment.amount_in_cents / (1 - APP_CONFIG.stripe[:ach_pct])).round
|
||||
charge.amount_in_cents = (effective_in_cents / (1 - APP_CONFIG.stripe[:ach_pct])).round
|
||||
charge.fee_in_cents = payment.fee_in_cents
|
||||
charge.save!
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,17 +19,29 @@ module JamRuby
|
|||
teacher
|
||||
end
|
||||
|
||||
def actual_charge_in_cents
|
||||
amount_in_cents
|
||||
end
|
||||
def do_charge(force)
|
||||
|
||||
# source will let you supply a token. But... how to get a token in this case?
|
||||
metadata = {}
|
||||
|
||||
begin
|
||||
metadata = {
|
||||
teacher_id: teacher.id,
|
||||
teacher_name: teacher.name,
|
||||
tax: 0,
|
||||
}
|
||||
rescue Exception => e
|
||||
metadata = {metaerror: true}
|
||||
end
|
||||
|
||||
@stripe_charge = Stripe::Charge.create(
|
||||
:amount => amount_in_cents,
|
||||
:amount => actual_charge_in_cents,
|
||||
:currency => "usd",
|
||||
:customer => APP_CONFIG.stripe[:source_customer],
|
||||
:description => construct_description,
|
||||
:destination => teacher.teacher.stripe_account_id,
|
||||
:application_fee => fee_in_cents,
|
||||
:metadata => metadata
|
||||
)
|
||||
|
||||
stripe_charge
|
||||
|
|
|
|||
|
|
@ -204,11 +204,13 @@ module JamRuby
|
|||
has_many :taught_lessons, :class_name => "JamRuby::LessonSession", inverse_of: :teacher, foreign_key: :teacher_id
|
||||
belongs_to :school, :class_name => "JamRuby::School", inverse_of: :students
|
||||
has_one :owned_school, :class_name => "JamRuby::School", inverse_of: :user
|
||||
has_one :owned_retailer, :class_name => "JamRuby::Retailer", inverse_of: :user
|
||||
has_many :test_drive_package_choices, :class_name =>"JamRuby::TestDrivePackageChoice"
|
||||
has_many :jamblasters_users, class_name: "JamRuby::JamblasterUser"
|
||||
has_many :jamblasters, class_name: 'JamRuby::Jamblaster', through: :jamblasters_users
|
||||
has_many :proposed_slots, class_name: 'JamRuby::LessonBookingSlot', inverse_of: :proposer, dependent: :destroy, foreign_key: :proposer_id
|
||||
has_many :charges, class_name: 'JamRuby::Charge', dependent: :destroy
|
||||
has_many :posa_cards, class_name: 'JamRuby::PosaCard', dependent: :destroy
|
||||
|
||||
before_save :default_anonymous_names
|
||||
before_save :create_remember_token, :if => :should_validate_password?
|
||||
|
|
@ -264,13 +266,27 @@ module JamRuby
|
|||
|
||||
def after_save
|
||||
if school_interest && !school_interest_was
|
||||
AdminMailer.partner({body: "#{email} signed up via the https://www.jamkazam.com/landing/jamclass/schools page.\n\nFull list is here: https://www.jamkazam.com/admin/admin/school_interests", subject: "#{email} is interested in schools"}).deliver_now
|
||||
if education_interest
|
||||
AdminMailer.partner({body: "#{email} signed up via the https://www.jamkazam.com/landing/jamclass/education page.\n\nFull list is here: https://www.jamkazam.com/admin/admin/education_interests", subject: "#{email} is interested in education"}).deliver_now
|
||||
else
|
||||
AdminMailer.partner({body: "#{email} signed up via the https://www.jamkazam.com/landing/jamclass/schools page.\n\nFull list is here: https://www.jamkazam.com/admin/admin/school_interests", subject: "#{email} is interested in schools"}).deliver_now
|
||||
end
|
||||
if owned_school.nil?
|
||||
school = School.new
|
||||
school.user = self
|
||||
school.education = education_interest
|
||||
school.save!
|
||||
end
|
||||
end
|
||||
|
||||
if retailer_interest && !retailer_interest_was
|
||||
AdminMailer.partner({body: "#{email} signed up via the https://www.jamkazam.com/landing/jamclass/retailers page.\n\nFull list is here: https://www.jamkazam.com/admin/admin/retailer_interests", subject: "#{email} is interested in retailer program"}).deliver_now
|
||||
if owned_retailer.nil?
|
||||
retailer = Retailer.new
|
||||
retailer.user = self
|
||||
retailer.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
def update_teacher_pct
|
||||
if teacher
|
||||
|
|
@ -1129,13 +1145,18 @@ module JamRuby
|
|||
teacher = options[:teacher]
|
||||
school_invitation_code = options[:school_invitation_code]
|
||||
school_id = options[:school_id]
|
||||
retailer_invitation_code = options[:retailer_invitation_code]
|
||||
retailer_id = options[:retailer_id]
|
||||
retailer_interest = options[:retailer_interest]
|
||||
school_interest = options[:school_interest]
|
||||
education_interest = options[:education_interest]
|
||||
origin = options[:origin]
|
||||
test_drive_package_details = options[:test_drive_package]
|
||||
|
||||
test_drive_package = TestDrivePackage.find_by_name(test_drive_package_details[:name]) if test_drive_package_details
|
||||
|
||||
school = School.find(school_id) if school_id
|
||||
retailer = School.find(retailer_id) if retailer_id
|
||||
user = User.new
|
||||
user.validate_instruments = true
|
||||
UserManager.active_record_transaction do |user_manager|
|
||||
|
|
@ -1150,6 +1171,16 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
if retailer_invitation_code
|
||||
retailer_invitation = RetailerInvitation.find_by_invitation_code(retailer_invitation_code)
|
||||
if retailer_invitation
|
||||
first_name ||= retailer_invitation.first_name
|
||||
last_name ||= retailer_invitation.last_name
|
||||
retailer_invitation.accepted = true
|
||||
retailer_invitation.save
|
||||
end
|
||||
end
|
||||
|
||||
user.first_name = first_name if first_name.present?
|
||||
user.last_name = last_name if last_name.present?
|
||||
user.email = email
|
||||
|
|
@ -1157,10 +1188,13 @@ module JamRuby
|
|||
user.terms_of_service = terms_of_service
|
||||
user.reuse_card unless reuse_card.nil?
|
||||
user.gifted_jamtracks = 0
|
||||
user.jamclass_credits = 0
|
||||
user.has_redeemable_jamtrack = true
|
||||
user.is_a_student = !!student
|
||||
user.is_a_teacher = !!teacher
|
||||
user.retailer_interest = !!retailer_interest
|
||||
user.school_interest = !!school_interest
|
||||
user.education_interest = !!education_interest
|
||||
if user.is_a_student || user.is_a_teacher
|
||||
musician = true
|
||||
end
|
||||
|
|
@ -1185,10 +1219,18 @@ module JamRuby
|
|||
user.affiliate_referral = school.affiliate_partner
|
||||
elsif user.is_a_teacher
|
||||
school = School.find_by_id(school_id)
|
||||
school_name = school ? school.name : 'a music school'
|
||||
user.teacher = Teacher.build_teacher(user, validate_introduction: true, biography: "Empty biography", school_id: school_id)
|
||||
user.affiliate_referral = school.affiliate_partner
|
||||
end
|
||||
elsif retailer_id.present?
|
||||
if user.is_a_student
|
||||
user.retailer_id = school_id
|
||||
user.affiliate_referral = retailer.affiliate_partner
|
||||
elsif user.is_a_teacher
|
||||
retailer = Retailer.find_by_id(retailer_id)
|
||||
user.teacher = Teacher.build_teacher(user, validate_introduction: true, biography: "Empty biography", retailer_id: retailer_id)
|
||||
user.affiliate_referral = retailer.affiliate_partner
|
||||
end
|
||||
else
|
||||
if user.is_a_teacher
|
||||
user.teacher = Teacher.build_teacher(user, validate_introduction: true, biography: "Empty biography")
|
||||
|
|
@ -1304,9 +1346,18 @@ module JamRuby
|
|||
|
||||
# if a gift card value was passed in, then try to find that gift card and apply it to user
|
||||
if gift_card
|
||||
user.expecting_gift_card = true
|
||||
found_gift_card = GiftCard.where(code: gift_card).where(user_id: nil).first
|
||||
user.gift_cards << found_gift_card if found_gift_card
|
||||
|
||||
# first try posa card
|
||||
posa_card = PosaCard.where(code: gift_card).first
|
||||
|
||||
if posa_card
|
||||
posa_card.claim(user)
|
||||
user.posa_cards << posa_card
|
||||
else
|
||||
user.expecting_gift_card = true
|
||||
found_gift_card = GiftCard.where(code: gift_card).where(user_id: nil).first
|
||||
user.gift_cards << found_gift_card if found_gift_card
|
||||
end
|
||||
end
|
||||
|
||||
user.save
|
||||
|
|
@ -1382,11 +1433,19 @@ module JamRuby
|
|||
user.handle_test_drive_package(test_drive_package, test_drive_package_details) if test_drive_package
|
||||
|
||||
if user.is_a_student
|
||||
UserMailer.student_welcome_message(user).deliver_now
|
||||
#if school && school.education
|
||||
# UserMailer.student_education_welcome_message(user).deliver_now
|
||||
#else
|
||||
UserMailer.student_welcome_message(user).deliver_now
|
||||
#end
|
||||
elsif user.is_a_teacher
|
||||
UserMailer.teacher_welcome_message(user).deliver_now
|
||||
elsif user.education_interest
|
||||
UserMailer.education_owner_welcome_message(user).deliver_now
|
||||
elsif user.school_interest
|
||||
UserMailer.school_owner_welcome_message(user).deliver_now
|
||||
elsif user.retailer_interest
|
||||
UserMailer.retailer_owner_welcome_message(user).deliver_now
|
||||
else
|
||||
UserMailer.welcome_message(user).deliver_now
|
||||
end
|
||||
|
|
@ -1961,6 +2020,11 @@ module JamRuby
|
|||
lesson_purchases.where('lesson_package_type_id in (?)', LessonPackageType.test_drive_package_ids).where('created_at > ?', APP_CONFIG.test_drive_wait_period_year.years.ago).count == 0
|
||||
end
|
||||
|
||||
# validate if within waiting period
|
||||
def can_claim_posa_card
|
||||
posa_cards.where('card_type = ?', PosaCard::JAM_CLASS_4).where('claimed_at > ?', APP_CONFIG.jam_class_card_wait_period_year.years.ago).count == 0
|
||||
end
|
||||
|
||||
def lessons_with_teacher(teacher)
|
||||
taken_lessons.where(teacher_id: teacher.id)
|
||||
end
|
||||
|
|
@ -1973,6 +2037,10 @@ module JamRuby
|
|||
remaining_test_drives > 0
|
||||
end
|
||||
|
||||
def has_posa_credits?
|
||||
jamclass_credits > 0
|
||||
end
|
||||
|
||||
def has_unprocessed_test_drives?
|
||||
!unprocessed_test_drive.nil?
|
||||
end
|
||||
|
|
@ -2016,6 +2084,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
|
||||
|
|
@ -2137,6 +2223,10 @@ module JamRuby
|
|||
LessonBooking.unprocessed(self).where(lesson_type: LessonBooking::LESSON_TYPE_PAID).first
|
||||
end
|
||||
|
||||
def most_recent_posa_purchase
|
||||
lesson_purchases.where('lesson_package_type_id in (?)', LessonPackageType.test_drive_package_ids).where('posa_card_id is not null').order('created_at desc').first
|
||||
end
|
||||
|
||||
def most_recent_test_drive_purchase
|
||||
lesson_purchases.where('lesson_package_type_id in (?)', LessonPackageType.test_drive_package_ids).order('created_at desc').first
|
||||
end
|
||||
|
|
@ -2150,8 +2240,18 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
def total_posa_credits
|
||||
purchase = most_recent_posa_purchase
|
||||
if purchase
|
||||
purchase.posa_card.credits
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def test_drive_succeeded(lesson_session)
|
||||
if self.remaining_test_drives <= 0
|
||||
if (lesson_session.posa_card && self.jamclass_credits <= 0) || (!lesson_session.posa_card && self.remaining_test_drives <= 0)
|
||||
UserMailer.student_test_drive_lesson_done(lesson_session).deliver_now
|
||||
UserMailer.teacher_lesson_completed(lesson_session).deliver_now
|
||||
else
|
||||
|
|
@ -2163,7 +2263,13 @@ module JamRuby
|
|||
def test_drive_declined(lesson_session)
|
||||
# because we decrement test_drive credits as soon as you book, we need to bring it back now
|
||||
if lesson_session.lesson_booking.user_decremented
|
||||
self.remaining_test_drives = self.remaining_test_drives + 1
|
||||
if lesson_session.posa_card
|
||||
self.jamclass_credits = self.jamclass_credits + 1
|
||||
else
|
||||
self.remaining_test_drives = self.remaining_test_drives + 1
|
||||
|
||||
end
|
||||
|
||||
self.save(validate: false)
|
||||
end
|
||||
|
||||
|
|
@ -2173,7 +2279,12 @@ module JamRuby
|
|||
|
||||
if lesson_session.lesson_booking.user_decremented
|
||||
# because we decrement test_drive credits as soon as you book, we need to bring it back now
|
||||
self.remaining_test_drives = self.remaining_test_drives + 1
|
||||
if lesson_session.posa_card
|
||||
self.jamclass_credits = self.jamclass_credits + 1
|
||||
else
|
||||
self.remaining_test_drives = self.remaining_test_drives + 1
|
||||
end
|
||||
|
||||
self.save(validate: false)
|
||||
end
|
||||
UserMailer.teacher_test_drive_no_bill(lesson_session).deliver_now
|
||||
|
|
@ -2184,6 +2295,10 @@ module JamRuby
|
|||
total_test_drives - remaining_test_drives
|
||||
end
|
||||
|
||||
def used_posa_credits
|
||||
total_posa_credits - jamclass_credits
|
||||
end
|
||||
|
||||
def uncollectables(limit = 10)
|
||||
LessonPaymentCharge.where(user_id:self.id).order(:created_at).where('billing_attempts > 0').where(billed: false).limit(limit)
|
||||
end
|
||||
|
|
@ -2244,6 +2359,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
|
||||
|
|
|
|||
|
|
@ -22,6 +22,14 @@ module JamRuby
|
|||
APP_CONFIG.admin_root_url + "/admin/user_whitelists/" + id
|
||||
end
|
||||
|
||||
# if a user claims a gift card or posa card, whitelist their account so they don't get messed with by fraud code
|
||||
def self.card_create(user, notes)
|
||||
user_whitelist = UserWhitelist.new
|
||||
user_whitelist.user = user
|
||||
user_whitelist.notes = notes
|
||||
user_whitelist.save
|
||||
end
|
||||
|
||||
def to_s
|
||||
user
|
||||
end
|
||||
|
|
|
|||
|
|
@ -898,6 +898,19 @@ FactoryGirl.define do
|
|||
association :user, factory: :user
|
||||
end
|
||||
|
||||
factory :posa_card, class: 'JamRuby::PosaCard' do
|
||||
sequence(:code) { |n| n.to_s }
|
||||
card_type JamRuby::PosaCardType::JAM_TRACKS_5
|
||||
end
|
||||
|
||||
factory :posa_card_type, class: 'JamRuby::PosaCardType' do
|
||||
card_type JamRuby::PosaCardType::JAM_TRACKS_5
|
||||
end
|
||||
|
||||
factory :posa_card_purchase, class: 'JamRuby::PosaCardPurchase' do
|
||||
association :user, factory: :user
|
||||
end
|
||||
|
||||
factory :jamblaster, class: 'JamRuby::Jamblaster' do
|
||||
|
||||
association :user, factory: :user
|
||||
|
|
@ -932,6 +945,22 @@ FactoryGirl.define do
|
|||
accepted false
|
||||
end
|
||||
|
||||
factory :retailer, class: 'JamRuby::Retailer' do
|
||||
association :user, factory: :user
|
||||
sequence(:name) { |n| "Dat Music Retailer" }
|
||||
sequence(:slug) { |n| "retailer-#{n}" }
|
||||
enabled true
|
||||
end
|
||||
|
||||
factory :retailer_invitation, class: 'JamRuby::RetailerInvitation' do
|
||||
association :retailer, factory: :retailer
|
||||
note "hey come in in"
|
||||
sequence(:email) { |n| "retail_person#{n}@example.com" }
|
||||
sequence(:first_name) { |n| "FirstName" }
|
||||
sequence(:last_name) { |n| "LastName" }
|
||||
accepted false
|
||||
end
|
||||
|
||||
factory :lesson_booking_slot, class: 'JamRuby::LessonBookingSlot' do
|
||||
factory :lesson_booking_slot_single do
|
||||
slot_type 'single'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -175,7 +177,6 @@ describe "Monthly Recurring Lesson Flow" do
|
|||
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
|
||||
|
|
@ -213,6 +214,253 @@ 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.same_school.should be_true
|
||||
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
|
||||
|
||||
########## 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]
|
||||
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
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ describe "Normal Lesson Flow" do
|
|||
payment = TeacherPayment.first
|
||||
payment.amount_in_cents.should eql 3000
|
||||
payment.fee_in_cents.should eql (3000 * 0.28).round
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql (3000 + 3000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql ((3000 * 0.72) + (3000 * 0.72) * 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
|
||||
|
|
@ -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,225 @@ 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
|
||||
booking.same_school_free.should be_true
|
||||
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!
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ describe "TestDrive Lesson Flow" do
|
|||
let(:affiliate_partner) { FactoryGirl.create(:affiliate_partner) }
|
||||
let(:affiliate_partner2) { FactoryGirl.create(:affiliate_partner, lesson_rate: 0.30) }
|
||||
let(:school) { FactoryGirl.create(:school) }
|
||||
|
||||
let(:card_lessons) {FactoryGirl.create(:posa_card, card_type: JamRuby::PosaCardType::JAM_CLASS_4)}
|
||||
let(:retailer) {FactoryGirl.create(:retailer)}
|
||||
|
||||
before {
|
||||
teacher.stripe_account_id = stripe_account1_id
|
||||
|
|
@ -250,8 +251,208 @@ describe "TestDrive Lesson Flow" do
|
|||
LessonBooking.bookings(user, teacher_user, nil).count.should eql 1
|
||||
LessonBooking.engaged_bookings(user, teacher_user, nil).count.should eql 1
|
||||
teacher_user.has_booked_test_drive_with_student?(user).should be_true
|
||||
end
|
||||
|
||||
it "works using posa card" do
|
||||
|
||||
PosaCard.activate(card_lessons, retailer)
|
||||
card_lessons.reload
|
||||
card_lessons.claim(user)
|
||||
card_lessons.errors.any?.should be false
|
||||
|
||||
user.reload
|
||||
user.jamclass_credits.should eql 4
|
||||
|
||||
|
||||
# user has no test drives, no credit card on file, but attempts to book a lesson
|
||||
booking = LessonBooking.book_test_drive(user, teacher_user, valid_single_slots, "Hey I've heard of you before.")
|
||||
booking.errors.any?.should be_false
|
||||
booking.card_presumed_ok.should be_false
|
||||
booking.user.should eql user
|
||||
booking.sent_notices.should be_true
|
||||
booking.posa_card.should eql card_lessons
|
||||
user.unprocessed_test_drive.should be_nil
|
||||
teacher_user.has_booked_test_drive_with_student?(user).should be_true
|
||||
|
||||
user.reload
|
||||
user.jamclass_credits.should eql 3
|
||||
lesson_session = booking.lesson_sessions[0]
|
||||
lesson_session.posa_card.should eql card_lessons
|
||||
lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
|
||||
lesson_session.reload
|
||||
|
||||
#booking.lesson_package_purchases.should eql [card_lessons.lesson_package_purchase]
|
||||
user.stripe_customer_id.should be nil
|
||||
user.lesson_purchases.length.should eql 1
|
||||
lesson_purchase = user.lesson_purchases[0]
|
||||
lesson_purchase.price.should eql 49.99
|
||||
lesson_purchase.lesson_package_type.is_test_drive?.should eql true
|
||||
lesson_purchase.posa_card.should eql card_lessons
|
||||
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.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
|
||||
notification.message.should be_nil
|
||||
|
||||
|
||||
# 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
|
||||
lesson_session.analyse
|
||||
lesson_session.session_completed
|
||||
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 false
|
||||
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_error_reason.should be_nil
|
||||
lesson_session.sent_notices.should be true
|
||||
purchase = lesson_session.lesson_package_purchase
|
||||
purchase.should_not be_nil
|
||||
purchase.price_in_cents.should eql 4999
|
||||
purchase.lesson_package_type.is_test_drive?.should be true
|
||||
user.reload
|
||||
user.remaining_test_drives.should eql 0
|
||||
UserMailer.deliveries.length.should eql 2 # one for student, one for teacher
|
||||
found_student_email = false
|
||||
UserMailer.deliveries.each do |d|
|
||||
puts d.subject
|
||||
if d.subject == "You have used 1 of 4 TestDrive lesson credits"
|
||||
found_student_email = true
|
||||
end
|
||||
end
|
||||
found_student_email.should be_true
|
||||
|
||||
teacher_distribution = lesson_session.teacher_distribution
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
teacher_distribution.ready.should be_true
|
||||
teacher_distribution.distributed.should be_false
|
||||
|
||||
LessonBooking.hourly_check
|
||||
LessonSession.hourly_check
|
||||
|
||||
teacher_distribution.reload
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
teacher_distribution.ready.should be_true
|
||||
teacher_distribution.distributed.should be_false
|
||||
|
||||
TeacherPayment.count.should eql 0
|
||||
TeacherPayment.hourly_check
|
||||
TeacherPayment.count.should eql 1
|
||||
|
||||
lesson_session.reload
|
||||
purchase.reload
|
||||
|
||||
purchase.teacher_distribution.should be_nil
|
||||
|
||||
teacher_payment = TeacherPayment.first
|
||||
teacher_payment.amount_in_cents.should eql 1000
|
||||
teacher_payment.fee_in_cents.should eql 0
|
||||
teacher_payment.teacher.should eql teacher_user
|
||||
|
||||
teacher_distribution.reload
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
teacher_distribution.ready.should be_true
|
||||
teacher_distribution.distributed.should be_true
|
||||
|
||||
teacher_payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
teacher_payment.teacher_payment_charge.fee_in_cents.should eql 0
|
||||
|
||||
user.sales.count.should eql 1
|
||||
sale = user.sales[0]
|
||||
sale.sale_line_items.count.should eql 1
|
||||
sale.sale_line_items[0].affiliate_distributions.count.should eql 0
|
||||
|
||||
lesson_session.lesson_booking.status.should eql LessonBooking::STATUS_COMPLETED
|
||||
lesson_session.lesson_booking.success.should be_true
|
||||
LessonBooking.bookings(user, teacher_user, nil).count.should eql 1
|
||||
LessonBooking.engaged_bookings(user, teacher_user, nil).count.should eql 1
|
||||
teacher_user.has_booked_test_drive_with_student?(user).should be_true
|
||||
end
|
||||
|
||||
# VRFS-4069
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe PosaCard do
|
||||
|
||||
let(:user) {FactoryGirl.create(:user)}
|
||||
let(:card) {FactoryGirl.create(:posa_card)}
|
||||
let(:card2) {FactoryGirl.create(:posa_card)}
|
||||
let(:card_lessons) {FactoryGirl.create(:posa_card, card_type: JamRuby::PosaCardType::JAM_CLASS_4)}
|
||||
let(:retailer) {FactoryGirl.create(:retailer)}
|
||||
it "created by factory" do
|
||||
card.touch
|
||||
end
|
||||
|
||||
describe "activated" do
|
||||
it "succeeds" do
|
||||
card.activate(retailer)
|
||||
|
||||
card.errors.any?.should be false
|
||||
card.activated_at.should_not be_nil
|
||||
card.retailer.should eql retailer
|
||||
end
|
||||
|
||||
it "cant be re-activated" do
|
||||
|
||||
card.activate(retailer)
|
||||
|
||||
Timecop.travel(Time.now + 1)
|
||||
|
||||
card.activate(retailer)
|
||||
|
||||
card.errors.any?.should be true
|
||||
card.errors[:activated_at].should eql ['already activated. Please give card to customer. Thank you!']
|
||||
end
|
||||
|
||||
it "must have retailer" do
|
||||
|
||||
card.activate(nil)
|
||||
|
||||
card.errors.any?.should be true
|
||||
card.errors[:retailer].should eql ['must be specified']
|
||||
end
|
||||
end
|
||||
|
||||
describe "claim" do
|
||||
it "succeeds" do
|
||||
PosaCard.activate(card, retailer)
|
||||
card.reload
|
||||
card.claim(user)
|
||||
|
||||
card.errors.any?.should be false
|
||||
card.claimed_at.should_not be_nil
|
||||
card.user.should eql user
|
||||
end
|
||||
|
||||
it "succeeds with jamclass type" do
|
||||
PosaCard.activate(card_lessons, retailer)
|
||||
card_lessons.reload
|
||||
card_lessons.claim(user)
|
||||
|
||||
card_lessons.errors.any?.should be false
|
||||
card_lessons.claimed_at.should_not be_nil
|
||||
card_lessons.user.should eql user
|
||||
card_lessons.reload
|
||||
card_lessons.lesson_package_purchase.should_not be_nil
|
||||
card_lessons.lesson_package_purchase.lesson_package_type.should eql LessonPackageType.test_drive_4
|
||||
card_lessons.lesson_package_purchase.posa_card.should eql card_lessons
|
||||
end
|
||||
|
||||
|
||||
it "must be already activated" do
|
||||
|
||||
card.claim(user)
|
||||
|
||||
card.errors.any?.should be true
|
||||
card.errors[:activated_at].should eql ['must already be set']
|
||||
end
|
||||
|
||||
it "cant be re-claimed" do
|
||||
|
||||
PosaCard.activate(card, retailer)
|
||||
card.reload
|
||||
card.claim(user)
|
||||
|
||||
Timecop.travel(Time.now + 1)
|
||||
|
||||
card.claim(user)
|
||||
|
||||
card.errors.any?.should be true
|
||||
card.errors[:claimed_at].should eql ['already claimed']
|
||||
end
|
||||
|
||||
it "must have user" do
|
||||
PosaCard.activate(card, retailer)
|
||||
card.reload
|
||||
card.claim(nil)
|
||||
|
||||
card.errors.any?.should be true
|
||||
card.errors[:user].should eql ['must be specified']
|
||||
end
|
||||
|
||||
it "can't be within one year" do
|
||||
PosaCard.activate(card, retailer)
|
||||
card.reload
|
||||
card.claim(user)
|
||||
|
||||
PosaCard.activate(card2, retailer)
|
||||
card2.reload
|
||||
card2.claim(user)
|
||||
|
||||
card2.errors.any?.should be true
|
||||
card2.errors[:user].should eql ['was within 1 year']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe RetailerInvitation do
|
||||
|
||||
let(:retailer) {FactoryGirl.create(:retailer)}
|
||||
|
||||
it "created by factory" do
|
||||
FactoryGirl.create(:retailer_invitation)
|
||||
end
|
||||
|
||||
it "created by method" do
|
||||
RetailerInvitation.create(retailer.user, retailer, {first_name: "Bobby", last_name: "Jimes", email: 'somewhere@jamkazam.com'})
|
||||
end
|
||||
|
||||
describe "index" do
|
||||
it "works" do
|
||||
RetailerInvitation.index(retailer, {})[:query].count.should eql 0
|
||||
|
||||
FactoryGirl.create(:retailer_invitation)
|
||||
RetailerInvitation.index(retailer, {})[:query].count.should eql 0
|
||||
RetailerInvitation.index(retailer, {})[:query].count.should eql 0
|
||||
|
||||
FactoryGirl.create(:retailer_invitation, retailer: retailer, )
|
||||
RetailerInvitation.index(retailer, {})[:query].count.should eql 1
|
||||
|
||||
FactoryGirl.create(:retailer_invitation, retailer: retailer, )
|
||||
RetailerInvitation.index(retailer, {})[:query].count.should eql 2
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Retailer do
|
||||
|
||||
it "created by factory" do
|
||||
FactoryGirl.create(:retailer)
|
||||
end
|
||||
|
||||
it "doesn't match uuid password" do
|
||||
retailer= FactoryGirl.create(:retailer)
|
||||
retailer.reload
|
||||
retailer.matches_password('hha').should be false
|
||||
end
|
||||
|
||||
it "automatic slug creation" do
|
||||
retailer= FactoryGirl.create(:retailer, slug: nil)
|
||||
retailer.id.should_not be_blank
|
||||
retailer.slug.should eql retailer.id.to_s
|
||||
|
||||
end
|
||||
it "has correct associations" do
|
||||
retailer = FactoryGirl.create(:retailer)
|
||||
retailer.slug.should eql retailer.id
|
||||
|
||||
retailer.should eql retailer.user.owned_retailer
|
||||
|
||||
teacher = FactoryGirl.create(:teacher, retailer: retailer)
|
||||
|
||||
retailer.reload
|
||||
retailer.teachers.to_a.should eql [teacher]
|
||||
|
||||
teacher.retailer.should eql retailer
|
||||
end
|
||||
|
||||
it "updates" do
|
||||
retailer = FactoryGirl.create(:retailer)
|
||||
retailer.update_from_params({name: 'hahah'})
|
||||
retailer.errors.any?.should be false
|
||||
end
|
||||
|
||||
it "updates password" do
|
||||
retailer = FactoryGirl.create(:retailer)
|
||||
retailer.update_from_params({name: 'hahah', password: 'abc'})
|
||||
retailer.errors.any?.should be true
|
||||
retailer.errors[:password].should eql ['is too short (minimum is 6 characters)']
|
||||
|
||||
retailer.update_from_params({name: 'hahah', password: 'abcdef'})
|
||||
retailer.errors.any?.should be false
|
||||
retailer.matches_password('abcdef').should be true
|
||||
|
||||
retailer = Retailer.find_by_id(retailer.id)
|
||||
retailer.matches_password('abcdef').should be true
|
||||
end
|
||||
end
|
||||
|
|
@ -8,6 +8,8 @@ describe Sale do
|
|||
let(:jam_track2) { FactoryGirl.create(:jam_track) }
|
||||
let(:jam_track3) { FactoryGirl.create(:jam_track) }
|
||||
let(:gift_card) { GiftCardType.jam_track_5 }
|
||||
let(:posa_card) {FactoryGirl.create(:posa_card)}
|
||||
let(:retailer) {FactoryGirl.create(:retailer)}
|
||||
|
||||
after(:each) {
|
||||
Timecop.return
|
||||
|
|
@ -27,6 +29,39 @@ describe Sale do
|
|||
sale_line_item.product_id.should eq(jamtrack.id)
|
||||
end
|
||||
|
||||
describe "posa_cards" do
|
||||
it "works" do
|
||||
posa_card.card_type.should eql PosaCard::JAM_TRACKS_5
|
||||
|
||||
result = Sale.posa_activate(posa_card, retailer)
|
||||
posa_card.errors.any?.should be false
|
||||
posa_card.activated_at.should_not be_nil
|
||||
sale = result[:sale]
|
||||
|
||||
sale = Sale.find(sale.id)
|
||||
sale.sale_line_items.count.should eql 1
|
||||
sale_line_item = sale.sale_line_items.first
|
||||
sale.retailer.should eql retailer
|
||||
sale.sale_type.should eql Sale::POSA_SALE
|
||||
sale_line_item.retailer.should eql retailer
|
||||
sale_line_item.unit_price.should eql 9.99 #
|
||||
sale_line_item.quantity.should eql 1
|
||||
end
|
||||
|
||||
it "already activated" do
|
||||
result = Sale.posa_activate(posa_card, retailer)
|
||||
posa_card.activated_at.should_not be_nil
|
||||
posa_card.errors.any?.should be false
|
||||
sale = result[:sale]
|
||||
|
||||
result2 = Sale.posa_activate(posa_card, retailer)
|
||||
posa_card.activated_at.should_not be_nil
|
||||
posa_card.errors.any?.should be true
|
||||
result2[:sale].should be_nil
|
||||
posa_card.errors[:activated_at].should eq ["already activated. Please give card to customer. Thank you!"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "index" do
|
||||
it "empty" do
|
||||
result = Sale.index(user)
|
||||
|
|
@ -151,6 +186,7 @@ describe Sale do
|
|||
purchase.state.should eq('invoiced')
|
||||
purchase.uuid.should eq(sale_line_item.recurly_adjustment_uuid)
|
||||
|
||||
sleep 2
|
||||
invoices = recurly_account.invoices
|
||||
invoices.should have(1).items
|
||||
invoice = invoices[0]
|
||||
|
|
@ -454,6 +490,7 @@ describe Sale do
|
|||
purchase.state.should eq('invoiced')
|
||||
purchase.uuid.should eq(sale_line_item.recurly_adjustment_uuid)
|
||||
|
||||
sleep 2
|
||||
invoices = recurly_account.invoices
|
||||
invoices.should have(1).items
|
||||
invoice = invoices[0]
|
||||
|
|
@ -533,6 +570,7 @@ describe Sale do
|
|||
purchase.state.should eq('invoiced')
|
||||
purchase.uuid.should eq(sale_line_item.recurly_adjustment_uuid)
|
||||
|
||||
sleep 2
|
||||
invoices = recurly_account.invoices
|
||||
invoices.should have(1).items
|
||||
invoice = invoices[0]
|
||||
|
|
@ -941,6 +979,7 @@ describe Sale do
|
|||
r.voided.to_i.should eq(1)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -167,14 +167,14 @@ describe TeacherPayment do
|
|||
# only one confirm email to teacher
|
||||
UserMailer.deliveries.length.should eql 1
|
||||
payment.teacher_payment_charge.billed.should eql true
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql ((1000 * 0.72) + (1000 * 0.72) * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.fee_in_cents.should eql 280
|
||||
payment.teacher_payment_charge.teacher.should eql teacher
|
||||
teacher_distribution = payment.teacher_payment_charge.distribution
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
|
||||
charge.amount.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
charge.application_fee.should include("fee_")
|
||||
charge.amount.should eql ((1000 * 0.72) + (1000 * 0.72) * APP_CONFIG.stripe[:ach_pct]).round
|
||||
charge.application_fee.should be_nil
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -199,13 +199,13 @@ describe TeacherPayment do
|
|||
puts payment.teacher_payment_charge.billing_error_detail
|
||||
end
|
||||
payment.teacher_payment_charge.billed.should eql true
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql ((1000 * 0.72) + (1000 * 0.72) * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.fee_in_cents.should eql 280
|
||||
teacher_distribution = payment.teacher_payment_charge.distribution
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
|
||||
charge.amount.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
charge.application_fee.should include("fee_")
|
||||
charge.amount.should eql ((1000 * 0.72) + (1000 * 0.72) * APP_CONFIG.stripe[:ach_pct]).round
|
||||
charge.application_fee.should be_nil
|
||||
|
||||
test_drive_distribution.reload
|
||||
payment = test_drive_distribution.teacher_payment
|
||||
|
|
@ -220,7 +220,7 @@ describe TeacherPayment do
|
|||
teacher_distribution = payment.teacher_payment_charge.distribution
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
|
||||
charge.amount.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
charge.amount.should eql 1000 + (1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
charge.application_fee.should be_nil
|
||||
end
|
||||
|
||||
|
|
@ -259,15 +259,15 @@ describe TeacherPayment do
|
|||
# one to school owner, one to teacher
|
||||
UserMailer.deliveries.length.should eql 2
|
||||
payment.teacher_payment_charge.billed.should eql true
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql ((1000 * 0.72) + (1000 * 0.72) * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.fee_in_cents.should eql 280
|
||||
payment.teacher_payment_charge.user.should eql school.owner
|
||||
teacher_distribution = payment.teacher_payment_charge.distribution
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
|
||||
charge.destination.should eql school.owner.teacher.stripe_account_id
|
||||
charge.amount.should eql 1008
|
||||
charge.application_fee.should include("fee_")
|
||||
charge.destination.should be_nil
|
||||
charge.amount.should eql 726
|
||||
charge.application_fee.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -304,7 +304,7 @@ describe TeacherPayment do
|
|||
payment.teacher_payment_charge.billing_error_detail.should include("declined")
|
||||
|
||||
payment.teacher_payment_charge.billed.should eql false
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql ((1000 * 0.72) + (1000 * 0.72) * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.fee_in_cents.should eql 280
|
||||
teacher_distribution = payment.teacher_payment_charge.distribution
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
|
|
@ -326,7 +326,7 @@ describe TeacherPayment do
|
|||
# no attempt should be made because a day hasn't gone by
|
||||
payment = normal_distribution.teacher_payment
|
||||
payment.teacher_payment_charge.billed.should eql false
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql ((1000 * 0.72) + (1000 * 0.72) * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.fee_in_cents.should eql 280
|
||||
teacher_distribution = payment.teacher_payment_charge.distribution
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
|
|
@ -347,12 +347,12 @@ describe TeacherPayment do
|
|||
payment = normal_distribution.teacher_payment
|
||||
payment.reload
|
||||
payment.teacher_payment_charge.billed.should eql true
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql ((1000 * 0.72) + (1000 * 0.72) * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.fee_in_cents.should eql 280
|
||||
teacher_distribution = payment.teacher_payment_charge.distribution
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
|
||||
charge.amount.should eql 1008
|
||||
charge.amount.should eql 726
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -386,12 +386,12 @@ describe TeacherPayment do
|
|||
payment = normal_distribution.teacher_payment
|
||||
|
||||
payment.teacher_payment_charge.billed.should eql true
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql ((1000 * 0.72) + (1000 * 0.72) * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.fee_in_cents.should eql 280
|
||||
teacher_distribution = payment.teacher_payment_charge.distribution
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
|
||||
charge.amount.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
charge.amount.should eql ((1000 * 0.72) + (1000 * 0.72) * APP_CONFIG.stripe[:ach_pct]).round
|
||||
|
||||
test_drive_distribution.reload
|
||||
payment = test_drive_distribution.teacher_payment
|
||||
|
|
@ -402,7 +402,7 @@ describe TeacherPayment do
|
|||
teacher_distribution = payment.teacher_payment_charge.distribution
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
|
||||
charge.amount.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
charge.amount.should eql 1000 + (1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -434,7 +434,7 @@ describe TeacherPayment do
|
|||
payment.teacher_payment_charge.billing_error_detail.should include("declined")
|
||||
|
||||
payment.teacher_payment_charge.billed.should eql false
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql ((1000 * 0.72) + (1000 * 0.72) * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.fee_in_cents.should eql 280
|
||||
teacher_distribution = payment.teacher_payment_charge.distribution
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
|
|
@ -456,7 +456,7 @@ describe TeacherPayment do
|
|||
# no attempt should be made because a day hasn't gone by
|
||||
payment = normal_distribution.teacher_payment
|
||||
payment.teacher_payment_charge.billed.should eql false
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql ((1000 * 0.72) + (1000 * 0.72) * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.fee_in_cents.should eql 280
|
||||
teacher_distribution = payment.teacher_payment_charge.distribution
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
|
|
@ -477,12 +477,12 @@ describe TeacherPayment do
|
|||
payment = normal_distribution.teacher_payment
|
||||
payment.reload
|
||||
payment.teacher_payment_charge.billed.should eql true
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.amount_in_cents.should eql ((1000 * 0.72) + (1000 * 0.72) * APP_CONFIG.stripe[:ach_pct]).round
|
||||
payment.teacher_payment_charge.fee_in_cents.should eql 280
|
||||
teacher_distribution = payment.teacher_payment_charge.distribution
|
||||
teacher_distribution.amount_in_cents.should eql 1000
|
||||
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
|
||||
charge.amount.should eql 1008
|
||||
charge.amount.should eql 726
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ require "spec_helper"
|
|||
describe "RenderMailers", :slow => true do
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:school) {FactoryGirl.create(:school, education:true)}
|
||||
|
||||
before(:each) do
|
||||
@filename = nil # set this on your test to pin the filename; i just make it the name of the mailer method responsible for sending the mail
|
||||
|
|
@ -27,7 +28,9 @@ describe "RenderMailers", :slow => true do
|
|||
|
||||
it { @filename="welcome_message"; UserMailer.welcome_message(user).deliver_now }
|
||||
it { @filename="student_welcome_message"; UserMailer.student_welcome_message(user).deliver_now }
|
||||
it { @filename="student_welcome_message_education"; user.school = school; user.save!; UserMailer.student_welcome_message(user).deliver_now }
|
||||
it { @filename="school_owner_welcome_message"; UserMailer.school_owner_welcome_message(user).deliver_now }
|
||||
it { @filename="education_owner_welcome_message"; UserMailer.education_owner_welcome_message(user).deliver_now }
|
||||
it { @filename="confirm_email"; UserMailer.confirm_email(user, "/signup").deliver_now }
|
||||
it { @filename="password_reset"; UserMailer.password_reset(user, '/reset_password').deliver_now }
|
||||
it { @filename="password_changed"; UserMailer.password_changed(user).deliver_now }
|
||||
|
|
@ -210,6 +213,22 @@ describe "RenderMailers", :slow => true do
|
|||
end
|
||||
end
|
||||
|
||||
describe "Retailer emails" do
|
||||
let(:retailer) {FactoryGirl.create(:retailer)}
|
||||
|
||||
before(:each) do
|
||||
UserMailer.deliveries.clear
|
||||
end
|
||||
after(:each) do
|
||||
UserMailer.deliveries.length.should == 1
|
||||
# NOTE! we take the second email, because the act of creating the InvitedUser model
|
||||
# sends an email too, before our it {} block runs. This is because we have an InvitedUserObserver
|
||||
mail = UserMailer.deliveries[0]
|
||||
save_emails_to_disk(mail, @filename)
|
||||
end
|
||||
it {@filename="retailer_customer_blast"; UserMailer.retailer_customer_blast('seth@jamkazam.com', retailer).deliver_now}
|
||||
end
|
||||
|
||||
describe "InvitedUserMailer emails" do
|
||||
|
||||
let(:user2) { FactoryGirl.create(:user) }
|
||||
|
|
|
|||
|
|
@ -294,6 +294,10 @@ def app_config
|
|||
1
|
||||
end
|
||||
|
||||
def jam_class_card_wait_period_year
|
||||
1
|
||||
end
|
||||
|
||||
def check_bounced_emails
|
||||
false
|
||||
end
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
|
|
@ -76,10 +76,12 @@
|
|||
isNativeClient: gon.isNativeClient,
|
||||
musician: context.JK.currentUserMusician,
|
||||
sales_count: userDetail.sales_count,
|
||||
owned_retailer_id: userDetail.owned_retailer_id,
|
||||
is_affiliate_partner: userDetail.is_affiliate_partner,
|
||||
affiliate_earnings: (userDetail.affiliate_earnings / 100).toFixed(2),
|
||||
affiliate_referral_count: userDetail.affiliate_referral_count,
|
||||
owns_school: !!userDetail.owned_school_id,
|
||||
owns_retailer: !!userDetail.owned_retailer_id,
|
||||
webcamName: webcamName
|
||||
} , { variable: 'data' }));
|
||||
|
||||
|
|
@ -145,6 +147,7 @@
|
|||
$("#account-content-scroller").on('click', '#account-payment-history-link', function(evt) {evt.stopPropagation(); navToPaymentHistory(); return false; } );
|
||||
$("#account-content-scroller").on('click', '#account-affiliate-partner-link', function(evt) {evt.stopPropagation(); navToAffiliates(); return false; } );
|
||||
$("#account-content-scroller").on('click', '#account-school-link', function(evt) {evt.stopPropagation(); navToSchool(); return false; } );
|
||||
$("#account-content-scroller").on('click', '#account-retailer-link', function(evt) {evt.stopPropagation(); navToRetailer(); return false; } );
|
||||
}
|
||||
|
||||
function renderAccount() {
|
||||
|
|
@ -207,6 +210,11 @@
|
|||
window.location = '/client#/account/school'
|
||||
}
|
||||
|
||||
function navToRetailer() {
|
||||
resetForm()
|
||||
window.location = '/client#/account/retailer'
|
||||
}
|
||||
|
||||
// handle update avatar event
|
||||
function updateAvatar(avatar_url) {
|
||||
var photoUrl = context.JK.resolveAvatarUrl(avatar_url);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
(function(context,$) {
|
||||
|
||||
"use strict";
|
||||
|
|
@ -22,17 +23,17 @@
|
|||
|
||||
function afterShow(data) {
|
||||
|
||||
if (window.ProfileStore.solo) {
|
||||
$btnBack.hide()
|
||||
$btnSubmit.text('SAVE & RETURN TO PROFILE');
|
||||
}
|
||||
else {
|
||||
$btnBack.show()
|
||||
$btnSubmit.text('SAVE & NEXT');
|
||||
}
|
||||
if (window.ProfileStore.solo) {
|
||||
$btnBack.hide()
|
||||
$btnSubmit.text('SAVE & RETURN TO PROFILE');
|
||||
}
|
||||
else {
|
||||
$btnBack.show()
|
||||
$btnSubmit.text('SAVE & NEXT');
|
||||
}
|
||||
|
||||
resetForm();
|
||||
renderExperience();
|
||||
resetForm();
|
||||
renderExperience();
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
|
|
@ -64,6 +65,7 @@
|
|||
$screen.find('select[name=skill_level]').val(userDetail.skill_level);
|
||||
$screen.find('select[name=concert_count]').val(userDetail.concert_count);
|
||||
$screen.find('select[name=studio_session_count]').val(userDetail.studio_session_count);
|
||||
context.JK.checkbox($instrumentSelector.find('input[type="checkbox"]'), true)
|
||||
}
|
||||
|
||||
function isUserInstrument(instrument, userInstruments) {
|
||||
|
|
@ -101,6 +103,8 @@
|
|||
});
|
||||
$userGenres.append(genreHtml);
|
||||
});
|
||||
|
||||
context.JK.checkbox($userGenres.find('input[type="checkbox"]'), true)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -132,7 +136,7 @@
|
|||
navigateTo('/client#/account/profile/');
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
enableSubmits()
|
||||
}
|
||||
|
||||
|
|
@ -178,9 +182,9 @@
|
|||
concert_count: $screen.find('select[name=concert_count]').val(),
|
||||
studio_session_count: $screen.find('select[name=studio_session_count]').val()
|
||||
})
|
||||
.done(postUpdateProfileSuccess)
|
||||
.fail(postUpdateProfileFailure)
|
||||
.always(enableSubmits)
|
||||
.done(postUpdateProfileSuccess)
|
||||
.fail(postUpdateProfileFailure)
|
||||
.always(enableSubmits)
|
||||
}
|
||||
|
||||
function postUpdateProfileSuccess(response) {
|
||||
|
|
@ -216,7 +220,7 @@
|
|||
instrument_id: instrumentElement.attr('data-instrument-id'),
|
||||
proficiency_level: proficiency,
|
||||
priority : i
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return instruments;
|
||||
|
|
@ -239,4 +243,4 @@
|
|||
return this;
|
||||
};
|
||||
|
||||
})(window,jQuery);
|
||||
})(window,jQuery);
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@
|
|||
}
|
||||
|
||||
function initializeInfluxDB() {
|
||||
/**
|
||||
context.stats = new InfluxDB({
|
||||
"host" : gon.global.influxdb_host,
|
||||
"port" : gon.global.influxdb_port,
|
||||
|
|
@ -190,6 +191,8 @@
|
|||
});
|
||||
|
||||
context.stats.write = context.stats.writePoint;
|
||||
*/
|
||||
context.stats = {write:function() {}}
|
||||
}
|
||||
|
||||
function initializeStun(app) {
|
||||
|
|
|
|||
|
|
@ -199,6 +199,17 @@
|
|||
})
|
||||
}})
|
||||
}
|
||||
helpBubble.showUseRemainingJamClassCreditsBubble = function($element, $offsetParent, user, callback) {
|
||||
return context.JK.onceBubble($element, 'side-remaining-jamclass-credits', user, {offsetParent:$offsetParent, width:260, positions:['right'], postShow: function(container) {
|
||||
|
||||
var $bookNow = $('a.book-now')
|
||||
$bookNow.off('click').on('click', function(e) {
|
||||
e.preventDefault()
|
||||
callback()
|
||||
return false;
|
||||
})
|
||||
}})
|
||||
}
|
||||
|
||||
helpBubble.showBuyTestDrive = function($element, $offsetParent, user, callback) {
|
||||
return context.JK.onceBubble($element, 'side-buy-test-drive', user, {offsetParent:$offsetParent, width:260, positions:['right'], postShow: function(container) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
var $screen = null;
|
||||
|
||||
function beforeShow(data) {
|
||||
|
||||
}
|
||||
|
||||
function afterShow(data) {
|
||||
|
|
|
|||
|
|
@ -2367,8 +2367,6 @@
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
function updateSchoolAvatar(options) {
|
||||
var id = getId(options);
|
||||
|
||||
|
|
@ -2488,6 +2486,171 @@
|
|||
});
|
||||
}
|
||||
|
||||
function deleteSchoolTeacher(options) {
|
||||
|
||||
var id = getId(options);
|
||||
|
||||
return $.ajax({
|
||||
type: "DELETE",
|
||||
url: "/api/schools/" + id + '/teachers/' + options.teacher_id,
|
||||
dataType: "json",
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
function getRetailer(options) {
|
||||
|
||||
var id = getId(options);
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
url: "/api/retailers/" + id,
|
||||
dataType: "json",
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
function updateRetailer(options) {
|
||||
var id = getId(options);
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
url: '/api/retailers/' + id,
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function updateRetailerAvatar(options) {
|
||||
var id = getId(options);
|
||||
|
||||
var original_fpfile = options['original_fpfile'];
|
||||
var cropped_fpfile = options['cropped_fpfile'];
|
||||
var cropped_large_fpfile = options['cropped_large_fpfile'];
|
||||
var crop_selection = options['crop_selection'];
|
||||
|
||||
logger.debug(JSON.stringify({
|
||||
original_fpfile : original_fpfile,
|
||||
cropped_fpfile : cropped_fpfile,
|
||||
cropped_large_fpfile : cropped_large_fpfile,
|
||||
crop_selection : crop_selection
|
||||
}));
|
||||
|
||||
var url = "/api/retailers/" + id + "/avatar";
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: url,
|
||||
contentType: 'application/json',
|
||||
processData:false,
|
||||
data: JSON.stringify({
|
||||
original_fpfile : original_fpfile,
|
||||
cropped_fpfile : cropped_fpfile,
|
||||
cropped_large_fpfile : cropped_large_fpfile,
|
||||
crop_selection : crop_selection
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function deleteRetailerAvatar(options) {
|
||||
var id = getId(options);
|
||||
|
||||
var url = "/api/retailers/" + id + "/avatar";
|
||||
return $.ajax({
|
||||
type: "DELETE",
|
||||
dataType: "json",
|
||||
url: url,
|
||||
contentType: 'application/json',
|
||||
processData:false
|
||||
});
|
||||
}
|
||||
|
||||
function generateRetailerFilePickerPolicy(options) {
|
||||
var id = getId(options);
|
||||
var handle = options && options["handle"];
|
||||
var convert = options && options["convert"]
|
||||
|
||||
var url = "/api/retailers/" + id + "/filepicker_policy";
|
||||
|
||||
return $.ajax(url, {
|
||||
data : { handle : handle, convert: convert },
|
||||
dataType : 'json'
|
||||
});
|
||||
}
|
||||
|
||||
function listRetailerInvitations(options) {
|
||||
|
||||
var id = getId(options);
|
||||
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
url: "/api/retailers/" + id + '/invitations?' + $.param(options) ,
|
||||
dataType: "json",
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
function createRetailerInvitation(options) {
|
||||
|
||||
var id = getId(options);
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
url: "/api/retailers/" + id + '/invitations?' + $.param(options) ,
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(options)
|
||||
});
|
||||
}
|
||||
|
||||
function deleteRetailerInvitation(options) {
|
||||
|
||||
var id = getId(options);
|
||||
|
||||
return $.ajax({
|
||||
type: "DELETE",
|
||||
url: "/api/retailers/" + id + '/invitations/' + options.invitation_id,
|
||||
dataType: "json",
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
function resendRetailerInvitation(options) {
|
||||
|
||||
var id = getId(options);
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
url: "/api/retaiers/" + id + '/invitations/' + options.invitation_id + '/resend',
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(options)
|
||||
});
|
||||
}
|
||||
|
||||
function deleteRetailerStudent(options) {
|
||||
|
||||
var id = getId(options);
|
||||
|
||||
return $.ajax({
|
||||
type: "DELETE",
|
||||
url: "/api/retailers/" + id + '/students/' + options.student_id,
|
||||
dataType: "json",
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
function deleteRetailerTeacher(options) {
|
||||
|
||||
var id = getId(options);
|
||||
|
||||
return $.ajax({
|
||||
type: "DELETE",
|
||||
url: "/api/retailers/" + id + '/teachers/' + options.teacher_id,
|
||||
dataType: "json",
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
function listTeacherDistributions(options) {
|
||||
|
||||
if(!options) {
|
||||
|
|
@ -2502,18 +2665,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
function deleteSchoolTeacher(options) {
|
||||
|
||||
var id = getId(options);
|
||||
|
||||
return $.ajax({
|
||||
type: "DELETE",
|
||||
url: "/api/schools/" + id + '/teachers/' + options.teacher_id,
|
||||
dataType: "json",
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
function createReview(options) {
|
||||
|
||||
return $.ajax({
|
||||
|
|
@ -2545,6 +2696,45 @@
|
|||
})
|
||||
}
|
||||
|
||||
function posaActivate(options) {
|
||||
var slug = options.slug
|
||||
delete options.slug
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
url: '/api/posa/' + slug + '/activate',
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
function posaClaim(options) {
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
url: '/api/posa/claim',
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
function sendRetailerCustomerEmail(options) {
|
||||
options = options || {}
|
||||
var retailerId = options.retailer
|
||||
delete options.retailer
|
||||
|
||||
return $.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/retailers/' + retailerId + '/customer_email',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function initialize() {
|
||||
return self;
|
||||
}
|
||||
|
|
@ -2768,11 +2958,25 @@
|
|||
this.resendSchoolInvitation = resendSchoolInvitation;
|
||||
this.deleteSchoolTeacher = deleteSchoolTeacher;
|
||||
this.deleteSchoolStudent = deleteSchoolStudent;
|
||||
this.getRetailer = getRetailer;
|
||||
this.updateRetailer = updateRetailer;
|
||||
this.updateRetailerAvatar = updateRetailerAvatar;
|
||||
this.deleteRetailerAvatar = deleteRetailerAvatar;
|
||||
this.generateRetailerFilePickerPolicy = generateRetailerFilePickerPolicy;
|
||||
this.listRetailerInvitations = listRetailerInvitations;
|
||||
this.createRetailerInvitation = createRetailerInvitation;
|
||||
this.deleteRetailerInvitation = deleteRetailerInvitation;
|
||||
this.resendRetailerInvitation = resendRetailerInvitation;
|
||||
this.deleteRetailerTeacher = deleteRetailerTeacher;
|
||||
this.deleteRetailerStudent = deleteRetailerStudent;
|
||||
this.listTeacherDistributions = listTeacherDistributions;
|
||||
this.lessonStartTime = lessonStartTime;
|
||||
this.createReview = createReview;
|
||||
this.askSearchHelp = askSearchHelp;
|
||||
this.ratingDecision = ratingDecision;
|
||||
this.posaActivate = posaActivate;
|
||||
this.posaClaim = posaClaim;
|
||||
this.sendRetailerCustomerEmail = sendRetailerCustomerEmail;
|
||||
return this;
|
||||
};
|
||||
})(window,jQuery);
|
||||
|
|
|
|||
|
|
@ -298,7 +298,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
monitorPlaybackTimeout = setTimeout(monitorRecordingPlayback, 500);
|
||||
if (setTimeout) {
|
||||
monitorPlaybackTimeout = setTimeout(monitorRecordingPlayback, 500);
|
||||
}
|
||||
}
|
||||
|
||||
function monitorRecordingPlayback() {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
//= require ./react-components/stores/UserActivityStore
|
||||
//= require ./react-components/stores/LessonTimerStore
|
||||
//= require ./react-components/stores/SchoolStore
|
||||
//= require ./react-components/stores/RetailerStore
|
||||
//= require ./react-components/stores/JamBlasterStore
|
||||
//= require ./react-components/stores/StripeStore
|
||||
//= require ./react-components/stores/AvatarStore
|
||||
|
|
|
|||
|
|
@ -0,0 +1,398 @@
|
|||
context = window
|
||||
rest = context.JK.Rest()
|
||||
logger = context.JK.logger
|
||||
|
||||
AppStore = context.AppStore
|
||||
LocationActions = context.LocationActions
|
||||
RetailerActions = context.RetailerActions
|
||||
RetailerStore = context.RetailerStore
|
||||
UserStore = context.UserStore
|
||||
|
||||
profileUtils = context.JK.ProfileUtils
|
||||
|
||||
@AccountRetailerScreen = React.createClass({
|
||||
|
||||
mixins: [
|
||||
ICheckMixin,
|
||||
Reflux.listenTo(AppStore, "onAppInit"),
|
||||
Reflux.listenTo(RetailerStore, "onRetailerChanged")
|
||||
Reflux.listenTo(UserStore, "onUserChanged")
|
||||
]
|
||||
|
||||
shownOnce: false
|
||||
screenVisible: false
|
||||
|
||||
TILE_ACCOUNT: 'account'
|
||||
TILE_TEACHERS: 'teachers'
|
||||
TILE_SALES: 'sales'
|
||||
TILE_AGREEMENT: 'agreement'
|
||||
|
||||
TILES: ['account', 'teachers', 'sales', 'agreement']
|
||||
|
||||
onAppInit: (@app) ->
|
||||
@app.bindScreen('account/retailer', {beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide})
|
||||
|
||||
onRetailerChanged: (retailerState) ->
|
||||
@setState(retailerState)
|
||||
|
||||
onUserChanged: (userState) ->
|
||||
@noRetailerCheck(userState?.user)
|
||||
@setState({user: userState?.user})
|
||||
|
||||
componentDidMount: () ->
|
||||
@checkboxes = [{selector: 'input.slot-decision', stateKey: 'slot-decision'}]
|
||||
@root = $(@getDOMNode())
|
||||
@iCheckify()
|
||||
|
||||
componentDidUpdate: () ->
|
||||
@iCheckify()
|
||||
|
||||
checkboxChanged: (e) ->
|
||||
checked = $(e.target).is(':checked')
|
||||
|
||||
value = $(e.target).val()
|
||||
|
||||
#@setState({userSchedulingComm: value})
|
||||
|
||||
|
||||
beforeHide: (e) ->
|
||||
#ProfileActions.viewTeacherProfileDone()
|
||||
@screenVisible = false
|
||||
return true
|
||||
|
||||
beforeShow: (e) ->
|
||||
LocationActions.load()
|
||||
|
||||
noRetailerCheck: (user) ->
|
||||
if user?.id? && @screenVisible
|
||||
|
||||
if !user.owned_retailer_id?
|
||||
window.JK.Banner.showAlert("You are not the owner of a retailer in our systems. If you are, please contact support@jamkazam.com and we'll update your account.")
|
||||
return false
|
||||
else
|
||||
if !@shownOnce
|
||||
@shownOnce = true
|
||||
RetailerActions.refresh(user.owned_retailer_id)
|
||||
|
||||
return true
|
||||
|
||||
else
|
||||
return false
|
||||
|
||||
afterShow: (e) ->
|
||||
@screenVisible = true
|
||||
logger.debug("AccountRetailerScreen: afterShow")
|
||||
logger.debug("after show", @state.user)
|
||||
@noRetailerCheck(@state.user)
|
||||
|
||||
getInitialState: () ->
|
||||
{
|
||||
retailer: null,
|
||||
user: null,
|
||||
selected: 'account',
|
||||
updateErrors: null,
|
||||
retailerName: null,
|
||||
teacherInvitations: null,
|
||||
updating: false
|
||||
}
|
||||
|
||||
nameValue: () ->
|
||||
if this.state.retailerName?
|
||||
this.state.retailerName
|
||||
else
|
||||
this.state.retailer.name
|
||||
|
||||
nameChanged: (e) ->
|
||||
$target = $(e.target)
|
||||
val = $target.val()
|
||||
@setState({retailerName: val})
|
||||
|
||||
onCancel: (e) ->
|
||||
e.preventDefault()
|
||||
context.location.href = '/client#/account'
|
||||
|
||||
onUpdate: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
if this.state.updating
|
||||
return
|
||||
name = @root.find('input[name="name"]').val()
|
||||
region = @root.find('select[name="regions"]').val()
|
||||
city = @root.find('select[name="cities"]').val()
|
||||
password = @root.find('input[type="password"]').val()
|
||||
|
||||
@setState(updating: true)
|
||||
rest.updateRetailer({
|
||||
id: this.state.retailer.id,
|
||||
name: name,
|
||||
state: region,
|
||||
city: city,
|
||||
password:password
|
||||
|
||||
}).done((response) => @onUpdateDone(response)).fail((jqXHR) => @onUpdateFail(jqXHR))
|
||||
|
||||
onUpdateDone: (response) ->
|
||||
@setState({retailer: response, retailerName: null, updateErrors: null, updating: false})
|
||||
|
||||
@app.layout.notify({title: "update success", text: "Your retailer information has been successfully updated"})
|
||||
|
||||
onUpdateFail: (jqXHR) ->
|
||||
handled = false
|
||||
|
||||
@setState({updating: false})
|
||||
|
||||
if jqXHR.status == 422
|
||||
errors = JSON.parse(jqXHR.responseText)
|
||||
handled = true
|
||||
@setState({updateErrors: errors})
|
||||
|
||||
|
||||
if !handled
|
||||
@app.ajaxError(jqXHR, null, null)
|
||||
|
||||
inviteTeacher: () ->
|
||||
@app.layout.showDialog('invite-retailer-user', {d1: true})
|
||||
|
||||
resendInvitation: (id, e) ->
|
||||
e.preventDefault()
|
||||
rest.resendRetailerInvitation({
|
||||
id: this.state.retailer.id, invitation_id: id
|
||||
}).done((response) => @resendInvitationDone(response)).fail((jqXHR) => @resendInvitationFail(jqXHR))
|
||||
|
||||
resendInvitationDone: (response) ->
|
||||
@app.layout.notify({title: 'invitation resent', text: 'Invitation was resent to ' + response.email})
|
||||
|
||||
resendInvitationFail: (jqXHR) ->
|
||||
@app.ajaxError(jqXHR)
|
||||
|
||||
deleteInvitation: (id, e) ->
|
||||
e.preventDefault()
|
||||
rest.deleteRetailerInvitation({
|
||||
id: this.state.retailer.id, invitation_id: id
|
||||
}).done((response) => @deleteInvitationDone(id, response)).fail((jqXHR) => @deleteInvitationFail(jqXHR))
|
||||
|
||||
deleteInvitationDone: (id, response) ->
|
||||
context.RetailerActions.deleteInvitation(id)
|
||||
|
||||
deleteInvitationFail: (jqXHR) ->
|
||||
@app.ajaxError(jqXHR)
|
||||
|
||||
removeFromRetailer: (id, isTeacher, e) ->
|
||||
if isTeacher
|
||||
rest.deleteRetailerTeacher({id: this.state.retailer.id, teacher_id: id}).done((response) => @removeFromRetailerDone(response)).fail((jqXHR) => @removeFromRetailerFail(jqXHR))
|
||||
|
||||
removeFromRetailerDone: (retailer) ->
|
||||
context.JK.Banner.showNotice("User removed", "User was removed from your retailer.")
|
||||
context.RetailerActions.updateRetailer(retailer)
|
||||
|
||||
removeFromRetailerFail: (jqXHR) ->
|
||||
@app.ajaxError(jqXHR)
|
||||
|
||||
renderUser: (user, isTeacher) ->
|
||||
photo_url = user.photo_url
|
||||
if !photo_url?
|
||||
photo_url = '/assets/shared/avatar_generic.png'
|
||||
|
||||
`<div className="retailer-user">
|
||||
<div className="avatar">
|
||||
<img src={photo_url} />
|
||||
</div>
|
||||
<div className="usersname">
|
||||
{user.name}
|
||||
</div>
|
||||
<div className="user-actions">
|
||||
<a onClick={this.removeFromRetailer.bind(this, user.id, isTeacher)}>remove from retailer</a>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
|
||||
renderInvitation: (invitation) ->
|
||||
`<div key={invitation.id} className="retailer-invitation">
|
||||
<table>
|
||||
<tbody>
|
||||
<td className="description">{invitation.first_name} {invitation.last_name}</td>
|
||||
<td className="message">
|
||||
<div className="detail-block">has not yet accepted invitation<br/>
|
||||
<a className="resend" onClick={this.resendInvitation.bind(this, invitation.id)}>resend invitation</a>
|
||||
<a className="delete" onClick={this.deleteInvitation.bind(this, invitation.id)}>delete</a>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>`
|
||||
|
||||
renderTeachers: () ->
|
||||
teachers = []
|
||||
|
||||
if this.state.retailer.teachers? && this.state.retailer.teachers.length > 0
|
||||
for teacher in this.state.retailer.teachers
|
||||
if teacher.user
|
||||
teachers.push(@renderUser(teacher.user, true))
|
||||
else
|
||||
teachers = `<p>No teachers</p>`
|
||||
|
||||
teachers
|
||||
|
||||
renderTeacherInvitations: () ->
|
||||
invitations = []
|
||||
|
||||
if this.state.teacherInvitations? && this.state.teacherInvitations.length > 0
|
||||
for invitation in this.state.teacherInvitations
|
||||
invitations.push(@renderInvitation(invitation))
|
||||
else
|
||||
invitations = `<p>No pending invitations</p>`
|
||||
invitations
|
||||
|
||||
mainContent: () ->
|
||||
if !@state.user? || !@state.retailer?
|
||||
`<div className="loading">Loading...</div>`
|
||||
else if @state.selected == @TILE_ACCOUNT
|
||||
@account()
|
||||
else if @state.selected == @TILE_TEACHERS
|
||||
@teachers()
|
||||
else if @state.selected == @TILE_SALES
|
||||
@earnings()
|
||||
else if @state.selected == @TILE_AGREEMENT
|
||||
@agreement()
|
||||
else
|
||||
@account()
|
||||
|
||||
handleLocationChange: (country, region, city) ->
|
||||
logger.debug("handleLocationChange #{country} #{region} ${city}")
|
||||
@setState({city: city, region: region})
|
||||
|
||||
account: () ->
|
||||
|
||||
nameErrors = context.JK.reactSingleFieldErrors('name', @state.updateErrors)
|
||||
correspondenceEmailErrors = context.JK.reactSingleFieldErrors('correspondence_email', @state.updateErrors)
|
||||
nameClasses = classNames({name: true, error: nameErrors?, field: true})
|
||||
|
||||
cancelClasses = { "button-grey": true, "cancel" : true, disabled: this.state.updating }
|
||||
updateClasses = { "button-orange": true, "update" : true, disabled: this.state.updating }
|
||||
|
||||
processUrl = context.JK.makeAbsolute("/posa/#{this.state.retailer.slug}")
|
||||
processSaleUrl = `<a href={processUrl}>{processUrl}</a>`
|
||||
|
||||
`<div className="account-block info-block">
|
||||
<div className={nameClasses}>
|
||||
<label>Retailer Name:</label>
|
||||
<input type="text" name="name" value={this.nameValue()} onChange={this.nameChanged}/>
|
||||
{nameErrors}
|
||||
</div>
|
||||
<div className="field logo">
|
||||
<label>Retailer Logo:</label>
|
||||
<AvatarEditLink target={this.state.retailer} target_type="retailer"/>
|
||||
</div>
|
||||
|
||||
<SelectLocation defaultText={'Not Specified'} showCity={true} hideCountry={true} onItemChanged={this.handleLocationChange} selectedCountry={'US'} selectedCity={this.state.retailer.city} selectedRegion={this.state.retailer.state} />
|
||||
|
||||
|
||||
<div className="field password">
|
||||
|
||||
</div>
|
||||
|
||||
<div className="field password">
|
||||
<div className="scooter">
|
||||
<label>Retailer Username:</label>
|
||||
<label >Administrator</label>
|
||||
</div>
|
||||
<div className="scooter">
|
||||
<label>Retailer Password:</label>
|
||||
<input type="password" defaultValue="" placeholder="leave blank for no change"/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Process Sale URL:</label>
|
||||
{processSaleUrl} <span className="usage-hint">(enter Administrator/password to access this page)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4>Payments</h4>
|
||||
|
||||
<div className="field stripe-connect">
|
||||
<StripeConnect purpose='retailer' user={this.state.user}/>
|
||||
</div>
|
||||
|
||||
<div className="actions">
|
||||
<a className={classNames(cancelClasses)} onClick={this.onCancel}>CANCEL</a>
|
||||
<a className={classNames(updateClasses)} onClick={this.onUpdate}>UPDATE</a>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
|
||||
teachers: () ->
|
||||
teachers = @renderTeachers()
|
||||
teacherInvitations = @renderTeacherInvitations()
|
||||
|
||||
`<div className="members-block info-block">
|
||||
<div className="column column-left">
|
||||
<div>
|
||||
<h3>teachers:</h3>
|
||||
<a onClick={this.inviteTeacher} className="button-orange invite-dialog">INVITE TEACHER</a>
|
||||
<br className="clearall" />
|
||||
</div>
|
||||
<div className="teacher-invites">
|
||||
{teacherInvitations}
|
||||
</div>
|
||||
|
||||
<div className="teachers">
|
||||
{teachers}
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
earnings: () ->
|
||||
`<div className="earnings-block info-block">
|
||||
<p>Coming soon</p>
|
||||
</div>`
|
||||
|
||||
agreement: () ->
|
||||
`<div className="agreement-block info-block">
|
||||
<p>The agreement between your retailer and JamKazam is part of JamKazam's terms of service. You can find the
|
||||
complete terms of service <a href="/corp/terms" target="_blank">here</a>. And you can find the section that is
|
||||
most specific to the retailer terms <a href="/corp/terms" target="_blank">here</a>.</p>
|
||||
</div>`
|
||||
|
||||
selectionMade: (selection, e) ->
|
||||
e.preventDefault()
|
||||
@setState({selected: selection})
|
||||
|
||||
createTileLink: (i, tile) ->
|
||||
active = this.state.selected == tile
|
||||
classes = classNames({last: i == @TILES.length - 1, activeTile: active})
|
||||
|
||||
return `<div key={i} className="profile-tile"><a className={classes}
|
||||
onClick={this.selectionMade.bind(this, tile)}>{tile}</a></div>`
|
||||
|
||||
onCustomBack: (customBack, e) ->
|
||||
e.preventDefault()
|
||||
context.location = customBack
|
||||
|
||||
render: () ->
|
||||
mainContent = @mainContent()
|
||||
|
||||
profileSelections = []
|
||||
for tile, i in @TILES
|
||||
profileSelections.push(@createTileLink(i, tile, profileSelections))
|
||||
|
||||
profileNav = `<div className="profile-nav">
|
||||
{profileSelections}
|
||||
</div>`
|
||||
|
||||
`<div className="content-body-scroller">
|
||||
<div className="profile-header profile-head">
|
||||
<div className="store-header">retailer:</div>
|
||||
{profileNav}
|
||||
<div className="clearall"></div>
|
||||
</div>
|
||||
|
||||
<div className="profile-body">
|
||||
<div className="profile-wrapper">
|
||||
<div className="main-content">
|
||||
{mainContent}
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
|
|
@ -57,6 +57,7 @@ profileUtils = context.JK.ProfileUtils
|
|||
beforeHide: (e) ->
|
||||
#ProfileActions.viewTeacherProfileDone()
|
||||
@screenVisible = false
|
||||
return true
|
||||
|
||||
beforeShow: (e) ->
|
||||
|
||||
|
|
@ -92,7 +93,8 @@ profileUtils = context.JK.ProfileUtils
|
|||
schoolName: null,
|
||||
studentInvitations: null,
|
||||
teacherInvitations: null,
|
||||
updating: false
|
||||
updating: false,
|
||||
distributions: []
|
||||
}
|
||||
|
||||
isSchoolManaged: () ->
|
||||
|
|
@ -187,9 +189,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.")
|
||||
|
|
@ -203,12 +211,15 @@ profileUtils = context.JK.ProfileUtils
|
|||
if !photo_url?
|
||||
photo_url = '/assets/shared/avatar_generic.png'
|
||||
|
||||
mailto = "mailto:#{user.email}"
|
||||
|
||||
`<div className="school-user">
|
||||
<div className="avatar">
|
||||
<img src={photo_url} />
|
||||
<img src={photo_url}/>
|
||||
</div>
|
||||
<div className="usersname">
|
||||
{user.name}
|
||||
<span className="just-name">{user.name}</span>
|
||||
<span className="just-email"><a href={mailto}>{user.email}</a></span>
|
||||
</div>
|
||||
<div className="user-actions">
|
||||
<a onClick={this.removeFromSchool.bind(this, user.id, isTeacher)}>remove from school</a>
|
||||
|
|
@ -237,7 +248,8 @@ profileUtils = context.JK.ProfileUtils
|
|||
|
||||
if this.state.school.teachers? && this.state.school.teachers.length > 0
|
||||
for teacher in this.state.school.teachers
|
||||
teachers.push(@renderUser(teacher.user, true))
|
||||
if teacher.user
|
||||
teachers.push(@renderUser(teacher.user, true))
|
||||
else
|
||||
teachers = `<p>No teachers</p>`
|
||||
|
||||
|
|
@ -302,8 +314,39 @@ profileUtils = context.JK.ProfileUtils
|
|||
field: true
|
||||
})
|
||||
|
||||
cancelClasses = { "button-grey": true, "cancel" : true, disabled: this.state.updating }
|
||||
updateClasses = { "button-orange": true, "update" : true, disabled: this.state.updating }
|
||||
cancelClasses = {"button-grey": true, "cancel": true, disabled: this.state.updating}
|
||||
updateClasses = {"button-orange": true, "update": true, disabled: this.state.updating}
|
||||
|
||||
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
|
||||
by JamKazam</label>
|
||||
</div>
|
||||
<div className="scheduling_communication teacher">
|
||||
<input type="radio" name="scheduling_communication" readOnly={true} value="teacher"
|
||||
checked={!this.isSchoolManaged()}/><label>Teacher will manage scheduling of lessons</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={correspondenceEmailClasses}>
|
||||
<label>Correspondence Email:</label>
|
||||
<input type="text" name="correspondence_email" placeholder={ownerEmail} defaultValue={correspondenceEmail}
|
||||
disabled={correspondenceDisabled}/>
|
||||
|
||||
<div className="hint">All emails relating to lesson scheduling will go to this email if school owner manages
|
||||
scheduling.
|
||||
</div>
|
||||
{correspondenceEmailErrors}
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
|
||||
`<div className="account-block info-block">
|
||||
<div className={nameClasses}>
|
||||
|
|
@ -316,29 +359,7 @@ profileUtils = context.JK.ProfileUtils
|
|||
<AvatarEditLink target={this.state.school} target_type="school"/>
|
||||
</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
|
||||
by JamKazam</label>
|
||||
</div>
|
||||
<div className="scheduling_communication teacher">
|
||||
<input type="radio" name="scheduling_communication" readOnly={true} value="teacher"
|
||||
checked={!this.isSchoolManaged()}/><label>Teacher will manage scheduling of lessons</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={correspondenceEmailClasses}>
|
||||
<label>Correspondence Email:</label>
|
||||
<input type="text" name="correspondence_email" placeholder={ownerEmail} defaultValue={correspondenceEmail}
|
||||
disabled={correspondenceDisabled}/>
|
||||
|
||||
<div className="hint">All emails relating to lesson scheduling will go to this email if school owner manages
|
||||
scheduling.
|
||||
</div>
|
||||
{correspondenceEmailErrors}
|
||||
</div>
|
||||
{management}
|
||||
|
||||
<h4>Payments</h4>
|
||||
|
||||
|
|
@ -365,7 +386,7 @@ profileUtils = context.JK.ProfileUtils
|
|||
<div>
|
||||
<h3>teachers:</h3>
|
||||
<a onClick={this.inviteTeacher} className="button-orange invite-dialog">INVITE TEACHER</a>
|
||||
<br className="clearall" />
|
||||
<br className="clearall"/>
|
||||
</div>
|
||||
<div className="teacher-invites">
|
||||
{teacherInvitations}
|
||||
|
|
@ -397,6 +418,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
|
||||
|
|
|
|||
|
|
@ -31,18 +31,28 @@ AvatarStore = context.AvatarStore
|
|||
render: () ->
|
||||
if this.props.target?.photo_url?
|
||||
|
||||
testStudentUrl = "/school/#{this.props.target.id}/student?preview=true"
|
||||
testTeacherUrl = "/school/#{this.props.target.id}/teacher?preview=true"
|
||||
target_type = this.props.target_type
|
||||
|
||||
testStudentUrl = "/#{target_type}/#{this.props.target.id}/student?preview=true"
|
||||
testTeacherUrl = "/#{target_type}/#{this.props.target.id}/teacher?preview=true"
|
||||
|
||||
if target_type == 'school'
|
||||
previewArea = `<div className="hint">See how it will look to
|
||||
<a href={testStudentUrl} target="_blank">students</a> and
|
||||
<a href={testTeacherUrl} target="_blank">teachers</a>
|
||||
</div>`
|
||||
else
|
||||
previewArea = `<div className="hint">See how it will look to
|
||||
<a href={testTeacherUrl} target="_blank">teachers</a>
|
||||
</div>`
|
||||
|
||||
|
||||
`<div className="avatar-edit-link">
|
||||
<img src={this.props.target.photo_url}></img>
|
||||
<br/>
|
||||
<a onClick={this.startUpdate}>change/update logo</a><br/>
|
||||
|
||||
<div className="hint">See how it will look to
|
||||
<a href={testStudentUrl} target="_blank">students</a> and
|
||||
<a href={testTeacherUrl} target="_blank">teachers</a>
|
||||
</div>
|
||||
{previewArea}
|
||||
</div>`
|
||||
else
|
||||
`<div className="avatar-edit-link">
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -234,7 +234,7 @@ UserStore = context.UserStore
|
|||
booked: (response) ->
|
||||
@setState({updating: false})
|
||||
UserActions.refresh()
|
||||
if response.user['has_stored_credit_card?'] || @state.school_on_school
|
||||
if response.user['has_stored_credit_card?'] || @state.school_on_school || response.posa_card_id?
|
||||
context.JK.Banner.showNotice("Lesson Requested","The teacher has been notified of your lesson request, and should respond soon.<br/><br/>We've taken you back to the JamClass home page, where you can check the status of this lesson, as well as any other past and future lessons.")
|
||||
url = "/client#/jamclass/lesson-booking/#{response.id}"
|
||||
url = "/client#/jamclass"
|
||||
|
|
@ -441,11 +441,15 @@ UserStore = context.UserStore
|
|||
|
||||
|
||||
if @isTestDrive()
|
||||
credits = this.state.user.remaining_test_drives
|
||||
if this.state.user.jamclass_credits > 0
|
||||
credits = this.state.user.jamclass_credits
|
||||
|
||||
header = `<h2>book testdrive lesson</h2>`
|
||||
if @state.user?.remaining_test_drives == 1
|
||||
if credits == 1
|
||||
testDriveLessons = "1 TestDrive lesson credit"
|
||||
else
|
||||
testDriveLessons = "#{this.state.user.remaining_test_drives} TestDrive lesson credits"
|
||||
testDriveLessons = "#{credits} TestDrive lesson credits"
|
||||
|
||||
actions = `<div className="actions left">
|
||||
<a className={cancelClasses} onClick={this.onCancel}>CANCEL</a>
|
||||
|
|
@ -461,7 +465,7 @@ UserStore = context.UserStore
|
|||
else if this.state.user.lesson_package_type_id == 'test-drive-2'
|
||||
testDriveCredits = 2
|
||||
|
||||
if this.state.user.remaining_test_drives > 0
|
||||
if credits > 0
|
||||
testDriveBookingInfo = `<div className="booking-info">
|
||||
|
||||
<p>You are booking a single 30-minute TestDrive session.</p>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
context = window
|
||||
RetailerStore = context.RetailerStore
|
||||
|
||||
@InviteRetailerUserDialog = React.createClass({
|
||||
|
||||
mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(RetailerStore, "onRetailerChanged")]
|
||||
teacher: false
|
||||
|
||||
beforeShow: (args) ->
|
||||
logger.debug("InviteRetailerUserDialog.beforeShow", args.d1)
|
||||
@firstName = ''
|
||||
@lastName = ''
|
||||
@email = ''
|
||||
|
||||
@setState({inviteErrors: null, teacher: args.d1})
|
||||
afterHide: () ->
|
||||
|
||||
onRetailerChanged: (retailerState) ->
|
||||
@setState(retailerState)
|
||||
|
||||
onAppInit: (@app) ->
|
||||
dialogBindings = {
|
||||
'beforeShow': @beforeShow,
|
||||
'afterHide': @afterHide
|
||||
};
|
||||
|
||||
@app.bindDialog('invite-retailer-user', dialogBindings);
|
||||
|
||||
componentDidMount: () ->
|
||||
@root = $(@getDOMNode())
|
||||
|
||||
getInitialState: () ->
|
||||
{inviteErrors: null, retailer: null, sending: false}
|
||||
|
||||
doCancel: (e) ->
|
||||
e.preventDefault()
|
||||
@app.layout.closeDialog('invite-retailer-user', true);
|
||||
|
||||
doInvite: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
if this.state.sending
|
||||
console.log("sending already")
|
||||
return
|
||||
|
||||
|
||||
email = @root.find('input[name="email"]').val()
|
||||
lastName = @root.find('input[name="last_name"]').val()
|
||||
firstName = @root.find('input[name="first_name"]').val()
|
||||
retailer = context.RetailerStore.getState().retailer
|
||||
@setState({inviteErrors: null, sending: true})
|
||||
rest.createRetailerInvitation({
|
||||
id: retailer.id,
|
||||
as_teacher: this.state.teacher,
|
||||
email: email,
|
||||
last_name: lastName,
|
||||
first_name: firstName
|
||||
}).done((response) => @createDone(response)).fail((jqXHR) => @createFail(jqXHR))
|
||||
|
||||
createDone: (response) ->
|
||||
console.log("invitation added", response)
|
||||
@setState({inviteErrors:null, sending: false})
|
||||
context.RetailerActions.addInvitation(this.state.teacher, response)
|
||||
context.JK.Banner.showNotice("invitation sent", "Your invitation has been sent!")
|
||||
@app.layout.closeDialog('invite-retailer-user')
|
||||
|
||||
createFail: (jqXHR) ->
|
||||
handled = false
|
||||
|
||||
if jqXHR.status == 422
|
||||
errors = JSON.parse(jqXHR.responseText)
|
||||
@setState({inviteErrors: errors, sending: false})
|
||||
handled = true
|
||||
|
||||
if !handled
|
||||
@app.ajaxError(jqXHR, null, null)
|
||||
|
||||
|
||||
close: (e) ->
|
||||
e.preventDefault()
|
||||
@app.layout.closeDialog('invite-retailer-user');
|
||||
|
||||
|
||||
renderRetailer: () ->
|
||||
firstNameErrors = context.JK.reactSingleFieldErrors('first_name', @state.inviteErrors)
|
||||
lastNameErrors = context.JK.reactSingleFieldErrors('last_name', @state.inviteErrors)
|
||||
emailErrors = context.JK.reactSingleFieldErrors('email', @state.inviteErrors)
|
||||
|
||||
firstNameClasses = classNames({first_name: true, error: firstNameErrors?, field: true})
|
||||
lastNameClasses = classNames({last_name: true, error: lastNameErrors?, field: true})
|
||||
emailClasses = classNames({email: true, error: emailErrors?, field: true})
|
||||
sendInvitationClasses = classNames({'button-orange': true, disabled: this.state.sending})
|
||||
|
||||
if @state.teacher
|
||||
title = 'invite teacher'
|
||||
help = `<p>Send invitations to teachers who teach through your music store. When your teachers accept this invitation to create teacher accounts on JamKazam, you can easily send emails to customers who purchase online lessons pointing these customers to your preferred teachers from your store. </p>`
|
||||
else
|
||||
title = 'invite student'
|
||||
help = `<p>
|
||||
Shouldn't be here...
|
||||
</p>`
|
||||
|
||||
`<div>
|
||||
<div className="content-head">
|
||||
<img className="content-icon" src="/assets/content/icon_add.png" height={19} width={19}/>
|
||||
|
||||
<h1>{title}</h1>
|
||||
</div>
|
||||
<div className="dialog-inner">
|
||||
|
||||
{help}
|
||||
|
||||
<div className={firstNameClasses}>
|
||||
<label>First Name: </label>
|
||||
<input type="text" defaultValue={this.firstName} name="first_name"/>
|
||||
{firstNameErrors}
|
||||
</div>
|
||||
|
||||
<div className={lastNameClasses}>
|
||||
<label>Last Name: </label>
|
||||
<input type="text" defaultValue={this.lastName} name="last_name"/>
|
||||
{lastNameErrors}
|
||||
</div>
|
||||
|
||||
<div className={emailClasses}>
|
||||
<label>Email Name: </label>
|
||||
<input type="text" defaultValue={this.email} name="email"/>
|
||||
{emailErrors}
|
||||
</div>
|
||||
|
||||
<div className="actions">
|
||||
<a onClick={this.doCancel} className="button-grey">CANCEL</a>
|
||||
<a onClick={this.doInvite} className={sendInvitationClasses}>SEND INVITATION</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
render: () ->
|
||||
retailer = this.state.retailer
|
||||
|
||||
if !retailer?
|
||||
return `<div>no retailer</div>`
|
||||
|
||||
@renderRetailer()
|
||||
|
||||
|
||||
})
|
||||
|
|
@ -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, sending: false}
|
||||
|
||||
doCancel: (e) ->
|
||||
e.preventDefault()
|
||||
|
|
@ -36,39 +39,93 @@ context = window
|
|||
doInvite: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
if this.state.sending
|
||||
console.log("sending already")
|
||||
return
|
||||
|
||||
email = @root.find('input[name="email"]').val()
|
||||
lastName = @root.find('input[name="last_name"]').val()
|
||||
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))
|
||||
@setState({inviteErrors: null, sending: true})
|
||||
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(@state.teacher, response)
|
||||
createDone: (response) ->
|
||||
console.log("invitation added", response)
|
||||
@setState({inviteErrors:null, sending: false})
|
||||
context.SchoolActions.addInvitation(this.state.teacher, response)
|
||||
context.JK.Banner.showNotice("invitation sent", "Your invitation has been sent!")
|
||||
@app.layout.closeDialog('invite-school-user')
|
||||
|
||||
createFail: (jqXHR) ->
|
||||
|
||||
handled = false
|
||||
|
||||
if jqXHR.status == 422
|
||||
errors = JSON.parse(jqXHR.responseText)
|
||||
@setState({inviteErrors: errors})
|
||||
@setState({inviteErrors: errors, sending: false})
|
||||
handled = true
|
||||
|
||||
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)
|
||||
|
||||
firstNameClasses = classNames({first_name: true, error: firstNameErrors?, field: true})
|
||||
lastNameClasses = classNames({last_name: true, error: lastNameErrors?, field: true})
|
||||
emailClasses = classNames({email: true, error: emailErrors?, field: true})
|
||||
firstNameClasses = classNames({first_name: true, error: firstNameErrors?, field: true})
|
||||
lastNameClasses = classNames({last_name: true, error: lastNameErrors?, field: true})
|
||||
emailClasses = classNames({email: true, error: emailErrors?, field: true})
|
||||
sendInvitationClasses = classNames({'button-orange': true, disabled: this.state.sending})
|
||||
|
||||
if @state.teacher
|
||||
title = 'invite teacher'
|
||||
|
|
@ -98,27 +155,39 @@ context = window
|
|||
|
||||
<div className={firstNameClasses}>
|
||||
<label>First Name: </label>
|
||||
<input type="text" defaultValue={this.firstName} name="first_name" />
|
||||
<input type="text" defaultValue={this.firstName} name="first_name"/>
|
||||
{firstNameErrors}
|
||||
</div>
|
||||
|
||||
<div className={lastNameClasses}>
|
||||
<label>Last Name: </label>
|
||||
<input type="text" defaultValue={this.lastName} name="last_name" />
|
||||
<input type="text" defaultValue={this.lastName} name="last_name"/>
|
||||
{lastNameErrors}
|
||||
</div>
|
||||
|
||||
<div className={emailClasses}>
|
||||
<label>Email Name: </label>
|
||||
<input type="text" defaultValue={this.email} name="email" />
|
||||
<input type="text" defaultValue={this.email} name="email"/>
|
||||
{emailErrors}
|
||||
</div>
|
||||
|
||||
<div className="actions">
|
||||
<a onClick={this.doCancel} className="button-grey">CANCEL</a>
|
||||
<a onClick={this.doInvite} className="button-orange">SEND INVITATION</a>
|
||||
<a onClick={this.doInvite} className={sendInvitationClasses}>SEND INVITATION</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
render: () ->
|
||||
school = this.state.school
|
||||
|
||||
if !school?
|
||||
return `<div>no school</div>`
|
||||
|
||||
if school.education && !@state.teacher
|
||||
@renderEducation()
|
||||
else
|
||||
@renderSchool()
|
||||
|
||||
|
||||
})
|
||||
|
|
@ -32,10 +32,21 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackStateChanged'))
|
|||
tempos : [ 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 63, 66, 69, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 126, 132, 138, 144, 152, 160, 168, 176, 184, 192, 200, 208 ]
|
||||
|
||||
onJamTrackStateChanged: (jamTrackState) ->
|
||||
if window.unloaded
|
||||
return
|
||||
|
||||
if window.closed
|
||||
return
|
||||
|
||||
@monitorControls(@state.controls, @state.mediaSummary, jamTrackState)
|
||||
@setState({jamTrackState: jamTrackState})
|
||||
|
||||
onMediaStateChanged: (changes) ->
|
||||
if window.unloaded
|
||||
return
|
||||
if window.closed
|
||||
return
|
||||
|
||||
if changes.playbackStateChanged
|
||||
if @state.controls?
|
||||
if changes.playbackState == 'play_start'
|
||||
|
|
@ -51,6 +62,8 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackStateChanged'))
|
|||
@setState({time: changes.time})
|
||||
|
||||
onInputsChanged: (sessionMixers) ->
|
||||
if window.unloaded
|
||||
return
|
||||
|
||||
session = sessionMixers.session
|
||||
mixers = sessionMixers.mixers
|
||||
|
|
@ -60,9 +73,16 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackStateChanged'))
|
|||
metro = mixers.metro
|
||||
|
||||
@monitorControls(@state.controls, mediaSummary, @state.jamTrackState)
|
||||
@setState({mediaSummary: mediaSummary, metro: metro})
|
||||
|
||||
@updateMetronomeDetails(metro, @state.initializedMetronomeControls)
|
||||
state = {mediaSummary: mediaSummary, metro: metro}
|
||||
try
|
||||
@setState(state)
|
||||
catch e
|
||||
logger.error('MediaControls: unable to set state', state, e)
|
||||
try
|
||||
@updateMetronomeDetails(metro, @state.initializedMetronomeControls)
|
||||
catch e
|
||||
logger.error('MediaControls: unable to update metronome details', e)
|
||||
|
||||
updateMetronomeDetails: (metro, initializedMetronomeControls) ->
|
||||
logger.debug("MediaControls: setting tempo/sound/cricket", metro)
|
||||
|
|
@ -197,6 +217,8 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackStateChanged'))
|
|||
@updateMetronomeDetails(metro, true)
|
||||
@setState({initializedMetronomeControls: true})
|
||||
|
||||
shouldComponentUpdate:() ->
|
||||
return !window.unloaded
|
||||
|
||||
componentDidUpdate: (prevProps, prevState) ->
|
||||
@tryPrepareMetronome(@state.metro)
|
||||
|
|
|
|||
|
|
@ -60,14 +60,33 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
|
|||
session = sessionMixers.session
|
||||
mixers = sessionMixers.mixers
|
||||
|
||||
if @unloaded
|
||||
#console.log("PopupMediaControls unloaded. ignore onMixersChnaged")
|
||||
return
|
||||
if window.closed
|
||||
return
|
||||
@setState(@updateFromMixerHelper(mixers, session))
|
||||
|
||||
|
||||
onMediaStateChanged: (changes) ->
|
||||
if @unloaded
|
||||
#console.log("PopupMediaControls unloaded. ignore onMixersChnaged")
|
||||
return
|
||||
|
||||
if window.closed
|
||||
return
|
||||
|
||||
if changes.currentTimeChanged && @root?
|
||||
@setState({time: changes.time})
|
||||
|
||||
onJamTrackChanged: (changes) ->
|
||||
if @unloaded
|
||||
#console.log("PopupMediaControls unloaded. ignore onMixersChnaged")
|
||||
return
|
||||
|
||||
if window.closed
|
||||
return
|
||||
|
||||
logger.debug("PopupMediaControls: jamtrack changed", changes)
|
||||
@setState({jamTrackState: changes})
|
||||
|
||||
|
|
@ -446,7 +465,7 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
|
|||
|
||||
`<div className="media-controls-popup">
|
||||
{header}
|
||||
<MediaControls disabled={this.state.downloadingJamTrack || this.disableLoading}/>
|
||||
<MediaControls unloaded={this.unloaded} disabled={this.state.downloadingJamTrack || this.disableLoading}/>
|
||||
{extraControls}
|
||||
<div className="actions">
|
||||
{helpButton}
|
||||
|
|
@ -454,7 +473,12 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
|
|||
</div>
|
||||
</div>`
|
||||
|
||||
|
||||
windowUnloaded: () ->
|
||||
logger.debug('PopupMediaControls: window uploaded')
|
||||
@unloaded = true
|
||||
window.unloaded = true
|
||||
|
||||
SessionActions.closeMedia(false) unless window.DontAutoCloseMedia
|
||||
|
||||
toggleMyMixes: (e) ->
|
||||
|
|
@ -691,6 +715,10 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
|
|||
@resizeWindow()
|
||||
setTimeout(@resizeWindow, 1000)
|
||||
|
||||
shouldComponentUpdate: () ->
|
||||
console.log("THIS UNLOADED", @unloaded)
|
||||
return !@unloaded
|
||||
|
||||
resizeWindow: () =>
|
||||
$container = $('#minimal-container')
|
||||
width = $container.width()
|
||||
|
|
|
|||
|
|
@ -4,59 +4,106 @@ logger = context.JK.logger
|
|||
|
||||
@SelectLocation = React.createClass({
|
||||
|
||||
mixins: [Reflux.listenTo(@LocationStore,"onLocationsChanged")]
|
||||
mixins: [Reflux.listenTo(@LocationStore, "onLocationsChanged")]
|
||||
|
||||
propTypes: {
|
||||
onItemChanged: React.PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
getInitialState:() ->
|
||||
{selectedCountry: null, countries:{US: {name: 'United States', region: null}}}
|
||||
getInitialState: () ->
|
||||
|
||||
{selectedCountry: null, countries: LocationStore.countries || {US: {name: 'United States', regions: []}}}
|
||||
|
||||
onLocationsChanged: (countries) ->
|
||||
console.log("countires in ", countries)
|
||||
@setState({countries: countries})
|
||||
|
||||
onCountryChanged: (e) ->
|
||||
val = $(e.target).val()
|
||||
@changed(val, null)
|
||||
@setState({selectedCountry: val, selectedRegion: null })
|
||||
@changed(val, null, null)
|
||||
@setState({selectedCountry: val, selectedRegion: null, selectedCity: null})
|
||||
|
||||
if val?
|
||||
LocationActions.selectCountry(val)
|
||||
|
||||
onRegionChanged: (e) ->
|
||||
val = $(e.target).val()
|
||||
@changed(@state.selectedCountry, val)
|
||||
@setState({selectedRegion: val })
|
||||
@changed(this.currentCountry(), val, null)
|
||||
@setState({selectedRegion: val, selectedCity: null})
|
||||
|
||||
changed: (country, region) ->
|
||||
if val? && this.props.showCity
|
||||
LocationActions.selectRegion(this.currentCountry(), val)
|
||||
|
||||
onCityChanged: (e) ->
|
||||
val = $(e.target).val()
|
||||
@changed(this.currentCountry(), this.currentRegion(), val)
|
||||
@setState({selectedCity: val})
|
||||
|
||||
|
||||
changed: (country, region, city) ->
|
||||
if country == ''
|
||||
country = null
|
||||
|
||||
if region == ''
|
||||
region = null
|
||||
|
||||
@props.onItemChanged(country, region)
|
||||
if city == ''
|
||||
city = null
|
||||
|
||||
@props.onItemChanged(country, region, city)
|
||||
|
||||
currentCity: () ->
|
||||
this.state.selectedCity || this.props.selectedCity
|
||||
|
||||
currentCountry: () ->
|
||||
this.state.selectedCountry || this.props.selectedCountry || 'US'
|
||||
|
||||
currentRegion: () ->
|
||||
this.state.selectedRegion || this.props.selectedRegion
|
||||
|
||||
defaultText: () ->
|
||||
if this.props.defaultText?
|
||||
this.props.defaultText
|
||||
else
|
||||
'Any'
|
||||
|
||||
render: () ->
|
||||
countries = [`<option key="" value="">Any</option>`]
|
||||
countries = [`<option key="" value="">{this.defaultText()}</option>`]
|
||||
for countryId, countryInfo of @state.countries
|
||||
countries.push(`<option key={countryId} value={countryId}>{countryInfo.name}</option>`)
|
||||
|
||||
country = @state.countries[@state.selectedCountry]
|
||||
|
||||
regions = [`<option key="" value="">Any</option>`]
|
||||
country = @state.countries[this.currentCountry()]
|
||||
|
||||
regions = [`<option key="" value="">{this.defaultText()}</option>`]
|
||||
|
||||
cities = [`<option key="" value="">{this.defaultText()}</option>`]
|
||||
|
||||
|
||||
if country? && country.regions
|
||||
for region in country.regions
|
||||
regions.push(`<option key={region.id} value={region.id}>{region.name}</option>`)
|
||||
|
||||
if this.currentRegion() == region.id && this.props.showCity
|
||||
for city in region.cities
|
||||
cities.push(`<option key={city} value={city}>{city}</option>`)
|
||||
|
||||
if !this.props.hideCountry
|
||||
countryJsx = `<div><h3>Country:</h3>
|
||||
<select name="countries" onChange={this.onCountryChanged} value={this.currentCountry()}>{countries}</select>
|
||||
</div>`
|
||||
|
||||
disabled = regions.length == 1
|
||||
if this.props.showCity
|
||||
cityJsx = `<div><h3>City:</h3>
|
||||
<select name="cities" disabled={cities.length == 1} onChange={this.onCityChanged} value={this.currentCity()}>{cities}</select>
|
||||
</div>`
|
||||
|
||||
`<div className="SelectLocation">
|
||||
<h3>Country:</h3>
|
||||
<select name="countries" onChange={this.onCountryChanged} value={this.state.selectedCountry}>{countries}</select>
|
||||
{countryJsx}
|
||||
<h3>State/Region:</h3>
|
||||
<select name="regions" disabled={disabled} onChange={this.onRegionChanged} value={this.state.selectedRegion}>{regions}</select>
|
||||
<select name="regions" disabled={disabled} onChange={this.onRegionChanged}
|
||||
value={this.currentRegion()}>{regions}</select>
|
||||
{cityJsx}
|
||||
</div>`
|
||||
})
|
||||
|
|
@ -28,7 +28,7 @@ ChannelGroupIds = context.JK.ChannelGroupIds
|
|||
SessionActions.downloadingJamTrack(false)
|
||||
@setState({downloadJamTrack: null})
|
||||
|
||||
SessionActions.closeMedia(true)
|
||||
SessionActions.closeMedia.trigger(true)
|
||||
|
||||
|
||||
#inputsChangedProcessed: (state) ->
|
||||
|
|
|
|||
|
|
@ -142,15 +142,20 @@ proficiencyDescriptionMap = {
|
|||
|
||||
showSideBubble: () ->
|
||||
# :remaining_test_drives, :can_buy_test_drive?
|
||||
if @state.user?['has_booked_test_drive_with_student']
|
||||
if @state.user?['same_school_with_student']
|
||||
@showBuyNormalLessonBubble()
|
||||
else if @user['jamclass_credits'] > 0
|
||||
@showUseRemainingJamClassCreditsBubble()
|
||||
else
|
||||
if @user['remaining_test_drives'] > 0
|
||||
@showUseRemainingTestDrivesBubble()
|
||||
else if @user['can_buy_test_drive?']
|
||||
@showBuyTestDriveBubble()
|
||||
else
|
||||
if @state.user?['has_booked_test_drive_with_student']
|
||||
@showBuyNormalLessonBubble()
|
||||
else
|
||||
if @user['remaining_test_drives'] > 0
|
||||
@showUseRemainingTestDrivesBubble()
|
||||
else if @user['can_buy_test_drive?']
|
||||
@showBuyTestDriveBubble()
|
||||
else
|
||||
@showBuyNormalLessonBubble()
|
||||
|
||||
hideSideBubble: () ->
|
||||
if @screen.btOff
|
||||
|
|
@ -159,6 +164,9 @@ proficiencyDescriptionMap = {
|
|||
showUseRemainingTestDrivesBubble: ( ) ->
|
||||
context.JK.HelpBubbleHelper.showUseRemainingTestDrives(@screen, @screen, @user, (() => @useRemainingTestDrives()))
|
||||
|
||||
showUseRemainingJamClassCreditsBubble: ( ) ->
|
||||
context.JK.HelpBubbleHelper.showUseRemainingJamClassCreditsBubble(@screen, @screen, @user, (() => @useRemainingTestDrives()))
|
||||
|
||||
showBuyTestDriveBubble: () ->
|
||||
context.JK.HelpBubbleHelper.showBuyTestDrive(@screen, @screen, @user, (() => @buyTestDrive()))
|
||||
|
||||
|
|
|
|||
|
|
@ -158,7 +158,10 @@ ProfileActions = @ProfileActions
|
|||
|
||||
rest.getTestDriveStatus({id: context.JK.currentUserId, teacher_id: user.id})
|
||||
.done((response) =>
|
||||
if response.remaining_test_drives == 0 && response['can_buy_test_drive?']
|
||||
if response.jamclass_credits > 0
|
||||
logger.debug('TeacherSearchScreen: user has jamclass credits available')
|
||||
window.location.href = '/client#/jamclass/book-lesson/test-drive_' + user.id
|
||||
else if response.remaining_test_drives == 0 && response['can_buy_test_drive?']
|
||||
logger.debug("TeacherSearchScreen: user offered test drive")
|
||||
#@app.layout.showDialog('try-test-drive', {d1: user.teacher.id})
|
||||
window.location.href = '/client#/jamclass/test-drive-selection/' + user.id
|
||||
|
|
@ -235,7 +238,7 @@ ProfileActions = @ProfileActions
|
|||
bookSingleBtn = null
|
||||
bookTestDriveBtn = null
|
||||
|
||||
if !school_on_school && (!@state.user? || @state.user.remaining_test_drives > 0 || @state.user['can_buy_test_drive?'])
|
||||
if !school_on_school && (!@state.user? || @state.user.jamclass_credits > 0 || @state.user.remaining_test_drives > 0 || @state.user['can_buy_test_drive?'])
|
||||
bookTestDriveBtn = `<a className="button-orange try-test-drive" onClick={this.bookTestDrive.bind(this, user)}>BOOK TESTDRIVE LESSON</a>`
|
||||
else
|
||||
bookSingleBtn = `<a className="button-orange try-normal" onClick={this.bookNormalLesson.bind(this, user)}>BOOK LESSON</a>`
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ context = window
|
|||
|
||||
load: {}
|
||||
selectCountry: {}
|
||||
selectRegion: {}
|
||||
})
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
context = window
|
||||
|
||||
@RetailerActions = Reflux.createActions({
|
||||
refresh: {},
|
||||
addInvitation: {},
|
||||
deleteInvitation: {}
|
||||
updateRetailer: {}
|
||||
})
|
||||
|
||||
|
|
@ -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 can’t
|
||||
assess the student’s 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 wasn’t 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, we’ve spent years designing, patenting, and building technology specifically to enable musicians
|
||||
to play online live in sync with studio quality audio. We’ve built a wide variety of critical online music
|
||||
performance features into this platform. And now we’ve 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 they’re learning in lessons to play with
|
||||
|
||||
others, which again reinforces and motivates students to stay engaged, as it’s 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, we’ll
|
||||
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>`
|
||||
})
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
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 = "SET UP SCHOOL"
|
||||
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 you’re interested in partnering, and we’ll follow up to answer your
|
||||
|
||||
questions.</p>
|
||||
|
||||
<p>If this is a good fit for your school, we’ll 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 we’ve 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,education_interest: true})
|
||||
.done((response) =>
|
||||
this.setState({done: true})
|
||||
context.location = '/client#/account/school'
|
||||
)
|
||||
.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_interest: true
|
||||
})
|
||||
.done((response) =>
|
||||
if response.autologin
|
||||
context.location = '/client#/account/school'
|
||||
else
|
||||
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})
|
||||
})
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
context = window
|
||||
rest = context.JK.Rest()
|
||||
|
||||
@JamClassRetailerLandingBottomPage = React.createClass({
|
||||
|
||||
render: () ->
|
||||
`<div className="top-container">
|
||||
<div className="row awesome jam-class teachers">
|
||||
<h2 className="awesome">How Our Retail Partner Program Can Help Your Store</h2>
|
||||
|
||||
<p>By simply adding our free countertop display of music lesson and JamTracks gift cards to your store, you can
|
||||
instantly be enabled to offer an amazing deal on music lessons to every customer who buys an instrument from
|
||||
your store. Students can connect with amazing teachers anywhere in the country, avoid the time and hassle of
|
||||
travel to/from lessons, and even record lessons to avoid forgetting what they’ve learned. Even if your store
|
||||
offers lessons through in-store teachers, often your customers may live too far away or may not want to deal
|
||||
with travel to get back to your store for lessons, and this is a great service you can offer, while earning
|
||||
about $100 per year per student for students who stick with their lessons, in addition to 30% on the initial
|
||||
gift card sale.</p>
|
||||
|
||||
<p>And for more advanced musicians who frequently wander into your store just to look around because they have
|
||||
“the bug”, JamTracks are a terrific product you can sell to both beginner and advanced musicians. JamTracks
|
||||
are full multitrack recordings of more than 4,000 popular songs. Your customers can solo a part they want to
|
||||
play to hear all its nuances, mute that part out to play with the rest of the band, slow it down to practice,
|
||||
record themselves playing along and share it with their friends on YouTube, and more. You’ll earn 30% margins
|
||||
on JamTracks gift cards as well.</p>
|
||||
|
||||
<p>Watch the videos below that explain and show our JamClass online music lesson service and our JamTracks
|
||||
products in more detail.</p>
|
||||
|
||||
<div className="testimonials jam-class retailer">
|
||||
<h3>JamClass Kudos</h3>
|
||||
|
||||
<div className="testimonial">
|
||||
<img src="/assets/landing/Julie Bonk - Jam Class - Speech Bubble.png"
|
||||
className="testimonial-speech-bubble"/>
|
||||
<img src="/assets/landing/Julie Bonk - Avatar.png" className="testimonial-avatar"/>
|
||||
<h4><strong>Julie Bonk</strong></h4>
|
||||
|
||||
<div className="testiminal-background">
|
||||
Oft-recorded pianist, teacher, mentor to Grammy winner Norah Jones and Scott Hoying of Pentatonix
|
||||
</div>
|
||||
</div>
|
||||
<div className="testimonial">
|
||||
<img src="/assets/landing/Carl Brown - Jam Class - Speech Bubble.png"
|
||||
className="testimonial-speech-bubble"/>
|
||||
<img src="/assets/landing/Carl Brown - Avatar.png" className="testimonial-avatar"/>
|
||||
<a rel="external" href="https://www.youtube.com/channel/UCvnfBBzEizi1T5unOXNCxdQ"><img
|
||||
src="/assets/landing/Carl Brown - YouTube.png" className="testimonial-youtube"/></a>
|
||||
<h4><strong>Carl Brown</strong> of GuitarLessions365</h4>
|
||||
</div>
|
||||
<div className="testimonial">
|
||||
<img src="/assets/landing/Justin Pierce - Jam Class - Speech Bubble.png"
|
||||
className="testimonial-speech-bubble"/>
|
||||
<img src="/assets/landing/Justin Pierce - Avatar.png" className="testimonial-avatar"/>
|
||||
<h4><strong>Justin Pierce</strong></h4>
|
||||
|
||||
<div className="testiminal-background">
|
||||
Masters degree in jazz studies, performer in multiple bands, saxophone instructor
|
||||
</div>
|
||||
</div>
|
||||
<div className="testimonial">
|
||||
<img src="/assets/landing/Dave Sebree - Jam Class - Speech Bubble.png"
|
||||
className="testimonial-speech-bubble"/>
|
||||
<img src="/assets/landing/Dave Sebree - Avatar.png" className="testimonial-avatar"/>
|
||||
<h4><strong>Dave Sebree</strong></h4>
|
||||
|
||||
<div className="testiminal-background">
|
||||
Founder of Austin School of Music, Gibson-endorsed guitarist, touring musician
|
||||
</div>
|
||||
</div>
|
||||
<div className="testimonial">
|
||||
<img src="/assets/landing/Sara Nelson - Jam Class - Speech Bubble.png"
|
||||
className="testimonial-speech-bubble"/>
|
||||
<img src="/assets/landing/Sara Nelson - Avatar.png" className="testimonial-avatar"/>
|
||||
<h4><strong>Sara Nelson</strong></h4>
|
||||
|
||||
<div className="testiminal-background">
|
||||
Cellist for Austin Lyric Opera, frequently recorded with major artists
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="video-section">
|
||||
<div className="video-wrapper">
|
||||
<div className="video-container">
|
||||
<iframe src="https://www.youtube.com/v/Y9m16G_86oU" frameborder="0" allowfullscreen="allowfullscreen"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="video-section second">
|
||||
<div className="video-wrapper">
|
||||
<div className="video-container">
|
||||
<iframe src="https://www.youtube.com/v/-rHfJggbgqk" frameborder="0" allowfullscreen="allowfullscreen"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br className="clearall"/>
|
||||
</div>`
|
||||
})
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
context = window
|
||||
rest = context.JK.Rest()
|
||||
|
||||
@JamClassRetailerLandingPage = React.createClass({
|
||||
|
||||
render: () ->
|
||||
loggedIn = context.JK.currentUserId?
|
||||
|
||||
if this.state.done
|
||||
ctaButtonText = 'sending you in...'
|
||||
else if this.state.processing
|
||||
ctaButtonText = 'hold on...'
|
||||
else
|
||||
if loggedIn
|
||||
ctaButtonText = "SIGN UP"
|
||||
else
|
||||
ctaButtonText = "SIGN UP"
|
||||
|
||||
if loggedIn
|
||||
register = `<button className={classNames({'cta-button' : true, 'processing': this.state.processing})}
|
||||
onClick={this.ctaClick}>{ctaButtonText}</button>`
|
||||
else
|
||||
if this.state.loginErrors?
|
||||
for key, value of this.state.loginErrors
|
||||
break
|
||||
|
||||
errorText = context.JK.getFullFirstError(key, this.state.loginErrors,
|
||||
{email: 'Email', password: 'Password', 'terms_of_service': 'The terms of service'})
|
||||
|
||||
register = `<div className="register-area jam-class">
|
||||
<div className={classNames({'errors': true, 'active': this.state.loginErrors})}>
|
||||
{errorText}
|
||||
</div>
|
||||
<form className="jamtrack-signup-form">
|
||||
<label>Email: </label><input type="text" name="email"/>
|
||||
<label>Password: </label><input type="password" name="password"/>
|
||||
|
||||
<div className="clearall"/>
|
||||
<input className="terms-checkbox" type="checkbox" name="terms"/><label className="terms-help">I have read and
|
||||
agree to the JamKazam <a href="/corp/terms" onClick={this.termsClicked}>terms of service</a></label>
|
||||
|
||||
<div className="clearall"/>
|
||||
<button className={classNames({'cta-button' : true, 'processing': this.state.processing})}
|
||||
onClick={this.ctaClick}>{ctaButtonText}</button>
|
||||
</form>
|
||||
</div>`
|
||||
|
||||
|
||||
`<div className="top-container">
|
||||
<div className="full-row name-and-artist">
|
||||
<div>
|
||||
<img className="jam-class-teacher" width="375" height="215" src="/assets/landing/jam_class.png"
|
||||
alt="teacher instructing a jam class"/>
|
||||
|
||||
<h1 className="jam-track-name">MANAGE A MUSIC INSTRUMENT STORE?</h1>
|
||||
|
||||
<h2 className="original-artist">Increase revenues without more inventory or space</h2>
|
||||
|
||||
<div className="clearall"/>
|
||||
</div>
|
||||
<JamClassPhone customClass="retailer"/>
|
||||
|
||||
<div className="preview-and-action-box jamclass retailer">
|
||||
<img src="/assets/landing/arrow-1-student.png" className="arrow1-jamclass"/>
|
||||
|
||||
<div className="preview-jamtrack-header">
|
||||
Sign Up Your Store
|
||||
</div>
|
||||
<div className={classNames({'preview-area': true, 'jam-class': true})}>
|
||||
<p>Sign up to let us know you’re interested in partnering, and we’ll follow up to answer your questions.</p>
|
||||
|
||||
<p>If this is a good fit for your store, we’ll help set up your JamKazam account and ship you a countertop
|
||||
display with POSA cards.</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 increase revenues without additional inventory or floor space.</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 built
|
||||
technology that lets musicians play together live in sync with studio quality audio over the Internet. Now
|
||||
we’ve launched both an online music lesson marketplace, as well as a JamTracks marketplace that lets musicians
|
||||
play along with their favorite bands and songs in compelling new ways. And we’ve created a simple, profitable
|
||||
POSA card (point of sale activated gift card) program that retailers can use to sell these products without
|
||||
any inventory investment or floor space.
|
||||
</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({retailer_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,
|
||||
retailer_interest: true
|
||||
})
|
||||
.done((response) =>
|
||||
context.location = '/client#/home'
|
||||
).fail((jqXHR) =>
|
||||
@setState({processing: false})
|
||||
if jqXHR.status == 422
|
||||
response = JSON.parse(jqXHR.responseText)
|
||||
if response.errors
|
||||
@setState({loginErrors: response.errors})
|
||||
else
|
||||
context.JK.app.notify({title: 'Unknown Signup Error', text: jqXHR.responseText})
|
||||
else
|
||||
context.JK.app.notifyServerError(jqXHR, "Unable to Sign Up")
|
||||
)
|
||||
|
||||
|
||||
@setState({processing: true})
|
||||
})
|
||||
|
|
@ -4,8 +4,6 @@ rest = context.JK.Rest()
|
|||
@JamClassSchoolLandingPage = React.createClass({
|
||||
|
||||
render: () ->
|
||||
|
||||
|
||||
loggedIn = context.JK.currentUserId?
|
||||
|
||||
if this.state.done
|
||||
|
|
@ -19,26 +17,31 @@ 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})}>
|
||||
{errorText}
|
||||
</div>
|
||||
<form className="jamtrack-signup-form">
|
||||
<label>Email: </label><input type="text" name="email" />
|
||||
<label>Password: </label><input type="password" name="password" />
|
||||
<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 SCHOOL’S REACH & INCOME</h1>
|
||||
|
||||
<h2 className="original-artist">Do you own/operate a music school?</h2>
|
||||
|
||||
<div className="clearall"/>
|
||||
</div>
|
||||
<JamClassPhone/>
|
||||
<div className="preview-and-action-box jamclass">
|
||||
<img src="/assets/landing/arrow-1-student.png" className="arrow1-jamclass" />
|
||||
<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,36 +70,31 @@ rest = context.JK.Rest()
|
|||
<p>Sign up to let us know you’re interested in partnering, and we’ll follow up to answer your
|
||||
|
||||
questions.</p>
|
||||
|
||||
<p>If this is a good fit for your school, we’ll 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 we’ve set up a program specifically to
|
||||
|
||||
partner with music schools to help you attract and engage students across the country,
|
||||
|
||||
extending your school’s 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 we’ve
|
||||
set up a program specifically to partner with music schools to help you attract and engage students across the
|
||||
country, extending your school’s reach and generating more income.
|
||||
</p>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
getInitialState: () ->
|
||||
{loginErrors: null, processing:false}
|
||||
{loginErrors: null, processing: false}
|
||||
|
||||
privacyPolicy: (e) ->
|
||||
e.preventDefault()
|
||||
|
|
@ -102,12 +106,12 @@ rest = context.JK.Rest()
|
|||
|
||||
context.JK.popExternalLink('/corp/terms')
|
||||
|
||||
componentDidMount:() ->
|
||||
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.
|
||||
# add item to cart, create the user if necessary, and then place the order to get the free JamTrack.
|
||||
ctaClick: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
|
|
@ -122,7 +126,7 @@ rest = context.JK.Rest()
|
|||
else
|
||||
@createUser()
|
||||
|
||||
@setState({processing:true})
|
||||
@setState({processing: true})
|
||||
|
||||
|
||||
markTeacher: () ->
|
||||
|
|
@ -142,11 +146,18 @@ 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) =>
|
||||
@setState({processing:false})
|
||||
@setState({processing: false})
|
||||
if jqXHR.status == 422
|
||||
response = JSON.parse(jqXHR.responseText)
|
||||
if response.errors
|
||||
|
|
@ -158,5 +169,5 @@ rest = context.JK.Rest()
|
|||
)
|
||||
|
||||
|
||||
@setState({processing:true})
|
||||
@setState({processing: true})
|
||||
})
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
context = window
|
||||
rest = context.JK.Rest()
|
||||
|
||||
@PosaActivationPage = React.createClass({
|
||||
|
||||
render: () ->
|
||||
if this.props.retailer.large_photo_url?
|
||||
logoImg = `<img src={this.props.retailer.large_photo_url}/>`
|
||||
logo = `<div className="retailer-logo">
|
||||
{logoImg}
|
||||
<div className="retailer-name">
|
||||
{this.props.retailer.name}
|
||||
</div>
|
||||
<br className="clearall"/>
|
||||
</div>`
|
||||
|
||||
ctaButtonText = 'ACTIVATE'
|
||||
if @state.processing
|
||||
ctaButtonText = 'HOLD ON'
|
||||
|
||||
console.log("this.props.retailer", this.props.retailer, this.props.has_teachers)
|
||||
if this.state.loginErrors?
|
||||
for key, value of this.state.loginErrors
|
||||
break
|
||||
if this.state.emailErrors?
|
||||
for errorKey, value of this.state.emailErrors
|
||||
break
|
||||
|
||||
success = `<div className="success-message">
|
||||
{this.state.success}
|
||||
</div>`
|
||||
|
||||
emailSuccess = `<div className="success-message">
|
||||
{this.state.emailSuccess}
|
||||
</div>`
|
||||
|
||||
posaErrors = context.JK.getFullFirstError(key, this.state.loginErrors,
|
||||
{code: 'This code', activated_at: 'This code', claimed_at: 'Claimed', user: 'User', retailer: 'Retailer'})
|
||||
emailErrors = context.JK.getFullFirstError(errorKey, this.state.emailErrors,
|
||||
{email: 'Email address', retailer: 'Retailer'})
|
||||
|
||||
register = `<div className="register-area jam-class">
|
||||
<form className="retailer-signup-form" onSubmit={this.submit}>
|
||||
<div className="field">
|
||||
<label>POSA Card Code: </label><input type="text" name="code"/>
|
||||
</div>
|
||||
<a
|
||||
className={classNames({'activate-btn': true, 'button-orange':true, 'cta-button' : true, 'processing': this.state.processing})}
|
||||
onClick={this.activateClick}>{'ACTIVATE'}</a>
|
||||
</form>
|
||||
{success}
|
||||
<div className={classNames({'errors': true, 'active': this.state.loginErrors})}>
|
||||
{posaErrors}
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
sendEmail = `<div className="send-email jam-class">
|
||||
<form className="retailer-email-form" onSubmit={this.submitEmail}>
|
||||
<div className="field">
|
||||
<label>Customer Email: </label><input type="text" name="email"/>
|
||||
</div>
|
||||
<a
|
||||
className={classNames({'activate-btn': true, 'button-orange':true, 'cta-button' : true, 'processing': this.state.processing})}
|
||||
onClick={this.submitEmail}>{'SEND LINKS'}</a>
|
||||
</form>
|
||||
{emailSuccess}
|
||||
<div className={classNames({'errors': true, 'active': this.state.emailErrors})}>
|
||||
{emailErrors}
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
leftColumnClasses = classNames({column: true, has_teachers: this.props.has_teachers})
|
||||
rightColumnClasses = classNames({column: true, has_teachers: this.props.has_teachers})
|
||||
|
||||
`<div className="container">
|
||||
<div className={leftColumnClasses}>
|
||||
<div className="header-area">
|
||||
<div className="header-content">
|
||||
{logo}
|
||||
<div className="headers">
|
||||
<h1>ACTIVATE JAMKAZAM POSA CARD</h1>
|
||||
</div>
|
||||
<br className="clearall"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="explain">
|
||||
<p>
|
||||
Please enter the 10-digit code from the back of the POSA card you have sold, and then click the Activate
|
||||
Card button:
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{register}
|
||||
</div>
|
||||
<div className={rightColumnClasses}>
|
||||
<div className="header-area">
|
||||
<div className="header-content">
|
||||
|
||||
<div className="headers">
|
||||
<h1>SEND TEACHER LINKS TO STUDENT</h1>
|
||||
</div>
|
||||
<br className="clearall"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="explain">
|
||||
<p>
|
||||
If you want to send links to your store’s teachers to thfcustomer, enter their email address below, and click the Send Links button.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{sendEmail}
|
||||
</div>
|
||||
<div className="clearall"/>
|
||||
|
||||
</div>`
|
||||
|
||||
submit: (e) ->
|
||||
@activateClick(e)
|
||||
|
||||
getInitialState: () ->
|
||||
{loginErrors: null, processing: false, emailErrors: null}
|
||||
|
||||
componentDidMount: () ->
|
||||
|
||||
# add item to cart, create the user if necessary, and then place the order to get the free JamTrack.
|
||||
activateClick: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
return if @state.processing
|
||||
|
||||
@setState({processing: true, success: null, emailSuccess:null, loginErrors: null})
|
||||
|
||||
@activateCode()
|
||||
|
||||
submitEmail: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
return if @state.processing
|
||||
|
||||
$form = $('.retailer-email-form')
|
||||
email = $form.find('input[name="email"]').val()
|
||||
|
||||
emailErrors = null
|
||||
if(!email)
|
||||
emailErrors = {"email": ['must be specified']}
|
||||
processing = false
|
||||
else
|
||||
|
||||
processing = true
|
||||
rest.sendRetailerCustomerEmail({retailer: this.props.retailer.id, email: email})
|
||||
.done((response) =>
|
||||
@setState({processing: false, emailSuccess: "List of teachers sent to #{email}."})
|
||||
$form.find('input[name="email"]').val('')
|
||||
)
|
||||
.fail((jqXHR) =>
|
||||
@setState({processing: false})
|
||||
|
||||
if jqXHR.status == 404
|
||||
@setState({emailErrors: {"retailer": ['is not valid']}})
|
||||
else if jqXHR.status == 422
|
||||
response = JSON.parse(jqXHR.responseText)
|
||||
if response.errors
|
||||
@setState({emailErrors: response.errors})
|
||||
else
|
||||
context.JK.app.notify({title: 'Unable to Send Email', text: jqXHR.responseText})
|
||||
else
|
||||
context.JK.app.notifyServerError(jqXHR, "Unable to Send Email")
|
||||
)
|
||||
|
||||
@setState({processing: processing, success: null, emailSuccess: null, emailErrors: emailErrors})
|
||||
|
||||
|
||||
activateCode: () ->
|
||||
$form = $('.retailer-signup-form')
|
||||
code = $form.find('input[name="code"]').val()
|
||||
|
||||
rest.posaActivate({
|
||||
code: code,
|
||||
slug: @props.retailer.slug
|
||||
})
|
||||
.done((response) =>
|
||||
@setState({processing: false, success: 'Card successfully activated. Please give card to customer. Thank you!'})
|
||||
).fail((jqXHR) =>
|
||||
@setState({processing: false})
|
||||
|
||||
console.log("jqXHR.status", jqXHR.status)
|
||||
if jqXHR.status == 404
|
||||
@setState({loginErrors: {"code": ['is invalid. Please try entering the code again. If it still will not work, try a different card, and please email us at support@jamkazam.com so we can resolve the problem with this card.']}})
|
||||
else if jqXHR.status == 422
|
||||
response = JSON.parse(jqXHR.responseText)
|
||||
if response.errors
|
||||
@setState({loginErrors: response.errors})
|
||||
else
|
||||
context.JK.app.notify({title: 'Unable to Activate POSA Card', text: jqXHR.responseText})
|
||||
else
|
||||
context.JK.app.notifyServerError(jqXHR, "Unable to Activate POSA Card")
|
||||
)
|
||||
})
|
||||
|
|
@ -2,6 +2,7 @@ context = window
|
|||
rest = context.JK.Rest()
|
||||
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
|
||||
|
||||
badCode = 'This is not a valid code. Please carefully re-enter the code and try again. If it still does not work, please email us at support@jamkazam.com to report this problem.'
|
||||
@RedeemGiftCardPage = React.createClass({
|
||||
|
||||
render: () ->
|
||||
|
|
@ -13,14 +14,32 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
|
|||
|
||||
errorText = context.JK.getFullFirstError(key, @state.formErrors, {email: 'Email', password: 'Password', gift_card: 'Gift Card Code', 'terms_of_service' : 'The terms of service'})
|
||||
|
||||
if errorText? && errorText.indexOf('does not exist') > -1
|
||||
errorText = 'This is not a valid code. Please carefully re-enter the code and try again. If it still does not work, please email us at support@jamkazam.com to report this problem.'
|
||||
|
||||
if errorText? && errorText.indexOf('must already be set') > -1
|
||||
errorText = 'This card has not been activated by a retailer and cannot currently be used. If you purchased this card from a store, please return to the store and have the store activate the card. Only the store where you purchased this card can activate it.'
|
||||
|
||||
if errorText? && errorText.indexOf('already claimed') > -1
|
||||
errorText = 'This card has already been claimed. If you believe this is in error, please email us at support@jamkazam.com to report this problem.'
|
||||
|
||||
buttonClassnames = classNames({'redeem-giftcard': true, 'button-orange': true, disabled: @state.processing || @state.done })
|
||||
|
||||
|
||||
if @state.done
|
||||
button =
|
||||
`<div key="done" className="done-action">
|
||||
<div>You have {this.state.gifted_jamtracks} free JamTracks on your account!</div>
|
||||
<div>You can now <a className="go-browse" href="/client#/jamtrack">browse our collection</a> and redeem them.</div>
|
||||
</div>`
|
||||
if this.state.gifted_jamtracks
|
||||
|
||||
button =
|
||||
`<div key="done" className="done-action">
|
||||
<div>You now have {this.state.gifted_jamtracks} JamTracks credits on your account!</div>
|
||||
<div><a className="go-browse" href="/client#/jamtrack">go to JamTracks home page</a></div>
|
||||
</div>`
|
||||
else
|
||||
button =
|
||||
`<div key="done" className="done-action">
|
||||
<div>You now have {this.state.gifted_jamclass}, 30-minute JamClass credits on your account!</div>
|
||||
<div><a className="go-browse" href="/client#/jamclass/searchOptions">go to JamClass teacher search page</a></div>
|
||||
</div>`
|
||||
else
|
||||
button = `<button key="button" className={buttonClassnames} onClick={this.action}>REDEEM GIFT CARD</button>`
|
||||
|
||||
|
|
@ -35,7 +54,7 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
|
|||
<label>Gift Card Code:</label><input type="text" name="code"/>
|
||||
{action}
|
||||
</form>`
|
||||
instruments = `<p className="instructions">Enter the code from the back of your gift card to associate it with your account.</p>`
|
||||
instruments = `<p className="instructions">Enter the 10-digit code from the back of your gift card and click the Redeem button below.</p>`
|
||||
else
|
||||
form =
|
||||
`<form onSubmit={this.submit}>
|
||||
|
|
@ -47,7 +66,7 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
|
|||
<div className="clearall"/>
|
||||
{action}
|
||||
</form>`
|
||||
instruments = `<p className="instructions">Enter the code from the back of your gift card to associate it with your new JamKazam account.</p>`
|
||||
instruments = `<p className="instructions">“Enter the 10-digit code from the back of your gift card and click the Redeem button below.</p>`
|
||||
|
||||
|
||||
classes = classNames({'redeem-container': true, 'not-logged-in': !context.JK.currentUserId?, 'logged-in': context.JK.currentUserId? })
|
||||
|
|
@ -105,7 +124,7 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
|
|||
rest.redeemGiftCard({gift_card: code})
|
||||
.done((response) =>
|
||||
|
||||
@setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks})
|
||||
@setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks, gifted_jamclass: response.gifted_jamclass})
|
||||
|
||||
).fail((jqXHR) =>
|
||||
@setState({processing:false})
|
||||
|
|
@ -142,7 +161,7 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
|
|||
rest.signup({email: email, password: password, gift_card: code, terms: terms})
|
||||
.done((response) =>
|
||||
|
||||
@setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks})
|
||||
@setState({formErrors: null, processing:false, done: true, gifted_jamtracks: response.gifted_jamtracks, gifted_jamclass: response.gifted_jamclass})
|
||||
|
||||
).fail((jqXHR) =>
|
||||
@setState({processing:false})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
context = window
|
||||
rest = context.JK.Rest()
|
||||
|
||||
@RetailerTeacherLandingPage = React.createClass({
|
||||
|
||||
render: () ->
|
||||
if this.props.retailer.large_photo_url?
|
||||
logo = `<div className="retailer-logo">
|
||||
<img src={this.props.retailer.large_photo_url}/>
|
||||
</div>`
|
||||
loggedIn = context.JK.currentUserId? && !this.props.preview
|
||||
|
||||
if this.state.done
|
||||
ctaButtonText = 'sending you in...'
|
||||
else if this.state.processing
|
||||
ctaButtonText = 'hold on...'
|
||||
else
|
||||
if loggedIn
|
||||
ctaButtonText = "ALREADY A JAMKAZAM USER"
|
||||
else
|
||||
ctaButtonText = "SIGN UP"
|
||||
|
||||
if loggedIn
|
||||
register =
|
||||
`<div className="register-area jam-class">
|
||||
<button className={classNames({'cta-button' : true, 'processing': this.state.processing})}
|
||||
onClick={this.ctaClick}>{ctaButtonText}</button>
|
||||
</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="retailer-signup-form">
|
||||
<div className="field">
|
||||
<label>Email: </label><input type="text" defaultValue={this.props.defaultEmail} name="email"/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>Password: </label><input type="password" name="password"/>
|
||||
</div>
|
||||
|
||||
<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" target="_blank">terms of service</a></label>
|
||||
|
||||
<div className="clearall"/>
|
||||
<button className={classNames({'cta-button' : true, 'processing': this.state.processing})}
|
||||
onClick={this.ctaClick}>{ctaButtonText}</button>
|
||||
</form>
|
||||
<p className="privacy-policy">
|
||||
We will not share your email.<br/>See our <a href="/corp/privacy" target="_blank">privacy policy</a>.
|
||||
</p>
|
||||
</div>`
|
||||
|
||||
|
||||
`<div className="container">
|
||||
<div className="header-area">
|
||||
<div className="header-content">
|
||||
{logo}
|
||||
|
||||
<div className="headers">
|
||||
<h1>REGISTER AS A TEACHER</h1>
|
||||
|
||||
<h2>with {this.props.retailer.name}</h2>
|
||||
</div>
|
||||
<br className="clearall"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="explain">
|
||||
<p>
|
||||
Please register here if you are currently a teacher with {this.props.retailer.name}, and if you plan to teach
|
||||
online music lessons for students of {this.props.retailer.name} using the JamKazam service. When you have registered, we
|
||||
will
|
||||
email you instructions to set up your online teacher profile, and we'll schedule a brief online training session to make sure
|
||||
you are comfortable using the service and ready to go with students in online lessons.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{register}
|
||||
</div>`
|
||||
|
||||
getInitialState: () ->
|
||||
{loginErrors: null, processing: false}
|
||||
|
||||
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 = $('.retailer-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,
|
||||
teacher: true,
|
||||
retailer_invitation_code: this.props.invitation_code,
|
||||
retailer_id: this.props.retailer.id
|
||||
})
|
||||
.done((response) =>
|
||||
@setState({done: true})
|
||||
#window.location.href = "/client#/jamclass"
|
||||
window.location.href = "/client#/profile/#{response.id}"
|
||||
).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")
|
||||
)
|
||||
})
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ rest = context.JK.Rest()
|
|||
</div>
|
||||
<form className="school-signup-form">
|
||||
<div className="field">
|
||||
<label>Email: </label><input type="text" defaultValue={this.props.defaultEmail} name="email"/>
|
||||
<label>Email: </label><input type="text" defaultValue={this.props.defaultEmail} name="email"/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>Password: </label><input type="password" name="password"/>
|
||||
|
|
@ -54,11 +54,27 @@ rest = context.JK.Rest()
|
|||
<button className={classNames({'cta-button' : true, 'processing': this.state.processing})}
|
||||
onClick={this.ctaClick}>{ctaButtonText}</button>
|
||||
</form>
|
||||
<p className="privacy-policy">
|
||||
<div className="privacy-policy">
|
||||
We will not share your email.<br/>See our <a href="/corp/privacy" target="_blank">privacy policy</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
if !this.props.school.education
|
||||
explain = `<p>
|
||||
Please register here if you are currently a student with {this.props.school.name}, and if you plan to take
|
||||
online music lessons from {this.props.school.name} using the JamKazam service. When you have registered, we
|
||||
will
|
||||
email you instructions to set up your profile, and we'll schedule a brief online training session to make sure
|
||||
you are comfortable using the service and ready to go for your first online lesson.
|
||||
</p>`
|
||||
else
|
||||
explain = `<p>
|
||||
Please register here if you are currently a student with {this.props.school.name}, and if you are interested in
|
||||
taking online music lessons using JamKazam. When you have registered, someone from JamKazam will contact you to
|
||||
answer any questions you have about our online lesson service, and to help you decide if this is service is a
|
||||
good option for you. If it is, we'll help you get set up and ready to go, and will get into an online session to
|
||||
make sure everything is working properly.
|
||||
</p>`
|
||||
|
||||
`<div className="container">
|
||||
<div className="header-area">
|
||||
|
|
@ -75,15 +91,8 @@ rest = context.JK.Rest()
|
|||
</div>
|
||||
|
||||
<div className="explain">
|
||||
<p>
|
||||
Please register here if you are currently a student with {this.props.school.name}, and if you plan to take
|
||||
online music lessons from {this.props.school.name} using the JamKazam service. When you have registered, we
|
||||
will
|
||||
email you instructions to set up your profile, and we'll schedule a brief online training session to make sure
|
||||
you are comfortable using the service and ready to go for your first online lesson.
|
||||
</p>
|
||||
{explain}
|
||||
</div>
|
||||
|
||||
{register}
|
||||
</div>`
|
||||
|
||||
|
|
@ -106,12 +115,12 @@ rest = context.JK.Rest()
|
|||
loggedIn = context.JK.currentUserId?
|
||||
|
||||
if loggedIn
|
||||
#window.location.href = "/client#/jamclass"
|
||||
window.location.href = "/client#/profile/#{context.JK.currentUserId}"
|
||||
window.location.href = "/client#/home"
|
||||
#window.location.href = "/client#/profile/#{context.JK.currentUserId}"
|
||||
else
|
||||
@createUser()
|
||||
|
||||
@setState({processing:true})
|
||||
@setState({processing: true})
|
||||
|
||||
createUser: () ->
|
||||
$form = $('.school-signup-form')
|
||||
|
|
@ -131,8 +140,15 @@ rest = context.JK.Rest()
|
|||
})
|
||||
.done((response) =>
|
||||
@setState({done: true})
|
||||
#window.location.href = "/client#/jamclass"
|
||||
window.location.href = "/client#/profile/#{response.id}"
|
||||
|
||||
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#/home"
|
||||
).fail((jqXHR) =>
|
||||
@setState({processing: false})
|
||||
if jqXHR.status == 422
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ rest = context.JK.Rest()
|
|||
</div>
|
||||
<form className="school-signup-form">
|
||||
<div className="field">
|
||||
<label>Email: </label><input type="text" defaultValue={this.props.defaultEmail} name="email"/>
|
||||
<label>Email: </label><input type="text" defaultValue={this.props.defaultEmail} name="email"/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>Password: </label><input type="password" name="password"/>
|
||||
|
|
@ -54,12 +54,29 @@ rest = context.JK.Rest()
|
|||
<button className={classNames({'cta-button' : true, 'processing': this.state.processing})}
|
||||
onClick={this.ctaClick}>{ctaButtonText}</button>
|
||||
</form>
|
||||
<p className="privacy-policy">
|
||||
<div className="privacy-policy">
|
||||
We will not share your email.<br/>See our <a href="/corp/privacy" target="_blank">privacy policy</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
|
||||
if this.props.school.education
|
||||
explain = `<p>Please register here if you are a private music lesson teacher affiliated with
|
||||
the {this.props.school.name} music program, and if you are interested in teaching online music lessons using the
|
||||
JamKazam service. When you
|
||||
have registered, someone from JamKazam will contact you to answer any questions you have about our online lesson
|
||||
service. We'll help you get set up and ready to go, and we'll get into an online session with you to make sure
|
||||
everything is working properly.</p>`
|
||||
else
|
||||
explain = `<p> Please register here if you are currently a teacher with {this.props.school.name}, and if you plan
|
||||
to teach
|
||||
online music lessons for students of {this.props.school.name} using the JamKazam service. When you have
|
||||
registered, we
|
||||
will
|
||||
email you instructions to set up your online teacher profile, and we'll schedule a brief online training session
|
||||
to make sure
|
||||
you are comfortable using the service and ready to go with students in online lessons.</p>`
|
||||
|
||||
`<div className="container">
|
||||
<div className="header-area">
|
||||
<div className="header-content">
|
||||
|
|
@ -75,13 +92,7 @@ rest = context.JK.Rest()
|
|||
</div>
|
||||
|
||||
<div className="explain">
|
||||
<p>
|
||||
Please register here if you are currently a teacher with {this.props.school.name}, and if you plan to teach
|
||||
online music lessons for students of {this.props.school.name} using the JamKazam service. When you have registered, we
|
||||
will
|
||||
email you instructions to set up your online teacher profile, and we'll schedule a brief online training session to make sure
|
||||
you are comfortable using the service and ready to go with students in online lessons.
|
||||
</p>
|
||||
{explain}
|
||||
</div>
|
||||
|
||||
{register}
|
||||
|
|
@ -111,7 +122,7 @@ rest = context.JK.Rest()
|
|||
else
|
||||
@createUser()
|
||||
|
||||
@setState({processing:true})
|
||||
@setState({processing: true})
|
||||
|
||||
createUser: () ->
|
||||
$form = $('.school-signup-form')
|
||||
|
|
|
|||
|
|
@ -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 you’re 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")
|
||||
)
|
||||
})
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -41,8 +41,12 @@ rest = new context.JK.Rest()
|
|||
|
||||
onPick: () ->
|
||||
|
||||
rest.generateSchoolFilePickerPolicy({id: @target.id})
|
||||
.done((filepickerPolicy) =>
|
||||
if @type == 'school'
|
||||
genpolicy = rest.generateSchoolFilePickerPolicy({id: @target.id})
|
||||
else if @type == 'retailer'
|
||||
genpolicy = rest.generateRetailerFilePickerPolicy({id: @target.id})
|
||||
|
||||
genpolicy.done((filepickerPolicy) =>
|
||||
@pickerOpen = true
|
||||
@changed()
|
||||
window.filepicker.setKey(gon.fp_apikey);
|
||||
|
|
@ -69,7 +73,7 @@ rest = new context.JK.Rest()
|
|||
.fail(@app.ajaxError)
|
||||
|
||||
afterImageUpload: (fpfile) ->
|
||||
logger.debug("afterImageUploaded")
|
||||
logger.debug("afterImageUploaded", typeof fpfile, fpfile)
|
||||
$.cookie('original_fpfile', JSON.stringify(fpfile));
|
||||
|
||||
@currentFpfile = fpfile
|
||||
|
|
@ -79,8 +83,12 @@ rest = new context.JK.Rest()
|
|||
@signFpfile()
|
||||
|
||||
signFpfile: () ->
|
||||
rest.generateSchoolFilePickerPolicy({ id: @target.id})
|
||||
.done((policy) => (
|
||||
if @type == 'school'
|
||||
genpolicy = rest.generateSchoolFilePickerPolicy({id: @target.id})
|
||||
else if @type == 'retailer'
|
||||
genpolicy = rest.generateRetailerFilePickerPolicy({id: @target.id})
|
||||
|
||||
genpolicy.done((policy) => (
|
||||
@signedCurrentFpfile = @currentFpfile.url + '?signature=' + policy.signature + '&policy=' + policy.policy;
|
||||
@changed()
|
||||
))
|
||||
|
|
@ -125,6 +133,8 @@ rest = new context.JK.Rest()
|
|||
|
||||
if @type == 'school'
|
||||
window.SchoolActions.refresh()
|
||||
if @type == 'retailer'
|
||||
window.RetailerActions.refresh()
|
||||
|
||||
@app.layout.closeDialog('upload-avatar')
|
||||
|
||||
|
|
@ -184,7 +194,10 @@ rest = new context.JK.Rest()
|
|||
@updatingAvatar = true
|
||||
@changed()
|
||||
|
||||
rest.deleteSchoolAvatar({id: @target.id}).done((response) => @deleteDone(response)).fail((jqXHR) => @deleteFail(jqXHR))
|
||||
if @type == 'school'
|
||||
rest.deleteSchoolAvatar({id: @target.id}).done((response) => @deleteDone(response)).fail((jqXHR) => @deleteFail(jqXHR))
|
||||
else if @type == 'retailer'
|
||||
rest.deleteRetailerAvatar({id: @target.id}).done((response) => @deleteDone(response)).fail((jqXHR) => @deleteFail(jqXHR))
|
||||
|
||||
deleteDone: (response) ->
|
||||
@currentFpfile = null
|
||||
|
|
@ -194,6 +207,8 @@ rest = new context.JK.Rest()
|
|||
@currentCropSelection = null
|
||||
if @type == 'school'
|
||||
window.SchoolActions.refresh()
|
||||
else if @type == 'retailer'
|
||||
window.RetailerActions.refresh()
|
||||
|
||||
@app.layout.closeDialog('upload-avatar');
|
||||
|
||||
|
|
@ -219,8 +234,12 @@ rest = new context.JK.Rest()
|
|||
logger.debug("Converting...");
|
||||
fpfile = @determineCurrentFpfile();
|
||||
|
||||
rest.generateSchoolFilePickerPolicy({ id: @target.id, handle: fpfile.url, convert: true })
|
||||
.done((filepickerPolicy) =>
|
||||
if @type == 'school'
|
||||
genpolicy = rest.generateSchoolFilePickerPolicy({ id: @target.id, handle: fpfile.url, convert: true })
|
||||
else if @type == 'retailer'
|
||||
genpolicy = rest.generateRetailerFilePickerPolicy({ id: @target.id, handle: fpfile.url, convert: true })
|
||||
|
||||
genpolicy.done((filepickerPolicy) =>
|
||||
window.filepicker.setKey(gon.fp_apikey)
|
||||
window.filepicker.convert(fpfile, {
|
||||
crop: [
|
||||
|
|
@ -243,8 +262,12 @@ rest = new context.JK.Rest()
|
|||
scale: (cropped) ->
|
||||
logger.debug("converting cropped");
|
||||
|
||||
rest.generateSchoolFilePickerPolicy({id: @target.id, handle: cropped.url, convert: true})
|
||||
.done((filepickerPolicy) => (
|
||||
if @type == 'school'
|
||||
genpolicy = rest.generateSchoolFilePickerPolicy({id: @target.id, handle: cropped.url, convert: true})
|
||||
else if @type == 'retailer'
|
||||
genpolicy = rest.generateRetailerFilePickerPolicy({id: @target.id, handle: cropped.url, convert: true})
|
||||
|
||||
genpolicy.done((filepickerPolicy) => (
|
||||
window.filepicker.convert(cropped, {
|
||||
height: @targetCropSize,
|
||||
width: @targetCropSize,
|
||||
|
|
@ -275,14 +298,24 @@ rest = new context.JK.Rest()
|
|||
|
||||
updateServer: (scaledLarger, scaled, cropped) ->
|
||||
logger.debug("converted and scaled final image %o", scaled);
|
||||
rest.updateSchoolAvatar({
|
||||
id: @target.id,
|
||||
original_fpfile: @determineCurrentFpfile(),
|
||||
cropped_fpfile: scaled,
|
||||
cropped_large_fpfile: scaledLarger,
|
||||
crop_selection: @selection
|
||||
})
|
||||
.done((response) => @updateAvatarSuccess(response))
|
||||
if @type == 'school'
|
||||
update = rest.updateSchoolAvatar({
|
||||
id: @target.id,
|
||||
original_fpfile: @determineCurrentFpfile(),
|
||||
cropped_fpfile: scaled,
|
||||
cropped_large_fpfile: scaledLarger,
|
||||
crop_selection: @selection
|
||||
})
|
||||
else if @type == 'retailer'
|
||||
update = rest.updateRetailerAvatar({
|
||||
id: @target.id,
|
||||
original_fpfile: @determineCurrentFpfile(),
|
||||
cropped_fpfile: scaled,
|
||||
cropped_large_fpfile: scaledLarger,
|
||||
crop_selection: @selection
|
||||
})
|
||||
|
||||
update.done((response) => @updateAvatarSuccess(response))
|
||||
.fail(@app.ajaxError)
|
||||
.always(() => (
|
||||
@updatingAvatar = false
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue