diff --git a/admin/app/controllers/artifacts_controller.rb b/admin/app/controllers/artifacts_controller.rb
index f9ee76b4b..88699cd80 100644
--- a/admin/app/controllers/artifacts_controller.rb
+++ b/admin/app/controllers/artifacts_controller.rb
@@ -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
diff --git a/db/manifest b/db/manifest
index 66680d0e7..3719337d9 100755
--- a/db/manifest
+++ b/db/manifest
@@ -363,4 +363,5 @@ jamblasters_network.sql
immediate_recordings.sql
nullable_user_id_jamblaster.sql
rails4_migration.sql
-non_free_jamtracks.sql
\ No newline at end of file
+non_free_jamtracks.sql
+retailers.sql
\ No newline at end of file
diff --git a/db/up/retailers.sql b/db/up/retailers.sql
new file mode 100644
index 000000000..0a3d564b7
--- /dev/null
+++ b/db/up/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);
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb
index 45fc7b857..4afe09eb5 100755
--- a/ruby/lib/jam_ruby.rb
+++ b/ruby/lib/jam_ruby.rb
@@ -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"
diff --git a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb
index aab962c1f..14b491c2b 100644
--- a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb
+++ b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb
@@ -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
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/invite_retailer_student.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/invite_retailer_student.html.erb
new file mode 100644
index 000000000..f097b022e
--- /dev/null
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/invite_retailer_student.html.erb
@@ -0,0 +1,16 @@
+<% provide(:title, @subject) %>
+
+Hello <%= @retailer_invitation.first_name %> -
+
<%= @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!
+
+
+Best Regards,
+Team JamKazam
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/invite_retailer_teacher.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/invite_retailer_teacher.html.erb
new file mode 100644
index 000000000..25c0d5ef4
--- /dev/null
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/invite_retailer_teacher.html.erb
@@ -0,0 +1,18 @@
+<% 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 SIGN UP NOW button below,
+ and follow the instructions on the web page to which you are taken. Thanks, and welcome to JamKazam!
+
+
+
+Best Regards,
+Team JamKazam
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/invite_retailer_teacher.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/invite_retailer_teacher.text.erb
new file mode 100644
index 000000000..f4b5bffe9
--- /dev/null
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/invite_retailer_teacher.text.erb
@@ -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
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/retailer_customer_blast.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/retailer_customer_blast.html.erb
new file mode 100644
index 000000000..204701323
--- /dev/null
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/retailer_customer_blast.html.erb
@@ -0,0 +1,8 @@
+<% provide(:title, @subject) %>
+
+
Click the link of each teacher's profile at <%= @retailer.name %> to find the best fit for you:
The agreement between your music school and JamKazam is part of JamKazam's terms of service. You can find the
+ complete terms of service here. And you can find the section that is
+ most specific to the retailer terms here.
How Our Retail Partner Program Can Help Your Store
+
+
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.
+
+
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.
+
+
Watch the videos below that explain and show our JamClass online music lesson service and our JamTracks
+ products in more detail.
+
+
+
JamClass Kudos
+
+
+
+
+
Julie Bonk
+
+
+ Oft-recorded pianist, teacher, mentor to Grammy winner Norah Jones and Scott Hoying of Pentatonix
+
+
+
+
+
+
+
Carl Brown of GuitarLessions365
+
+
+
+
+
Justin Pierce
+
+
+ Masters degree in jazz studies, performer in multiple bands, saxophone instructor
+
+
+
+
+
+
Dave Sebree
+
+
+ Founder of Austin School of Music, Gibson-endorsed guitarist, touring musician
+
+
+
+
+
+
Sara Nelson
+
+
+ Cellist for Austin Lyric Opera, frequently recorded with major artists
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`
+})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/landing/JamClassRetailerLandingPage.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/JamClassRetailerLandingPage.js.jsx.coffee
new file mode 100644
index 000000000..6fb662ddc
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/landing/JamClassRetailerLandingPage.js.jsx.coffee
@@ -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 = ``
+ 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 = `
+
+ {errorText}
+
+
+
`
+
+
+ `
+
+
+
+
+
MANAGE A MUSIC INSTRUMENT STORE?
+
+
Increase revenues without more inventory or space
+
+
+
+
+
+
+
+
+
+ Sign Up Your Store
+
+
+
Sign up to let us know you’re interested in partnering, and we’ll follow up to answer your questions.
+
+
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.
Learn how we can help you increase revenues without additional inventory or floor space.
+
+
+
+
+
+ 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.
+
`
+ 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 =
+ `
+
+
`
+ 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 = `
+
+ {errorText}
+
+
+
+ We will not share your email. See our privacy policy.
+
+
`
+
+
+ `
+
+
+ {logo}
+
+
+
REGISTER AS A TEACHER
+
+
with {this.props.retailer.name}
+
+
+
+
+
+
+
+ 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.
+
+
+
+ {register}
+
`
+
+ 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")
+ )
+})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/stores/RetailerStore.js.coffee b/web/app/assets/javascripts/react-components/stores/RetailerStore.js.coffee
new file mode 100644
index 000000000..43ebec4fb
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/stores/RetailerStore.js.coffee
@@ -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}
+ }
+)
diff --git a/web/app/assets/stylesheets/landing/landing.css b/web/app/assets/stylesheets/landing/landing.css
index a37a24675..d18d95761 100644
--- a/web/app/assets/stylesheets/landing/landing.css
+++ b/web/app/assets/stylesheets/landing/landing.css
@@ -11,4 +11,5 @@
*= require users/signin
*= require dialogs/dialog
*= require icheck/minimal/minimal
+*= require landings/posa_activation
*/
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/landings/individual_jamtrack.scss b/web/app/assets/stylesheets/landings/individual_jamtrack.scss
index cf19b8a6a..3e8e8e583 100644
--- a/web/app/assets/stylesheets/landings/individual_jamtrack.scss
+++ b/web/app/assets/stylesheets/landings/individual_jamtrack.scss
@@ -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;
diff --git a/web/app/assets/stylesheets/landings/posa_activation.scss b/web/app/assets/stylesheets/landings/posa_activation.scss
new file mode 100644
index 000000000..1bedcbb83
--- /dev/null
+++ b/web/app/assets/stylesheets/landings/posa_activation.scss
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/landings/retailer_landing.scss b/web/app/assets/stylesheets/landings/retailer_landing.scss
new file mode 100644
index 000000000..71350f0fd
--- /dev/null
+++ b/web/app/assets/stylesheets/landings/retailer_landing.scss
@@ -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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/app/controllers/api_posa_cards_controller.rb b/web/app/controllers/api_posa_cards_controller.rb
new file mode 100644
index 000000000..be053921d
--- /dev/null
+++ b/web/app/controllers/api_posa_cards_controller.rb
@@ -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
diff --git a/web/app/controllers/api_retailer_invitations_controller.rb b/web/app/controllers/api_retailer_invitations_controller.rb
new file mode 100644
index 000000000..93a38e297
--- /dev/null
+++ b/web/app/controllers/api_retailer_invitations_controller.rb
@@ -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
+
diff --git a/web/app/controllers/api_retailers_controller.rb b/web/app/controllers/api_retailers_controller.rb
new file mode 100644
index 000000000..7d43f9377
--- /dev/null
+++ b/web/app/controllers/api_retailers_controller.rb
@@ -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
+
diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb
index 4f6753670..00f7a8d57 100644
--- a/web/app/controllers/api_users_controller.rb
+++ b/web/app/controllers/api_users_controller.rb
@@ -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])
diff --git a/web/app/controllers/landings_controller.rb b/web/app/controllers/landings_controller.rb
index 1f15c8eb2..f05c29a8c 100644
--- a/web/app/controllers/landings_controller.rb
+++ b/web/app/controllers/landings_controller.rb
@@ -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
diff --git a/web/app/helpers/sessions_helper.rb b/web/app/helpers/sessions_helper.rb
index 30257fe1f..6301e08a0 100644
--- a/web/app/helpers/sessions_helper.rb
+++ b/web/app/helpers/sessions_helper.rb
@@ -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
diff --git a/web/app/views/api_posa_cards/activate.rabl b/web/app/views/api_posa_cards/activate.rabl
new file mode 100644
index 000000000..2eb6bccd3
--- /dev/null
+++ b/web/app/views/api_posa_cards/activate.rabl
@@ -0,0 +1,3 @@
+object @posa_card
+
+extends "api_posa_cards/show"
\ No newline at end of file
diff --git a/web/app/views/api_posa_cards/claim.rabl b/web/app/views/api_posa_cards/claim.rabl
new file mode 100644
index 000000000..2eb6bccd3
--- /dev/null
+++ b/web/app/views/api_posa_cards/claim.rabl
@@ -0,0 +1,3 @@
+object @posa_card
+
+extends "api_posa_cards/show"
\ No newline at end of file
diff --git a/web/app/views/api_posa_cards/show.rabl b/web/app/views/api_posa_cards/show.rabl
new file mode 100644
index 000000000..d95087adb
--- /dev/null
+++ b/web/app/views/api_posa_cards/show.rabl
@@ -0,0 +1,3 @@
+@posa_card
+
+attributes :id
\ No newline at end of file
diff --git a/web/app/views/api_retailer_invitations/create.rabl b/web/app/views/api_retailer_invitations/create.rabl
new file mode 100644
index 000000000..2cbf20032
--- /dev/null
+++ b/web/app/views/api_retailer_invitations/create.rabl
@@ -0,0 +1,3 @@
+object @retailer_invitation
+
+extends "api_retailer_invitations/show"
\ No newline at end of file
diff --git a/web/app/views/api_retailer_invitations/index.rabl b/web/app/views/api_retailer_invitations/index.rabl
new file mode 100644
index 000000000..14ad9ba90
--- /dev/null
+++ b/web/app/views/api_retailer_invitations/index.rabl
@@ -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
diff --git a/web/app/views/api_retailer_invitations/resend.rabl b/web/app/views/api_retailer_invitations/resend.rabl
new file mode 100644
index 000000000..2cbf20032
--- /dev/null
+++ b/web/app/views/api_retailer_invitations/resend.rabl
@@ -0,0 +1,3 @@
+object @retailer_invitation
+
+extends "api_retailer_invitations/show"
\ No newline at end of file
diff --git a/web/app/views/api_retailer_invitations/show.rabl b/web/app/views/api_retailer_invitations/show.rabl
new file mode 100644
index 000000000..dc1c2d61a
--- /dev/null
+++ b/web/app/views/api_retailer_invitations/show.rabl
@@ -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
\ No newline at end of file
diff --git a/web/app/views/api_retailers/delete_avatar.rabl b/web/app/views/api_retailers/delete_avatar.rabl
new file mode 100644
index 000000000..0fbc7dbcd
--- /dev/null
+++ b/web/app/views/api_retailers/delete_avatar.rabl
@@ -0,0 +1,3 @@
+object @retailer
+
+extends "api_retailers/show"
\ No newline at end of file
diff --git a/web/app/views/api_retailers/remove_teacher.rabl b/web/app/views/api_retailers/remove_teacher.rabl
new file mode 100644
index 000000000..0fbc7dbcd
--- /dev/null
+++ b/web/app/views/api_retailers/remove_teacher.rabl
@@ -0,0 +1,3 @@
+object @retailer
+
+extends "api_retailers/show"
\ No newline at end of file
diff --git a/web/app/views/api_retailers/show.rabl b/web/app/views/api_retailers/show.rabl
new file mode 100644
index 000000000..7b7367988
--- /dev/null
+++ b/web/app/views/api_retailers/show.rabl
@@ -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
\ No newline at end of file
diff --git a/web/app/views/api_retailers/update.rabl b/web/app/views/api_retailers/update.rabl
new file mode 100644
index 000000000..0fbc7dbcd
--- /dev/null
+++ b/web/app/views/api_retailers/update.rabl
@@ -0,0 +1,3 @@
+object @retailer
+
+extends "api_retailers/show"
\ No newline at end of file
diff --git a/web/app/views/api_retailers/update_avatar.rabl b/web/app/views/api_retailers/update_avatar.rabl
new file mode 100644
index 000000000..0fbc7dbcd
--- /dev/null
+++ b/web/app/views/api_retailers/update_avatar.rabl
@@ -0,0 +1,3 @@
+object @retailer
+
+extends "api_retailers/show"
\ No newline at end of file
diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl
index b141b182f..a12056a76 100644
--- a/web/app/views/api_users/show.rabl
+++ b/web/app/views/api_users/show.rabl
@@ -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
diff --git a/web/app/views/clients/_account_retailer.html.slim b/web/app/views/clients/_account_retailer.html.slim
new file mode 100644
index 000000000..5ebfa65c1
--- /dev/null
+++ b/web/app/views/clients/_account_retailer.html.slim
@@ -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', {}
\ No newline at end of file
diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb
index 4a4574a14..220bca602 100644
--- a/web/app/views/clients/index.html.erb
+++ b/web/app/views/clients/index.html.erb
@@ -86,6 +86,7 @@
<%= render "account_session_properties" %>
<%= render "account_payment_history" %>
<%= render "account_school" %>
+<%= render "account_retailer" %>
<%= render "inviteMusicians" %>
<%= render "hoverBand" %>
<%= render "hoverFan" %>
diff --git a/web/app/views/landings/jam_class_retailers.html.slim b/web/app/views/landings/jam_class_retailers.html.slim
new file mode 100644
index 000000000..46d4d0bd2
--- /dev/null
+++ b/web/app/views/landings/jam_class_retailers.html.slim
@@ -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
diff --git a/web/app/views/landings/posa_activation.html.slim b/web/app/views/landings/posa_activation.html.slim
new file mode 100644
index 000000000..f2d4ca774
--- /dev/null
+++ b/web/app/views/landings/posa_activation.html.slim
@@ -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
\ No newline at end of file
diff --git a/web/app/views/landings/retailer_teacher_register.html.slim b/web/app/views/landings/retailer_teacher_register.html.slim
new file mode 100644
index 000000000..88b6ec0f4
--- /dev/null
+++ b/web/app/views/landings/retailer_teacher_register.html.slim
@@ -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
\ No newline at end of file
diff --git a/web/config/routes.rb b/web/config/routes.rb
index 1de51ea0b..2064b7e6d 100644
--- a/web/config/routes.rb
+++ b/web/config/routes.rb
@@ -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
diff --git a/web/lib/google_client.rb b/web/lib/google_client.rb
index b15d23b7f..1406f67f2 100644
--- a/web/lib/google_client.rb
+++ b/web/lib/google_client.rb
@@ -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:
diff --git a/web/spec/controllers/api_posa_cards_controller_spec.rb b/web/spec/controllers/api_posa_cards_controller_spec.rb
new file mode 100644
index 000000000..fb1940348
--- /dev/null
+++ b/web/spec/controllers/api_posa_cards_controller_spec.rb
@@ -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
diff --git a/web/spec/controllers/api_retailer_invitations_controller_spec.rb b/web/spec/controllers/api_retailer_invitations_controller_spec.rb
new file mode 100644
index 000000000..4875dd0d0
--- /dev/null
+++ b/web/spec/controllers/api_retailer_invitations_controller_spec.rb
@@ -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
diff --git a/web/spec/controllers/api_retailers_controller_spec.rb b/web/spec/controllers/api_retailers_controller_spec.rb
new file mode 100644
index 000000000..5a3edcb4f
--- /dev/null
+++ b/web/spec/controllers/api_retailers_controller_spec.rb
@@ -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
diff --git a/web/spec/factories.rb b/web/spec/factories.rb
index 51090a8a0..892cb7df5 100644
--- a/web/spec/factories.rb
+++ b/web/spec/factories.rb
@@ -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'
diff --git a/web/spec/support/app_config.rb b/web/spec/support/app_config.rb
index a635f0020..6a6f649ec 100644
--- a/web/spec/support/app_config.rb
+++ b/web/spec/support/app_config.rb
@@ -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