From 3117c7ed3e84d52bdea2c027b1b9ff62b60a7e63 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 26 Sep 2016 21:56:12 -0500 Subject: [PATCH] posa2wip --- db/manifest | 3 +- db/up/retailers_v2.sql | 3 + ruby/lib/jam_ruby/app/mailers/user_mailer.rb | 15 +- ...udent_test_drive_lesson_completed.html.erb | 4 +- ...udent_test_drive_lesson_completed.text.erb | 2 +- ruby/lib/jam_ruby/models/lesson_booking.rb | 19 +- .../models/lesson_package_purchase.rb | 14 +- ruby/lib/jam_ruby/models/lesson_session.rb | 16 +- ruby/lib/jam_ruby/models/posa_card.rb | 12 + ruby/lib/jam_ruby/models/retailer.rb | 16 +- ruby/lib/jam_ruby/models/school.rb | 10 +- ruby/lib/jam_ruby/models/teacher.rb | 13 +- ruby/lib/jam_ruby/models/user.rb | 62 +++- web/app/assets/javascripts/accounts.js | 7 + .../accounts_profile_experience.js | 3 + .../AccountRetailerScreen.js.jsx.coffee | 57 +--- .../AvatarEditLink.js.jsx.coffee | 22 +- .../InviteRetailerUserDialog.js.jsx.coffee | 147 ++++++++++ .../InviteSchoolUserDialog.js.jsx.coffee | 14 +- .../stores/AvatarStore.js.coffee | 69 +++-- .../stores/RetailerStore.js.coffee | 2 +- .../stores/StripeStore.js.coffee | 2 + .../client/accountProfileExperience.scss | 35 +++ .../AccountRetailerScreen.scss | 265 ++++++++++++++++++ .../dialogs/inviteRetailerUser.scss | 39 +++ web/app/controllers/api_users_controller.rb | 2 + web/app/views/clients/_account.html.erb | 19 +- .../_account_profile_experience.html.erb | 6 +- web/app/views/dialogs/_dialogs.html.haml | 1 + .../_inviteRetailerUserDialog.html.slim | 2 + web/lib/user_manager.rb | 4 + 31 files changed, 779 insertions(+), 106 deletions(-) create mode 100644 db/up/retailers_v2.sql create mode 100644 web/app/assets/javascripts/react-components/InviteRetailerUserDialog.js.jsx.coffee create mode 100644 web/app/assets/stylesheets/client/react-components/AccountRetailerScreen.scss create mode 100644 web/app/assets/stylesheets/dialogs/inviteRetailerUser.scss create mode 100644 web/app/views/dialogs/_inviteRetailerUserDialog.html.slim diff --git a/db/manifest b/db/manifest index 66532119f..eafff9a41 100755 --- a/db/manifest +++ b/db/manifest @@ -366,4 +366,5 @@ rails4_migration.sql non_free_jamtracks.sql retailers.sql second_ed.sql -second_ed_v2.sql \ No newline at end of file +second_ed_v2.sql +retailers_v2.sql \ No newline at end of file diff --git a/db/up/retailers_v2.sql b/db/up/retailers_v2.sql new file mode 100644 index 000000000..73f5ea21d --- /dev/null +++ b/db/up/retailers_v2.sql @@ -0,0 +1,3 @@ +ALTER TABLE lesson_bookings ADD COLUMN posa_card_id VARCHAR(64); +ALTER TABLE jam_track_rights ADD COLUMN posa_card_id VARCHAR(64); +ALTER TABLE lesson_package_purchases ADD COLUMN posa_card_id VARCHAR(64); \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb index 0ea5a7940..b8835b7bf 100644 --- a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb @@ -1038,7 +1038,20 @@ module JamRuby @user = lesson_session.student email = @student.email - subject = "You have used #{@student.used_test_drives} of #{@student.total_test_drives} TestDrive lesson credits" + + + if lesson_session.posa_card + @total_credits = @student.total_posa_credits + @used_credits = @student.used_posa_credits + @remaining_credits = @student.jamclass_credits + else + @total_credits = @student.total_test_drives + @used_credits = @student.used_test_drives + @remaining_credits = @student.remaining_test_drives + end + + subject = "You have used #{@used_credits} of #{@total_credits} TestDrive lesson credits" + unique_args = {:type => "student_test_drive_success"} sendgrid_category "Notification" diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_test_drive_lesson_completed.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_test_drive_lesson_completed.html.erb index 56e8750e5..8323ff836 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_test_drive_lesson_completed.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_test_drive_lesson_completed.html.erb @@ -1,4 +1,4 @@ -<% provide(:title, "You have used #{@student.used_test_drives} of #{@student.total_test_drives} TestDrive lesson credits") %> +<% provide(:title, "You have used #{@used_credits} of #{@total_credits} TestDrive lesson credits") %> <% provide(:photo_url, @teacher.resolved_photo_url) %> <% content_for :note do %> @@ -7,7 +7,7 @@

We hope you enjoyed your JamClass lesson today with <%= @teacher.name %>. You have - used <%= @student.used_test_drives %> TestDrive credits, and you have <%= @student.remaining_test_drives %> + used <%= @used_credits %> TestDrive credits, and you have <%= @remaining_credits %> remaining TestDrive lesson(s) available. If you haven’t booked your next TestDrive lesson, click here to search our teachers and get your next lesson lined up today!

diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_test_drive_lesson_completed.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_test_drive_lesson_completed.text.erb index 34db7e7bc..320ae8fc1 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_test_drive_lesson_completed.text.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_test_drive_lesson_completed.text.erb @@ -1,4 +1,4 @@ -You have used <%= @student.used_test_drives %> of <%= @student.total_test_drives %> TestDrive lesson credits. +You have used <%= @used_credits %> of <%= @total_credits %> TestDrive lesson credits. <% if @student.has_rated_teacher(@teacher) %> Also, please rate your teacher at <%= @teacher.ratings_url %> now for today’s lesson to help other students in the community find the best instructors. diff --git a/ruby/lib/jam_ruby/models/lesson_booking.rb b/ruby/lib/jam_ruby/models/lesson_booking.rb index 4ab4c9406..4235d486c 100644 --- a/ruby/lib/jam_ruby/models/lesson_booking.rb +++ b/ruby/lib/jam_ruby/models/lesson_booking.rb @@ -221,7 +221,12 @@ module JamRuby if is_single_free? user.remaining_free_lessons = user.remaining_free_lessons - 1 elsif is_test_drive? - user.remaining_test_drives = user.remaining_test_drives - 1 + if posa_card + user.jamclass_credits = user.jamclass_credits - 1 + else + user.remaining_test_drives = user.remaining_test_drives - 1 + end + end user.save(validate: false) end @@ -762,7 +767,17 @@ module JamRuby lesson_booking.payment_style = payment_style lesson_booking.description = description lesson_booking.status = STATUS_REQUESTED - lesson_booking.test_drive_package_choice = test_drive_package_choice + if lesson_type == LESSON_TYPE_TEST_DRIVE + # if the user has any jamclass credits, then we should get their most recent posa purchase + if user.jamclass_credits > 0 + lesson_booking.posa_card = most_recent_posa_purchase.posa_card + else + # otherwise, it's a normal test drive, and we should honor test_drive_package_choice if specified + lesson_booking.test_drive_package_choice = test_drive_package_choice + end + end + + if lesson_booking.teacher && lesson_booking.teacher.teacher.school lesson_booking.school = lesson_booking.teacher.teacher.school end diff --git a/ruby/lib/jam_ruby/models/lesson_package_purchase.rb b/ruby/lib/jam_ruby/models/lesson_package_purchase.rb index 0f99ebe1e..dd3aa914e 100644 --- a/ruby/lib/jam_ruby/models/lesson_package_purchase.rb +++ b/ruby/lib/jam_ruby/models/lesson_package_purchase.rb @@ -15,6 +15,7 @@ module JamRuby belongs_to :teacher, class_name: "JamRuby::User" belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking" belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id + belongs_to :posa_card, class_name: "JamRuby::PosaCard", foreign_key: :posa_card_id has_one :lesson_session, class_name: "JamRuby::LessonSession", dependent: :destroy has_many :teacher_distributions, class_name: "JamRuby::TeacherDistribution" @@ -30,6 +31,10 @@ module JamRuby def validate_test_drive if user + # if this is a posa card purchase, we won't stop it from getting created + if posa_card_id + return + end if lesson_package_type.is_test_drive? && !user.can_buy_test_drive? errors.add(:user, "can not buy test drive right now because you have already purchased it within the last year") end @@ -56,6 +61,11 @@ module JamRuby end def add_test_drives + if posa_card_id + #user.jamclass_credits incremented in posa_card.rb + return + end + if self.lesson_package_type.is_test_drive? new_test_drives = user.remaining_test_drives + lesson_package_type.test_drive_count User.where(id: user.id).update_all(remaining_test_drives: new_test_drives) @@ -75,17 +85,19 @@ module JamRuby lesson_payment_charge.amount_in_cents / 100.0 end - def self.create(user, lesson_booking, lesson_package_type, year = nil, month = nil) + def self.create(user, lesson_booking, lesson_package_type, year = nil, month = nil, posa_card = nil) purchase = LessonPackagePurchase.new purchase.user = user purchase.lesson_booking = lesson_booking purchase.teacher = lesson_booking.teacher if lesson_booking + purchase.posa_card = posa_card if year purchase.year = year purchase.month = month purchase.recurring = true + # this is for monthly if lesson_booking && lesson_booking.requires_teacher_distribution?(purchase) teacher_dist = TeacherDistribution.create_for_lesson_package_purchase(purchase, false) purchase.teacher_distributions << teacher_dist diff --git a/ruby/lib/jam_ruby/models/lesson_session.rb b/ruby/lib/jam_ruby/models/lesson_session.rb index 88a42a2e9..1a4e01add 100644 --- a/ruby/lib/jam_ruby/models/lesson_session.rb +++ b/ruby/lib/jam_ruby/models/lesson_session.rb @@ -11,7 +11,7 @@ module JamRuby @@log = Logging.logger[LessonSession] delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed_at, :billing_error_detail, :billing_error_reason, :is_card_declined?, :is_card_expired?, :last_billed_at_date, :sent_billing_notices, to: :lesson_payment_charge, allow_nil: true - delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, :recurring, :is_monthly_payment?, :school_on_school?, :school_on_school_payment?, :no_school_on_school_payment?, :payment_if_school_on_school?, :scheduling_email, :teacher_school_emails, :school_and_teacher, :school_over_teacher, :school_and_teacher_ids, :school_over_teacher_ids, to: :lesson_booking + delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, :recurring, :is_monthly_payment?, :school_on_school?, :school_on_school_payment?, :no_school_on_school_payment?, :payment_if_school_on_school?, :scheduling_email, :teacher_school_emails, :school_and_teacher, :school_over_teacher, :school_and_teacher_ids, :school_over_teacher_ids, :posa_card, to: :lesson_booking delegate :pretty_scheduled_start, to: :music_session @@ -581,8 +581,12 @@ module JamRuby lesson_session.slot = booking.default_slot lesson_session.assigned_student = booking.student lesson_session.user = booking.student - if booking.is_test_drive? && booking.student.remaining_test_drives > 0 - lesson_session.lesson_package_purchase = booking.student.most_recent_test_drive_purchase + if booking.is_test_drive? + if booking.student.jamclass_credits > 0 + lesson_session.lesson_package_purchase = booking.student.most_recent_posa_purchase + elsif booking.student.remaining_test_drives > 0 + lesson_session.lesson_package_purchase = booking.student.most_recent_test_drive_purchase + end end lesson_session.save @@ -739,7 +743,11 @@ module JamRuby # 1st time this has ever been approved; there are other things we need to do if lesson_package_purchase.nil? && lesson_booking.is_test_drive? - self.lesson_package_purchase = student.most_recent_test_drive_purchase + if student.jamclass_credits > 0 + self.lesson_package_purchase = student.most_recent_posa_purchase + elsif student.remaining_test_drives > 0 + self.lesson_package_purchase = student.most_recent_test_drive_purchase + end end if self.save diff --git a/ruby/lib/jam_ruby/models/posa_card.rb b/ruby/lib/jam_ruby/models/posa_card.rb index 58028ed5a..696d2ee80 100644 --- a/ruby/lib/jam_ruby/models/posa_card.rb +++ b/ruby/lib/jam_ruby/models/posa_card.rb @@ -31,6 +31,18 @@ module JamRuby validate :must_be_activated validate :within_one_year + def credits + if card_type == JAM_TRACKS_5 + 5 + elsif card_type == JAM_TRACKS_10 + 10 + elsif card_type == JAM_CLASS_4 + 4 + else + raise "unknown card type #{card_type}" + end + end + def already_activated if activated_at && activated_at_was && activated_at_changed? if retailer && retailer_id == retailer_id_was diff --git a/ruby/lib/jam_ruby/models/retailer.rb b/ruby/lib/jam_ruby/models/retailer.rb index 40169e5ec..7c76576a2 100644 --- a/ruby/lib/jam_ruby/models/retailer.rb +++ b/ruby/lib/jam_ruby/models/retailer.rb @@ -23,7 +23,7 @@ module JamRuby validates_length_of :password, minimum: 6, maximum: 100, :if => :should_validate_password after_create :create_affiliate - before_save :stringify_avatar_info, :if => :updating_avatar + # before_save :stringify_avatar_info, :if => :updating_avatar def create_affiliate AffiliatePartner.create_from_retailer(self) @@ -77,12 +77,12 @@ module JamRuby cropped_large_s3_path = cropped_large_fpfile["key"] self.update_attributes( - :original_fpfile => original_fpfile, - :cropped_fpfile => cropped_fpfile, - :cropped_large_fpfile => cropped_large_fpfile, + :original_fpfile => original_fpfile.to_json, + :cropped_fpfile => cropped_fpfile.to_json, + :cropped_large_fpfile => cropped_large_fpfile.to_json, :cropped_s3_path => cropped_s3_path, :cropped_large_s3_path => cropped_large_s3_path, - :crop_selection => crop_selection, + :crop_selection => crop_selection.to_json, :photo_url => S3Util.url(aws_bucket, escape_filename(cropped_s3_path), :secure => true), :large_photo_url => S3Util.url(aws_bucket, escape_filename(cropped_large_s3_path), :secure => true) ) @@ -116,9 +116,9 @@ module JamRuby # 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? + self.original_fpfile = original_fpfile.to_json if !original_fpfile.nil? && original_fpfile.class != String + self.cropped_fpfile = cropped_fpfile.to_json if !cropped_fpfile.nil? && cropped_fpfile.class != String + self.crop_selection = crop_selection.to_json if !crop_selection.nil? && crop_selection.class != String end end end diff --git a/ruby/lib/jam_ruby/models/school.rb b/ruby/lib/jam_ruby/models/school.rb index 94aaea30e..804407ce6 100644 --- a/ruby/lib/jam_ruby/models/school.rb +++ b/ruby/lib/jam_ruby/models/school.rb @@ -30,7 +30,7 @@ module JamRuby validate :validate_avatar_info after_create :create_affiliate - before_save :stringify_avatar_info, :if => :updating_avatar + #before_save :stringify_avatar_info, :if => :updating_avatar def is_education? education @@ -84,12 +84,12 @@ module JamRuby cropped_large_s3_path = cropped_large_fpfile["key"] self.update_attributes( - :original_fpfile => original_fpfile, - :cropped_fpfile => cropped_fpfile, - :cropped_large_fpfile => cropped_large_fpfile, + :original_fpfile => original_fpfile.to_json, + :cropped_fpfile => cropped_fpfile.to_json, + :cropped_large_fpfile => cropped_large_fpfile.to_json, :cropped_s3_path => cropped_s3_path, :cropped_large_s3_path => cropped_large_s3_path, - :crop_selection => crop_selection, + :crop_selection => crop_selection.to_json, :photo_url => S3Util.url(aws_bucket, escape_filename(cropped_s3_path), :secure => true), :large_photo_url => S3Util.url(aws_bucket, escape_filename(cropped_large_s3_path), :secure => true) ) diff --git a/ruby/lib/jam_ruby/models/teacher.rb b/ruby/lib/jam_ruby/models/teacher.rb index 126847f48..42b71a9e6 100644 --- a/ruby/lib/jam_ruby/models/teacher.rb +++ b/ruby/lib/jam_ruby/models/teacher.rb @@ -219,7 +219,18 @@ module JamRuby teacher.teaches_test_drive = params[:teaches_test_drive] if params.key?(:teaches_test_drive) teacher.test_drives_per_week = params[:test_drives_per_week] if params.key?(:test_drives_per_week) teacher.test_drives_per_week = 10 if !params.key?(:test_drives_per_week) # default to 10 in absence of others - teacher.school_id = params[:school_id] if params.key?(:school_id) + if params.key?(:school_id) + teacher.school_id = params[:school_id] + if !teacher.joined_school_at + teacher.joined_school_at = Time.now + end + end + if params.key?(:retailer_id) + teacher.retailer_id = params[:retailer_id] + if !teacher.joined_retailer_at + teacher.joined_retailer_at = Time.now + end + end # How to validate: teacher.validate_introduction = !!params[:validate_introduction] diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 83e4ab5eb..9fa45c4fc 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -1136,6 +1136,8 @@ module JamRuby teacher = options[:teacher] school_invitation_code = options[:school_invitation_code] school_id = options[:school_id] + retailer_invitation_code = options[:retailer_invitation_code] + retailer_id = options[:retailer_id] school_interest = options[:school_interest] education_interest = options[:education_interest] origin = options[:origin] @@ -1144,6 +1146,7 @@ module JamRuby test_drive_package = TestDrivePackage.find_by_name(test_drive_package_details[:name]) if test_drive_package_details school = School.find(school_id) if school_id + retailer = School.find(retailer_id) if retailer_id user = User.new user.validate_instruments = true UserManager.active_record_transaction do |user_manager| @@ -1158,6 +1161,16 @@ module JamRuby end end + if retailer_invitation_code + retailer_invitation = RetailerInvitation.find_by_invitation_code(retailer_invitation_code) + if retailer_invitation + first_name ||= retailer_invitation.first_name + last_name ||= retailer_invitation.last_name + retailer_invitation.accepted = true + retailer_invitation.save + end + end + user.first_name = first_name if first_name.present? user.last_name = last_name if last_name.present? user.email = email @@ -1195,10 +1208,18 @@ module JamRuby user.affiliate_referral = school.affiliate_partner elsif user.is_a_teacher school = School.find_by_id(school_id) - school_name = school ? school.name : 'a music school' user.teacher = Teacher.build_teacher(user, validate_introduction: true, biography: "Empty biography", school_id: school_id) user.affiliate_referral = school.affiliate_partner end + elsif retailer_id.present? + if user.is_a_student + user.retailer_id = school_id + user.affiliate_referral = retailer.affiliate_partner + elsif user.is_a_teacher + retailer = Retailer.find_by_id(retailer_id) + user.teacher = Teacher.build_teacher(user, validate_introduction: true, biography: "Empty biography", retailer_id: retailer_id) + user.affiliate_referral = retailer.affiliate_partner + end else if user.is_a_teacher user.teacher = Teacher.build_teacher(user, validate_introduction: true, biography: "Empty biography") @@ -2003,6 +2024,10 @@ module JamRuby remaining_test_drives > 0 end + def has_posa_credits? + jamclass_credits > 0 + end + def has_unprocessed_test_drives? !unprocessed_test_drive.nil? end @@ -2185,6 +2210,10 @@ module JamRuby LessonBooking.unprocessed(self).where(lesson_type: LessonBooking::LESSON_TYPE_PAID).first end + def most_recent_posa_purchase + lesson_purchases.where('lesson_package_type_id in (?)', LessonPackageType.test_drive_package_ids).where('posa_card_id is not null').order('created_at desc').first + end + def most_recent_test_drive_purchase lesson_purchases.where('lesson_package_type_id in (?)', LessonPackageType.test_drive_package_ids).order('created_at desc').first end @@ -2198,8 +2227,18 @@ module JamRuby end end + + def total_posa_credits + purchase = most_recent_posa_purchase + if purchase + purchase.posa_card.credits + else + 0 + end + end + def test_drive_succeeded(lesson_session) - if self.remaining_test_drives <= 0 + if (lesson_session.posa_card && self.jamclass_credits <= 0) || self.remaining_test_drives <= 0 UserMailer.student_test_drive_lesson_done(lesson_session).deliver_now UserMailer.teacher_lesson_completed(lesson_session).deliver_now else @@ -2211,7 +2250,13 @@ module JamRuby def test_drive_declined(lesson_session) # because we decrement test_drive credits as soon as you book, we need to bring it back now if lesson_session.lesson_booking.user_decremented - self.remaining_test_drives = self.remaining_test_drives + 1 + if lesson_session.posa_card + self.jamclass_credits = self.jamclass_credits + 1 + else + self.remaining_test_drives = self.remaining_test_drives + 1 + + end + self.save(validate: false) end @@ -2221,7 +2266,12 @@ module JamRuby if lesson_session.lesson_booking.user_decremented # because we decrement test_drive credits as soon as you book, we need to bring it back now - self.remaining_test_drives = self.remaining_test_drives + 1 + if lesson_session.posa_card + self.jamclass_credits = self.jamclass_credits + 1 + else + self.remaining_test_drives = self.remaining_test_drives + 1 + end + self.save(validate: false) end UserMailer.teacher_test_drive_no_bill(lesson_session).deliver_now @@ -2232,6 +2282,10 @@ module JamRuby total_test_drives - remaining_test_drives end + def used_posa_credits + total_posa_credits - jamclass_credits + end + def uncollectables(limit = 10) LessonPaymentCharge.where(user_id:self.id).order(:created_at).where('billing_attempts > 0').where(billed: false).limit(limit) end diff --git a/web/app/assets/javascripts/accounts.js b/web/app/assets/javascripts/accounts.js index 6f6e189f3..b7025e1ea 100644 --- a/web/app/assets/javascripts/accounts.js +++ b/web/app/assets/javascripts/accounts.js @@ -76,6 +76,7 @@ isNativeClient: gon.isNativeClient, musician: context.JK.currentUserMusician, sales_count: userDetail.sales_count, + owned_retailer_id: userDetail.owned_retailer_id, is_affiliate_partner: userDetail.is_affiliate_partner, affiliate_earnings: (userDetail.affiliate_earnings / 100).toFixed(2), affiliate_referral_count: userDetail.affiliate_referral_count, @@ -146,6 +147,7 @@ $("#account-content-scroller").on('click', '#account-payment-history-link', function(evt) {evt.stopPropagation(); navToPaymentHistory(); return false; } ); $("#account-content-scroller").on('click', '#account-affiliate-partner-link', function(evt) {evt.stopPropagation(); navToAffiliates(); return false; } ); $("#account-content-scroller").on('click', '#account-school-link', function(evt) {evt.stopPropagation(); navToSchool(); return false; } ); + $("#account-content-scroller").on('click', '#account-retailer-link', function(evt) {evt.stopPropagation(); navToRetailer(); return false; } ); } function renderAccount() { @@ -208,6 +210,11 @@ window.location = '/client#/account/school' } + function navToRetailer() { + resetForm() + window.location = '/client#/account/retailer' + } + // handle update avatar event function updateAvatar(avatar_url) { var photoUrl = context.JK.resolveAvatarUrl(avatar_url); diff --git a/web/app/assets/javascripts/accounts_profile_experience.js b/web/app/assets/javascripts/accounts_profile_experience.js index c058edb1f..6cdf67749 100644 --- a/web/app/assets/javascripts/accounts_profile_experience.js +++ b/web/app/assets/javascripts/accounts_profile_experience.js @@ -64,6 +64,7 @@ $screen.find('select[name=skill_level]').val(userDetail.skill_level); $screen.find('select[name=concert_count]').val(userDetail.concert_count); $screen.find('select[name=studio_session_count]').val(userDetail.studio_session_count); + context.JK.checkbox($instrumentSelector.find('input[type="checkbox"]'), true) } function isUserInstrument(instrument, userInstruments) { @@ -101,6 +102,8 @@ }); $userGenres.append(genreHtml); }); + + context.JK.checkbox($userGenres.find('input[type="checkbox"]'), true) }); } diff --git a/web/app/assets/javascripts/react-components/AccountRetailerScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/AccountRetailerScreen.js.jsx.coffee index d59f88b4b..24881a3ba 100644 --- a/web/app/assets/javascripts/react-components/AccountRetailerScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/AccountRetailerScreen.js.jsx.coffee @@ -3,8 +3,8 @@ rest = context.JK.Rest() logger = context.JK.logger AppStore = context.AppStore -SchoolActions = context.RetailerActions -SchoolStore = context.RetailerStore +RetailerActions = context.RetailerActions +RetailerStore = context.RetailerStore UserStore = context.UserStore profileUtils = context.JK.ProfileUtils @@ -31,7 +31,7 @@ profileUtils = context.JK.ProfileUtils onAppInit: (@app) -> @app.bindScreen('account/retailer', {beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide}) - onSchoolChanged: (retailerState) -> + onRetailerChanged: (retailerState) -> @setState(retailerState) onUserChanged: (userState) -> @@ -57,6 +57,7 @@ profileUtils = context.JK.ProfileUtils beforeHide: (e) -> #ProfileActions.viewTeacherProfileDone() @screenVisible = false + return true beforeShow: (e) -> @@ -141,7 +142,7 @@ profileUtils = context.JK.ProfileUtils @app.ajaxError(jqXHR, null, null) inviteTeacher: () -> - @app.layout.showDialog('invite-school-user', {d1: true}) + @app.layout.showDialog('invite-retailer-user', {d1: true}) resendInvitation: (id, e) -> e.preventDefault() @@ -169,13 +170,13 @@ profileUtils = context.JK.ProfileUtils removeFromRetailer: (id, isTeacher, e) -> if isTeacher - rest.deleteRetailerTeacher({id: this.state.retailer.id, teacher_id: id}).done((response) => @removeFromRetailerDone(response)).fail((jqXHR) => @removeFromSchoolFail(jqXHR)) + rest.deleteRetailerTeacher({id: this.state.retailer.id, teacher_id: id}).done((response) => @removeFromRetailerDone(response)).fail((jqXHR) => @removeFromRetailerFail(jqXHR)) removeFromRetailerDone: (retailer) -> context.JK.Banner.showNotice("User removed", "User was removed from your retailer.") context.RetailerActions.updateRetailer(retailer) - removeFromSchoolFail: (jqXHR) -> + removeFromRetailerFail: (jqXHR) -> @app.ajaxError(jqXHR) renderUser: (user, isTeacher) -> @@ -217,7 +218,8 @@ profileUtils = context.JK.ProfileUtils if this.state.retailer.teachers? && this.state.retailer.teachers.length > 0 for teacher in this.state.retailer.teachers - teachers.push(@renderUser(teacher.user, true)) + if teacher.user + teachers.push(@renderUser(teacher.user, true)) else teachers = `

No teachers

` @@ -248,61 +250,30 @@ profileUtils = context.JK.ProfileUtils @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 } `
- + {nameErrors}
- - + +
-

Management Preference

- -
-
- -
-
- -
-
-
- - - -
All emails relating to lesson scheduling will go to this email if school owner manages - scheduling. -
- {correspondenceEmailErrors} -

Payments

- +
@@ -340,7 +311,7 @@ profileUtils = context.JK.ProfileUtils agreement: () -> `
-

The agreement between your music school and JamKazam is part of JamKazam's terms of service. You can find the +

The agreement between your retailer 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.

` diff --git a/web/app/assets/javascripts/react-components/AvatarEditLink.js.jsx.coffee b/web/app/assets/javascripts/react-components/AvatarEditLink.js.jsx.coffee index 4b08de848..ab4b20b4a 100644 --- a/web/app/assets/javascripts/react-components/AvatarEditLink.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/AvatarEditLink.js.jsx.coffee @@ -31,18 +31,28 @@ AvatarStore = context.AvatarStore render: () -> if this.props.target?.photo_url? - testStudentUrl = "/school/#{this.props.target.id}/student?preview=true" - testTeacherUrl = "/school/#{this.props.target.id}/teacher?preview=true" + target_type = this.props.target_type + + testStudentUrl = "/#{target_type}/#{this.props.target.id}/student?preview=true" + testTeacherUrl = "/#{target_type}/#{this.props.target.id}/teacher?preview=true" + + if target_type == 'school' + previewArea = `
See how it will look to  + students and  + teachers +
` + else + previewArea = `
See how it will look to  + teachers +
` + `

change/update logo
-
See how it will look to  - students and  - teachers -
+ {previewArea}
` else `
diff --git a/web/app/assets/javascripts/react-components/InviteRetailerUserDialog.js.jsx.coffee b/web/app/assets/javascripts/react-components/InviteRetailerUserDialog.js.jsx.coffee new file mode 100644 index 000000000..0e164c45f --- /dev/null +++ b/web/app/assets/javascripts/react-components/InviteRetailerUserDialog.js.jsx.coffee @@ -0,0 +1,147 @@ +context = window +RetailerStore = context.RetailerStore + +@InviteRetailerUserDialog = React.createClass({ + + mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(RetailerStore, "onRetailerChanged")] + teacher: false + + beforeShow: (args) -> + logger.debug("InviteRetailerUserDialog.beforeShow", args.d1) + @firstName = '' + @lastName = '' + @email = '' + + @setState({inviteErrors: null, teacher: args.d1}) + afterHide: () -> + + onRetailerChanged: (retailerState) -> + @setState(retailerState) + + onAppInit: (@app) -> + dialogBindings = { + 'beforeShow': @beforeShow, + 'afterHide': @afterHide + }; + + @app.bindDialog('invite-retailer-user', dialogBindings); + + componentDidMount: () -> + @root = $(@getDOMNode()) + + getInitialState: () -> + {inviteErrors: null, retailer: null, sending: false} + + doCancel: (e) -> + e.preventDefault() + @app.layout.closeDialog('invite-retailer-user', true); + + doInvite: (e) -> + e.preventDefault() + + if this.state.sending + console.log("sending already") + return + + + email = @root.find('input[name="email"]').val() + lastName = @root.find('input[name="last_name"]').val() + firstName = @root.find('input[name="first_name"]').val() + retailer = context.RetailerStore.getState().retailer + @setState({inviteErrors: null, sending: true}) + rest.createRetailerInvitation({ + id: retailer.id, + as_teacher: this.state.teacher, + email: email, + last_name: lastName, + first_name: firstName + }).done((response) => @createDone(response)).fail((jqXHR) => @createFail(jqXHR)) + + createDone: (response) -> + console.log("invitation added", response) + @setState({inviteErrors:null, sending: false}) + context.RetailerActions.addInvitation(this.state.teacher, response) + context.JK.Banner.showNotice("invitation sent", "Your invitation has been sent!") + @app.layout.closeDialog('invite-retailer-user') + + createFail: (jqXHR) -> + handled = false + + if jqXHR.status == 422 + errors = JSON.parse(jqXHR.responseText) + @setState({inviteErrors: errors, sending: false}) + handled = true + + if !handled + @app.ajaxError(jqXHR, null, null) + + + close: (e) -> + e.preventDefault() + @app.layout.closeDialog('invite-retailer-user'); + + + renderRetailer: () -> + firstNameErrors = context.JK.reactSingleFieldErrors('first_name', @state.inviteErrors) + lastNameErrors = context.JK.reactSingleFieldErrors('last_name', @state.inviteErrors) + emailErrors = context.JK.reactSingleFieldErrors('email', @state.inviteErrors) + + firstNameClasses = classNames({first_name: true, error: firstNameErrors?, field: true}) + lastNameClasses = classNames({last_name: true, error: lastNameErrors?, field: true}) + emailClasses = classNames({email: true, error: emailErrors?, field: true}) + sendInvitationClasses = classNames({'button-orange': true, disabled: this.state.sending}) + + if @state.teacher + title = 'invite teacher' + help = `

Send invitations to teachers who teach through your music store. When your teachers accept this invitation to create teacher accounts on JamKazam, you can easily send emails to customers who purchase online lessons pointing these customers to your preferred teachers from your store.

` + else + title = 'invite student' + help = `

+ Shouldn't be here... +

` + + `
+
+ + +

{title}

+
+
+ + {help} + +
+ + + {firstNameErrors} +
+ +
+ + + {lastNameErrors} +
+ +
+ + + {emailErrors} +
+ + +
+
` + + render: () -> + retailer = this.state.retailer + + if !retailer? + return `
no retailer
` + + @renderRetailer() + + +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/InviteSchoolUserDialog.js.jsx.coffee b/web/app/assets/javascripts/react-components/InviteSchoolUserDialog.js.jsx.coffee index 5ef30a370..6352eef89 100644 --- a/web/app/assets/javascripts/react-components/InviteSchoolUserDialog.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/InviteSchoolUserDialog.js.jsx.coffee @@ -30,7 +30,7 @@ SchoolStore = context.SchoolStore @root = $(@getDOMNode()) getInitialState: () -> - {inviteErrors: null, school: null} + {inviteErrors: null, school: null, sending: false} doCancel: (e) -> e.preventDefault() @@ -39,11 +39,15 @@ SchoolStore = context.SchoolStore doInvite: (e) -> e.preventDefault() + if this.state.sending + console.log("sending already") + return + email = @root.find('input[name="email"]').val() lastName = @root.find('input[name="last_name"]').val() firstName = @root.find('input[name="first_name"]').val() school = context.SchoolStore.getState().school - @setState({inviteErrors: null}) + @setState({inviteErrors: null, sending: true}) rest.createSchoolInvitation({ id: school.id, as_teacher: this.state.teacher, @@ -54,6 +58,7 @@ SchoolStore = context.SchoolStore createDone: (response) -> console.log("invitation added", response) + @setState({inviteErrors:null, sending: false}) context.SchoolActions.addInvitation(this.state.teacher, response) context.JK.Banner.showNotice("invitation sent", "Your invitation has been sent!") @app.layout.closeDialog('invite-school-user') @@ -63,7 +68,7 @@ SchoolStore = context.SchoolStore if jqXHR.status == 422 errors = JSON.parse(jqXHR.responseText) - @setState({inviteErrors: errors}) + @setState({inviteErrors: errors, sending: false}) handled = true if !handled @@ -120,6 +125,7 @@ I'm writing to make you aware of a very interesting new option for private music firstNameClasses = classNames({first_name: true, error: firstNameErrors?, field: true}) lastNameClasses = classNames({last_name: true, error: lastNameErrors?, field: true}) emailClasses = classNames({email: true, error: emailErrors?, field: true}) + sendInvitationClasses = classNames({'button-orange': true, disabled: this.state.sending}) if @state.teacher title = 'invite teacher' @@ -167,7 +173,7 @@ I'm writing to make you aware of a very interesting new option for private music
CANCEL - SEND INVITATION + SEND INVITATION
` diff --git a/web/app/assets/javascripts/react-components/stores/AvatarStore.js.coffee b/web/app/assets/javascripts/react-components/stores/AvatarStore.js.coffee index bf3a4d381..d0c2e693a 100644 --- a/web/app/assets/javascripts/react-components/stores/AvatarStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/AvatarStore.js.coffee @@ -41,8 +41,12 @@ rest = new context.JK.Rest() onPick: () -> - rest.generateSchoolFilePickerPolicy({id: @target.id}) - .done((filepickerPolicy) => + if @type == 'school' + genpolicy = rest.generateSchoolFilePickerPolicy({id: @target.id}) + else if @type == 'retailer' + genpolicy = rest.generateRetailerFilePickerPolicy({id: @target.id}) + + genpolicy.done((filepickerPolicy) => @pickerOpen = true @changed() window.filepicker.setKey(gon.fp_apikey); @@ -69,7 +73,7 @@ rest = new context.JK.Rest() .fail(@app.ajaxError) afterImageUpload: (fpfile) -> - logger.debug("afterImageUploaded") + logger.debug("afterImageUploaded", typeof fpfile, fpfile) $.cookie('original_fpfile', JSON.stringify(fpfile)); @currentFpfile = fpfile @@ -79,8 +83,12 @@ rest = new context.JK.Rest() @signFpfile() signFpfile: () -> - rest.generateSchoolFilePickerPolicy({ id: @target.id}) - .done((policy) => ( + if @type == 'school' + genpolicy = rest.generateSchoolFilePickerPolicy({id: @target.id}) + else if @type == 'retailer' + genpolicy = rest.generateRetailerFilePickerPolicy({id: @target.id}) + + genpolicy.done((policy) => ( @signedCurrentFpfile = @currentFpfile.url + '?signature=' + policy.signature + '&policy=' + policy.policy; @changed() )) @@ -125,6 +133,8 @@ rest = new context.JK.Rest() if @type == 'school' window.SchoolActions.refresh() + if @type == 'retailer' + window.RetailerActions.refresh() @app.layout.closeDialog('upload-avatar') @@ -184,7 +194,10 @@ rest = new context.JK.Rest() @updatingAvatar = true @changed() - rest.deleteSchoolAvatar({id: @target.id}).done((response) => @deleteDone(response)).fail((jqXHR) => @deleteFail(jqXHR)) + if @type == 'school' + rest.deleteSchoolAvatar({id: @target.id}).done((response) => @deleteDone(response)).fail((jqXHR) => @deleteFail(jqXHR)) + else if @type == 'retailer' + rest.deleteRetailerAvatar({id: @target.id}).done((response) => @deleteDone(response)).fail((jqXHR) => @deleteFail(jqXHR)) deleteDone: (response) -> @currentFpfile = null @@ -194,6 +207,8 @@ rest = new context.JK.Rest() @currentCropSelection = null if @type == 'school' window.SchoolActions.refresh() + else if @type == 'retailer' + window.RetailerActions.refresh() @app.layout.closeDialog('upload-avatar'); @@ -219,8 +234,12 @@ rest = new context.JK.Rest() logger.debug("Converting..."); fpfile = @determineCurrentFpfile(); - rest.generateSchoolFilePickerPolicy({ id: @target.id, handle: fpfile.url, convert: true }) - .done((filepickerPolicy) => + if @type == 'school' + genpolicy = rest.generateSchoolFilePickerPolicy({ id: @target.id, handle: fpfile.url, convert: true }) + else if @type == 'retailer' + genpolicy = rest.generateRetailerFilePickerPolicy({ id: @target.id, handle: fpfile.url, convert: true }) + + genpolicy.done((filepickerPolicy) => window.filepicker.setKey(gon.fp_apikey) window.filepicker.convert(fpfile, { crop: [ @@ -243,8 +262,12 @@ rest = new context.JK.Rest() scale: (cropped) -> logger.debug("converting cropped"); - rest.generateSchoolFilePickerPolicy({id: @target.id, handle: cropped.url, convert: true}) - .done((filepickerPolicy) => ( + if @type == 'school' + genpolicy = rest.generateSchoolFilePickerPolicy({id: @target.id, handle: cropped.url, convert: true}) + else if @type == 'retailer' + genpolicy = rest.generateRetailerFilePickerPolicy({id: @target.id, handle: cropped.url, convert: true}) + + genpolicy.done((filepickerPolicy) => ( window.filepicker.convert(cropped, { height: @targetCropSize, width: @targetCropSize, @@ -275,14 +298,24 @@ rest = new context.JK.Rest() updateServer: (scaledLarger, scaled, cropped) -> logger.debug("converted and scaled final image %o", scaled); - rest.updateSchoolAvatar({ - id: @target.id, - original_fpfile: @determineCurrentFpfile(), - cropped_fpfile: scaled, - cropped_large_fpfile: scaledLarger, - crop_selection: @selection - }) - .done((response) => @updateAvatarSuccess(response)) + if @type == 'school' + update = rest.updateSchoolAvatar({ + id: @target.id, + original_fpfile: @determineCurrentFpfile(), + cropped_fpfile: scaled, + cropped_large_fpfile: scaledLarger, + crop_selection: @selection + }) + else if @type == 'retailer' + update = rest.updateRetailerAvatar({ + id: @target.id, + original_fpfile: @determineCurrentFpfile(), + cropped_fpfile: scaled, + cropped_large_fpfile: scaledLarger, + crop_selection: @selection + }) + + update.done((response) => @updateAvatarSuccess(response)) .fail(@app.ajaxError) .always(() => ( @updatingAvatar = false diff --git a/web/app/assets/javascripts/react-components/stores/RetailerStore.js.coffee b/web/app/assets/javascripts/react-components/stores/RetailerStore.js.coffee index 43ebec4fb..aefcef637 100644 --- a/web/app/assets/javascripts/react-components/stores/RetailerStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/RetailerStore.js.coffee @@ -31,7 +31,7 @@ rest = new context.JK.Rest() @teacherInvitations = response.entries @changed() - onAddInvitation: (invitation) -> + onAddInvitation: (teacher, invitation) -> @teacherInvitations.push(invitation) @changed() diff --git a/web/app/assets/javascripts/react-components/stores/StripeStore.js.coffee b/web/app/assets/javascripts/react-components/stores/StripeStore.js.coffee index 7b2cd26c6..e697b0115 100644 --- a/web/app/assets/javascripts/react-components/stores/StripeStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/StripeStore.js.coffee @@ -18,6 +18,8 @@ rest = new context.JK.Rest() redirect = '/client#/account/school' else if purpose == 'jamclass-home' redirect = '/client#/jamclass' + else if purpose == 'retailer' + redirect = '/client#/account/retailer' else throw "unknown purpose #{purpose}" diff --git a/web/app/assets/stylesheets/client/accountProfileExperience.scss b/web/app/assets/stylesheets/client/accountProfileExperience.scss index eca6a1b26..46f90255f 100644 --- a/web/app/assets/stylesheets/client/accountProfileExperience.scss +++ b/web/app/assets/stylesheets/client/accountProfileExperience.scss @@ -48,4 +48,39 @@ margin-right: 3px; } } + + .acct-prf-inst-ck { + .icheckbox_minimal { + margin-right: 8px; + position: relative; + top: 3px; + } + } + + .acct-prf-inst-drdwn { + select.proficiency_selector { + color:black; + } + } + + .user-genre-desc { + + margin: 4px; + padding: 4px; + + .icheckbox_minimal { + top: 4px; + position: relative; + } + + span { + vertical-align: middle; + margin: 4px; + padding: 4px; + } + } + + .instrument_selector { + + } } \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/react-components/AccountRetailerScreen.scss b/web/app/assets/stylesheets/client/react-components/AccountRetailerScreen.scss new file mode 100644 index 000000000..2b023d260 --- /dev/null +++ b/web/app/assets/stylesheets/client/react-components/AccountRetailerScreen.scss @@ -0,0 +1,265 @@ +@import "client/common"; + + +#account-retailer { + div[data-react-class="AccountRetailerScreen"] { + height: 100%; + } + + .profile-header { + padding: 10px 30px !important; + } + + label { + display: inline-block; + min-width: 200px; + } + input { + min-width:200px; + } + .hint { + margin-left: 200px; + font-size: 12px; + font-style: italic; + margin-top: 5px; + } + .iradio_minimal { + display: inline-block; + top: 4px; + margin-right: 5px; + } + .field { + margin-bottom: 30px; + + &.stripe-connect { + margin-bottom: 10px; + label { + margin-bottom: 10px; + } + } + } + .store-header { + float: left; + padding-top: 10px; + font-size: 20px; + font-weight: bold; + } + .profile-nav a { + position: absolute; + text-align: center; + height: 100%; + width: 98%; + margin: 0 auto; + padding: 11px 0 0 0; + @include border-box_sizing; + } + + .profile-tile { + width: 25%; + float: left; + @include border-box_sizing; + height: 40px; + position: relative; + } + .profile-body { + padding-top: 100px; + } + .profile-photo { + width: 16%; + @include border-box_sizing; + } + .profile-nav { + margin: 0; + width: 84%; + } + .profile-wrapper { + padding: 10px 20px + } + + .main-content { + float: left; + @include border-box_sizing; + width: 84%; + } + + .info-block { + min-height:400px; + h3 { + font-weight: bold; + font-size: 18px; + margin-bottom: 10px; + } + + h4 { + margin-bottom: 10px; + } + + .section { + margin-bottom: 40px; + &.teachers { + clear: both; + } + } + + table.jamtable { + font-size: 12px; + width: 100%; + } + } + + .stripe-connect { + padding: 0; + border: 0; + background: transparent; + outline:transparent; + cursor:pointer; + } + + .actions { + float: left; + margin-top: 30px; + margin-bottom: 10px; + } + + a.cancel { + margin-left:3px; + } + + + .avatar-edit-link { + display:inline-block; + img { + max-width:200px; + } + } + + .avatar-edit-link { + .hint { + margin-left:0; + } + } + + .column { + width:50%; + @include border_box_sizing; + + h3 { + float:left; + } + .invite-dialog { + float:right; + margin-right:2px; + } + &.column-left { + float:left; + padding-right:30px; + + } + &.column-right { + float:right; + padding-left:30px; + } + + .username { + max-width:40%; + font-size:16px; + color:white; + } + table { + width:100%; + } + td.description { + font-size:16px; + color: white; + vertical-align: top; + white-space: nowrap; + } + td.message { + color: $ColorTextTypical; + padding-left: 10px; + vertical-align: top; + text-align:right; + } + .detail-block { + display:inline-block; + font-size:12px; + } + .resend { + float:left; + } + .delete { + float:right; + } + .teacher-invites, .student-invites { + margin-bottom: 20px; + margin-top:40px; + font-size:12px; + min-height:40px; + p { + font-size:12px; + } + } + .teachers, .students { + margin-bottom:20px; + } + p { + font-size:12px; + margin-left:0; + } + .retailer-invitation { + margin-bottom:20px; + } + } + .retailer-user { + margin-bottom:20px; + + .avatar { + position:absolute; + padding:1px; + width:32px; + height:32px; + background-color:#ed4818; + margin:0; + -webkit-border-radius:16px; + -moz-border-radius:16px; + border-radius:16px; + float:none; + } + .avatar img { + width: 32px; + height: 32px; + -webkit-border-radius:16px; + -moz-border-radius:16px; + border-radius:16px; + } + + .usersname { + margin-left:56px; + line-height:32px; + vertical-align: middle; + display: inline-block; + + } + + .just-name { + display:block; + } + .just-email { + position: relative; + top: -14px; + font-size:12px; + } + + .user-actions { + float: right; + line-height: 32px; + height: 32px; + vertical-align: middle; + font-size:12px; + } + } + p { + font-size:12px; + margin:0; + } +} diff --git a/web/app/assets/stylesheets/dialogs/inviteRetailerUser.scss b/web/app/assets/stylesheets/dialogs/inviteRetailerUser.scss new file mode 100644 index 000000000..a6fa8d6aa --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/inviteRetailerUser.scss @@ -0,0 +1,39 @@ +@import "client/common"; + +#invite-retailer-user-dialog { + width: 500px; + + h3 { + color:white; + margin-bottom:20px; + } + .dialog-inner { + width: auto; + } + + .actions { + clear: both; + text-align: center; + } + + p { margin-bottom:20px;} + + label { + width:150px; + display:inline-block; + } + input { + display:inline-block; + width:250px; + } + + .field { + margin-bottom:20px; + } + + textarea { + height:500px; + width:100%; + margin-bottom:20px; + } +} \ No newline at end of file diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index 379288d54..4566b2510 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -115,6 +115,8 @@ class ApiUsersController < ApiController teacher: params[:teacher], school_invitation_code: params[:school_invitation_code], school_id: params[:school_id], + retailer_invitation_code: params[:retailer_invitation_code], + retailer_id: params[:retailer_id], school_interest: params[:school_interest], education_interest: params[:education_interest], affiliate_referral_id: cookies[:affiliate_visitor], diff --git a/web/app/views/clients/_account.html.erb b/web/app/views/clients/_account.html.erb index e0f52a819..6f0543f18 100644 --- a/web/app/views/clients/_account.html.erb +++ b/web/app/views/clients/_account.html.erb @@ -194,8 +194,25 @@ +
{% } %} -
+ + {% if (data.owned_retailer_id) { %} +
+ + +
+ UPDATE +
+
+ {% } %} +
diff --git a/web/app/views/clients/_account_profile_experience.html.erb b/web/app/views/clients/_account_profile_experience.html.erb index d6f0809a6..36372b366 100644 --- a/web/app/views/clients/_account_profile_experience.html.erb +++ b/web/app/views/clients/_account_profile_experience.html.erb @@ -84,8 +84,8 @@ diff --git a/web/app/views/dialogs/_dialogs.html.haml b/web/app/views/dialogs/_dialogs.html.haml index f76211f13..61145002e 100644 --- a/web/app/views/dialogs/_dialogs.html.haml +++ b/web/app/views/dialogs/_dialogs.html.haml @@ -50,6 +50,7 @@ = render 'dialogs/tryTestDriveDialog' = render 'dialogs/uploadAvatarDialog' = render 'dialogs/inviteSchoolUserDialog' += render 'dialogs/inviteRetailerUserDialog' = render 'dialogs/chatDialog' = render 'dialogs/cancelLessonDialog' = render 'dialogs/rescheduleLessonDialog' diff --git a/web/app/views/dialogs/_inviteRetailerUserDialog.html.slim b/web/app/views/dialogs/_inviteRetailerUserDialog.html.slim new file mode 100644 index 000000000..ae8a9f69c --- /dev/null +++ b/web/app/views/dialogs/_inviteRetailerUserDialog.html.slim @@ -0,0 +1,2 @@ +.dialog.dialog-overlay-sm.top-parent layout='dialog' layout-id='invite-retailer-user' id='invite-retailer-user-dialog' + = react_component 'InviteRetailerUserDialog', {} diff --git a/web/lib/user_manager.rb b/web/lib/user_manager.rb index 48062d2cf..b0b24c14d 100644 --- a/web/lib/user_manager.rb +++ b/web/lib/user_manager.rb @@ -34,6 +34,8 @@ class UserManager < BaseManager teacher = options[:teacher] school_invitation_code = options[:school_invitation_code] school_id = options[:school_id] + retailer_invitation_code = options[:retailer_invitation_code] + retailer_id = options[:retailer_id] school_interest = options[:school_interest] education_interest = options[:education_interest] origin = options[:origin] @@ -87,6 +89,8 @@ class UserManager < BaseManager teacher: teacher, school_invitation_code: school_invitation_code, school_id: school_id, + retailer_invitation_code: retailer_invitation_code, + retailer_id: retailer_id, school_interest: school_interest, education_interest: education_interest, origin: origin,