posa wip
This commit is contained in:
parent
c5f7711850
commit
5353b75c2e
|
|
@ -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,5 @@ jamblasters_network.sql
|
|||
immediate_recordings.sql
|
||||
nullable_user_id_jamblaster.sql
|
||||
rails4_migration.sql
|
||||
non_free_jamtracks.sql
|
||||
non_free_jamtracks.sql
|
||||
retailers.sql
|
||||
|
|
@ -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);
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1698,6 +1698,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 +1902,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,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></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%>
|
||||
<% end %>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,126 @@
|
|||
# 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_tracks_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'
|
||||
|
||||
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 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 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
|
||||
self.save
|
||||
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_tracks_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,124 @@
|
|||
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
|
||||
before_save :stringify_avatar_info, :if => :updating_avatar
|
||||
|
||||
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)
|
||||
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] if params[:city].present?
|
||||
self.city = params[:state] if params[:state].present?
|
||||
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,
|
||||
:cropped_fpfile => cropped_fpfile,
|
||||
:cropped_large_fpfile => cropped_large_fpfile,
|
||||
:cropped_s3_path => cropped_s3_path,
|
||||
:cropped_large_s3_path => cropped_large_s3_path,
|
||||
:crop_selection => crop_selection,
|
||||
: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?
|
||||
self.cropped_fpfile = cropped_fpfile.to_json if !cropped_fpfile.nil?
|
||||
self.crop_selection = crop_selection.to_json if !crop_selection.nil?
|
||||
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}
|
||||
|
|
@ -634,6 +657,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 = 0
|
||||
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}
|
||||
|
|
@ -128,6 +130,33 @@ module JamRuby
|
|||
line_item
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
@ -1157,6 +1159,7 @@ 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
|
||||
|
|
@ -1304,9 +1307,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)
|
||||
|
||||
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
|
||||
|
|
@ -1961,6 +1973,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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe PosaCard do
|
||||
|
||||
let(:user) {FactoryGirl.create(:user)}
|
||||
let(:card) {FactoryGirl.create(:posa_card)}
|
||||
let(:card2) {FactoryGirl.create(:posa_card)}
|
||||
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
|
||||
card.activate(retailer)
|
||||
card.claim(user)
|
||||
|
||||
card.errors.any?.should be false
|
||||
card.claimed_at.should_not be_nil
|
||||
card.user.should eql user
|
||||
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
|
||||
|
||||
card.activate(retailer)
|
||||
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
|
||||
card.activate(retailer)
|
||||
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
|
||||
card.activate(retailer)
|
||||
card.claim(user)
|
||||
|
||||
card2.activate(retailer)
|
||||
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,41 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Retailer do
|
||||
|
||||
it "created by factory" do
|
||||
FactoryGirl.create(:retailer)
|
||||
end
|
||||
|
||||
it "has correct associations" do
|
||||
retailer = FactoryGirl.create(:retailer)
|
||||
|
||||
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)
|
||||
|
|
@ -941,6 +976,7 @@ describe Sale do
|
|||
r.voided.to_i.should eq(1)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -210,6 +210,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
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@
|
|||
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' }));
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,390 @@
|
|||
context = window
|
||||
rest = context.JK.Rest()
|
||||
logger = context.JK.logger
|
||||
|
||||
AppStore = context.AppStore
|
||||
SchoolActions = context.RetailerActions
|
||||
SchoolStore = 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})
|
||||
|
||||
onSchoolChanged: (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
|
||||
|
||||
beforeShow: (e) ->
|
||||
|
||||
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()
|
||||
|
||||
@setState(updating: true)
|
||||
rest.updateRetailer({
|
||||
id: this.state.retailer.id,
|
||||
name: name,
|
||||
}).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) => @removeFromSchoolFail(jqXHR))
|
||||
|
||||
removeFromRetailerDone: (retailer) ->
|
||||
context.JK.Banner.showNotice("User removed", "User was removed from your retailer.")
|
||||
context.RetailerActions.updateRetailer(retailer)
|
||||
|
||||
removeFromSchoolFail: (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
|
||||
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()
|
||||
|
||||
account: () ->
|
||||
ownerEmail = this.state.school.owner.email
|
||||
correspondenceEmail = this.state.school.correspondence_email
|
||||
correspondenceDisabled = !@isSchoolManaged()
|
||||
|
||||
nameErrors = context.JK.reactSingleFieldErrors('name', @state.updateErrors)
|
||||
correspondenceEmailErrors = context.JK.reactSingleFieldErrors('correspondence_email', @state.updateErrors)
|
||||
nameClasses = classNames({name: true, error: nameErrors?, field: true})
|
||||
correspondenceEmailClasses = classNames({
|
||||
correspondence_email: true,
|
||||
error: correspondenceEmailErrors?,
|
||||
field: true
|
||||
})
|
||||
|
||||
cancelClasses = { "button-grey": true, "cancel" : true, disabled: this.state.updating }
|
||||
updateClasses = { "button-orange": true, "update" : true, disabled: this.state.updating }
|
||||
|
||||
`<div className="account-block info-block">
|
||||
<div className={nameClasses}>
|
||||
<label>School Name:</label>
|
||||
<input type="text" name="name" value={this.nameValue()} onChange={this.nameChanged}/>
|
||||
{nameErrors}
|
||||
</div>
|
||||
<div className="field logo">
|
||||
<label>School Logo:</label>
|
||||
<AvatarEditLink target={this.state.school} target_type="school"/>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<h4>Payments</h4>
|
||||
|
||||
<div className="field stripe-connect">
|
||||
<StripeConnect purpose='school' 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 music school 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>`
|
||||
})
|
||||
|
|
@ -44,9 +44,9 @@ context = window
|
|||
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)
|
||||
context.SchoolActions.addInvitation(response)
|
||||
context.JK.Banner.showNotice("invitation sent", "Your invitation has been sent!")
|
||||
@app.layout.closeDialog('invite-school-user')
|
||||
@app.layout.closeDialog('invite-retailer-user')
|
||||
|
||||
createFail: (jqXHR) ->
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
context = window
|
||||
|
||||
@RetailerActions = Reflux.createActions({
|
||||
refresh: {},
|
||||
addInvitation: {},
|
||||
deleteInvitation: {}
|
||||
updateRetailer: {}
|
||||
})
|
||||
|
||||
|
|
@ -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({school_interest: true})
|
||||
.done((response) =>
|
||||
this.setState({done: true})
|
||||
context.location = '/client#/home'
|
||||
)
|
||||
.fail((jqXHR) =>
|
||||
this.setState({processing: false})
|
||||
context.JK.app.notifyServerError(jqXHR, "Unable to Mark As Interested in School")
|
||||
)
|
||||
|
||||
createUser: () ->
|
||||
$form = $('.jamtrack-signup-form')
|
||||
email = $form.find('input[name="email"]').val()
|
||||
password = $form.find('input[name="password"]').val()
|
||||
terms = $form.find('input[name="terms"]').is(':checked')
|
||||
|
||||
rest.signup({
|
||||
email: email,
|
||||
password: password,
|
||||
first_name: null,
|
||||
last_name: null,
|
||||
terms: terms,
|
||||
school_interest: true
|
||||
})
|
||||
.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})
|
||||
})
|
||||
|
|
@ -51,8 +51,8 @@ rest = context.JK.Rest()
|
|||
<h2 className="original-artist">Do you own/operate a music school?</h2>
|
||||
<div className="clearall"/>
|
||||
</div>
|
||||
<JamClassPhone/>
|
||||
<div className="preview-and-action-box 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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,196 @@
|
|||
context = window
|
||||
rest = context.JK.Rest()
|
||||
|
||||
@PosaActivationPage = React.createClass({
|
||||
|
||||
render: () ->
|
||||
if this.props.retailer.large_photo_url?
|
||||
logo = `<div className="retailer-logo">
|
||||
<img src={this.props.retailer.large_photo_url}/>
|
||||
</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: 'POSA Card', activated_at: 'POSA Card', 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 this customer, 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 not valid']}})
|
||||
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: () ->
|
||||
|
|
@ -15,12 +16,21 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
|
|||
|
||||
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 +45,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 +57,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 +115,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 +152,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 = "GO TO JAMKAZAM"
|
||||
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")
|
||||
)
|
||||
})
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
$ = jQuery
|
||||
context = window
|
||||
logger = context.JK.logger
|
||||
rest = new context.JK.Rest()
|
||||
|
||||
@RetailerStore = Reflux.createStore(
|
||||
{
|
||||
retailer: null,
|
||||
teacherInvitations: null
|
||||
|
||||
listenables: @RetailerActions
|
||||
|
||||
init: ->
|
||||
this.listenTo(context.AppStore, this.onAppInit)
|
||||
|
||||
onAppInit: (@app) ->
|
||||
|
||||
onLoaded: (response) ->
|
||||
@retailer = response
|
||||
|
||||
if @retailer.photo_url?
|
||||
@retailer.photo_url = @retailer.photo_url + '?cache-bust=' + new Date().getTime()
|
||||
|
||||
if @retailer.large_photo_url?
|
||||
@retailer.large_photo_url = @retailer.large_photo_url + '?cache-bust=' + new Date().getTime()
|
||||
|
||||
@changed()
|
||||
rest.listRetailerInvitations({id:@retailer.id}).done((response) => @onLoadedTeacherInvitations(response)).fail((jqXHR) => @onRetailerInvitationFail(jqXHR))
|
||||
|
||||
onLoadedTeacherInvitations: (response) ->
|
||||
@teacherInvitations = response.entries
|
||||
@changed()
|
||||
|
||||
onAddInvitation: (invitation) ->
|
||||
@teacherInvitations.push(invitation)
|
||||
@changed()
|
||||
|
||||
onDeleteInvitation: (id) ->
|
||||
if @teacherInvitations?
|
||||
@teacherInvitations = @teacherInvitations.filter (invitation) -> invitation.id isnt id
|
||||
|
||||
@changed()
|
||||
|
||||
onRefresh: (retailerId) ->
|
||||
if !retailerId?
|
||||
retailerId = @retailer?.id
|
||||
rest.getRetailer({id: retailerId}).done((response) => @onLoaded(response)).fail((jqXHR) => @onRetailerFail(jqXHR))
|
||||
|
||||
onUpdateRetailer: (retailer) ->
|
||||
@retailer = retailer
|
||||
@changed()
|
||||
|
||||
onRetailerFail:(jqXHR) ->
|
||||
@app.layout.notify({title: 'Unable to Request Retailer Info', text: "We recommend you refresh the page."})
|
||||
|
||||
onRetailerInvitationFail:(jqXHR) ->
|
||||
@app.layout.notify({title: 'Unable to Request Retailer Invitation Info', text: "We recommend you refresh the page."})
|
||||
|
||||
changed:() ->
|
||||
@trigger(@getState())
|
||||
|
||||
getState:() ->
|
||||
{retailer: @retailer, teacherInvitations: @teacherInvitations}
|
||||
}
|
||||
)
|
||||
|
|
@ -11,4 +11,5 @@
|
|||
*= require users/signin
|
||||
*= require dialogs/dialog
|
||||
*= require icheck/minimal/minimal
|
||||
*= require landings/posa_activation
|
||||
*/
|
||||
|
|
@ -77,12 +77,37 @@ body.web.individual_jamtrack {
|
|||
}
|
||||
}
|
||||
|
||||
.video-section {
|
||||
height:460px;
|
||||
width:661px;
|
||||
|
||||
.video-wrapper {
|
||||
height:100%;
|
||||
width:100%;
|
||||
.video-container {
|
||||
height:100%;
|
||||
width:100%;
|
||||
}
|
||||
}
|
||||
|
||||
margin-bottom:40px;
|
||||
|
||||
&.second {
|
||||
margin-bottom:400px;
|
||||
}
|
||||
}
|
||||
.jamclass-phone {
|
||||
position: relative;
|
||||
top: -150px;
|
||||
right: 107px;
|
||||
background-color: black;
|
||||
|
||||
&.school {
|
||||
top:8px;
|
||||
}
|
||||
&.retailer {
|
||||
top:8px;
|
||||
}
|
||||
&.student {
|
||||
top: -13px;
|
||||
}
|
||||
|
|
@ -141,6 +166,10 @@ body.web.individual_jamtrack {
|
|||
&.teachers {
|
||||
padding-top: 100px;
|
||||
}
|
||||
|
||||
&.retailers {
|
||||
|
||||
}
|
||||
}
|
||||
width: 1050px;
|
||||
p {
|
||||
|
|
@ -714,6 +743,13 @@ body.web.individual_jamtrack {
|
|||
&.jamclass {
|
||||
top: 209px;
|
||||
|
||||
&.school {
|
||||
top:356px;
|
||||
}
|
||||
|
||||
&.retailer {
|
||||
top:356px;
|
||||
}
|
||||
&.student {
|
||||
top: 541px;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
@import "client/common.scss";
|
||||
|
||||
body.landing_page.full.posa_activation .landing-content {
|
||||
|
||||
font-size:1em;
|
||||
h1 {
|
||||
font-size:1.5rem;
|
||||
}
|
||||
h2 {
|
||||
font-size:1.5rem;
|
||||
}
|
||||
.column {
|
||||
float:left;
|
||||
width:50%;
|
||||
@include border_box_sizing;
|
||||
|
||||
&:nth-child(1) {
|
||||
width:100%;
|
||||
&.has_teachers {
|
||||
width:50%;
|
||||
}
|
||||
}
|
||||
&:nth-child(2) {
|
||||
display:none;
|
||||
&.has_teachers {
|
||||
width:50%;
|
||||
display:block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.explain {
|
||||
margin:2rem 0;
|
||||
}
|
||||
label {
|
||||
display:inline-block;
|
||||
margin-right:1rem;
|
||||
}
|
||||
input[name="code"] {
|
||||
margin-bottom:1rem;
|
||||
}
|
||||
.activate-btn {
|
||||
margin: 0 1rem 0 1rem;
|
||||
height: 1rem;
|
||||
line-height: 1rem;
|
||||
top: -1px;
|
||||
position: relative;
|
||||
}
|
||||
.field {
|
||||
display:inline-block;
|
||||
}
|
||||
.success-message {
|
||||
font-weight:bold;
|
||||
font-size:1rem;
|
||||
margin-top:1rem;
|
||||
}
|
||||
.errors {
|
||||
font-weight:bold;
|
||||
font-size:1rem;
|
||||
margin-top:1rem;
|
||||
color:red;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
@import "client/common";
|
||||
|
||||
$fluid-break: 1100px;
|
||||
$copy-color-on-dark: #b9b9b9;
|
||||
$cta-color: #e03d04;
|
||||
|
||||
|
||||
@mixin layout-small {
|
||||
@media (max-width: #{$fluid-break - 1px}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin layout-normal {
|
||||
@media (min-width: #{$fluid-break}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
body.web.retailer_register {
|
||||
|
||||
h1.web-tagline {
|
||||
@include layout-small {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-area {
|
||||
padding-top:30px;
|
||||
text-align:center;
|
||||
margin-bottom:40px;
|
||||
}
|
||||
|
||||
.explain {
|
||||
margin-bottom:40px;
|
||||
text-align:center;
|
||||
|
||||
p {
|
||||
display:inline-block;
|
||||
text-align: left;
|
||||
width:600px;
|
||||
}
|
||||
}
|
||||
|
||||
.field {
|
||||
margin-top:1px;
|
||||
}
|
||||
.header-content {
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
.retailer-logo {
|
||||
margin-right:60px;
|
||||
float:left;
|
||||
|
||||
img {
|
||||
max-width:225px;
|
||||
max-height:225px;
|
||||
}
|
||||
}
|
||||
|
||||
.headers {
|
||||
float:left;
|
||||
text-align:left;
|
||||
padding-top:64px;
|
||||
h1 {
|
||||
margin-bottom:10px;
|
||||
}
|
||||
h2 {
|
||||
font-size:16px;
|
||||
}
|
||||
}
|
||||
|
||||
.register-area {
|
||||
text-align:center;
|
||||
width:400px;
|
||||
margin:0 auto;
|
||||
|
||||
input {
|
||||
background-color: $copy-color-on-dark;
|
||||
color: black;
|
||||
font-size: 16px;
|
||||
|
||||
@include layout-small {
|
||||
font-size:30pt;
|
||||
}
|
||||
&[name="terms"] {
|
||||
width:auto;
|
||||
line-height:24px;
|
||||
vertical-align:middle;
|
||||
|
||||
@include layout-small {
|
||||
line-height:125%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.checkbox-wrap {
|
||||
float: left;
|
||||
margin-top: 6px;
|
||||
margin-left:64px;
|
||||
@include border_box_sizing;
|
||||
text-align:right;
|
||||
|
||||
input {
|
||||
height:auto;
|
||||
@include layout-small {
|
||||
height: 30pt !important;
|
||||
width: 30pt !important;
|
||||
}
|
||||
}
|
||||
@include layout-small {
|
||||
width:40%;
|
||||
margin-left:0;
|
||||
.icheckbox_minimal {
|
||||
right: -18px;
|
||||
}
|
||||
}
|
||||
.icheckbox_minimal {
|
||||
|
||||
}
|
||||
}
|
||||
.cta-button {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
background-color: $cta-color;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 1px outset buttonface;
|
||||
font-family: Raleway, Arial, Helvetica, sans-serif;
|
||||
|
||||
@include layout-small {
|
||||
font-size:30pt;
|
||||
}
|
||||
}
|
||||
|
||||
.privacy-policy {
|
||||
margin-top:10px;
|
||||
line-height:125%;
|
||||
|
||||
}
|
||||
|
||||
form {
|
||||
display:inline-block;
|
||||
}
|
||||
.errors {
|
||||
font-size:12px;
|
||||
height:20px;
|
||||
margin:0;
|
||||
visibility: hidden;
|
||||
text-align: center;
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
|
||||
&.active {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@include layout-small {
|
||||
font-size:20pt;
|
||||
height:32pt;
|
||||
}
|
||||
}
|
||||
label {
|
||||
text-align:left;
|
||||
width:100px;
|
||||
display: inline-block;
|
||||
height: 36px;
|
||||
vertical-align: middle;
|
||||
line-height: 36px;
|
||||
margin-bottom: 15px;
|
||||
@include border-box_sizing;
|
||||
|
||||
&.terms-help {
|
||||
color:$ColorTextTypical;
|
||||
width:205px;
|
||||
height:28px;
|
||||
line-height:14px;
|
||||
float:right;
|
||||
@include layout-small {
|
||||
line-height:125%;
|
||||
}
|
||||
}
|
||||
@include layout-small {
|
||||
height:40pt;
|
||||
font-size:30pt;
|
||||
}
|
||||
}
|
||||
input {
|
||||
width: 206px;
|
||||
height: 36px;
|
||||
float: right;
|
||||
margin-bottom: 15px;
|
||||
@include border-box_sizing;
|
||||
|
||||
@include layout-small {
|
||||
height:40pt;
|
||||
font-size:30pt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
require 'sanitize'
|
||||
class ApiPosaCardsController < ApiController
|
||||
before_filter :api_signed_in_user, :only => [:claim]
|
||||
before_filter :posa_http_basic_auth, :only => [:activate]
|
||||
|
||||
#before_filter :lookup_review_summary, :only => [:details]
|
||||
#before_filter :lookup_review, :only => [:update, :delete, :show]
|
||||
|
||||
respond_to :json
|
||||
|
||||
# Create a review:
|
||||
def activate
|
||||
|
||||
@posa_card = PosaCard.find_by_code!(params[:code])
|
||||
|
||||
PosaCard.activate(@posa_card, @retailer)
|
||||
|
||||
if @posa_card.errors.any?
|
||||
respond_with_model(@posa_card)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def claim
|
||||
@posa_card = PosaCard.find_by_code!(params[:code])
|
||||
|
||||
@posa_card.claim(current_user)
|
||||
|
||||
if @posa_card.errors.any?
|
||||
respond_with_model(@posa_card)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
class ApiRetailerInvitationsController < ApiController
|
||||
|
||||
before_filter :api_signed_in_user
|
||||
before_filter :lookup_retailer, :only => [:index, :create]
|
||||
before_filter :auth_retailer, :only => [:index, :create]
|
||||
before_filter :lookup_retailer_invitation, :only => [:delete, :resend]
|
||||
before_filter :auth_retailer_invitation, :only => [:delete, :resend]
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
data = RetailerInvitation.index(@retailer, params)
|
||||
|
||||
@retailer_invitations = data[:query]
|
||||
|
||||
@next = data[:next_page]
|
||||
render "api_retailer_invitations/index", :layout => nil
|
||||
end
|
||||
|
||||
def create
|
||||
@retailer_invitation = RetailerInvitation.create(current_user, @retailer, params)
|
||||
|
||||
if @retailer_invitation.errors.any?
|
||||
respond_with @retailer_invitation, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def delete
|
||||
@retailer_invitation.destroy
|
||||
respond_with responder: ApiResponder, :status => 204
|
||||
end
|
||||
|
||||
def resend
|
||||
@retailer_invitation.resend
|
||||
end
|
||||
|
||||
private
|
||||
def lookup_retailer_invitation
|
||||
@retailer_invitation = RetailerInvitation.find_by_id(params[:invitation_id])
|
||||
raise ActiveRecord::RecordNotFound, "Can't find retailer invitation" if @retailer_invitation.nil?
|
||||
end
|
||||
|
||||
def auth_retailer_invitation
|
||||
if current_user.id != @retailer_invitation.retailer.owner.id && current_user.id != @retailer_invitation.retailer.owner.id
|
||||
raise JamPermissionError, "You do not have access to this retailer"
|
||||
end
|
||||
end
|
||||
|
||||
def lookup_retailer
|
||||
@retailer = Retailer.find_by_id(params[:id])
|
||||
raise ActiveRecord::RecordNotFound, "Can't find retailer" if @retailer.nil?
|
||||
end
|
||||
|
||||
def auth_retailer
|
||||
if current_user.id != @retailer.owner.id && current_user.id != @retailer.owner.id
|
||||
raise JamPermissionError, "You do not have access to this retailer"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
class ApiRetailersController < ApiController
|
||||
|
||||
before_filter :api_signed_in_user, :except => [:customer_email]
|
||||
before_filter :lookup_retailer, :only => [:show, :update, :update_avatar, :delete_avatar, :generate_filepicker_policy, :remove_student, :remove_teacher, :customer_email]
|
||||
before_filter :auth_retailer, :only => [:show, :update, :update_avatar, :delete_avatar, :generate_filepicker_policy, :remove_student, :remove_teacher]
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
|
||||
end
|
||||
|
||||
def update
|
||||
@retailer.update_from_params(params)
|
||||
|
||||
respond_with_model(@retailer)
|
||||
end
|
||||
|
||||
def update_avatar
|
||||
original_fpfile = params[:original_fpfile]
|
||||
cropped_fpfile = params[:cropped_fpfile]
|
||||
cropped_large_fpfile = params[:cropped_large_fpfile]
|
||||
crop_selection = params[:crop_selection]
|
||||
|
||||
# public bucket to allow images to be available to public
|
||||
@retailer.update_avatar(original_fpfile, cropped_fpfile, cropped_large_fpfile, crop_selection, Rails.application.config.aws_bucket_public)
|
||||
|
||||
if @retailer.errors.any?
|
||||
respond_with @retailer, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
def delete_avatar
|
||||
@retailer.delete_avatar(Rails.application.config.aws_bucket_public)
|
||||
|
||||
if @retailer.errors.any?
|
||||
respond_with @retailer, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def generate_filepicker_policy
|
||||
# generates a soon-expiring filepicker policy so that a user can only upload to their own folder in their bucket
|
||||
|
||||
handle = params[:handle]
|
||||
|
||||
call = 'pick,convert,store'
|
||||
|
||||
policy = { :expiry => (DateTime.now + 5.minutes).to_i(),
|
||||
:call => call,
|
||||
#:path => 'avatars/' + @user.id + '/.*jpg'
|
||||
}
|
||||
|
||||
# if the caller specifies a handle, add it to the hash
|
||||
unless handle.nil?
|
||||
start = handle.rindex('/') + 1
|
||||
policy[:handle] = handle[start..-1]
|
||||
end
|
||||
|
||||
policy = Base64.urlsafe_encode64( policy.to_json )
|
||||
digest = OpenSSL::Digest::Digest.new('sha256')
|
||||
signature = OpenSSL::HMAC.hexdigest(digest, Rails.application.config.fp_secret, policy)
|
||||
|
||||
render :json => {
|
||||
:signature => signature,
|
||||
:policy => policy
|
||||
}, :status => :ok
|
||||
end
|
||||
|
||||
def remove_student
|
||||
user = User.find(params[:user_id])
|
||||
user.retailer_id = nil
|
||||
if !user.save
|
||||
respond_with user, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def remove_teacher
|
||||
teacher = User.find(params[:teacher_id])
|
||||
teacher.teacher.retailer_id = nil
|
||||
if !teacher.teacher.save
|
||||
respond_with teacher.teacher, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def customer_email
|
||||
if !User::VALID_EMAIL_REGEX.match(params[:email])
|
||||
raise JamRuby::JamArgumentError.new('is not valid', :email)
|
||||
end
|
||||
|
||||
UserMailer.retailer_customer_blast(params[:email], @retailer).deliver_now
|
||||
|
||||
render :json => {}, status: 200
|
||||
end
|
||||
private
|
||||
def lookup_retailer
|
||||
@retailer = Retailer.find_by_id(params[:id])
|
||||
raise ActiveRecord::RecordNotFound, "Can't find retailer" if @retailer.nil?
|
||||
end
|
||||
|
||||
def auth_retailer
|
||||
if current_user.id != @retailer.owner.id && current_user.id != @retailer.owner.id
|
||||
raise JamPermissionError, "You do not have access to this retailer"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -3,17 +3,17 @@ class ApiUsersController < ApiController
|
|||
|
||||
before_filter :api_signed_in_user, :except => [:create, :calendar, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump, :validate_data, :google_auth, :user_event]
|
||||
before_filter :auth_user, :only => [:session_settings_show, :session_history_index, :session_user_history_index, :update, :delete, :authorizations, :test_drive_status,
|
||||
:liking_create, :liking_destroy, # likes
|
||||
:following_create, :following_show, :following_destroy, # followings
|
||||
:recording_update, :recording_destroy, # recordings
|
||||
:favorite_create, :favorite_destroy, # favorites
|
||||
:friend_request_index, :friend_request_show, :friend_request_create, :friend_request_update, # friend requests
|
||||
:friend_show, :friend_destroy, # friends
|
||||
:notification_index, :notification_destroy, # notifications
|
||||
:band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations
|
||||
:set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy,
|
||||
:share_session, :share_recording,
|
||||
:affiliate_report, :audio_latency, :broadcast_notification, :redeem_giftcard]
|
||||
:liking_create, :liking_destroy, # likes
|
||||
:following_create, :following_show, :following_destroy, # followings
|
||||
:recording_update, :recording_destroy, # recordings
|
||||
:favorite_create, :favorite_destroy, # favorites
|
||||
:friend_request_index, :friend_request_show, :friend_request_create, :friend_request_update, # friend requests
|
||||
:friend_show, :friend_destroy, # friends
|
||||
:notification_index, :notification_destroy, # notifications
|
||||
:band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations
|
||||
:set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy,
|
||||
:share_session, :share_recording,
|
||||
:affiliate_report, :audio_latency, :broadcast_notification, :redeem_giftcard]
|
||||
before_filter :ip_blacklist, :only => [:create, :redeem_giftcard]
|
||||
|
||||
respond_to :json, :except => :calendar
|
||||
|
|
@ -60,10 +60,10 @@ class ApiUsersController < ApiController
|
|||
|
||||
def profile_show
|
||||
@profile = User.includes([{musician_instruments: :instrument},
|
||||
{band_musicians: :user},
|
||||
{genre_players: :genre},
|
||||
:bands, :instruments, :genres,
|
||||
:online_presences, :performance_samples])
|
||||
{band_musicians: :user},
|
||||
{genre_players: :genre},
|
||||
:bands, :instruments, :genres,
|
||||
:online_presences, :performance_samples])
|
||||
.find(params[:id])
|
||||
|
||||
@show_teacher_profile = params[:show_teacher]
|
||||
|
|
@ -215,20 +215,20 @@ class ApiUsersController < ApiController
|
|||
user.updating_password = true
|
||||
|
||||
user.easy_save(
|
||||
params[:first_name],
|
||||
params[:last_name],
|
||||
nil, # email can't be edited at this phase. We need to get them into the site, and they can edit on profile page if they really want
|
||||
params[:password],
|
||||
params[:password_confirmation],
|
||||
true, # musician
|
||||
params[:gender],
|
||||
params[:birth_date],
|
||||
params[:isp],
|
||||
params[:city],
|
||||
params[:state],
|
||||
params[:country],
|
||||
params[:instruments],
|
||||
params[:photo_url])
|
||||
params[:first_name],
|
||||
params[:last_name],
|
||||
nil, # email can't be edited at this phase. We need to get them into the site, and they can edit on profile page if they really want
|
||||
params[:password],
|
||||
params[:password_confirmation],
|
||||
true, # musician
|
||||
params[:gender],
|
||||
params[:birth_date],
|
||||
params[:isp],
|
||||
params[:city],
|
||||
params[:state],
|
||||
params[:country],
|
||||
params[:instruments],
|
||||
params[:photo_url])
|
||||
|
||||
if user.errors.any?
|
||||
render :json => user.errors.full_messages(), :status => :unprocessable_entity
|
||||
|
|
@ -273,7 +273,7 @@ class ApiUsersController < ApiController
|
|||
begin
|
||||
User.reset_password(params[:email], ApplicationHelper.base_uri(request))
|
||||
rescue JamRuby::JamArgumentError
|
||||
render :json => { :message => ValidationMessages::EMAIL_NOT_FOUND }, :status => 403
|
||||
render :json => {:message => ValidationMessages::EMAIL_NOT_FOUND}, :status => 403
|
||||
end
|
||||
respond_with responder: ApiResponder, :status => 204
|
||||
end
|
||||
|
|
@ -284,7 +284,7 @@ class ApiUsersController < ApiController
|
|||
rescue JamRuby::JamArgumentError
|
||||
# FIXME
|
||||
# There are some other errors that can happen here, besides just EMAIL_NOT_FOUND
|
||||
render :json => { :message => ValidationMessages::EMAIL_NOT_FOUND }, :status => 403
|
||||
render :json => {:message => ValidationMessages::EMAIL_NOT_FOUND}, :status => 403
|
||||
end
|
||||
set_remember_token(@user)
|
||||
respond_with responder: ApiResponder, :status => 204
|
||||
|
|
@ -295,16 +295,16 @@ class ApiUsersController < ApiController
|
|||
@user = User.authenticate(params[:email], params[:password])
|
||||
|
||||
if @user.nil?
|
||||
render :json => { :success => false }, :status => 404
|
||||
render :json => {:success => false}, :status => 404
|
||||
else
|
||||
sign_in @user
|
||||
render :json => { :success => true }, :status => 200
|
||||
render :json => {:success => true}, :status => 200
|
||||
end
|
||||
end
|
||||
|
||||
def auth_session_delete
|
||||
sign_out
|
||||
render :json => { :success => true }, :status => 200
|
||||
render :json => {:success => true}, :status => 200
|
||||
end
|
||||
|
||||
###################### SESSION SETTINGS ###################
|
||||
|
|
@ -443,7 +443,7 @@ class ApiUsersController < ApiController
|
|||
|
||||
def friend_destroy
|
||||
if current_user.id != params[:id] && current_user.id != params[:friend_id]
|
||||
render :json => { :message => "You are not allowed to delete this friendship." }, :status => 403
|
||||
render :json => {:message => "You are not allowed to delete this friendship."}, :status => 403
|
||||
end
|
||||
# clean up both records representing this "friendship"
|
||||
JamRuby::Friendship.delete_all "(user_id = '#{params[:id]}' AND friend_id = '#{params[:friend_id]}') OR (user_id = '#{params[:friend_id]}' AND friend_id = '#{params[:id]}')"
|
||||
|
|
@ -455,9 +455,9 @@ class ApiUsersController < ApiController
|
|||
if params[:type] == 'TEXT_MESSAGE'
|
||||
# you can ask for just text_message notifications
|
||||
|
||||
raise JamArgumentError.new('can\'t be blank', 'receiver') if params[:receiver].blank?
|
||||
raise JamArgumentError.new('can\'t be blank', 'limit') if params[:limit].blank?
|
||||
raise JamArgumentError.new('can\'t be blank', 'offset') if params[:offset].blank?
|
||||
raise JamArgumentError.new('can\'t be blank', 'receiver') if params[:receiver].blank?
|
||||
raise JamArgumentError.new('can\'t be blank', 'limit') if params[:limit].blank?
|
||||
raise JamArgumentError.new('can\'t be blank', 'offset') if params[:offset].blank?
|
||||
|
||||
receiver_id = params[:receiver]
|
||||
limit = params[:limit].to_i
|
||||
|
|
@ -499,7 +499,7 @@ class ApiUsersController < ApiController
|
|||
respond_with @invitation, responder: ApiResponder, :status => 200
|
||||
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render :json => { :message => ValidationMessages::BAND_INVITATION_NOT_FOUND }, :status => 404
|
||||
render :json => {:message => ValidationMessages::BAND_INVITATION_NOT_FOUND}, :status => 404
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -514,7 +514,7 @@ class ApiUsersController < ApiController
|
|||
respond_with @invitation, responder: ApiResponder, :status => 200
|
||||
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render :json => { :message => ValidationMessages::BAND_INVITATION_NOT_FOUND }, :status => 404
|
||||
render :json => {:message => ValidationMessages::BAND_INVITATION_NOT_FOUND}, :status => 404
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -550,7 +550,7 @@ class ApiUsersController < ApiController
|
|||
if score.save
|
||||
render :text => 'scoring recorded'
|
||||
else
|
||||
render :text => "score invalid: #{score.errors.inspect}", status:422
|
||||
render :text => "score invalid: #{score.errors.inspect}", status: 422
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -589,9 +589,9 @@ class ApiUsersController < ApiController
|
|||
|
||||
call = 'pick,convert,store'
|
||||
|
||||
policy = { :expiry => (DateTime.now + 5.minutes).to_i(),
|
||||
:call => call,
|
||||
#:path => 'avatars/' + @user.id + '/.*jpg'
|
||||
policy = {:expiry => (DateTime.now + 5.minutes).to_i(),
|
||||
:call => call,
|
||||
#:path => 'avatars/' + @user.id + '/.*jpg'
|
||||
}
|
||||
|
||||
# if the caller specifies a handle, add it to the hash
|
||||
|
|
@ -600,14 +600,14 @@ class ApiUsersController < ApiController
|
|||
policy[:handle] = handle[start..-1]
|
||||
end
|
||||
|
||||
policy = Base64.urlsafe_encode64( policy.to_json )
|
||||
digest = OpenSSL::Digest.new('sha256')
|
||||
policy = Base64.urlsafe_encode64(policy.to_json)
|
||||
digest = OpenSSL::Digest.new('sha256')
|
||||
signature = OpenSSL::HMAC.hexdigest(digest, Rails.application.config.fp_secret, policy)
|
||||
|
||||
render :json => {
|
||||
:signature => signature,
|
||||
:policy => policy
|
||||
}, :status => :ok
|
||||
:signature => signature,
|
||||
:policy => policy
|
||||
}, :status => :ok
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -678,13 +678,13 @@ class ApiUsersController < ApiController
|
|||
end
|
||||
|
||||
logger.debug("sending crash email with subject#{subject}")
|
||||
AdminMailer.crash_alert(subject: subject, body:body).deliver_now
|
||||
AdminMailer.crash_alert(subject: subject, body: body).deliver_now
|
||||
|
||||
redirect_to write_url, status: 307
|
||||
else
|
||||
# we should store it here to aid in development, but we don't have to until someone wants the feature
|
||||
# so... just return 200
|
||||
render :json => { :id => @dump.id }, :status => 200
|
||||
render :json => {:id => @dump.id}, :status => 200
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -695,13 +695,14 @@ class ApiUsersController < ApiController
|
|||
@user = current_user
|
||||
@user.update_progression_field(:first_downloaded_client_at)
|
||||
|
||||
if @user.errors.any?
|
||||
respond_with @user, :status => :unprocessable_entity
|
||||
return
|
||||
end
|
||||
if @user.errors.any?
|
||||
respond_with @user, :status => :unprocessable_entity
|
||||
return
|
||||
end
|
||||
render :json => {}, :status => 200
|
||||
|
||||
end
|
||||
|
||||
# user progression tracking
|
||||
def qualified_gear
|
||||
@user = current_user
|
||||
|
|
@ -741,7 +742,7 @@ class ApiUsersController < ApiController
|
|||
end
|
||||
|
||||
def opened_jamtrack_web_player
|
||||
User.where(id: current_user.id).update_all(first_opened_jamtrack_web_player: Time.now)
|
||||
User.where(id: current_user.id).update_all(first_opened_jamtrack_web_player: Time.now)
|
||||
render :json => {}, :status => 200
|
||||
end
|
||||
|
||||
|
|
@ -764,21 +765,21 @@ class ApiUsersController < ApiController
|
|||
if provider == 'facebook'
|
||||
|
||||
render json: {
|
||||
description: view_context.description_for_music_session(history),
|
||||
title: view_context.title_for_music_session(history, current_user),
|
||||
photo_url: view_context.facebook_image_for_music_session(history),
|
||||
url: share_token_url(history.share_token.token),
|
||||
caption: 'www.jamkazam.com'
|
||||
}, status: 200
|
||||
description: view_context.description_for_music_session(history),
|
||||
title: view_context.title_for_music_session(history, current_user),
|
||||
photo_url: view_context.facebook_image_for_music_session(history),
|
||||
url: share_token_url(history.share_token.token),
|
||||
caption: 'www.jamkazam.com'
|
||||
}, status: 200
|
||||
|
||||
elsif provider == 'twitter'
|
||||
|
||||
render json: {
|
||||
message: view_context.title_for_music_session(history, current_user)
|
||||
}, status: 200
|
||||
message: view_context.title_for_music_session(history, current_user)
|
||||
}, status: 200
|
||||
|
||||
else
|
||||
render :json => { :errors => {:provider => ['not valid']} }, :status => 422
|
||||
render :json => {:errors => {:provider => ['not valid']}}, :status => 422
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -791,22 +792,22 @@ class ApiUsersController < ApiController
|
|||
if provider == 'facebook'
|
||||
|
||||
render json: {
|
||||
description: view_context.description_for_claimed_recording(claimed_recording),
|
||||
title: view_context.title_for_claimed_recording(claimed_recording, current_user),
|
||||
photo_url: view_context.facebook_image_for_claimed_recording(claimed_recording),
|
||||
url: share_token_url(claimed_recording.share_token.token),
|
||||
caption: 'www.jamkazam.com'
|
||||
}, status: 200
|
||||
description: view_context.description_for_claimed_recording(claimed_recording),
|
||||
title: view_context.title_for_claimed_recording(claimed_recording, current_user),
|
||||
photo_url: view_context.facebook_image_for_claimed_recording(claimed_recording),
|
||||
url: share_token_url(claimed_recording.share_token.token),
|
||||
caption: 'www.jamkazam.com'
|
||||
}, status: 200
|
||||
|
||||
elsif provider == 'twitter'
|
||||
|
||||
render json: {
|
||||
message: view_context.title_for_claimed_recording(history, current_user) + " at " + request.host_with_port
|
||||
}, status: 200
|
||||
message: view_context.title_for_claimed_recording(history, current_user) + " at " + request.host_with_port
|
||||
}, status: 200
|
||||
|
||||
|
||||
else
|
||||
render :json => { :errors => {:provider => ['not valid']} }, :status => 422
|
||||
render :json => {:errors => {:provider => ['not valid']}}, :status => 422
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -821,53 +822,53 @@ class ApiUsersController < ApiController
|
|||
elsif request.get?
|
||||
result = {}
|
||||
result['account'] = {
|
||||
'address' => oo.address.clone,
|
||||
'tax_identifier' => oo.tax_identifier,
|
||||
'entity_type' => oo.entity_type,
|
||||
'partner_name' => oo.partner_name,
|
||||
'partner_id' => oo.partner_user_id,
|
||||
'id' => oo.id
|
||||
'address' => oo.address.clone,
|
||||
'tax_identifier' => oo.tax_identifier,
|
||||
'entity_type' => oo.entity_type,
|
||||
'partner_name' => oo.partner_name,
|
||||
'partner_id' => oo.partner_user_id,
|
||||
'id' => oo.id
|
||||
}
|
||||
if txt = oo.affiliate_legalese.try(:legalese)
|
||||
txt = ControllerHelp.instance.simple_format(txt)
|
||||
end
|
||||
result['agreement'] = {
|
||||
'legalese' => txt,
|
||||
'signed_at' => oo.signed_at
|
||||
'legalese' => txt,
|
||||
'signed_at' => oo.signed_at
|
||||
}
|
||||
#result['signups'] = oo.referrals_by_date
|
||||
#result['earnings'] = [['April 2015', '1000 units', '$100']]
|
||||
render json: result.to_json, status: 200
|
||||
end
|
||||
else
|
||||
render :json => { :message => 'user not affiliate partner' }, :status => 400
|
||||
render :json => {:message => 'user not affiliate partner'}, :status => 400
|
||||
end
|
||||
end
|
||||
|
||||
def affiliate_report
|
||||
begin
|
||||
affiliate = User
|
||||
.where(:id => params[:id])
|
||||
.includes(:affiliate_partner)
|
||||
.limit(1)
|
||||
.first
|
||||
.affiliate_partner
|
||||
.where(:id => params[:id])
|
||||
.includes(:affiliate_partner)
|
||||
.limit(1)
|
||||
.first
|
||||
.affiliate_partner
|
||||
referrals_by_date = affiliate.referrals_by_date do |by_date|
|
||||
by_date.inject([]) { |rr, key| rr << key }
|
||||
end
|
||||
result = {
|
||||
:total_count => affiliate.referral_user_count,
|
||||
:by_date => referrals_by_date
|
||||
:total_count => affiliate.referral_user_count,
|
||||
:by_date => referrals_by_date
|
||||
}
|
||||
render json: result.to_json, status: 200
|
||||
rescue
|
||||
render :json => { :message => $!.to_s }, :status => 400
|
||||
render :json => {:message => $!.to_s}, :status => 400
|
||||
end
|
||||
end
|
||||
|
||||
def add_play
|
||||
if params[:id].blank?
|
||||
render :json => { :message => "Playable ID is required" }, :status => 400
|
||||
render :json => {:message => "Playable ID is required"}, :status => 400
|
||||
return
|
||||
end
|
||||
|
||||
|
|
@ -880,7 +881,7 @@ class ApiUsersController < ApiController
|
|||
play.save
|
||||
|
||||
if play.errors.any?
|
||||
render :json => { :errors => play.errors }, :status => 422
|
||||
render :json => {:errors => play.errors}, :status => 422
|
||||
else
|
||||
render :json => {}, :status => 201
|
||||
end
|
||||
|
|
@ -914,7 +915,7 @@ class ApiUsersController < ApiController
|
|||
|
||||
def validate_data
|
||||
unless (data = params[:data]).present?
|
||||
render(json: { message: "blank data #{data}" }, status: :unprocessable_entity) && return
|
||||
render(json: {message: "blank data #{data}"}, status: :unprocessable_entity) && return
|
||||
end
|
||||
url = nil
|
||||
site = params[:sitetype]
|
||||
|
|
@ -923,10 +924,10 @@ class ApiUsersController < ApiController
|
|||
elsif Utils.recording_source?(site)
|
||||
rec_data = Utils.extract_recording_data(site, data)
|
||||
if rec_data
|
||||
render json: { message: 'Valid Site', recording_id: rec_data["id"], recording_title: rec_data["title"], data: data }, status: 200
|
||||
render json: {message: 'Valid Site', recording_id: rec_data["id"], recording_title: rec_data["title"], data: data}, status: 200
|
||||
return
|
||||
else
|
||||
render json: { message: 'Invalid Site', data: data, errors: { site: ["Could not detect recording identifier"] } }, status: 200
|
||||
render json: {message: 'Invalid Site', data: data, errors: {site: ["Could not detect recording identifier"]}}, status: 200
|
||||
return
|
||||
end
|
||||
else
|
||||
|
|
@ -934,12 +935,12 @@ class ApiUsersController < ApiController
|
|||
end
|
||||
unless url.blank?
|
||||
if errmsg = Utils.site_validator(url, site)
|
||||
render json: { message: 'Invalid Site', data: data, errors: { site: [errmsg] } }, status: 200
|
||||
render json: {message: 'Invalid Site', data: data, errors: {site: [errmsg]}}, status: 200
|
||||
else
|
||||
render json: { message: 'Valid Site', data: data }, status: 200
|
||||
render json: {message: 'Valid Site', data: data}, status: 200
|
||||
end
|
||||
else
|
||||
render json: { message: "unknown validation for data '#{params[:data]}', site '#{params[:site]}'" }, status: :unprocessable_entity
|
||||
render json: {message: "unknown validation for data '#{params[:data]}', site '#{params[:site]}'"}, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -951,7 +952,7 @@ class ApiUsersController < ApiController
|
|||
@broadcast.did_view(current_user)
|
||||
respond_with_model(@broadcast)
|
||||
else
|
||||
render json: { }, status: 200
|
||||
render json: {}, status: 200
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -964,39 +965,66 @@ class ApiUsersController < ApiController
|
|||
@broadcast.save
|
||||
end
|
||||
|
||||
render json: { }, status: 200
|
||||
render json: {}, status: 200
|
||||
end
|
||||
|
||||
def lookup_user
|
||||
|
||||
User.includes([{musician_instruments: :instrument},
|
||||
{band_musicians: :user},
|
||||
{genre_players: :genre},
|
||||
:bands, :instruments, :genres, :jam_track_rights,
|
||||
:affiliate_partner, :reviews, :review_summary, :recordings,
|
||||
{band_musicians: :user},
|
||||
{genre_players: :genre},
|
||||
:bands, :instruments, :genres, :jam_track_rights,
|
||||
:affiliate_partner, :reviews, :review_summary, :recordings,
|
||||
:teacher => [:subjects, :instruments, :languages, :genres, :teachers_languages, :experiences_teaching, :experiences_award, :experiences_education, :reviews, :review_summary]])
|
||||
.find(params[:id])
|
||||
.find(params[:id])
|
||||
end
|
||||
|
||||
def redeem_giftcard
|
||||
def try_posa_card
|
||||
@posa_card = PosaCard.find_by_code(params[:gift_card])
|
||||
|
||||
if @posa_card.nil?
|
||||
return false
|
||||
end
|
||||
|
||||
@posa_card.claim(current_user)
|
||||
|
||||
if @posa_card.errors.any?
|
||||
respond_with_model(@posa_card)
|
||||
else
|
||||
if @posa_card.card_type == PosaCard::JAM_CLASS_4
|
||||
render json: {gifted_jamclass: 4}, status: 200
|
||||
elsif @posa_card.card_type == PosaCard::JAM_TRACKS_10
|
||||
render json: {gifted_jamtracks: 10}, status: 200
|
||||
elsif @posa_card.card_type == PosaCard::JAM_TRACKS_5
|
||||
render json: {gifted_jamtracks: 5}, status: 200
|
||||
else
|
||||
raise 'unknown card_type ' + @posa_card.card_type
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def try_gift_card
|
||||
|
||||
@gift_card = GiftCard.find_by_code(params[:gift_card])
|
||||
|
||||
if @gift_card.nil?
|
||||
render json: {errors:{gift_card: ['does not exist']}}, status: 422
|
||||
render json: {errors: {gift_card: ['does not exist']}}, status: 422
|
||||
return
|
||||
end
|
||||
|
||||
if current_user.gift_cards.count >= 5
|
||||
render json: {errors:{gift_card: ['has too many on account']}}, status: 422
|
||||
render json: {errors: {gift_card: ['has too many on account']}}, status: 422
|
||||
return
|
||||
end
|
||||
|
||||
if @gift_card.user
|
||||
if @gift_card.user == current_user
|
||||
render json: {errors:{gift_card: ['already redeemed by you']}}, status: 422
|
||||
render json: {errors: {gift_card: ['already redeemed by you']}}, status: 422
|
||||
return
|
||||
else
|
||||
render json: {errors:{gift_card: ['already redeemed by another']}}, status: 422
|
||||
render json: {errors: {gift_card: ['already redeemed by another']}}, status: 422
|
||||
return
|
||||
end
|
||||
end
|
||||
|
|
@ -1012,10 +1040,19 @@ class ApiUsersController < ApiController
|
|||
# apply gift card items to everything in shopping cart
|
||||
current_user.reload
|
||||
ShoppingCart.apply_gifted_jamtracks(current_user)
|
||||
render json: {gifted_jamtracks:current_user.gifted_jamtracks}, status: 200
|
||||
render json: {gifted_jamtracks: current_user.gifted_jamtracks}, status: 200
|
||||
end
|
||||
end
|
||||
|
||||
def redeem_giftcard
|
||||
|
||||
# first, try to find posa_card
|
||||
rendered = try_posa_card
|
||||
|
||||
try_gift_card if !rendered
|
||||
|
||||
end
|
||||
|
||||
def test_drive_status
|
||||
@user = current_user
|
||||
@teacher = User.find(params[:teacher_id])
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ class LandingsController < ApplicationController
|
|||
|
||||
respond_to :html
|
||||
|
||||
before_filter :posa_http_basic_auth, only: [:posa_activation]
|
||||
|
||||
def watch_bands
|
||||
@promo_buzz = PromoBuzz.active
|
||||
|
||||
|
|
@ -138,6 +140,14 @@ class LandingsController < ApplicationController
|
|||
render 'jam_class_schools', layout: 'web'
|
||||
end
|
||||
|
||||
def jam_class_retailers
|
||||
enable_olark
|
||||
@no_landing_tag = true
|
||||
@landing_tag_play_learn_earn = true
|
||||
@show_after_black_bar_border = true
|
||||
render 'jam_class_retailers', layout: 'web'
|
||||
end
|
||||
|
||||
def individual_jamtrack
|
||||
|
||||
enable_olark
|
||||
|
|
@ -367,5 +377,47 @@ class LandingsController < ApplicationController
|
|||
@page_data = {school: @school, invitation_code: params[:invitation_code], defaultEmail: defaultEmail, preview: @preview}
|
||||
render 'school_teacher_register', layout: 'web'
|
||||
end
|
||||
|
||||
def retailer_teacher_register
|
||||
@no_landing_tag = true
|
||||
@landing_tag_play_learn_earn = true
|
||||
@retailer = Retailer.find_by_id(params[:id])
|
||||
|
||||
if @retailer.nil?
|
||||
redirect_to '/signup'
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
@title = 'Become a teacher with ' + @retailer.name
|
||||
@description = "Using JamKazam, teach online lessons with " + @retailer.name
|
||||
@preview = !params[:preview].nil?
|
||||
|
||||
@invitation = RetailerInvitation.find_by_invitation_code(params[:invitation_code]) if params[:invitation_code]
|
||||
defaultEmail = ''
|
||||
if @invitation
|
||||
defaultEmail = @invitation.email
|
||||
end
|
||||
|
||||
|
||||
@page_data = {retailer: @retailer, invitation_code: params[:invitation_code], defaultEmail: defaultEmail, preview: @preview}
|
||||
render 'retailer_teacher_register', layout: 'web'
|
||||
end
|
||||
|
||||
def posa_activation
|
||||
@no_landing_tag = true
|
||||
@landing_tag_play_learn_earn = true
|
||||
@retailer = Retailer.find_by_slug(params[:slug])
|
||||
|
||||
if @retailer.nil?
|
||||
redirect_to '/signup'
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
|
||||
@page_data = {retailer: @retailer, has_teachers: @retailer.teachers.count > 0}
|
||||
render 'posa_activation', layout: 'web'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -145,6 +145,20 @@ module SessionsHelper
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
def posa_http_basic_auth
|
||||
@retailer = Retailer.find_by_slug(params[:slug])
|
||||
|
||||
if @retailer.nil?
|
||||
redirect_to signin_url, notice: "Please use the correct url for retailers in."
|
||||
return
|
||||
end
|
||||
|
||||
authenticate_or_request_with_http_basic('Administration') do |username, password|
|
||||
@retailer.matches_password(password)
|
||||
end
|
||||
end
|
||||
|
||||
def ip_blacklist
|
||||
if current_user && current_user.admin
|
||||
return
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
object @posa_card
|
||||
|
||||
extends "api_posa_cards/show"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object @posa_card
|
||||
|
||||
extends "api_posa_cards/show"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
@posa_card
|
||||
|
||||
attributes :id
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object @retailer_invitation
|
||||
|
||||
extends "api_retailer_invitations/show"
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
node :next do |page|
|
||||
@next
|
||||
end
|
||||
|
||||
node :entries do |page|
|
||||
partial "api_retailer_invitations/show", object: @retailer_invitations
|
||||
end
|
||||
|
||||
node :total_entries do |page|
|
||||
@retailer_invitations.total_entries
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object @retailer_invitation
|
||||
|
||||
extends "api_retailer_invitations/show"
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
object @retailer_invitation
|
||||
|
||||
attributes :id, :user_id, :retailer_id, :invitation_code, :note, :email, :first_name, :last_name, :accepted
|
||||
|
||||
child(:user => :user) do |user|
|
||||
partial "api_users/show_minimal", object: user
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object @retailer
|
||||
|
||||
extends "api_retailers/show"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object @retailer
|
||||
|
||||
extends "api_retailers/show"
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
object @retailer
|
||||
|
||||
attributes :id, :user_id, :name, :enabled, :original_fpfile, :cropped_fpfile, :crop_selection, :photo_url
|
||||
|
||||
child :owner => :owner do
|
||||
attributes :id, :email, :photo_url, :name, :first_name, :last_name
|
||||
end
|
||||
|
||||
|
||||
child :teachers => :teachers do |teacher|
|
||||
attributes :id
|
||||
|
||||
child :user => :user do
|
||||
attributes :id, :name, :first_name, :last_name, :photo_url
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object @retailer
|
||||
|
||||
extends "api_retailers/show"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object @retailer
|
||||
|
||||
extends "api_retailers/show"
|
||||
|
|
@ -34,6 +34,10 @@ if current_user && @user == current_user
|
|||
user.owned_school.id if user.owned_school
|
||||
end
|
||||
|
||||
node :owned_retailer_id do |user|
|
||||
user.owned_retailer.id if user.owned_retailer
|
||||
end
|
||||
|
||||
child :user_authorizations => :user_authorizations do |auth|
|
||||
attributes :uid, :provider, :token_expiration
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
#account-retailer.screen.secondary layout="screen" layout-id="account/retailer"
|
||||
.content-head
|
||||
.content-icon
|
||||
= image_tag "content/icon_account.png", :size => "27x20"
|
||||
h1
|
||||
| jamclass
|
||||
= render "screen_navigation"
|
||||
.content-body
|
||||
= react_component 'AccountRetailerScreen', {}
|
||||
|
|
@ -86,6 +86,7 @@
|
|||
<%= render "account_session_properties" %>
|
||||
<%= render "account_payment_history" %>
|
||||
<%= render "account_school" %>
|
||||
<%= render "account_retailer" %>
|
||||
<%= render "inviteMusicians" %>
|
||||
<%= render "hoverBand" %>
|
||||
<%= render "hoverFan" %>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
- provide(:page_name, 'landing_page full individual_jamtrack')
|
||||
- provide(:description, @description)
|
||||
- provide(:title, @title)
|
||||
|
||||
= react_component 'JamClassRetailerLandingPage', @page_data.to_json
|
||||
|
||||
- content_for :after_black_bar do
|
||||
.row.cta-row
|
||||
h2 SIGN UP YOUR STORE NOW!
|
||||
p Start generating more revenues, while helping your customers better engage with their instruments.
|
||||
p.cta-text Not sure if our retail partner program is for you? Scroll down to learn more.
|
||||
|
||||
- content_for :white_bar do
|
||||
= react_component 'JamClassRetailerLandingBottomPage', @page_data.to_json
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
- provide(:page_name, 'landing_page full posa_activation')
|
||||
- provide(:description, @description)
|
||||
- provide(:title, @title)
|
||||
|
||||
= react_component 'PosaActivationPage', @page_data.to_json
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
- provide(:page_name, 'landing_page full retailer_register teacher')
|
||||
- provide(:description, @description)
|
||||
- provide(:title, @title)
|
||||
|
||||
= react_component 'RetailerTeacherLandingPage', @page_data.to_json
|
||||
|
|
@ -49,6 +49,7 @@ Rails.application.routes.draw do
|
|||
get '/landing/jamclass/teachers', to: 'landings#jam_class_teachers', as: 'jamclass_teacher_signup'
|
||||
get '/landing/jamclass/affiliates', to: 'landings#jam_class_affiliates'
|
||||
get '/landing/jamclass/schools', to: 'landings#jam_class_schools'
|
||||
get '/landing/jamclass/retailers', to: 'landings#jam_class_retailers'
|
||||
|
||||
get '/affiliateProgram', to: 'landings#affiliate_program', as: 'affiliate_program'
|
||||
|
||||
|
|
@ -57,6 +58,9 @@ Rails.application.routes.draw do
|
|||
match '/school/:id/student', to: 'landings#school_student_register', via: :get, as: 'school_student_register'
|
||||
match '/school/:id/teacher', to: 'landings#school_teacher_register', via: :get, as: 'school_teacher_register'
|
||||
|
||||
match '/retailer/:id/teacher', to: 'landings#retailer_teacher_register', via: :get, as: 'retailer_teacher_register'
|
||||
match '/posa/:slug', to: 'landings#posa_activation', via: :get, as: 'posa_activation'
|
||||
|
||||
# redirect /jamtracks to jamtracks browse page
|
||||
get '/jamtracks', to: redirect('/client#/jamtrack/search')
|
||||
|
||||
|
|
@ -727,6 +731,21 @@ Rails.application.routes.draw do
|
|||
match '/schools/:id/students/:user_id' => 'api_schools#remove_student', :via => :delete
|
||||
match '/schools/:id/teachers/:teacher_id' => 'api_schools#remove_teacher', :via => :delete
|
||||
|
||||
match '/retailers/:id' => 'api_retailers#show', :via => :get
|
||||
match '/retailers/:id' => 'api_retailers#update', :via => :post
|
||||
match '/retailers/:id/avatar' => 'api_retailers#update_avatar', :via => :post
|
||||
match '/retailers/:id/avatar' => 'api_retailers#delete_avatar', :via => :delete
|
||||
match '/retailers/:id/filepicker_policy' => 'api_retailers#generate_filepicker_policy', :via => :get
|
||||
match '/retailers/:id/invitations' => 'api_retailer_invitations#index', :via => :get
|
||||
match '/retailers/:id/invitations' => 'api_retailer_invitations#create', :via => :post
|
||||
match '/retailers/:id/invitations/:invitation_id/resend' => 'api_retailer_invitations#resend', :via => :post
|
||||
match '/retailers/:id/invitations/:invitation_id' => 'api_retailer_invitations#delete', :via => :delete
|
||||
match '/retailers/:id/teachers/:teacher_id' => 'api_retailers#remove_teacher', :via => :delete
|
||||
match '/retailers/:id/customer_email' => 'api_retailers#customer_email', :via => :post
|
||||
match '/posa/:slug/activate' => 'api_posa_cards#activate', via: :post
|
||||
match '/posa/claim' => 'api_posa_cards#claim', via: :post
|
||||
|
||||
|
||||
match '/teacher_distributions' => 'api_teacher_distributions#index', :via => :get
|
||||
|
||||
match '/stripe' => 'api_stripe#store', :via => :post
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ module JamRuby
|
|||
|
||||
# https://developers.google.com/youtube/v3/docs/videos/insert
|
||||
# https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol
|
||||
def sign_youtube_upload(user, filename, length)
|
||||
def sign_youtube_upload(user, filename, length)
|
||||
raise ArgumentError, "Length is required and should be > 0" if length.to_i.zero?
|
||||
|
||||
# Something like this:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ApiPosaCardsController, type: :controller do
|
||||
render_views
|
||||
|
||||
let (:password) {'abcdef'}
|
||||
let (:posa_card) {FactoryGirl.create(:posa_card)}
|
||||
let (:owner) {FactoryGirl.create(:user)}
|
||||
let (:user) {FactoryGirl.create(:user)}
|
||||
let (:retailer) {FactoryGirl.create(:retailer, user: owner)}
|
||||
|
||||
let (:authorization) { 'Basic ' + Base64::encode64("#{password}:#{password}") }
|
||||
|
||||
|
||||
|
||||
before(:each) do
|
||||
retailer.update_from_params({password:password})
|
||||
end
|
||||
|
||||
describe "activate" do
|
||||
it "works" do
|
||||
request.headers['HTTP_AUTHORIZATION'] = authorization
|
||||
get :activate, slug: retailer.slug, code: posa_card.code
|
||||
response.should be_success
|
||||
JSON.parse(response.body)['id'].should eql posa_card.id
|
||||
posa_card.reload
|
||||
posa_card.activated_at.should_not be_nil
|
||||
posa_card.retailer.should eql retailer
|
||||
end
|
||||
end
|
||||
|
||||
describe "claim" do
|
||||
it "works" do
|
||||
controller.current_user = user
|
||||
posa_card.activate(retailer)
|
||||
get :claim, code: posa_card.code
|
||||
response.should be_success
|
||||
JSON.parse(response.body)['id'].should eql posa_card.id
|
||||
posa_card.reload
|
||||
posa_card.claimed_at.should_not be_nil
|
||||
posa_card.user.should eql user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ApiRetailerInvitationsController, type: :controller do
|
||||
render_views
|
||||
|
||||
let (:owner) {FactoryGirl.create(:user)}
|
||||
let (:retailer) {FactoryGirl.create(:retailer, user: owner)}
|
||||
let (:retailer_invitation_teacher) {FactoryGirl.create(:retailer_invitation, retailer: retailer)}
|
||||
|
||||
before(:each) do
|
||||
controller.current_user = owner
|
||||
end
|
||||
|
||||
describe "index" do
|
||||
it "works" do
|
||||
get :index, id: retailer.id
|
||||
response.should be_success
|
||||
JSON.parse(response.body)['total_entries'].should eql 0
|
||||
|
||||
retailer_invitation_teacher.touch
|
||||
get :index, id: retailer.id
|
||||
response.should be_success
|
||||
JSON.parse(response.body)['total_entries'].should eql 1
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "create" do
|
||||
it "works" do
|
||||
UserMailer.deliveries.clear
|
||||
post :create, id: retailer.id, first_name: "Seth", last_name: "Call", email: "seth@jamkazam.com", :format => 'json'
|
||||
response.should be_success
|
||||
UserMailer.deliveries.length.should eql 1
|
||||
JSON.parse(response.body)['id'].should eql RetailerInvitation.find_by_email("seth@jamkazam.com").id
|
||||
end
|
||||
end
|
||||
|
||||
describe "resend" do
|
||||
it "works" do
|
||||
UserMailer.deliveries.clear
|
||||
post :resend, id: retailer.id, invitation_id: retailer_invitation_teacher.id, :format => 'json'
|
||||
UserMailer.deliveries.length.should eql 1
|
||||
response.should be_success
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete" do
|
||||
it "works" do
|
||||
delete :delete, id: retailer.id, invitation_id: retailer_invitation_teacher.id, :format => 'json'
|
||||
response.should be_success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ApiRetailersController, type: :controller do
|
||||
render_views
|
||||
|
||||
let (:owner) {FactoryGirl.create(:user)}
|
||||
let (:retailer) {FactoryGirl.create(:retailer, user: owner)}
|
||||
|
||||
before(:each) do
|
||||
controller.current_user = owner
|
||||
end
|
||||
|
||||
describe "show" do
|
||||
it "works" do
|
||||
get :show, id: retailer.id
|
||||
response.should be_success
|
||||
JSON.parse(response.body)['id'].should eql retailer.id
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "update" do
|
||||
|
||||
it "works" do
|
||||
post :update, id: retailer.id, name: "Hardy har", format: 'json'
|
||||
response.should be_success
|
||||
json = JSON.parse(response.body)
|
||||
json['name'].should eql "Hardy har"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -867,6 +867,19 @@ FactoryGirl.define do
|
|||
card_type GiftCard::JAM_TRACKS_5
|
||||
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
|
||||
|
|
@ -901,6 +914,22 @@ FactoryGirl.define do
|
|||
accepted false
|
||||
end
|
||||
|
||||
factory :retailer, class: 'JamRuby::Retailer' do
|
||||
association :user, factory: :user
|
||||
sequence(:name) {|n| "Dat 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'
|
||||
|
|
|
|||
|
|
@ -151,9 +151,14 @@ def web_config
|
|||
def email_partners_alias
|
||||
"partner-dev@jamkazam.com"
|
||||
end
|
||||
|
||||
def test_drive_wait_period_year
|
||||
1
|
||||
end
|
||||
|
||||
def jam_class_card_wait_period_year
|
||||
1
|
||||
end
|
||||
end
|
||||
klass.new
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue