From d30b5326bc22c8dc48ef89059470e6842cb46bbf Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 29 May 2016 19:31:19 -0500 Subject: [PATCH 01/10] get dark image up --- .../assets/images/icheck/jamkazam-dark.png | Bin 0 -> 1123 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 web/vendor/assets/images/icheck/jamkazam-dark.png diff --git a/web/vendor/assets/images/icheck/jamkazam-dark.png b/web/vendor/assets/images/icheck/jamkazam-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..54aaa00a84791deeeba6ce39109bc4a9c3336e1b GIT binary patch literal 1123 zcmV-p1f2VcP)i zK~!ko?VHbQ9aR*^Kj*b%=VI={T~&l!vg%(T1X2tB0k$H=MN!)Ztlha2G)p(pAXG}= zEwl=a?yIpv+Pd){NVtJ4R9pnS0ddp7YMs-KYY6vGct5nY<)PID0g?o7~)tNi0q0OIe<(mbpM$b~2W{;LntIZ2tmq8+Z{| z2R;Y(!r@neH-I&3?LM&VoZBfFsuWK|_JL(p-6_yL6+C0iKCtY)Kf&uUvnWkO7J)6` z9&lb&S5J$}`3+ zOud72+&Kj311qZfbA=jk6ZjP9TWe(`J?#T4Kxv+;ro5o3k-pe@-uskijFG8zkS;q% zunWKoVWqEXUxjlo%y{kay5=AnvvU-UUsB&?U_CD_$V<0EF?xBFmzP%Mr8DDrqxwW- zPDH*Ck-tU6hu?E`d6UrRy+6V0Vg%8cBS+2?sj~W(fFB!3xE~nLoA(*;Az*+c{9ft0 zzOJTNS~U5ZK6-7_Nh<0)$N!Ha>)2jkJa67R8T;Pvy1p*gAR41{j5qew>ueRqL(MdVD?v8}adfcJoJMtMMGUeJ)q8`Y<( zhpKu@RZZA#sp?@}-X!!HV@~k8nqo<;X;qe_y2LE5uJeX2)l0y)B634SdO42Kv(|n9dE#-xkwU;yTdLokY zRCT+FzSw!jn4U2v<$3R);&mc|IOjNRrm9<^Bef6Q6p=d&Ut;Ysd=&XgC@8N1{{XKQ zw7YLH?C9+SH-S6AeGwVU%WEgEqc3(|R=c|}`(P?6UdD(fJ5L9;1TdW zusM=e9YvRMQMZt%Wj?g2pf7gb@wDp5k|Y^U!nQ2u3$|w#Ip*i*InB1ayW8k~l$>W% pYX=7hV~-_Cf}HjXG_#rI*?*y+mz7Kx{UrbZ002ovPDHLkV1iLw6?y;w literal 0 HcmV?d00001 From 01a096d6c35c7dba48ec132313eb0c8b731a74ab Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 29 May 2016 20:17:18 -0500 Subject: [PATCH 02/10] VRFS-4151 - checkboxes fixed on search screen --- .../CheckBoxList.js.jsx.coffee | 2 +- .../TeacherSearchOptionsScreen.js.jsx.coffee | 36 ++++++++++++++++--- .../TeacherSearchScreen.js.jsx.coffee | 2 +- .../mixins/ICheckMixin.js.coffee | 20 ++++++----- .../TeacherSearchOptions.css.scss | 27 ++++++++++++++ 5 files changed, 73 insertions(+), 14 deletions(-) diff --git a/web/app/assets/javascripts/react-components/CheckBoxList.js.jsx.coffee b/web/app/assets/javascripts/react-components/CheckBoxList.js.jsx.coffee index da12cb587..2364c52c5 100644 --- a/web/app/assets/javascripts/react-components/CheckBoxList.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/CheckBoxList.js.jsx.coffee @@ -44,7 +44,7 @@ logger = context.JK.logger for object in this.props.sourceObjects nm = "check_#{object.id}" checked = @isChecked(object.id) - object_options.push `
` + object_options.push `

` `
diff --git a/web/app/assets/javascripts/react-components/TeacherSearchOptionsScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherSearchOptionsScreen.js.jsx.coffee index 4921f7859..06770bd91 100644 --- a/web/app/assets/javascripts/react-components/TeacherSearchOptionsScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/TeacherSearchOptionsScreen.js.jsx.coffee @@ -5,11 +5,25 @@ LocationActions = @LocationActions @TeacherSearchOptionsScreen = React.createClass({ - mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged"), + mixins: [ICheckMixin, Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged"), Reflux.listenTo(@TeacherSearchStore, "onTeacherSearchChanged")] LIMIT: 20 + componentDidMount: () -> + @checkboxes = [ + {selector: 'input.onlyMySchool', stateKey: 'onlyMySchool'}, + {selector: 'input.beginner-level', stateKey: 'beginner-level'}, + {selector: 'input.intermediate-level', stateKey: 'intermediate-level'}, + {selector: 'input.advanced-level', stateKey: 'advanced-level'}] + + @root = $(@getDOMNode()) + + @iCheckify() + + componentDidUpdate: () -> + @iCheckify() + getInitialState: () -> {options: {onlyMySchool: true}} @@ -71,6 +85,20 @@ LocationActions = @LocationActions options.onlyMySchool = checked @setState(options) + checkboxChanged: (e) -> + $target = $(e.target) + + if $target.is('.onlyMySchool') + state = {} + state['onlyMySchool'] = $target.is(':checked') + @setState(state) + @onlyMySchoolChange(e) + else + state = {} + state[$target.attr('name')] = $target.is(':checked') + @setState(state) + @levelChanged(e) + render: () -> if @state.user?.school_id? onlySchoolOption = @@ -114,15 +142,15 @@ LocationActions = @LocationActions

Student Levels Taught:

- +
- +
- +
diff --git a/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee index 5e336f9be..c5a4539e2 100644 --- a/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee @@ -215,7 +215,7 @@ ProfileActions = @ProfileActions summary = '' - if searchOptions.onlyMySchool + if searchOptions.onlyMySchool && @state.user?.school_id? summary += "From My School Only" instruments = searchOptions.instruments diff --git a/web/app/assets/javascripts/react-components/mixins/ICheckMixin.js.coffee b/web/app/assets/javascripts/react-components/mixins/ICheckMixin.js.coffee index 0bf3cda13..a1a4eb622 100644 --- a/web/app/assets/javascripts/react-components/mixins/ICheckMixin.js.coffee +++ b/web/app/assets/javascripts/react-components/mixins/ICheckMixin.js.coffee @@ -40,14 +40,18 @@ teacherActions = window.JK.Actions.Teacher enableICheck: (e) -> if !@root? return - checkBoxes = @root.find('input[type="checkbox"]') - if checkBoxes.length > 0 - context.JK.checkbox(checkBoxes) - checkBoxes.on('ifChanged', (e) => @checkIfCanFire(e)) - radioBoxes = @root.find('input[type="radio"]') - if radioBoxes.length > 0 - context.JK.checkbox(radioBoxes) - radioBoxes.on('ifChanged', (e) => @checkIfCanFire(e)) + + for checkbox in @checkboxes + selector = checkbox.selector + + checkBoxes = @root.find(selector + '[type="checkbox"]') + if checkBoxes.length > 0 + context.JK.checkbox(checkBoxes) + checkBoxes.on('ifChanged', (e) => @checkIfCanFire(e)) + radioBoxes = @root.find(selector + '[type="radio"]') + if radioBoxes.length > 0 + context.JK.checkbox(radioBoxes) + radioBoxes.on('ifChanged', (e) => @checkIfCanFire(e)) true diff --git a/web/app/assets/stylesheets/client/react-components/TeacherSearchOptions.css.scss b/web/app/assets/stylesheets/client/react-components/TeacherSearchOptions.css.scss index 3738be5f5..73028e042 100644 --- a/web/app/assets/stylesheets/client/react-components/TeacherSearchOptions.css.scss +++ b/web/app/assets/stylesheets/client/react-components/TeacherSearchOptions.css.scss @@ -9,6 +9,29 @@ .checkbox-scroller { height:100px; margin-top:0; + .checkItem { + clear: both; + margin-bottom:4px; + label { + color: black; + display: inline; + float: left; + font-size: 1em; + } + .icheckbox_minimal { + color: black; + display: inline; + float: left; + font-size: 1em; + margin-right:5px; + } + input { + width: auto; + text-align: left; + float: left; + display: inline; + } + } } min-width:200px; width:25%; @@ -72,6 +95,10 @@ } .student-levels-taught { + .icheckbox_minimal { + top:3px; + margin-right:5px; + } .teaching-level { margin-bottom:10px; } From ad28d8d55dd08323c0789f86b1eda8290ae9e3a3 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 29 May 2016 22:30:33 -0500 Subject: [PATCH 03/10] VRFS-4157 - don't wipe out sessions when considering scheduling --- ruby/lib/jam_ruby/models/lesson_booking.rb | 7 +++++- .../jam_ruby/models/lesson_booking_spec.rb | 23 +++++++++++++++++-- .../LessonPayment.js.jsx.coffee | 10 ++++---- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/ruby/lib/jam_ruby/models/lesson_booking.rb b/ruby/lib/jam_ruby/models/lesson_booking.rb index 0d69548fc..e270027be 100644 --- a/ruby/lib/jam_ruby/models/lesson_booking.rb +++ b/ruby/lib/jam_ruby/models/lesson_booking.rb @@ -243,7 +243,12 @@ module JamRuby minimum_start_time = create_minimum_booking_time # get all sessions that are already scheduled for this booking ahead of the minimum time - sessions = MusicSession.joins(:lesson_session).where("lesson_sessions.lesson_booking_id = ?", id).where("scheduled_start is not null").where("scheduled_start > ?", minimum_start_time).order(:created_at) + + sessions= MusicSession.joins(:lesson_session).where("lesson_sessions.lesson_booking_id = ?", id).where("scheduled_start is not null").order(:created_at) + if recurring + # only want times ahead of this for recurring + sessions = sessions.where("scheduled_start > ?", minimum_start_time) + end if @default_slot_did_change # # adjust all session times diff --git a/ruby/spec/jam_ruby/models/lesson_booking_spec.rb b/ruby/spec/jam_ruby/models/lesson_booking_spec.rb index 4ac09e8ba..fde720cc8 100644 --- a/ruby/spec/jam_ruby/models/lesson_booking_spec.rb +++ b/ruby/spec/jam_ruby/models/lesson_booking_spec.rb @@ -753,9 +753,28 @@ describe LessonBooking do end describe "rescheduling" do - after do - Timecop.return + it "initial slot is in the past" do + booking = LessonBooking.book_normal(user, teacher_user, valid_single_slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60) + + lesson_session = booking.lesson_sessions[0] + + initial_scheduled_time = lesson_session.scheduled_start + + counter = FactoryGirl.build(:lesson_booking_slot_single, preferred_day: Date.today + 20) + lesson_session.counter({proposer: user, slot: counter, message: 'ACtually, let\'s do this instead for just this one'}) + + Timecop.travel(initial_scheduled_time + 1) + + lesson_session.accept({accepter: teacher_user, message: 'Yeah I got this', slot: counter, update_all: false}) + booking.reload + booking.status.should eql LessonBooking::STATUS_APPROVED + booking.lesson_sessions.count.should eql 1 + lesson_session.errors.any?.should be_false + lesson_session.reload + lesson_session.status.should eql LessonSession::STATUS_APPROVED + lesson_session.scheduled_start.should eql counter.scheduled_time(0) end + it "non-recurring, accepted with new slot" do booking = LessonBooking.book_normal(user, teacher_user, valid_single_slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60) lesson_session = booking.lesson_sessions[0] diff --git a/web/app/assets/javascripts/react-components/LessonPayment.js.jsx.coffee b/web/app/assets/javascripts/react-components/LessonPayment.js.jsx.coffee index 1202c0e3e..c0ce9c79a 100644 --- a/web/app/assets/javascripts/react-components/LessonPayment.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/LessonPayment.js.jsx.coffee @@ -296,16 +296,16 @@ UserStore = context.UserStore if response.test_drive?.teacher_id teacher_id = response.test_drive.teacher_id - if testDriveCount == 1 - text = "You have purchased a TestDrive credit and have used it to request a JamClass with #{@state.teacher.name}. The teacher has received your request and should respond shortly." + if testDriveCount == '1' + text = "You have purchased 1 TestDrive credit and have used it to request a JamClass with #{@state.teacher.name}. The teacher has received your request and should respond shortly." else - text = "You have purchased #{testDriveCount} TestDrive credits and have used 1 credit it to request a JamClass with #{@state.teacher.name}. The teacher has received your request and should respond shortly." + text = "You have purchased #{testDriveCount} TestDrive credits and have used 1 credit to request a JamClass with #{@state.teacher.name}. The teacher has received your request and should respond shortly." location = "/client#/jamclass" else if @state.teacher?.id # the user bought the testdrive, and there is a teacher of interest in context (but no booking) - if testDriveCount == 1 + if testDriveCount == '1' text = "You now have 1 TestDrive credit.

We've taken you to the lesson booking screen for the teacher you initially showed interest in." location = "/client#/jamclass/book-lesson/test-drive_" + teacher_id else @@ -313,7 +313,7 @@ UserStore = context.UserStore location = "/client#/jamclass/book-lesson/test-drive_" + teacher_id else # the user bought test drive, but 'cold' , i.e., no teacher in context - if testDriveCount == 1 + if testDriveCount == '1' text = "You now have 1 TestDrive credit.

We've taken you to the Teacher Search screen, so you can search for teachers right for you." location = "/client#/teachers/search" else From c6a532160154a3e6177f3eae35c13c5c9da56a5e Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 29 May 2016 22:50:57 -0500 Subject: [PATCH 04/10] VRFS-4160 - add checkbox styling and pop external in recording selector dialog --- web/app/assets/javascripts/feedHelper.js | 4 ++++ .../stylesheets/dialogs/recordingSelectorDialog.css.scss | 5 +++++ web/app/assets/stylesheets/web/audioWidgets.css.scss | 1 - web/app/views/users/_feed_recording_ajax.html.haml | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/web/app/assets/javascripts/feedHelper.js b/web/app/assets/javascripts/feedHelper.js index 3d68671ff..61f7b69e8 100644 --- a/web/app/assets/javascripts/feedHelper.js +++ b/web/app/assets/javascripts/feedHelper.js @@ -581,6 +581,9 @@ if(!defaults.show_checkbox) { $feedItem.find('.select-box').hide(); } + else { + context.JK.checkbox($feedItem.find('.select-box')) + } if(defaults.hide_avatar) { $feedItem.find('.avatar-small.ib').hide(); } @@ -598,6 +601,7 @@ $feedItem.data('original-max-height', $feedItem.css('height')); context.JK.bindHoverEvents($feedItem); context.JK.bindProfileClickEvents($feedItem); + context.JK.popExternalLinks($feedItem) } else { logger.warn("skipping feed type: " + feed.type); diff --git a/web/app/assets/stylesheets/dialogs/recordingSelectorDialog.css.scss b/web/app/assets/stylesheets/dialogs/recordingSelectorDialog.css.scss index e241ce388..0e32b36d1 100644 --- a/web/app/assets/stylesheets/dialogs/recordingSelectorDialog.css.scss +++ b/web/app/assets/stylesheets/dialogs/recordingSelectorDialog.css.scss @@ -16,6 +16,11 @@ overflow-y:auto; } + + .title-artist { + margin-left:25px; + } + .action-buttons { margin-bottom:10px; } diff --git a/web/app/assets/stylesheets/web/audioWidgets.css.scss b/web/app/assets/stylesheets/web/audioWidgets.css.scss index 621a173a3..a5a6b553e 100644 --- a/web/app/assets/stylesheets/web/audioWidgets.css.scss +++ b/web/app/assets/stylesheets/web/audioWidgets.css.scss @@ -13,7 +13,6 @@ height: 36px; } - .recording-controls { margin-top: 15px; padding: 3px 5px 3px 10px; diff --git a/web/app/views/users/_feed_recording_ajax.html.haml b/web/app/views/users/_feed_recording_ajax.html.haml index f410088d8..e66263d33 100644 --- a/web/app/views/users/_feed_recording_ajax.html.haml +++ b/web/app/views/users/_feed_recording_ajax.html.haml @@ -8,7 +8,7 @@ %a{:hoveraction => "{{data.feed_item.helpers.artist_hoveraction}}", :profileaction => "{{data.feed_item.helpers.artist_hoveraction}}", :"{{data.feed_item.helpers.artist_datakey}}" => "{{data.feed_item.helpers.artist_id}}"} %img{ src: '{{data.feed_item.helpers.avatar}}' } / type and artist - .left.ml20.w15 + .left.ml20.w15.title-artist .title %a.title-text{:href => "/recordings/{{data.candidate_claimed_recording.id}}", :rel => "external", :hoveraction => "recording", :'recording-id' => '{{data.candidate_claimed_recording.id}}'} RECORDING %a.edit-recording-dialog{href: "#"} (edit) From 197313dfc9267472b45542de3f3e8c47dc74da92 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 30 May 2016 00:32:55 -0500 Subject: [PATCH 05/10] VRFS-4158, VRFS-4159 - audio/notation attachments work better --- ruby/lib/jam_ruby/app/mailers/user_mailer.rb | 23 +++++++ .../user_mailer/lesson_attachment.html.erb | 20 ++++++ .../user_mailer/lesson_attachment.text.erb | 12 ++++ ruby/lib/jam_ruby/models/music_notation.rb | 3 + ruby/spec/factories.rb | 8 +++ ruby/spec/mailers/render_emails_spec.rb | 20 ++++++ .../AttachmentStatus.js.jsx.coffee | 6 +- .../MusicNotationUploadDialog.js.jsx.coffee | 49 ++++++++++++++ .../actions/AttachmentActions.js.coffee | 1 + .../stores/AttachmentStore.js.coffee | 67 +++++++++++++++++++ .../MusicNotationUploadDialog.css.scss | 35 ++++++++++ web/app/views/dialogs/_dialogs.html.haml | 1 + .../_musicNotationUploadDialog.html.slim | 2 + 13 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/lesson_attachment.html.erb create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/lesson_attachment.text.erb create mode 100644 web/app/assets/javascripts/react-components/MusicNotationUploadDialog.js.jsx.coffee create mode 100644 web/app/assets/stylesheets/dialogs/MusicNotationUploadDialog.css.scss create mode 100644 web/app/views/dialogs/_musicNotationUploadDialog.html.slim diff --git a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb index 6bccf6e58..b5f055419 100644 --- a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb @@ -1770,5 +1770,28 @@ module JamRuby format.html { render :layout => "from_user_mailer" } end end + + def lesson_attachment(sender, target, lesson_session, attachment) + @sender = sender + @target = target + @lesson_session = lesson_session + @attachment = attachment + + + email = target.email + @subject = "An attachment has been added to your lesson by #{sender.name}" + unique_args = {:type => "lesson_attachment"} + + sendgrid_category "Notification" + sendgrid_unique_args :type => unique_args[:type] + sendgrid_recipients([email]) + sendgrid_substitute('@USERID', [@target.id]) + + mail(:to => email, :subject => @subject) do |format| + format.text + format.html { render :layout => "from_user_mailer" } + end + + end end end diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/lesson_attachment.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/lesson_attachment.html.erb new file mode 100644 index 000000000..fc49d2163 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/lesson_attachment.html.erb @@ -0,0 +1,20 @@ +<% provide(:title, @subject) %> +<% provide(:photo_url, @sender.resolved_photo_url) %> + +<% content_for :note do %> +

+ <% if @attachment.is_a?(JamRuby::MusicNotation) %> + <% if @attachment.is_notation? %> + A music notation has been added to your lesson. You can download "><%= @attachment.file_name %> directly or at any time in the message window for this lesson. + <% else %> + A audio file has been added to your lesson. You can download "><%= @attachment.file_name %> directly or at any time in the message window for this lesson. + <% end %> + <% else %> + A recording named "<%= @attachment.name %>" has been added to your lesson. It can be viewed ">here or found within the message window for this lesson. + <% end %> +

+ VIEW LESSON DETAILS +

+<% end %> + + diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/lesson_attachment.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/lesson_attachment.text.erb new file mode 100644 index 000000000..8756d460c --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/lesson_attachment.text.erb @@ -0,0 +1,12 @@ +<% if @attachment.is_a?(JamRuby::MusicNotation) %> +<% if @attachment.is_notation? %> +A music notation has been added to your lesson. You can download (<%= APP_CONFIG.external_root_url + "/api/music_notations/#{@attachment.id}" %>">)<%= @attachment.file_name %> directly or at any time in the message window for this lesson. +<% else %> +A audio file has been added to your lesson. You can download (<%= APP_CONFIG.external_root_url + "/api/music_notations/#{@attachment.id}" %>">)<%= @attachment.file_name %> directly or at any time in the message window for this lesson. +<% end %> +<% else %> +A recording named "<%= @attachment.name %>" has been added to your lesson. It can be viewed (<%= APP_CONFIG.external_root_url + "/recordings/#{@attachment.id}" %>">) here or found within the message window for this lesson. +<% end %> +VIEW LESSON DETAILS (<%= @lesson_session.web_url %>) + + diff --git a/ruby/lib/jam_ruby/models/music_notation.rb b/ruby/lib/jam_ruby/models/music_notation.rb index 767453d14..21406b384 100644 --- a/ruby/lib/jam_ruby/models/music_notation.rb +++ b/ruby/lib/jam_ruby/models/music_notation.rb @@ -48,6 +48,9 @@ module JamRuby s3_manager.sign_url(self[:file_url], {:expires => expiration_time, :secure => true}) end + def is_notation? + self.attachment_type == TYPE_NOTATION + end private def self.construct_filename(notation) diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 4a3b6a247..8cde0eff7 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -1057,5 +1057,13 @@ FactoryGirl.define do factory :email_blacklist, class: "JamRuby::EmailBlacklist" do sequence(:email) { |n| "person_#{n}@example.com"} end + + factory :music_notation, class: "JamRuby::MusicNotation" do + attachment_type {JamRuby::MusicNotation::TYPE_NOTATION} + association :user, factory: :user + file_url 'abc' + size 100 + file_name 'some_file.jpg' + end end diff --git a/ruby/spec/mailers/render_emails_spec.rb b/ruby/spec/mailers/render_emails_spec.rb index ae09837eb..ea653fe61 100644 --- a/ruby/spec/mailers/render_emails_spec.rb +++ b/ruby/spec/mailers/render_emails_spec.rb @@ -162,6 +162,26 @@ describe "RenderMailers", :slow => true do UserMailer.deliveries.clear UserMailer.lesson_starting_soon_student(lesson).deliver end + + it "music_notation_attachment" do + @filename = "music_notation_attachment" + + lesson = testdrive_lesson(user, teacher) + + UserMailer.deliveries.clear + notation = FactoryGirl.create(:music_notation, user: user) + UserMailer.lesson_attachment(user, teacher, lesson, notation).deliver + end + + it "recording_attachment" do + @filename = "recording_attachment" + + lesson = testdrive_lesson(user, teacher) + + UserMailer.deliveries.clear + claim = FactoryGirl.create(:claimed_recording, user: user) + UserMailer.lesson_attachment(user, teacher, lesson, claim).deliver + end end end diff --git a/web/app/assets/javascripts/react-components/AttachmentStatus.js.jsx.coffee b/web/app/assets/javascripts/react-components/AttachmentStatus.js.jsx.coffee index 733cd77b9..75f5f84be 100644 --- a/web/app/assets/javascripts/react-components/AttachmentStatus.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/AttachmentStatus.js.jsx.coffee @@ -24,7 +24,7 @@ AttachmentStore = context.AttachmentStore notationUploadDone: () -> logger.debug("AttachmentStatus: notationUploadDone") - context.JK.Banner.showNotice('Notation Uploaded', 'The music notation file has been uploaded, and can be accessed from the Messages window for this lesson.') + #context.JK.Banner.showNotice('Notation Uploaded', 'The music notation file has been uploaded, and can be accessed from the Messages window for this lesson.') notationUploadFail: () -> logger.debug("AttachmentStatus: notationUploadFail") @@ -32,11 +32,11 @@ AttachmentStore = context.AttachmentStore audioSelected: (e) -> files = $(e.target).get(0).files logger.debug("audio files selected: ", files) - window.AttachmentActions.uploadAudio.trigger(files, @notationUploadDone, @notationUploadFail) + window.AttachmentActions.uploadAudios.trigger(files, @notationUploadDone, @notationUploadFail) audioUploadDone: () -> logger.debug("AttachmentStatus: audioUploadDone") - context.JK.Banner.showNotice('Audio file Uploaded', 'The audio file has been uploaded, and can be accessed from the Messages window for this lesson.') + #context.JK.Banner.showNotice('Audio file Uploaded', 'The audio file has been uploaded, and can be accessed from the Messages window for this lesson.') audioUploadFail: () -> logger.debug("AttachmentStatus: audioUploadFail") diff --git a/web/app/assets/javascripts/react-components/MusicNotationUploadDialog.js.jsx.coffee b/web/app/assets/javascripts/react-components/MusicNotationUploadDialog.js.jsx.coffee new file mode 100644 index 000000000..067a95186 --- /dev/null +++ b/web/app/assets/javascripts/react-components/MusicNotationUploadDialog.js.jsx.coffee @@ -0,0 +1,49 @@ +context = window + +@MusicNotationUploadDialog = React.createClass({ + + mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(AttachmentStore, "onAttachmentStore")] + + getInitialState: () -> + { + uploading: false + } + onAppInit: (@app) -> + + onAttachmentStore: (attachmentState) -> + @setState(attachmentState) + + handleCloseMessage: (e) -> + e.preventDefault() + + @app.layout.closeDialog('music-notation-upload-dialog') + + render: () -> + + if @state.uploading + state = + `
+

Please wait while we upload your attachment to the lesson...

+
+
` + else + state = + `
+

Your file has been uploaded.

+
` + + `
+
+ + +

Uploading Attachment

+
+
+ {state} +
+ CLOSE +
+
+
` +}) + diff --git a/web/app/assets/javascripts/react-components/actions/AttachmentActions.js.coffee b/web/app/assets/javascripts/react-components/actions/AttachmentActions.js.coffee index b744ca86e..78ff35912 100644 --- a/web/app/assets/javascripts/react-components/actions/AttachmentActions.js.coffee +++ b/web/app/assets/javascripts/react-components/actions/AttachmentActions.js.coffee @@ -5,4 +5,5 @@ context = window startAttachNotation: {} startAttachAudio: {} uploadNotations: {} + uploadAudios: {} }) diff --git a/web/app/assets/javascripts/react-components/stores/AttachmentStore.js.coffee b/web/app/assets/javascripts/react-components/stores/AttachmentStore.js.coffee index a24cdcd85..e6d795ab7 100644 --- a/web/app/assets/javascripts/react-components/stores/AttachmentStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/AttachmentStore.js.coffee @@ -105,6 +105,8 @@ AttachmentActions = @AttachmentActions formData.append('lesson_session_id', @lessonId); formData.append('attachment_type', 'notation') + @app.layout.showDialog('music-notation-upload-dialog') + rest.uploadMusicNotations(formData) .done((response) => @doneUploadingNotatations(notations, response, doneCallback, failCallback)) .fail((jqXHR) => @failUploadingNotations(jqXHR, failCallback)) @@ -137,6 +139,71 @@ AttachmentActions = @AttachmentActions else @app.notifyServerError(jqXHR, "Unable to upload music notations"); + onUploadAudios: (notations, doneCallback, failCallback) -> + logger.debug("beginning upload of audio", notations) + @uploading = true + @changed() + + formData = new FormData() + maxExceeded = false; + $.each(notations, (i, file) => ( + max = 10 * 1024 * 1024; + if file.size > max + maxExceeded = true + return false + + formData.append('files[]', file) + )) + + if maxExceeded + @app.notify({ + title: "Maximum Music Audio Size Exceeded", + text: "You can only upload files up to 10 megabytes in size." + }) + failCallback() + @uploading = false + @changed() + return + + + formData.append('lesson_session_id', @lessonId); + formData.append('attachment_type', 'audio') + + @app.layout.showDialog('music-notation-upload-dialog') + + rest.uploadMusicNotations(formData) + .done((response) => @doneUploadingAudios(notations, response, doneCallback, failCallback)) + .fail((jqXHR) => @failUploadingAudios(jqXHR, failCallback)) + + doneUploadingAudios: (notations, response, doneCallback, failCallback) -> + @uploading = false + @changed() + error_files = []; + $.each(response, (i, music_notation) => ( + if music_notation.errors + error_files.push(notations[i].name) + ) + ) + if error_files.length > 0 + failCallback() + @app.notifyAlert("Failed to upload audio files.", error_files.join(', ')); + else + doneCallback() + + failUploadingAudios: (jqXHR, failCallback) -> + @uploading = false + @changed() + if jqXHR.status == 413 + # the file is too big. Let the user know. + # This should happen when they select the file, but a misconfiguration on the server could cause this. + @app.notify({ + title: "Maximum Music Audio Size Exceeded", + text: "You can only upload files up to 10 megabytes in size." + }) + else + @app.notifyServerError(jqXHR, "Unable to upload music audio files"); + + changed: () -> this.trigger({lessonId: @lessonId, uploading: @uploading}) } diff --git a/web/app/assets/stylesheets/dialogs/MusicNotationUploadDialog.css.scss b/web/app/assets/stylesheets/dialogs/MusicNotationUploadDialog.css.scss new file mode 100644 index 000000000..6c280b210 --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/MusicNotationUploadDialog.css.scss @@ -0,0 +1,35 @@ +@import "client/common"; + +#music-notation-upload-dialog { + width: 500px; + max-height:500px; + + h3 { + color:white; + margin-bottom:20px; + } + .dialog-inner { + width: auto; + height:100%; + @include border_box_sizing; + margin-top: -29px; + padding: 50px 25px 25px; + } + + div[data-react-class="MusicNotationUploadDialog"] { + + } + .MusicNotationUploadDialog { + height:100%; + } + .spinner-large { + width:200px; + height:200px; + line-height: 200px; + position:relative; + margin:25px auto; + } + .actions { + text-align:right; + } +} \ No newline at end of file diff --git a/web/app/views/dialogs/_dialogs.html.haml b/web/app/views/dialogs/_dialogs.html.haml index 4c0ed3f1d..6d51c6828 100644 --- a/web/app/views/dialogs/_dialogs.html.haml +++ b/web/app/views/dialogs/_dialogs.html.haml @@ -54,3 +54,4 @@ = render 'dialogs/cancelLessonDialog' = render 'dialogs/rescheduleLessonDialog' = render 'dialogs/rateUserDialog' += render 'dialogs/musicNotationUploadDialog' \ No newline at end of file diff --git a/web/app/views/dialogs/_musicNotationUploadDialog.html.slim b/web/app/views/dialogs/_musicNotationUploadDialog.html.slim new file mode 100644 index 000000000..e9470ba87 --- /dev/null +++ b/web/app/views/dialogs/_musicNotationUploadDialog.html.slim @@ -0,0 +1,2 @@ +.dialog.dialog-overlay-sm.top-parent layout='dialog' layout-id='music-notation-upload-dialog' id='music-notation-upload-dialog' + = react_component 'MusicNotationUploadDialog', {} From 55a53325dd62733b5c9322272a44981f9d05c25c Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 30 May 2016 16:43:55 -0500 Subject: [PATCH 06/10] slew of fixes for recurring sessions, canceling sessions, and minor UI issues --- ruby/lib/jam_ruby/models/lesson_booking.rb | 3 + .../jam_ruby/models/lesson_booking_slot.rb | 2 +- .../jam_ruby/models/lesson_booking_spec.rb | 9 +- ruby/spec/support/lesson_session.rb | 2 +- .../react-components/BookLesson.js.jsx.coffee | 1 + .../react-components/ChatWindow.js.jsx.coffee | 1 + .../JamClassScreen.js.jsx.coffee | 27 ++- .../LessonBooking.js.jsx.coffee | 157 ++++++++++++++---- .../LessonBookingDecision.js.jsx.coffee | 60 +++++-- .../LessonBookingScreen.css.scss | 17 ++ .../api_lesson_bookings_controller.rb | 46 ++--- .../api_lesson_sessions_controller.rb | 3 +- .../api_music_notations_controller.rb | 2 + web/app/helpers/music_session_helper.rb | 4 + web/app/views/api_lesson_sessions/show.rabl | 12 +- web/spec/features/jamclass_screen_spec.rb | 53 ++++++ .../features/lesson_booking_status_spec.rb | 94 ++++++++++- web/spec/features/student_landing_spec.rb | 2 +- web/spec/support/lessons.rb | 5 +- 19 files changed, 412 insertions(+), 88 deletions(-) diff --git a/ruby/lib/jam_ruby/models/lesson_booking.rb b/ruby/lib/jam_ruby/models/lesson_booking.rb index e270027be..ca52d8131 100644 --- a/ruby/lib/jam_ruby/models/lesson_booking.rb +++ b/ruby/lib/jam_ruby/models/lesson_booking.rb @@ -426,6 +426,9 @@ module JamRuby self.errors.add(:status, "This lesson is already #{self.status}.") end + if self.accepter.nil? + self.errors.add(:accepter, "No one has been indicated as accepting the lesson") + end self.accepting = false end diff --git a/ruby/lib/jam_ruby/models/lesson_booking_slot.rb b/ruby/lib/jam_ruby/models/lesson_booking_slot.rb index 911fd8739..bba714ef4 100644 --- a/ruby/lib/jam_ruby/models/lesson_booking_slot.rb +++ b/ruby/lib/jam_ruby/models/lesson_booking_slot.rb @@ -173,7 +173,7 @@ module JamRuby duration = lesson_length * 60 # convert from minutes to seconds end_time = start_time + duration if with_timezone - "#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} #{tz.pretty_name}" + "#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} (#{tz.pretty_name})" else "#{start_time.strftime("%A, %B %e")} - #{start_time.strftime("%l:%M%P").strip}" end diff --git a/ruby/spec/jam_ruby/models/lesson_booking_spec.rb b/ruby/spec/jam_ruby/models/lesson_booking_spec.rb index fde720cc8..57a546006 100644 --- a/ruby/spec/jam_ruby/models/lesson_booking_spec.rb +++ b/ruby/spec/jam_ruby/models/lesson_booking_spec.rb @@ -741,6 +741,14 @@ describe LessonBooking do UserMailer.deliveries.clear Timecop.freeze(7.days.ago) + + mailer = mock + mailer.should_receive(:deliver).exactly(2).times + UserMailer.should_receive(:student_lesson_booking_canceled).and_return(mailer) + UserMailer.should_receive(:teacher_lesson_booking_canceled).and_return(mailer) + UserMailer.should_receive(:student_lesson_canceled).exactly(0).times + UserMailer.should_receive(:teacher_lesson_canceled).exactly(0).times + lesson_session.cancel({canceler: user, message: 'meh', slot: booking.default_slot.id, update_all: true}) lesson_session.errors.any?.should be_false lesson_session.reload @@ -748,7 +756,6 @@ describe LessonBooking do booking.reload booking.status.should eql LessonSession::STATUS_CANCELED booking.canceler.should eql user - UserMailer.deliveries.length.should eql 2 end end describe "rescheduling" do diff --git a/ruby/spec/support/lesson_session.rb b/ruby/spec/support/lesson_session.rb index 855f13a46..4943a9ca9 100644 --- a/ruby/spec/support/lesson_session.rb +++ b/ruby/spec/support/lesson_session.rb @@ -83,7 +83,7 @@ def book_lesson(user, teacher, options) end if options[:accept] - lesson.accept({message: 'Yeah I got this', slot: slots[0]}) + lesson.accept({message: 'Yeah I got this', slot: slots[0], accepter: teacher}) lesson.errors.any?.should be_false lesson.reload lesson.slot.should eql slots[0] diff --git a/web/app/assets/javascripts/react-components/BookLesson.js.jsx.coffee b/web/app/assets/javascripts/react-components/BookLesson.js.jsx.coffee index 7a7b64f39..54264ab47 100644 --- a/web/app/assets/javascripts/react-components/BookLesson.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/BookLesson.js.jsx.coffee @@ -1,3 +1,4 @@ + context = window rest = context.JK.Rest() logger = context.JK.logger diff --git a/web/app/assets/javascripts/react-components/ChatWindow.js.jsx.coffee b/web/app/assets/javascripts/react-components/ChatWindow.js.jsx.coffee index c01269302..beb1fc840 100644 --- a/web/app/assets/javascripts/react-components/ChatWindow.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/ChatWindow.js.jsx.coffee @@ -125,6 +125,7 @@ ChatActions = @ChatActions else purpose = null + additional = null if msg.purpose == 'Notation File' additional = `{msg.music_notation.file_name}` else if msg.purpose == 'Audio File' diff --git a/web/app/assets/javascripts/react-components/JamClassScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/JamClassScreen.js.jsx.coffee index 0969188d4..2c68b006b 100644 --- a/web/app/assets/javascripts/react-components/JamClassScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/JamClassScreen.js.jsx.coffee @@ -129,9 +129,9 @@ LessonTimerActions = context.LessonTimerActions rest.checkLessonReschedule({id: lesson.id, update_all: recurring}) .done((response) => ( if recurring - window.location.href = '/client#/jamclass/lesson-booking/' + lesson.lesson_booking_id + window.location.href = '/client#/jamclass/lesson-booking/' + lesson.lesson_booking_id + "_rescheduling" else - window.location.href = '/client#/jamclass/lesson-booking/' + lesson.id + window.location.href = '/client#/jamclass/lesson-booking/' + lesson.id + "_rescheduling" )) .fail((jqXHR) => ( if jqXHR.status == 422 @@ -179,7 +179,13 @@ LessonTimerActions = context.LessonTimerActions context.JK.Banner.showAlert('late cancellation warning', 'Cancelling a lesson less than 24 hours before it’s scheduled to start should be avoided, as it’s an inconvenience to the student. Repeated violations of this policy will negatively affect your teacher score.') - @refreshLesson(lesson.id) + lessonsFromBooking = [] + for check in @lessons() + if check.lesson_booking_id == lesson.lesson_booking_id + lessonsFromBooking.push(check) + + for check in lessonsFromBooking + @refreshLesson(check.id) cancelLessonBookingFail: (jqXHR) -> @app.ajaxError(jqXHR) @@ -224,12 +230,12 @@ LessonTimerActions = context.LessonTimerActions if lesson.recurring buttons = [] buttons.push({ - name: 'THIS SESSION', + name: 'THIS LESSON', buttonStyle: 'button-orange', click: (() => (@rescheduleSelected(lesson, false))) }) buttons.push({ - name: 'ALL SESSIONS', + name: 'ALL LESSONS', buttonStyle: 'button-orange', click: (() => (@rescheduleSelected(lesson, true))) }) @@ -251,13 +257,13 @@ LessonTimerActions = context.LessonTimerActions verbLower = 'cancel' if !lesson.isRequested || lesson.recurring buttons = [] - buttons.push({name: 'CANCEL', buttonStyle: 'button-grey'}) + buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'}) buttons.push({ - name: 'THIS SESSION', + name: 'CANCEL THIS LESSON', buttonStyle: 'button-orange', click: (() => (@cancelSelected(lesson, false))) }) - buttons.push({name: 'ALL SESSIONS', buttonStyle: 'button-orange', click: (() => (@cancelSelected(lesson, true)))}) + buttons.push({name: 'CANCEL ALL LESSONS', buttonStyle: 'button-orange', click: (() => (@cancelSelected(lesson, true)))}) context.JK.Banner.show({ title: 'Select One', html: "Do you wish to all #{verbLower} all lessons or just the one selected?", @@ -521,7 +527,10 @@ LessonTimerActions = context.LessonTimerActions else unreadMessages = null - timeStmt = lessonData.music_session.pretty_scheduled_start_with_timezone + if lessonData.status == 'countered' + timeStmt = lessonData.counter_slot.pretty_scheduled_start_with_timezone + else + timeStmt = lessonData.music_session.pretty_scheduled_start_with_timezone if lessonData.times? && lessonData.displayStatus == 'Scheduled' if lessonData.times.startingSoon diff --git a/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee b/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee index b942ab538..26fc25f19 100644 --- a/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee @@ -50,6 +50,44 @@ UserStore = context.UserStore slot.creatorRoleRelative = "your" slot.mySlot = @mySlot(slot) + processBooking:(booking) -> + booking.neverAccepted = booking.accepter_id? + booking.isCounter = booking.counter_slot? && booking.status != 'canceled' && booking.status != 'suspended' + booking.studentViewing = booking.user_id == context.JK.currentUserId + booking.teacherViewing = !booking.studentViewing + booking.isRequested = booking.status == 'requested' && !booking.isCounter + booking.isCanceled = booking.status == 'canceled' + booking.isSuspended = booking.status == 'suspended' + if booking.isCounter + if booking.counter_slot['is_teacher_created?'] + booking.countererId = booking.teacher_id + else + booking.countererId = booking.user_id + + selfLastToAct = false + if booking.isRequested + selfLastToAct = booking.studentViewing + else if booking.isCounter + selfLastToAct = booking.countererId == context.JK.currentUserId + else if booking.isCanceled + selfLastToAct = booking.canceler_id == context.JK.currentUserId + else if booking.isSuspended + selfLastToAct = booking.studentViewing + else + selfLastToAct = false + booking.selfLastToAct = selfLastToAct + + multipleOptions = false + if booking.neverAccepted + multipleOptions = !(!booking.isCounter && booking.selfLastToAct) + else if booking.isCounter + multipleOptions = !booking.selfLastToAct + else + multipleOptions = false + onlyOption = !multipleOptions + booking.onlyOption = onlyOption + +#nextProps.slot_decision = 'counter' componentWillUpdate: (nextProps, nextState) -> if nextState.booking? booking = nextState.booking @@ -61,6 +99,35 @@ UserStore = context.UserStore @processSlot(booking.default_slot, booking) @processSlot(booking.alt_slot, booking) + + + onlyOption:() -> + #(this.props.initial && !this.props.selfLastToAct )|| !(this.props.counter && !this.props.selfLastToAct) + + #@initialRequestSlotsVisible() || !@counteredSlotVisible() + #(this.props.initial && this.props.selfLastToAct ) || !(this.props.counter && !this.props.selfLastToAct) + #(@neverAccepted() && @selfLastToAct()) || !(@isCounter() && !@selfLastToAct()) + !@multipleOptions() + + multipleOptions: () -> + if @neverAccepted() + !(!@isCounter() && @selfLastToAct()) + else if this.props.counter + !@selfLastToAct() + else + false + initialRequestSlotsVisible: () -> + console.log("initialRequestSlotsVisible: " + this.neverAccepted() ) + #this.neverAccepted() && this.selfLastToAct() + + # is there a counter slot showing + counteredSlotVisible: () -> + console.log("isCounter " + this.isCounter() + ", this.selfLastToAct()" + this.selfLastToAct()) + this.isCounter() && !this.selfLastToAct() + + counterSlotVisible: () -> + true + getInitialState: () -> { user: null, @@ -73,14 +140,28 @@ UserStore = context.UserStore beforeShow: (e) -> + parseId: (id) -> + result = {purpose: null} + + bits = id.split('_') + if bits.length == 1 + result.id = id + else if bits.length > 1 + result.id =bits[0] + result.purpose = bits[1] + else + result.id = id + result + afterShow: (e) -> - @setState({updating: true, counterErrors: null, cancelErrors: null}) + parsed = @parseId(e.id) + @setState({updating: true, counterErrors: null, cancelErrors: null, purpose: parsed.purpose}) rest.getLessonBooking({ - id: e.id, + id: parsed.id, }).done((response) => @getLessonBookingDone(response)).fail(@app.ajaxError) hasFocusedLesson: () -> - this.state.booking.focused_lesson?.id? + @focusedLesson()? focusedLesson: () -> this.state?.booking?.focused_lesson @@ -91,10 +172,10 @@ UserStore = context.UserStore if booking.counter_slot? startSlotDecision = booking.counter_slot.id else - if booking.accepter_id? - startSlotDecision = 'counter' - else - startSlotDecision = booking.default_slot.id + if booking.accepter_id? + startSlotDecision = 'counter' + else + startSlotDecision = booking.default_slot.id update_all = !booking.focused_lesson?.id? @@ -105,6 +186,13 @@ UserStore = context.UserStore #booking.next_lesson.lesson_booking = booking @postProcessLesson(booking.next_lesson) + @processBooking(booking) + + + #if booking.onlyOptions + # you see two options (accept, and propose new) if it's a counter and you are not the last to act + # the only choice possible in most cases is to propose a new time + # nextState.slot_decision = 'counter' @setState({booking: booking, updating: false, slot_decision: startSlotDecision, updatingLesson: false, update_all: update_all}) getLessonBookingDone: (response) -> @@ -213,7 +301,8 @@ UserStore = context.UserStore minute = $slot.find('.minute').val() am_pm = $slot.find('.am_pm').val() - update_all = $slot.find('input.update-all').is(':checked') && @isRecurring() + #update_all = $slot.find('input.update-all').is(':checked') && @isRecurring() + update_all = @state.update_all && @isRecurring() if hour? and hour != '' hour = new Number(hour) @@ -227,7 +316,7 @@ UserStore = context.UserStore else minute = null - if !@isRecurring() + if !update_all date = picker.datepicker("getDate") if date? date = context.JK.formatDateYYYYMMDD(date) @@ -399,10 +488,10 @@ UserStore = context.UserStore text = "Preferred day/time for lesson is #{this.slotTime(defaultSlot)}. Secondary option is #{this.slotTime(altSlot)}." slotTime: (slot, booking = this.state.booking) -> - if @isRecurring(booking) - "#{this.dayOfWeek(slot)} at #{this.dayTime(slot)}" - else + if @hasFocusedLesson() || !@isRecurring(booking) slot.pretty_start_time + else + "#{this.dayOfWeek(slot)} at #{this.dayTime(slot)}" slotTimePhrase: (slot) -> if @isRecurring() @@ -704,9 +793,10 @@ UserStore = context.UserStore renderStudentRequested: () -> `
-
+
{this.userHeader(this.myself())} Your request has been sent. You will receive an email when {this.teacher().name} responds. + {this.createDetail()}
` @@ -719,6 +809,22 @@ UserStore = context.UserStore updateCreditCard: (e) -> window.location.href="/client#/account/paymentHistory" + createDetail: () -> + if @hasFocusedLesson() || !@isRecurring() + if @onlyOption() && @rescheduling() + detail = `

You are proposing to change the date/time of the lesson currently scheduled for {this.slotTime(this.state.booking.default_slot)}

` + else + detail = `

Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}

` + else + if @onlyOption() && @rescheduling() + detail = `

You are proposing to change the date/time of the lesson currently scheduled for {this.slotTime(this.state.booking.default_slot)}

` + else + detail = `

Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}

` + detail + + rescheduling: () -> + @state.purpose == 'rescheduling' + renderStudentComplete: () -> @renderStudentApproved() @@ -746,10 +852,7 @@ UserStore = context.UserStore if @studentMadeDefaultSlot() message = this.slotMessage(this.state.booking.default_slot, 'accept') - if @isRecurring() - detail = `

Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}

` - else - detail = `

Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}

` + detail = @createDetail() summary = `
{this.userHeader(this.teacher())}

Has accepted your lesson request.

@@ -766,10 +869,7 @@ UserStore = context.UserStore if @studentMadeDefaultSlot() message = this.slotMessage(this.state.booking.default_slot, 'accept') - if @isRecurring() - detail = `

Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}

` - else - detail = `

Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}

` + detail = @createDetail() summary = `
{this.userHeader(this.teacher())}

Has accepted your lesson request.

@@ -780,10 +880,7 @@ UserStore = context.UserStore if @studentMadeDefaultSlot() message = this.slotMessage(this.state.booking.default_slot, 'accept') - if @isRecurring() - detail = `

Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}

` - else - detail = `

Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}

` + detail = @createDetail() summary = `
{this.userHeader(this.teacher())} @@ -847,10 +944,7 @@ UserStore = context.UserStore if @studentMadeDefaultSlot() message = this.slotMessage(this.state.booking.default_slot, 'accept') - if @isRecurring() - detail = `

Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}

` - else - detail = `

Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}

` + detail = @createDetail() summary = `
{this.userHeader(this.student())}

Is ready to take the lesson.

@@ -869,10 +963,7 @@ UserStore = context.UserStore if @studentMadeDefaultSlot() message = this.slotMessage(this.state.booking.default_slot, 'accept') - if @isRecurring() - detail = `

Your {this.lessonDesc()} will take place each {this.slotTime(this.state.booking.default_slot)}

` - else - detail = `

Your {this.lessonDesc()} will take place this {this.slotTime(this.state.booking.default_slot)}

` + detail = @createDetail() summary = `
{this.userHeader(this.teacher())} diff --git a/web/app/assets/javascripts/react-components/LessonBookingDecision.js.jsx.coffee b/web/app/assets/javascripts/react-components/LessonBookingDecision.js.jsx.coffee index a744c9cea..96b12a084 100644 --- a/web/app/assets/javascripts/react-components/LessonBookingDecision.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/LessonBookingDecision.js.jsx.coffee @@ -57,6 +57,11 @@ @checkboxes = [{selector: 'input.slot-decision', stateKey: 'slot-decision'}, {selector: 'input.update-all', propsKey: 'update_all'}] @root = $(@getDOMNode()) @iCheckify() + @slotDate = @root.find('.date-picker') + @slotDate.datepicker({ + dateFormat: "D M d yy", + onSelect: ((e) => @toggleDate(e)) + }) componentDidUpdate: () -> @iCheckify() @@ -66,6 +71,14 @@ onSelect: ((e) => @toggleDate(e)) }) + toggleDate: (e) -> + + componentWillReceiveProps: (nextProps) -> + if @onlyOption() + console.log("setting it counter") + # if this isn't a counter situation, then there is no 'Accept their time', so there should only be one radio button, and we'll select that value already + @setState({"slot-decision": "counter"}) + checkboxChanged: (e) -> @@ -126,10 +139,21 @@ nullOp: ()-> + onlyOption: () -> + # (!this.props.initial && this.props.selfLastToAct ) || !(this.props.counter && !this.props.selfLastToAct) + !@multipleOptions() + + multipleOptions: () -> + if this.props.initial + !(!this.props.counter && this.props.selfLastToAct) + else if this.props.counter + !this.props.selfLastToAct + else + false + render: () -> - - showUpdateAll = !this.props.initial + #showUpdateAll = !this.props.initial if (!this.props.initial && !this.props.counter) || this.props.selfLastToAct userPromptHeader = `

Would you like to change this lesson?

` @@ -150,7 +174,7 @@ else verb = "CANCEL" - if this.props.update_all && showUpdateAll + if this.props.update_all actionBtnText = "#{verb} ALL LESSONS" else actionBtnText = "#{verb} LESSON" @@ -159,13 +183,13 @@ else actionBtnText = "ACCEPT & UPDATE LESSON" - counterClasses={field: true, 'slot-decision-field': true, error: this.props.counterErrors?, counterSelected: this.props.slot_decision == 'counter'} + counterClasses={field: true, 'slot-decision-field': true, error: this.props.counterErrors?, counterSelected: this.props.slot_decision == 'counter', onlyOption: @onlyOption()} if this.props.counterErrors? errorText = window.JK.reactErrors(this.props.counterErrors, {day_of_week: 'Day' }) - if this.props.is_recurring + if this.props.is_recurring && this.props.update_all slotAltPrompt = `
@@ -182,17 +206,18 @@
` - if showUpdateAll - updateAllField = - `
- -
` + #if @props.update_all + # updateAllField = + # `
+ # + #
` + updateAllField = null else slotAltPrompt = `
Date: - + Time: @@ -222,7 +247,7 @@ else slotDetail = `
{slot.slotTime}
` - slots.push(`
+ slots.push(`
@@ -234,6 +259,12 @@ # if you have issued a counter, you should be able to withdraw it # TODO + #cancelField = `
+ # + #
` + cancelField = null + `
{userPromptHeader} @@ -247,10 +278,7 @@
{slotAltPrompt}
-
- -
+ {cancelField} {updateAllField}
diff --git a/web/app/assets/stylesheets/client/react-components/LessonBookingScreen.css.scss b/web/app/assets/stylesheets/client/react-components/LessonBookingScreen.css.scss index 0221d328c..60c8524b7 100644 --- a/web/app/assets/stylesheets/client/react-components/LessonBookingScreen.css.scss +++ b/web/app/assets/stylesheets/client/react-components/LessonBookingScreen.css.scss @@ -79,6 +79,15 @@ } } + .onlyOption { + input[type="radio"], .iradio_minimal { + display:none; + } + .slot-alt-prompt { + padding-left:0; + } + } + .slot-detail { color: $ColorTextTypical; display: inline-block; @@ -160,6 +169,14 @@ .user-name { line-height:48px; vertical-align:middle; + color:white; + } + .request-sent { + color:$ColorTextTypical; + + .generic-time-stmt, .proposing-new-time { + margin-top:10px !important; + } } .avatar { position:absolute; diff --git a/web/app/controllers/api_lesson_bookings_controller.rb b/web/app/controllers/api_lesson_bookings_controller.rb index ef65f1459..fc6833dd5 100644 --- a/web/app/controllers/api_lesson_bookings_controller.rb +++ b/web/app/controllers/api_lesson_bookings_controller.rb @@ -52,7 +52,7 @@ class ApiLessonBookingsController < ApiController specified_slot.timezone = params[:timezone] slots << specified_slot end - + @lesson_booking = LessonBooking.book_free(current_user, teacher, slots, params[:description]) if @lesson_booking.errors.any? @@ -129,10 +129,10 @@ class ApiLessonBookingsController < ApiController def accept next_lesson = @lesson_booking.next_lesson result = next_lesson.accept({ - message: params[:message], - slot: params[:slot], - accepter: current_user - }) + message: params[:message], + slot: params[:slot], + accepter: current_user + }) if result.errors.any? if result.is_a?(JamRuby::LessonBooking) @@ -159,16 +159,20 @@ class ApiLessonBookingsController < ApiController slot = LessonBookingSlot.new if @lesson_booking.recurring - slot.slot_type = LessonBookingSlot::SLOT_TYPE_RECURRING - slot.day_of_week = params[:day_of_week] + if params[:update_all] + slot.slot_type = LessonBookingSlot::SLOT_TYPE_RECURRING + else + slot.slot_type = LessonBookingSlot::SLOT_TYPE_SINGLE + end else slot.slot_type = LessonBookingSlot::SLOT_TYPE_SINGLE + end - if params[:date].present? - day = params[:date] - day = Date.parse(day) if day && !day.include?('NaN') - slot.preferred_day = day - end + slot.day_of_week = params[:day_of_week] + if params[:date].present? + day = params[:date] + day = Date.parse(day) if day && !day.include?('NaN') + slot.preferred_day = day end slot.hour = params[:hour] slot.minute = params[:minute] @@ -176,10 +180,10 @@ class ApiLessonBookingsController < ApiController slot.update_all = params[:update_all] result = target_lesson.counter({ - proposer: current_user, - message: params[:message], - slot: slot - }) + proposer: current_user, + message: params[:message], + slot: slot + }) if result.errors.any? if result.is_a?(JamRuby::LessonBooking) @@ -188,7 +192,7 @@ class ApiLessonBookingsController < ApiController recursive_errors(result, [:lesson_booking_slots]) else raise "unknown response type in counter #{result.class}" - end + end return end @lesson_booking.reload @@ -203,10 +207,10 @@ class ApiLessonBookingsController < ApiController @lesson_session = target_lesson result = target_lesson.cancel({ - canceler: current_user, - message: params[:message], - update_all: true - }) + canceler: current_user, + message: params[:message], + update_all: params[:update_all] + }) if result.errors.any? if result.is_a?(JamRuby::LessonBooking) diff --git a/web/app/controllers/api_lesson_sessions_controller.rb b/web/app/controllers/api_lesson_sessions_controller.rb index 22a0bf5b1..06e37cc32 100644 --- a/web/app/controllers/api_lesson_sessions_controller.rb +++ b/web/app/controllers/api_lesson_sessions_controller.rb @@ -94,7 +94,7 @@ class ApiLessonSessionsController < ApiController if params[:update_all] # check if the next scheduled lesson is doable - if 24.hours.from_now > @lesson_session.booking.next_lesson.music_session.scheduled_start + if 24.hours.from_now > @lesson_session.lesson_booking.next_lesson.music_session.scheduled_start response = {message: 'time_limit'} render :json => response, :status => 422 return @@ -135,6 +135,7 @@ class ApiLessonSessionsController < ApiController end msg = ChatMessage.create(me, nil, '', ChatMessage::CHANNEL_LESSON, nil, other, @lesson_session, 'JamKazam Recording', nil, claimed_recording) + UserMailer.lesson_attachment(me, other, @lesson_session, claimed_recording) end render :json => {}, :status => 200 diff --git a/web/app/controllers/api_music_notations_controller.rb b/web/app/controllers/api_music_notations_controller.rb index c1c6822f1..fd5b05fad 100644 --- a/web/app/controllers/api_music_notations_controller.rb +++ b/web/app/controllers/api_music_notations_controller.rb @@ -40,6 +40,8 @@ class ApiMusicNotationsController < ApiController end msg = ChatMessage.create(me, nil, '', ChatMessage::CHANNEL_LESSON, nil, other, lesson_session, purpose, music_notation) + + UserMailer.lesson_attachment(me, other, lesson_session, music_notation) end end diff --git a/web/app/helpers/music_session_helper.rb b/web/app/helpers/music_session_helper.rb index dcc9191e5..98d292cc0 100644 --- a/web/app/helpers/music_session_helper.rb +++ b/web/app/helpers/music_session_helper.rb @@ -77,4 +77,8 @@ module MusicSessionHelper def pretty_scheduled_start(music_session, with_timezone, shorter = false) music_session.pretty_scheduled_start(with_timezone, shorter) end + + def pretty_scheduled_start_slot(slot, with_timezone) + slot.pretty_scheduled_start(with_timezone) + end end diff --git a/web/app/views/api_lesson_sessions/show.rabl b/web/app/views/api_lesson_sessions/show.rabl index 8a6c872a6..ab3068ec1 100644 --- a/web/app/views/api_lesson_sessions/show.rabl +++ b/web/app/views/api_lesson_sessions/show.rabl @@ -3,7 +3,8 @@ object @lesson_session attributes :id, :lesson_booking_id, :lesson_type, :duration, :price, :teacher_complete, :student_complete, :status, :student_canceled, :teacher_canceled, :student_canceled_at, :teacher_canceled_at, :student_canceled_reason, :teacher_canceled_reason, :status, :success, :teacher_unread_messages, :student_unread_messages, :is_active?, :recurring, - :analysed, :school_on_school?, :teacher_id, :student_id, :pretty_scheduled_start, :scheduled_start, :teacher_short_canceled + :analysed, :school_on_school?, :teacher_id, :student_id, :pretty_scheduled_start, :scheduled_start, :teacher_short_canceled, + :best_display_time node do |lesson_session| { @@ -17,6 +18,15 @@ child(:lesson_booking => :lesson_booking) { } + +child(:counter_slot => :counter_slot) { + attributes :id, :preferred_day, :day_of_week, :hour, :minute, :slot_type, :pretty_scheduled_start, :message, :pretty_start_time, :proposer_id, :is_student_created?, :is_teacher_created?, :timezone, :pretty_timezone + node :pretty_scheduled_start_with_timezone do |slot| + pretty_scheduled_start_slot(slot, true) + end +} + + child(:music_session => :music_session) { attributes :id, :music_session_id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :genre_id, :created_at, :like_count, :comment_count, :play_count, :scheduled_duration, diff --git a/web/spec/features/jamclass_screen_spec.rb b/web/spec/features/jamclass_screen_spec.rb index b15da2cb8..18fa92794 100644 --- a/web/spec/features/jamclass_screen_spec.rb +++ b/web/spec/features/jamclass_screen_spec.rb @@ -118,4 +118,57 @@ describe "JamClassScreen", :js => true, :type => :feature, :capybara_feature => find('#jam-class-student-screen td.displayStatusColumn', text: 'Canceled (Teacher)') end + + it "student cancels one of recurring" do + lesson = monthly_lesson(user, teacher_user, {accept: true}) + lesson1 = lesson.lesson_booking.lesson_sessions[0] + lesson2 = lesson.lesson_booking.lesson_sessions[1] + + fast_signin(user, "/client#/jamclass") + find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Scheduled') + find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Scheduled') + + # open up hover + find('tr[data-lesson-session-id="' + lesson1.id + '"] .lesson-session-actions-btn').trigger(:click) + find('li[data-lesson-option="cancel"] a', visible: false, text: 'Cancel Lesson').trigger(:click) + # confirm cancelation - + find('#banner a', text: 'CANCEL THIS LESSON').trigger(:click) + find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Canceled (Student)') + find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Scheduled') + + switch_user(teacher_user, "/client#/jamclass") + + find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Canceled (Student)') + find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Scheduled') + end + + it "student cancels all recurring" do + lesson = monthly_lesson(user, teacher_user, {accept: true}) + lesson1 = lesson.lesson_booking.lesson_sessions[0] + lesson2 = lesson.lesson_booking.lesson_sessions[1] + lesson1.recurring.should be_true + lesson.lesson_booking.recurring.should be_true + + fast_signin(user, "/client#/jamclass") + find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Scheduled') + find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Scheduled') + + # open up hover + find('tr[data-lesson-session-id="' + lesson1.id + '"] .lesson-session-actions-btn').trigger(:click) + find('li[data-lesson-option="cancel"] a', visible: false, text: 'Cancel Lesson').trigger(:click) + # confirm cancelation - + find('#banner a', text: 'CANCEL ALL LESSONS').trigger(:click) + find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Canceled (Student)') + lesson1.reload + lesson2.reload + lesson1.status.should eql LessonSession::STATUS_CANCELED + lesson2.status.should eql LessonSession::STATUS_CANCELED + + find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Canceled (Student)') + + switch_user(teacher_user, "/client#/jamclass") + + find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Canceled (Student)') + find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Canceled (Student)') + end end diff --git a/web/spec/features/lesson_booking_status_spec.rb b/web/spec/features/lesson_booking_status_spec.rb index 3857d226e..1a1c49000 100644 --- a/web/spec/features/lesson_booking_status_spec.rb +++ b/web/spec/features/lesson_booking_status_spec.rb @@ -25,6 +25,7 @@ describe "Lesson Booking Status page", :js => true, :type => :feature, :capybara find('h2', text: 'your lesson has been requested') + find('p.proposing-new-time') screenshot end @@ -32,11 +33,14 @@ describe "Lesson Booking Status page", :js => true, :type => :feature, :capybara it "approved" do lesson = testdrive_lesson(user, teacher, {accept:true, finish:false}) - fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.id) + lesson.reload + lesson.status.should + lesson.lesson_booking.accepter_id.should_not be_nil + fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.id ) find('h2', text: 'this lesson is coming up soon') - find('p.lesson-time', "will take place each") + find('p.generic-time-stmt') screenshot end @@ -185,4 +189,90 @@ describe "Lesson Booking Status page", :js => true, :type => :feature, :capybara find('#lesson-booking', text: 'US Central Time') end + it "requested recurring with focused lesson" do + lesson = monthly_lesson(user, teacher, {accept: false}) + + fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.id) + + find('.request-sent', text: 'Your request has been sent.') + page.should_not have_selector('p.proposing-new-time') + + # the teacher can either accept or propose a new time, so they see both + switch_user(teacher, "/client#/jamclass/lesson-booking/" + lesson.id) + + find('p.action', text: 'Has requested') + page.should_not have_selector('p.generic-time-stmt') + + find(".slot-decision-field[data-slot-id=\"#{lesson.lesson_booking.default_slot.id}\"] ins", visible: false).trigger(:click) + find('.schedule.button-orange').trigger(:click) + find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Scheduled') + + end + + it "requested recurring with unfocused lesson" do + lesson = monthly_lesson(user, teacher, {accept: false}) + + fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.lesson_booking.id) + + find('.request-sent', text: 'Your request has been sent.') + + page.should_not have_selector('p.proposing-new-time') + + switch_user(teacher, "/client#/jamclass/lesson-booking/" + lesson.id) + + find('p.action', text: 'Has requested') + page.should_not have_selector('p.generic-time-stmt') + + find(".slot-decision-field[data-slot-id=\"#{lesson.lesson_booking.default_slot.id}\"] ins", visible: false).trigger(:click) + find('.schedule.button-orange').trigger(:click) + find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Scheduled') + end + + it "accepted recurring with focused lesson" do + lesson = monthly_lesson(user, teacher, {accept: true}) + + fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.id + "_rescheduling") + + find('p.proposing-new-time') + + # the teacher can either accept or propose a new time, so they see both + switch_user(teacher, "/client#/jamclass/lesson-booking/" + lesson.id + "_rescheduling") + + find('p.proposing-new-time') + + # change the lesson time + fill_in "alt-date-input", with: date_picker_format(Date.new(Date.today.year, Date.today.month + 1, 17)) + find('td a', text: '17').trigger(:click) + sleep 3 + + find('.schedule.button-orange').trigger(:click) + find('#banner h1', text: 'Lesson Change Requested') + find('#banner .close-btn').trigger(:click) + find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Requested') + + switch_user(user, "/client#/jamclass") + end + + it "accepted recurring with unfocused lesson" do + lesson = monthly_lesson(user, teacher, {accept: true}) + + fast_signin(user, "/client#/jamclass/lesson-booking/" + lesson.lesson_booking.id + "_rescheduling") + + find('.request-sent', text: 'Your request has been sent.') + + page.should_not have_selector('p.proposing-new-time') + + switch_user(teacher, "/client#/jamclass/lesson-booking/" + lesson.lesson_booking.id + "_rescheduling") + + find('p.proposing-new-time') + + fill_in "alt-date-input", with: date_picker_format(Date.new(Date.today.year, Date.today.month + 1, 17)) + find('td a', text: '17').trigger(:click) + sleep 3 + + find(".slot-decision-field[data-slot-id=\"#{lesson.lesson_booking.default_slot.id}\"] ins", visible: false).trigger(:click) + find('.schedule.button-orange').trigger(:click) + find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Scheduled') + end + end \ No newline at end of file diff --git a/web/spec/features/student_landing_spec.rb b/web/spec/features/student_landing_spec.rb index 0182e4180..19056d4cb 100644 --- a/web/spec/features/student_landing_spec.rb +++ b/web/spec/features/student_landing_spec.rb @@ -29,7 +29,7 @@ describe "Student Landing", :js => true, :type => :feature, :capybara_feature => fill_in "email", with: 'student_123@jamkazam.com' fill_in "password", with: 'jam123' - find('.register-area ins', visible: false) .trigger(:click) + find('.register-area ins', visible: false).trigger(:click) find('button.cta-button', text: 'SIGN UP').trigger(:click) # this should show on the /client#/home page (WILL CHANGE) diff --git a/web/spec/support/lessons.rb b/web/spec/support/lessons.rb index b8befba48..791dbbfd9 100644 --- a/web/spec/support/lessons.rb +++ b/web/spec/support/lessons.rb @@ -31,6 +31,9 @@ def teacher_approve(lesson_session) find('tr[data-lesson-session-id="' + lesson_session.id + '"] .displayStatusColumn', text: 'Scheduled') end +def date_picker_format(date) + date.strftime('%a %b %d %Y') +end def fill_out_single_lesson find('h2', text: 'book testdrive lesson') @@ -159,7 +162,7 @@ def book_lesson(user, teacher, options) end if options[:accept] - lesson.accept({message: 'Yeah I got this', slot: slots[0]}) + lesson.accept({message: 'Yeah I got this', slot: slots[0], accepter: teacher}) lesson.errors.any?.should be_false unless options[:no_validate] lesson.reload lesson.slot.should eql slots[0] unless options[:no_validate] From e8843bfd6469c6799c97a71ee90dddf7abc3a3ee Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 31 May 2016 08:35:04 -0500 Subject: [PATCH 07/10] ready for release JamClass --- ruby/lib/jam_ruby/models/lesson_session.rb | 7 ++-- .../flows/monthly_recurring_lesson_spec.rb | 12 +++--- .../spec/jam_ruby/flows/normal_lesson_spec.rb | 8 ++-- .../jam_ruby/flows/recurring_lesson_spec.rb | 8 +++- .../jam_ruby/flows/testdrive_lesson_spec.rb | 4 +- .../jam_ruby/models/lesson_session_spec.rb | 8 ++++ ruby/spec/support/lesson_session.rb | 1 - web/app/assets/javascripts/ga.js | 13 ++++++- .../LessonBooking.js.jsx.coffee | 13 ++++--- .../LessonPayment.js.jsx.coffee | 6 +++ .../mixins/PostProcessorMixin.js.coffee | 2 +- web/app/assets/javascripts/web/welcome.js | 3 +- web/app/controllers/users_controller.rb | 3 +- .../clients/_lessonSessionActions.html.slim | 37 +++++++++---------- web/spec/features/book_test_drive_spec.rb | 2 +- 15 files changed, 80 insertions(+), 47 deletions(-) diff --git a/ruby/lib/jam_ruby/models/lesson_session.rb b/ruby/lib/jam_ruby/models/lesson_session.rb index a1c946359..00d3879d8 100644 --- a/ruby/lib/jam_ruby/models/lesson_session.rb +++ b/ruby/lib/jam_ruby/models/lesson_session.rb @@ -796,11 +796,12 @@ module JamRuby self.countered_slot = slot self.countered_lesson = self self.status = STATUS_COUNTERED - if !update_all + #if !update_all self.counter_slot = slot - end + #end if self.save - if update_all && !lesson_booking.counter(self, proposer, slot) + #if update_all && !lesson_booking.counter(self, proposer, slot) + if !lesson_booking.counter(self, proposer, slot) response = lesson_booking raise ActiveRecord::Rollback end diff --git a/ruby/spec/jam_ruby/flows/monthly_recurring_lesson_spec.rb b/ruby/spec/jam_ruby/flows/monthly_recurring_lesson_spec.rb index ee2aea0bf..ea39a1573 100644 --- a/ruby/spec/jam_ruby/flows/monthly_recurring_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/monthly_recurring_lesson_spec.rb @@ -42,7 +42,6 @@ describe "Monthly Recurring Lesson Flow" do ########## Need validate their credit card token = create_stripe_token result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id}) - puts "result #{result.inspect}" booking.reload booking.card_presumed_ok.should be_true booking.errors.any?.should be_false @@ -118,7 +117,7 @@ describe "Monthly Recurring Lesson Flow" do ######## Teacher accepts slot UserMailer.deliveries.clear - lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false}) + lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user}) UserMailer.deliveries.each do |del| # puts del.inspect end @@ -203,7 +202,7 @@ describe "Monthly Recurring Lesson Flow" do payment = TeacherPayment.first payment.amount_in_cents.should eql 3000 payment.fee_in_cents.should eql (3000 * 0.28).round - payment.teacher_payment_charge.amount_in_cents.should eql 3000 + payment.teacher_payment_charge.amount_in_cents.should eql (3000 + 3000 * APP_CONFIG.stripe[:ach_pct]).round payment.teacher_payment_charge.fee_in_cents.should eql (3000 * 0.28).round payment.teacher.should eql teacher_user payment.teacher_distribution.should eql teacher_distribution @@ -320,7 +319,7 @@ describe "Monthly Recurring Lesson Flow" do ######## Teacher accepts slot UserMailer.deliveries.clear - lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false}) + lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user}) UserMailer.deliveries.each do |del| # puts del.inspect end @@ -419,6 +418,7 @@ describe "Monthly Recurring Lesson Flow" do it "affiliate gets their cut" do + Timecop.travel(2016, 05, 15) user.affiliate_referral = affiliate_partner user.save! teacher_user.affiliate_referral = affiliate_partner2 @@ -429,7 +429,6 @@ describe "Monthly Recurring Lesson Flow" do user.reload - puts "user.lesson_purchases #{user.lesson_purchases}" user.lesson_purchases.count.should eql 1 lesson_package_purchase = user.lesson_purchases.first teacher_distribution = lesson_package_purchase.teacher_distribution @@ -447,6 +446,7 @@ describe "Monthly Recurring Lesson Flow" do end it "school affiliate gets nothing when teacher school is involved" do + Timecop.travel(2016, 05, 15) teacher.school = school teacher.save! @@ -469,6 +469,8 @@ describe "Monthly Recurring Lesson Flow" do end it "student school affiliates gets cut when student school is involved. so does teacher's" do + # in the middle of the month so that we don't get the next month's in-advance purchase put on us + Timecop.travel(2016, 05, 15) user.affiliate_referral = school.affiliate_partner user.save! diff --git a/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb b/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb index b9a558afd..e0fbde071 100644 --- a/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/normal_lesson_spec.rb @@ -68,7 +68,7 @@ describe "Normal Lesson Flow" do ######## Teacher accepts slot UserMailer.deliveries.clear - lesson_session.accept({message: 'Yeah I got this', slot: booking.default_slot.id, update_all: false}) + lesson_session.accept({message: 'Yeah I got this', slot: booking.default_slot.id, update_all: false, accepter: teacher_user}) lesson_session.errors.any?.should be_false lesson_session.reload lesson_session.slot.should eql booking.default_slot @@ -251,7 +251,7 @@ describe "Normal Lesson Flow" do payment = TeacherPayment.first payment.amount_in_cents.should eql 3000 payment.fee_in_cents.should eql (3000 * 0.28).round - payment.teacher_payment_charge.amount_in_cents.should eql 3000 + payment.teacher_payment_charge.amount_in_cents.should eql (3000 + 3000 * APP_CONFIG.stripe[:ach_pct]).round payment.teacher_payment_charge.fee_in_cents.should eql (3000 * 0.28).round payment.teacher.should eql teacher_user payment.teacher_distribution.should eql teacher_distribution @@ -352,7 +352,7 @@ describe "Normal Lesson Flow" do ######## Teacher accepts slot UserMailer.deliveries.clear - lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false}) + lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false,accepter: teacher_user}) lesson_session.errors.any?.should be_false lesson_session.reload lesson_session.slot.should eql student_counter @@ -508,7 +508,7 @@ describe "Normal Lesson Flow" do ######## Teacher accepts slot UserMailer.deliveries.clear - lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false}) + lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user}) lesson_session.errors.any?.should be_false lesson_session.reload lesson_session.slot.should eql student_counter diff --git a/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb b/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb index 00924d4e6..fed085dc9 100644 --- a/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb @@ -105,7 +105,7 @@ describe "Recurring Lesson Flow" do ######## Teacher accepts slot UserMailer.deliveries.clear - lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false}) + lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user}) UserMailer.deliveries.each do |del| # puts del.inspect end @@ -132,7 +132,8 @@ describe "Recurring Lesson Flow" do notification.student_directed.should eql true notification.purpose.should eql 'accept' notification.description.should eql NotificationTypes::LESSON_MESSAGE - + user.reload + user.sales.length.should eql 0 # teacher & student get into session start = lesson_session.scheduled_start @@ -181,6 +182,9 @@ describe "Recurring Lesson Flow" do lesson_session.sent_billing_notices.should be true user.reload user.remaining_test_drives.should eql 0 + UserMailer.deliveries.each do |d| + puts d.subject + end UserMailer.deliveries.length.should eql 2 # one for student, one for teacher end end diff --git a/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb b/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb index 5db43fb37..5d258f958 100644 --- a/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/testdrive_lesson_spec.rb @@ -140,7 +140,7 @@ describe "TestDrive Lesson Flow" do ######## Teacher accepts slot UserMailer.deliveries.clear - lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false}) + lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user}) lesson_session.errors.any?.should be_false lesson_session.reload lesson_session.slot.should eql student_counter @@ -237,7 +237,7 @@ describe "TestDrive Lesson Flow" do teacher_distribution.ready.should be_true teacher_distribution.distributed.should be_true - teacher_payment.teacher_payment_charge.amount_in_cents.should eql 1000 + teacher_payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round teacher_payment.teacher_payment_charge.fee_in_cents.should eql 0 user.sales.count.should eql 1 diff --git a/ruby/spec/jam_ruby/models/lesson_session_spec.rb b/ruby/spec/jam_ruby/models/lesson_session_spec.rb index 3988a2176..633152ca1 100644 --- a/ruby/spec/jam_ruby/models/lesson_session_spec.rb +++ b/ruby/spec/jam_ruby/models/lesson_session_spec.rb @@ -10,6 +10,14 @@ describe LessonSession do let(:lesson_booking) {b = LessonBooking.book_normal(user, teacher, [slot1, slot2], "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60); b.card_presumed_ok = true; b.save!; b} let(:lesson_session) {lesson_booking.lesson_sessions[0]} + describe "counter" do + describe "recurring" do + it "" do + + end + end + end + describe "autocancel" do it "can't autocancel in the past" do lesson_session.status.should eql LessonSession::STATUS_REQUESTED diff --git a/ruby/spec/support/lesson_session.rb b/ruby/spec/support/lesson_session.rb index 4943a9ca9..1a731423e 100644 --- a/ruby/spec/support/lesson_session.rb +++ b/ruby/spec/support/lesson_session.rb @@ -65,7 +65,6 @@ def book_lesson(user, teacher, options) LessonPackagePurchase.create(user, booking, LessonPackageType.package_for_test_drive_count(options[:package_count])) end elsif options[:monthly] - puts "did it" LessonPackagePurchase.create(user, booking, LessonPackageType.single, Date.today.year, Date.today.month) end diff --git a/web/app/assets/javascripts/ga.js b/web/app/assets/javascripts/ga.js index 0c47505ff..d68ece250 100644 --- a/web/app/assets/javascripts/ga.js +++ b/web/app/assets/javascripts/ga.js @@ -99,6 +99,10 @@ ioTargetFail : 'ioTargetFail' } + var jamClassReasons = { + testDrive: 'TestDrive' + } + var networkTestFailReasons = { stun : 'STUN', bandwidth : 'Bandwidth', @@ -129,7 +133,8 @@ jkFollow : 'jkFollow', jkFavorite : 'jkFavorite', jkComment : 'jkComment', - fileDownload: "DownloadFile" + fileDownload: "DownloadFile", + jamclass: 'JamClass' }; // JamTrack categories and actions: @@ -204,6 +209,11 @@ context.ga('send', 'event', categories.register, action, registrationType); } + function trackTestDrivePurchase(count) { + + context.ga('send', 'event', categories.jamclass, jamClassReasons.testDrive, count); + + } function trackDownload(platform) { var normalizedPlatform = translatePlatformForGA(platform); @@ -490,6 +500,7 @@ GA.virtualPageView = virtualPageView; GA.trackTiming = trackTiming; GA.trackFileDownload = trackFileDownload; + GA.trackTestDrivePurchase = trackTestDrivePurchase; context.JK.GA = GA; diff --git a/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee b/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee index 26fc25f19..023b9a66b 100644 --- a/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee @@ -172,10 +172,10 @@ UserStore = context.UserStore if booking.counter_slot? startSlotDecision = booking.counter_slot.id else - if booking.accepter_id? - startSlotDecision = 'counter' - else - startSlotDecision = booking.default_slot.id + if booking.accepter_id? + startSlotDecision = 'counter' + else + startSlotDecision = booking.default_slot.id update_all = !booking.focused_lesson?.id? @@ -189,10 +189,11 @@ UserStore = context.UserStore @processBooking(booking) - #if booking.onlyOptions + if booking.onlyOption # you see two options (accept, and propose new) if it's a counter and you are not the last to act # the only choice possible in most cases is to propose a new time - # nextState.slot_decision = 'counter' + #nextState.slot_decision = 'counter' + startSlotDecision = 'counter' @setState({booking: booking, updating: false, slot_decision: startSlotDecision, updatingLesson: false, update_all: update_all}) getLessonBookingDone: (response) -> diff --git a/web/app/assets/javascripts/react-components/LessonPayment.js.jsx.coffee b/web/app/assets/javascripts/react-components/LessonPayment.js.jsx.coffee index c0ce9c79a..e299a0780 100644 --- a/web/app/assets/javascripts/react-components/LessonPayment.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/LessonPayment.js.jsx.coffee @@ -294,6 +294,12 @@ UserStore = context.UserStore logger.debug("testDriveCount: " + testDriveCount) + testDriveCountInt = parseInt(testDriveCount); + if context._.isNaN(testDriveCountInt) + testDriveCountInt = 3 + + context.JK.GA.trackTestDrivePurchase(testDriveCountInt); + if response.test_drive?.teacher_id teacher_id = response.test_drive.teacher_id if testDriveCount == '1' diff --git a/web/app/assets/javascripts/react-components/mixins/PostProcessorMixin.js.coffee b/web/app/assets/javascripts/react-components/mixins/PostProcessorMixin.js.coffee index fdf561dd7..811f956ba 100644 --- a/web/app/assets/javascripts/react-components/mixins/PostProcessorMixin.js.coffee +++ b/web/app/assets/javascripts/react-components/mixins/PostProcessorMixin.js.coffee @@ -23,7 +23,7 @@ teacherActions = window.JK.Actions.Teacher lesson.isAdmin = context.JK.currentUserAdmin lesson.schoolOnSchool = lesson['school_on_school?'] lesson.cardNotOk = !lesson.schoolOnSchool && !lesson.lesson_booking.card_presumed_ok - + lesson.isActive = lesson['is_active?'] if (lesson.status == 'requested' || lesson.status == 'countered') lesson.isRequested = true if lesson.cardNotOk diff --git a/web/app/assets/javascripts/web/welcome.js b/web/app/assets/javascripts/web/welcome.js index dd570b763..61cc44f1a 100644 --- a/web/app/assets/javascripts/web/welcome.js +++ b/web/app/assets/javascripts/web/welcome.js @@ -76,7 +76,8 @@ $('.like-link').click(function() { var like_site = $(this).data('site'); - JK.GA.trackJKSocial(JK.GA.Categories.jkLike, like_site, JK.clientType()); + // removed because we are juggling 20 events max in GA + //JK.GA.trackJKSocial(JK.GA.Categories.jkLike, like_site, JK.clientType()); window.open("/endorse/0/"+like_site, '_blank'); }); } diff --git a/web/app/controllers/users_controller.rb b/web/app/controllers/users_controller.rb index 8d19082cd..62dca654c 100644 --- a/web/app/controllers/users_controller.rb +++ b/web/app/controllers/users_controller.rb @@ -380,7 +380,8 @@ class UsersController < ApplicationController js =< $(function() { - JK.GA.trackJKSocial(JK.GA.Categories.jkLike, '#{service}', 'email'); + // // removed because we are juggling 20 events max in GA + // JK.GA.trackJKSocial(JK.GA.Categories.jkLike, '#{service}', 'email'); window.location = "#{url}"; }); diff --git a/web/app/views/clients/_lessonSessionActions.html.slim b/web/app/views/clients/_lessonSessionActions.html.slim index 9e0a4fc18..266fcbb8d 100644 --- a/web/app/views/clients/_lessonSessionActions.html.slim +++ b/web/app/views/clients/_lessonSessionActions.html.slim @@ -14,25 +14,7 @@ script type='text/template' id='template-lesson-session-actions' li data-lesson-option="attach-audio" a href='#' Attach Audio File - = '{% } else if (data.isRequested) { %}' - ul - li data-lesson-option="status" - a href='#' View Status - - li data-lesson-option="messages" - a href='#' View Messages - - li data-lesson-option="messages" - a href='#' Attach Message - - li data-lesson-option="cancel" - = '{% if (data.isStudent) { %}' - a href='#' Cancel Request - = '{% } else { %}' - a href='#' Decline Request - = '{% } %}' - - = '{% } else if (data.isScheduled) { %}' + = '{% } else if (data.isActive) { %}' ul li data-lesson-option="status" a href='#' View Status @@ -67,6 +49,23 @@ script type='text/template' id='template-lesson-session-actions' li data-lesson-option="start-65-ago" a href='#' Set Start 65 Min Ago = '{% } %}' + = '{% } else if (data.isRequested) { %}' + ul + li data-lesson-option="status" + a href='#' View Status + + li data-lesson-option="messages" + a href='#' View Messages + + li data-lesson-option="messages" + a href='#' Attach Message + + li data-lesson-option="cancel" + = '{% if (data.isStudent) { %}' + a href='#' Cancel Request + = '{% } else { %}' + a href='#' Decline Request + = '{% } %}' = '{% } else { %}' ul diff --git a/web/spec/features/book_test_drive_spec.rb b/web/spec/features/book_test_drive_spec.rb index 631c0bd78..ba0cfa242 100644 --- a/web/spec/features/book_test_drive_spec.rb +++ b/web/spec/features/book_test_drive_spec.rb @@ -49,7 +49,7 @@ describe "Test Drive", :js => true, :type => :feature, :capybara_feature => true # we tell user they have test drive purchased, and take them to the teacher screen find('#banner h1', text: 'Test Drive Purchased') - find('#banner .dialog-inner', text: "You have purchased #{4} TestDrive credits and have used 1 credit it to request a JamClass with #{teacher_user.name}") + find('#banner .dialog-inner', text: "You have purchased #{4} TestDrive credits and have used 1 credit to request a JamClass with #{teacher_user.name}") # dismiss banner find('a.button-orange', text:'CLOSE').trigger(:click) From 8793c770335a62bd79a8b9ebb66f1c2efc349d0b Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 31 May 2016 11:13:49 -0500 Subject: [PATCH 08/10] ftue fixes VRFS-4171, VRFS-4172 --- admin/Rakefile | 1 + ruby/lib/jam_ruby/models/lesson_booking_slot.rb | 1 + ruby/lib/jam_ruby/models/lesson_session.rb | 6 +++++- ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb | 9 ++++++--- .../javascripts/wizard/gear/step_select_gear.js | 10 ++++++---- .../clients/wizard/gear/_gear_wizard.html.haml | 3 +-- web/lib/tasks/lesson.rake | 13 ++++++++----- web/spec/support/lessons.rb | 1 + 8 files changed, 29 insertions(+), 15 deletions(-) diff --git a/admin/Rakefile b/admin/Rakefile index 3065cf5e9..d88365d80 100644 --- a/admin/Rakefile +++ b/admin/Rakefile @@ -1,3 +1,4 @@ + #!/usr/bin/env rake # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. diff --git a/ruby/lib/jam_ruby/models/lesson_booking_slot.rb b/ruby/lib/jam_ruby/models/lesson_booking_slot.rb index bba714ef4..be52e3e8f 100644 --- a/ruby/lib/jam_ruby/models/lesson_booking_slot.rb +++ b/ruby/lib/jam_ruby/models/lesson_booking_slot.rb @@ -81,6 +81,7 @@ module JamRuby candidate = scheduled_time(i + week_offset) #puts "#{i}: candidate #{candidate} week_offset:#{week_offset}" + #puts "DAY_OF_WEEK #{day_of_week}" if day_of_week && candidate <= minimum_start_time # move it up a week week_offset += 1 diff --git a/ruby/lib/jam_ruby/models/lesson_session.rb b/ruby/lib/jam_ruby/models/lesson_session.rb index 00d3879d8..5786369dd 100644 --- a/ruby/lib/jam_ruby/models/lesson_session.rb +++ b/ruby/lib/jam_ruby/models/lesson_session.rb @@ -165,7 +165,7 @@ module JamRuby end # test drives don't have a lesson_payment_charge, so we don't join against them - MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where('lesson_sessions.lesson_type = ?', LESSON_TYPE_TEST_DRIVE).where("session_removed_at IS NOT NULL OR ? > scheduled_start + (INTERVAL '1 minutes' * duration)", Time.now).where('analysed = true').where('lesson_sessions.post_processed = false').each do |music_session| + MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where('lesson_sessions.lesson_type = ?', LESSON_TYPE_TEST_DRIVE).where("? > scheduled_start + (INTERVAL '1 minutes' * duration)", Time.now).where('analysed = true').where('lesson_sessions.post_processed = false').each do |music_session| lession_session = music_session.lesson_session lession_session.session_completed end @@ -366,7 +366,9 @@ module JamRuby end def recurring_completed + puts "RECURRING COMPLETED #{success}" if success + if lesson_booking.is_monthly_payment? # monthly payments are handled at beginning of month; just poke with email, and move on @@ -399,6 +401,7 @@ module JamRuby end else + puts "STUDENT NO BILL SENT #{self.id}" if !sent_notices if !school_on_school? # bad session; just poke user @@ -422,6 +425,7 @@ module JamRuby else if !sent_notices if !school_on_school? + puts "STUDENT NO BILL SENT #{success}" UserMailer.student_lesson_normal_no_bill(self).deliver UserMailer.teacher_lesson_normal_no_bill(self).deliver end diff --git a/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb b/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb index fed085dc9..d84e21e83 100644 --- a/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb +++ b/ruby/spec/jam_ruby/flows/recurring_lesson_spec.rb @@ -55,7 +55,7 @@ describe "Recurring Lesson Flow" do booking.status.should eql LessonBooking::STATUS_REQUESTED ######### Teacher counters with new slot - teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_single, hour: 14, update_all: true) + teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 14, update_all: true) UserMailer.deliveries.clear lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'}) booking.reload @@ -81,7 +81,7 @@ describe "Recurring Lesson Flow" do #notification.message.should eql "Instructor has proposed a different time for your lesson." ######### Student counters with new slot - student_countered_slot = FactoryGirl.build(:lesson_booking_slot_single, hour: 16, update_all: true) + student_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 16, update_all: true) UserMailer.deliveries.clear lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'}) lesson_session.errors.any?.should be false @@ -105,7 +105,7 @@ describe "Recurring Lesson Flow" do ######## Teacher accepts slot UserMailer.deliveries.clear - lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user}) + lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, accepter: teacher_user}) UserMailer.deliveries.each do |del| # puts del.inspect end @@ -135,6 +135,9 @@ describe "Recurring Lesson Flow" do user.reload user.sales.length.should eql 0 + booking.reload + booking.lesson_sessions[0].scheduled_start.should_not eql booking.lesson_sessions[1].scheduled_start + # teacher & student get into session start = lesson_session.scheduled_start end_time = lesson_session.scheduled_start + (60 * lesson_session.duration) diff --git a/web/app/assets/javascripts/wizard/gear/step_select_gear.js b/web/app/assets/javascripts/wizard/gear/step_select_gear.js index 0b41e3030..998be7c22 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -1105,6 +1105,11 @@ return false; } + if (!passedOnce) { + passedOnce = true; + autoAssignToSingleInput(); + } + if(!savedProfile) { context.jamClient.FTUESetMusicProfileName(gearUtils.createProfileName(selectedDeviceInfo)); var result = context.jamClient.FTUESave(true); @@ -1122,10 +1127,6 @@ } } - if (!passedOnce) { - passedOnce = true; - autoAssignToSingleInput(); - } // keep the shared state between step 2 and step 3 up-to-date wizard.setChosenInputs(context._.map($assignedInputs, function(input) { return $(input).attr('data-id') })); @@ -1182,6 +1183,7 @@ } function newSession() { + passedOnce = false; savedProfile = false; initialScan = false; deviceInformation = gearUtils.loadDeviceInfo(); diff --git a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml index 609682e34..cdbd11bd4 100644 --- a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml +++ b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml @@ -16,14 +16,13 @@ .wizard-step{ 'layout-wizard-step' => "1", 'dialog-title' => "Select & Test Audio Gear", 'dialog-purpose' => "SelectAudioGear" } .ftuesteps .clearall - .help-text In this step, you will select, configure, and test your audio gear. Please watch the video for best instructions. + .help-text In this step, you will select, configure, and test your audio gear. Please click Instructions button for guidance. .wizard-step-content .wizard-step-column %h2 Instructions .ftue-box.instructions %ul %li Select audio interface for inputs and outputs. - %li Check input ports to which you will connect instruments or mics. %li Check output ports you will use to monitor. %li Configure interface settings. %li View test results. diff --git a/web/lib/tasks/lesson.rake b/web/lib/tasks/lesson.rake index 99fdd999f..6ef8c0f6e 100644 --- a/web/lib/tasks/lesson.rake +++ b/web/lib/tasks/lesson.rake @@ -1,7 +1,11 @@ require 'factory_girl' require 'timecop' require 'rspec-rails' +begin require Rails.root.join('spec', 'support', 'lessons.rb') +rescue LoadError + puts "for production; we ignore LoadError" +end namespace :lessons do @@ -77,8 +81,8 @@ namespace :lessons do end task book_test_drive: :environment do |task, args| - user = User.find_by_email(ENV['STUDENT_EMAIL']) - teacher = User.find_by_email(ENV['TEACHER_EMAIL']) + user = User.find_by_email(ENV['STUDENT']) + teacher = User.find_by_email(ENV['TEACHER']) slots = [] @@ -91,7 +95,6 @@ namespace :lessons do user.save! end - booking = LessonBooking.book_test_drive(user, teacher, slots, "Hey I've heard of you before.") if booking.errors.any? puts booking.errors.inspect @@ -100,7 +103,7 @@ namespace :lessons do lesson = booking.lesson_sessions[0] if user.most_recent_test_drive_purchase.nil? - LessonPackagePurchase.create(user, lesson.booking, LessonPackageType.test_drive_4) + LessonPackagePurchase.create(user, lesson.lesson_booking, LessonPackageType.test_drive_4) end #lesson.accept({message: 'Yeah I got this', slot: slots[0]}) @@ -109,6 +112,6 @@ namespace :lessons do #lesson.slot.should eql slots[0] #lesson.status.should eql LessonSession::STATUS_APPROVED - puts "http://localhost:3000/client#/jamclass/lesson-booking/#{lesson.booking.id}" + puts "http://localhost:3000/client#/jamclass/lesson-booking/#{lesson.lesson_booking.id}" end end diff --git a/web/spec/support/lessons.rb b/web/spec/support/lessons.rb index 791dbbfd9..ff4aec5a3 100644 --- a/web/spec/support/lessons.rb +++ b/web/spec/support/lessons.rb @@ -25,6 +25,7 @@ def teacher_approve(lesson_session) sign_out_poltergeist(validate: true) sign_in_poltergeist(lesson_session.teacher, password: 'foobar') visit "/client#/jamclass/lesson-booking/" + lesson_session.id + find(".slot-decision-field[data-slot-id=\"#{lesson_session.lesson_booking.default_slot.id}\"] ins", visible: false).trigger(:click) find('.schedule.button-orange').trigger(:click) # dismiss banner #find('a.button-orange', text:'CLOSE').trigger(:click) From e449139b63fb73c438321d37cebc75ee1a419aec Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 31 May 2016 11:54:20 -0500 Subject: [PATCH 09/10] alpha the list of FTUE instruments VRFS-4172 --- admin/app/admin/user_source.rb | 30 +++++++++++++++++++ ruby/lib/jam_ruby/models/user.rb | 2 +- web/app/assets/javascripts/globals.js | 18 +++++++++-- .../ConfigureLiveTracksDialog.js.jsx.coffee | 8 ++--- 4 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 admin/app/admin/user_source.rb diff --git a/admin/app/admin/user_source.rb b/admin/app/admin/user_source.rb new file mode 100644 index 000000000..50f9a93af --- /dev/null +++ b/admin/app/admin/user_source.rb @@ -0,0 +1,30 @@ +ActiveAdmin.register JamRuby::User, :as => 'UserSource' do + + menu :label => 'User Campaigns', :parent => 'Users' + + config.sort_order = 'created_at DESC' + config.batch_actions = false + config.clear_action_items! + config.filters = false + + index do + column "Email" do |user| + user.email + end + column "Bought TestDrive" do |user| + !user.most_recent_test_drive_purchase.nil? ? "Yes" : "No" + end + column "UTM Source" do |user| + user.origin_utm_source + end + column "UTM Medium" do |user| + user.origin_utm_medium + end + column "UTM Campaign" do |user| + user.origin_utm_campaign + end + column "Referrer" do |user| + user.origin_referrer + end + end +end diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 91321138e..86c35c534 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -2090,7 +2090,7 @@ module JamRuby LessonBooking.unprocessed(self).where(lesson_type: LessonBooking::LESSON_TYPE_PAID).first end - def most_recent_test_drive_purchase + 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 diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index d59831d01..3954949c6 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -228,7 +228,7 @@ "Electric Guitar": { "client_id": 50, "server_id": "electric guitar" }, "Keyboard": { "client_id": 60, "server_id": "keyboard" }, "Piano": { "client_id": 61, "server_id": "piano" }, - "Upright Bass": { "client_id": 62, "server_id": "upright bass" }, + "Upright Bass": { "client_id": 62, "server_id": "double bass" }, "Voice": { "client_id": 70, "server_id": "voice" }, "Flute": { "client_id": 80, "server_id": "flute" }, "Clarinet": { "client_id": 90, "server_id": "clarinet" }, @@ -250,6 +250,9 @@ "Other": { "client_id": 250, "server_id": "other" } }; + + + context.JK.client_to_server_instrument_map = { 10: { "server_id": "acoustic guitar" }, 20: { "server_id": "bass guitar" }, @@ -259,7 +262,7 @@ 50: { "server_id": "electric guitar" }, 60: { "server_id": "keyboard" }, 61: { "server_id": "piano"} , - 62: { "server_id": "upright bass"} , + 62: { "server_id": "double bass"} , 70: { "server_id": "voice" }, 80: { "server_id": "flute" }, 90: { "server_id": "clarinet" }, @@ -283,10 +286,21 @@ context.JK.instrument_id_to_instrument = {}; + context.JK.server_to_client_instrument_alpha = []; + (function() { $.each(context.JK.server_to_client_instrument_map, function(key, value) { context.JK.instrument_id_to_instrument[value.server_id] = { client_id: value.client_id, display: key } + context.JK.server_to_client_instrument_alpha.push({ client_id: value.client_id, display: key, server_id: value.server_id }) }); + + context.JK.server_to_client_instrument_alpha.sort(function(a, b){ + if ( a.display < b.display ) + return -1; + if ( a.display > b.display ) + return 1; + return 0; + }); })(); diff --git a/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee index 4b3cc4cf9..f45531854 100644 --- a/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee @@ -29,8 +29,8 @@ ConfigureTracksStore = @ConfigureTracksStore instruments = [] instruments.push(``) - for displayName, value of context.JK.server_to_client_instrument_map - instruments.push(``) + for instrument in context.JK.server_to_client_instrument_alpha + instruments.push(``) vsts = [] @@ -134,8 +134,8 @@ ConfigureTracksStore = @ConfigureTracksStore midiInstruments = [] instruments = [] - for displayName, value of context.JK.server_to_client_instrument_map - instruments.push(``) + for instrument in context.JK.server_to_client_instrument_alpha + instruments.push(``) selectedMidiInterface = '' selectedInstrument = context.JK.client_to_server_instrument_map[50].server_id # default to electric guitar From 920d648a2b4ae40be4d899f2e218c439ecc68201 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 31 May 2016 19:20:03 -0500 Subject: [PATCH 10/10] slew of jamclass fixes --- admin/app/admin/user_source.rb | 2 + ruby/lib/jam_ruby.rb | 5 +- ruby/lib/jam_ruby/models/genre.rb | 3 +- ruby/lib/jam_ruby/models/instrument.rb | 3 +- ruby/lib/jam_ruby/models/language.rb | 3 +- .../jam_ruby/models/lesson_booking_slot.rb | 3 + ruby/lib/jam_ruby/models/lesson_session.rb | 20 +- ruby/lib/jam_ruby/models/subject.rb | 3 +- ruby/lib/jam_ruby/models/teacher.rb | 16 +- ruby/lib/jam_ruby/models/teacher_genre.rb | 11 + .../lib/jam_ruby/models/teacher_instrument.rb | 11 + ruby/lib/jam_ruby/models/teacher_language.rb | 11 + ruby/lib/jam_ruby/models/teacher_subject.rb | 11 + ruby/lib/jam_ruby/models/user.rb | 2 +- .../jam_ruby/models/lesson_session_spec.rb | 51 ++++- ruby/spec/jam_ruby/models/teacher_spec.rb | 16 +- .../resque/google_analytics_event_spec.rb | 4 + ruby/spec/spec_helper.rb | 2 +- .../assets/javascripts/helpBubbleHelper.js | 4 +- .../JamClassSearchHeader.js.jsx.coffee | 135 +++++++++++++ .../TeacherProfile.js.jsx.coffee | 26 ++- .../TeacherSearchScreen.js.jsx.coffee | 188 ++++-------------- ...lassStudentLandingBottomPage.js.jsx.coffee | 4 +- .../TeacherSearchResultsStore.js.coffee | 12 +- .../wizard/gear/step_select_gear.js | 10 +- .../react-components/TeacherProfile.css.scss | 17 +- .../react-components/TeacherSearch.css.scss | 70 ++++--- web/app/controllers/api_users_controller.rb | 5 +- web/app/views/api_teachers/index.rabl | 2 +- .../views/api_users/show_teacher_index.rabl | 8 + .../api_teachers_controller_spec.rb | 15 ++ web/spec/features/jamclass_screen_spec.rb | 38 ++++ web/spec/support/lessons.rb | 27 +++ 33 files changed, 520 insertions(+), 218 deletions(-) create mode 100644 ruby/lib/jam_ruby/models/teacher_genre.rb create mode 100644 ruby/lib/jam_ruby/models/teacher_instrument.rb create mode 100644 ruby/lib/jam_ruby/models/teacher_language.rb create mode 100644 ruby/lib/jam_ruby/models/teacher_subject.rb create mode 100644 web/app/assets/javascripts/react-components/JamClassSearchHeader.js.jsx.coffee create mode 100644 web/app/views/api_users/show_teacher_index.rabl diff --git a/admin/app/admin/user_source.rb b/admin/app/admin/user_source.rb index 50f9a93af..3e6c6556d 100644 --- a/admin/app/admin/user_source.rb +++ b/admin/app/admin/user_source.rb @@ -7,6 +7,8 @@ ActiveAdmin.register JamRuby::User, :as => 'UserSource' do config.clear_action_items! config.filters = false + scope("Most Recent First", default: true) { |scope| scope.unscoped.order('created_at desc')} + index do column "Email" do |user| user.email diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index d81652c2b..fdf7a5368 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -295,7 +295,10 @@ 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/teacher_instrument" +require "jam_ruby/models/teacher_subject" +require "jam_ruby/models/teacher_language" +require "jam_ruby/models/teacher_genre" include Jampb module JamRuby diff --git a/ruby/lib/jam_ruby/models/genre.rb b/ruby/lib/jam_ruby/models/genre.rb index 3f81b8bbf..24a93a830 100644 --- a/ruby/lib/jam_ruby/models/genre.rb +++ b/ruby/lib/jam_ruby/models/genre.rb @@ -16,7 +16,8 @@ module JamRuby has_and_belongs_to_many :recordings, :class_name => "JamRuby::Recording", :join_table => "recordings_genres" # teachers - has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_genres" + has_many :teachers, :class_name => "JamRuby::Teacher", :through => :teachers_genres + has_many :teachers_genres, :class_name => "JamRuby::TeacherGenre" # jam tracks has_many :genres_jam_tracks, :class_name => "JamRuby::GenreJamTrack", :foreign_key => "genre_id" diff --git a/ruby/lib/jam_ruby/models/instrument.rb b/ruby/lib/jam_ruby/models/instrument.rb index be18c5eb5..e8b407135 100644 --- a/ruby/lib/jam_ruby/models/instrument.rb +++ b/ruby/lib/jam_ruby/models/instrument.rb @@ -44,7 +44,8 @@ module JamRuby has_and_belongs_to_many :music_sessions, :class_name => "JamRuby::ActiveMusicSession", :join_table => "genres_music_sessions" # teachers - has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_instruments" + has_many :teachers, :class_name => "JamRuby::Teacher", through: :teachers_instruments + has_many :teachers_instruments, class_name: "JamRuby::TeacherInstrument" def self.standard_list return Instrument.where('instruments.popularity > 0').order('instruments.description ASC') diff --git a/ruby/lib/jam_ruby/models/language.rb b/ruby/lib/jam_ruby/models/language.rb index ae249ff8e..4492f800b 100644 --- a/ruby/lib/jam_ruby/models/language.rb +++ b/ruby/lib/jam_ruby/models/language.rb @@ -2,7 +2,8 @@ module JamRuby class Language < ActiveRecord::Base include HtmlSanitize html_sanitize strict: [:name, :description] - has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_languages" + has_many :teachers, :class_name => "JamRuby::Teacher", :through => :teachers_languages + has_many :teachers_languages, class_name: "JamRuby::TeacherLanguage" def self.english_sort languages = Language.order(:description) diff --git a/ruby/lib/jam_ruby/models/lesson_booking_slot.rb b/ruby/lib/jam_ruby/models/lesson_booking_slot.rb index be52e3e8f..6c64a39e6 100644 --- a/ruby/lib/jam_ruby/models/lesson_booking_slot.rb +++ b/ruby/lib/jam_ruby/models/lesson_booking_slot.rb @@ -33,6 +33,9 @@ module JamRuby validate :validate_proposer before_validation :before_validation + def is_recurring? + slot_type == SLOT_TYPE_RECURRING + end def before_validation if proposer.nil? self.proposer = container.student diff --git a/ruby/lib/jam_ruby/models/lesson_session.rb b/ruby/lib/jam_ruby/models/lesson_session.rb index 5786369dd..e4225be2e 100644 --- a/ruby/lib/jam_ruby/models/lesson_session.rb +++ b/ruby/lib/jam_ruby/models/lesson_session.rb @@ -5,7 +5,7 @@ module JamRuby include HtmlSanitize html_sanitize strict: [:cancel_message] - attr_accessor :accepting, :creating, :countering, :autocanceling, :countered_slot, :countered_lesson, :canceling, :assigned_student + attr_accessor :accepting, :creating, :countering, :countering_flag, :autocanceling, :countered_slot, :countered_lesson, :canceling, :assigned_student @@log = Logging.logger[LessonSession] @@ -61,6 +61,7 @@ module JamRuby validates :post_processed, inclusion: {in: [true, false]} validate :validate_creating, :if => :creating + validate :validate_countering, :if => :countering_flag validate :validate_accepted, :if => :accepting validate :validate_canceled, :if => :canceling validate :validate_autocancel, :if => :autocanceling @@ -505,6 +506,22 @@ module JamRuby end end + def validate_countering + + if counter_slot.nil? + errors.add(:counter_slot, "must be specified") + elsif !approved_before? && (status == STATUS_REQUESTED || status == STATUS_COUNTERED) + if recurring && !counter_slot.update_all + errors.add(:counter_slot, "Only 'update all' counter-proposals are allowed for un-approved, recurring lessons") + end + + if recurring && !counter_slot.is_recurring? + errors.add(:counter_slot, "Only 'recurring' counter-proposals are allowed for un-approved, recurring lessons") + end + end + + self.countering_flag = false + end def validate_accepted if self.status_was != STATUS_REQUESTED && self.status_was != STATUS_COUNTERED self.errors.add(:status, "This session is already #{self.status_was}.") @@ -791,6 +808,7 @@ module JamRuby update_all = slot.update_all || !lesson_booking.recurring self.countering = true + self.countering_flag = true slot.proposer = proposer slot.lesson_session = self slot.message = message diff --git a/ruby/lib/jam_ruby/models/subject.rb b/ruby/lib/jam_ruby/models/subject.rb index f20d75626..a99a9745d 100644 --- a/ruby/lib/jam_ruby/models/subject.rb +++ b/ruby/lib/jam_ruby/models/subject.rb @@ -2,6 +2,7 @@ module JamRuby class Subject < ActiveRecord::Base include HtmlSanitize html_sanitize strict: [:name, :description] - has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_subjects" + has_many :teachers, :class_name => "JamRuby::Teacher", :through => :teachers_subjects + has_many :teachers_subjects, class_name: "JamRuby::TeacherSubject" end end diff --git a/ruby/lib/jam_ruby/models/teacher.rb b/ruby/lib/jam_ruby/models/teacher.rb index 74e15d854..4e1485fb9 100644 --- a/ruby/lib/jam_ruby/models/teacher.rb +++ b/ruby/lib/jam_ruby/models/teacher.rb @@ -4,10 +4,14 @@ module JamRuby html_sanitize strict: [:biography, :website] attr_accessor :validate_introduction, :validate_basics, :validate_pricing attr_accessible :genres, :teacher_experiences, :experiences_teaching, :experiences_education, :experiences_award - has_and_belongs_to_many :genres, :class_name => "JamRuby::Genre", :join_table => "teachers_genres", :order => "description" - has_and_belongs_to_many :instruments, :class_name => "JamRuby::Instrument", :join_table => "teachers_instruments", :order => "description" - has_and_belongs_to_many :subjects, :class_name => "JamRuby::Subject", :join_table => "teachers_subjects", :order => "description" - has_and_belongs_to_many :languages, :class_name => "JamRuby::Language", :join_table => "teachers_languages", :order => "description" + has_many :genres, :class_name => "JamRuby::Genre", :through => :teachers_genres # , :order => "description" + has_many :teachers_genres, :class_name => "JamRuby::TeacherGenre" + has_many :instruments, :class_name => "JamRuby::Instrument", through: :teachers_instruments # , :order => "description" + has_many :teachers_instruments, class_name: "JamRuby::TeacherInstrument" + has_many :subjects, :class_name => "JamRuby::Subject", :through => :teachers_subjects # , :order => "description" + has_many :teachers_subjects, class_name: "JamRuby::TeacherSubject" + has_many :languages, :class_name => "JamRuby::Language", :through => :teachers_languages # , :order => "description" + has_many :teachers_languages, class_name: "JamRuby::TeacherLanguage" has_many :teacher_experiences, :class_name => "JamRuby::TeacherExperience" has_many :experiences_teaching, :class_name => "JamRuby::TeacherExperience", conditions: {experience_type: 'teaching'} has_many :experiences_education, :class_name => "JamRuby::TeacherExperience", conditions: {experience_type: 'education'} @@ -36,7 +40,7 @@ module JamRuby validate :offer_duration, :if => :validate_pricing validate :teaches_ages, :if => :validate_basics - default_scope { includes(:genres).order('created_at desc') } + #default_scope { includes(:genres).order('created_at desc') } after_save :update_profile_pct @@ -53,7 +57,7 @@ module JamRuby limit ||= 20 limit = limit.to_i - query = User.joins(:teacher) + query = User.unscoped.joins(:teacher) # only show teachers with ready for session set to true query = query.where('teachers.ready_for_session_at IS NOT NULL') diff --git a/ruby/lib/jam_ruby/models/teacher_genre.rb b/ruby/lib/jam_ruby/models/teacher_genre.rb new file mode 100644 index 000000000..549219dff --- /dev/null +++ b/ruby/lib/jam_ruby/models/teacher_genre.rb @@ -0,0 +1,11 @@ +module JamRuby + class TeacherGenre < ActiveRecord::Base + self.table_name = "teachers_genres" + + belongs_to :teacher, class_name: "JamRuby::Teacher" + belongs_to :genre, class_name: "JamRuby::Genre" + + validates :teacher, presence:true + validates :genre, presence: true + end +end diff --git a/ruby/lib/jam_ruby/models/teacher_instrument.rb b/ruby/lib/jam_ruby/models/teacher_instrument.rb new file mode 100644 index 000000000..f1f60db41 --- /dev/null +++ b/ruby/lib/jam_ruby/models/teacher_instrument.rb @@ -0,0 +1,11 @@ +module JamRuby + class TeacherInstrument < ActiveRecord::Base + self.table_name = "teachers_instruments" + + belongs_to :teacher, class_name: "JamRuby::Teacher" + belongs_to :instrument, class_name: "JamRuby::Instrument" + + validates :teacher, presence:true + validates :instrument, presence: true + end +end diff --git a/ruby/lib/jam_ruby/models/teacher_language.rb b/ruby/lib/jam_ruby/models/teacher_language.rb new file mode 100644 index 000000000..f0cfcf3d1 --- /dev/null +++ b/ruby/lib/jam_ruby/models/teacher_language.rb @@ -0,0 +1,11 @@ +module JamRuby + class TeacherLanguage < ActiveRecord::Base + self.table_name = "teachers_languages" + + belongs_to :teacher, class_name: "JamRuby::Teacher" + belongs_to :language, class_name: "JamRuby::Language" + + validates :teacher, presence:true + validates :language, presence: true + end +end diff --git a/ruby/lib/jam_ruby/models/teacher_subject.rb b/ruby/lib/jam_ruby/models/teacher_subject.rb new file mode 100644 index 000000000..953ff9fd2 --- /dev/null +++ b/ruby/lib/jam_ruby/models/teacher_subject.rb @@ -0,0 +1,11 @@ +module JamRuby + class TeacherSubject < ActiveRecord::Base + self.table_name = "teachers_subjects" + + belongs_to :teacher, class_name: "JamRuby::Teacher" + belongs_to :subject, class_name: "JamRuby::Subject" + + validates :teacher, presence:true + validates :subject, presence: true + end +end diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 86c35c534..93b19b48f 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -51,7 +51,7 @@ module JamRuby has_many :user_authorizations, :class_name => "JamRuby::UserAuthorization" has_many :reviews, :class_name => "JamRuby::Review" - has_one :review_summary, :class_name => "JamRuby::ReviewSummary" + has_one :review_summary, :class_name => "JamRuby::ReviewSummary", as: :target # calendars (for scheduling NOT in music_session) has_many :calendars, :class_name => "JamRuby::Calendar" diff --git a/ruby/spec/jam_ruby/models/lesson_session_spec.rb b/ruby/spec/jam_ruby/models/lesson_session_spec.rb index 633152ca1..c269ac0d3 100644 --- a/ruby/spec/jam_ruby/models/lesson_session_spec.rb +++ b/ruby/spec/jam_ruby/models/lesson_session_spec.rb @@ -12,8 +12,57 @@ describe LessonSession do describe "counter" do describe "recurring" do - it "" do + it "counter madness" do + lesson = monthly_lesson(user, teacher, {accept:false}) + # start with the student + invalid = FactoryGirl.build(:lesson_booking_slot_single, update_all: false) + lesson.counter({proposer: user, message: "crumble and bumble", slot: invalid}) + lesson.errors.any?.should be_true + lesson.errors[:counter_slot].should eql ["Only 'update all' counter-proposals are allowed for un-approved, recurring lessons", "Only 'recurring' counter-proposals are allowed for un-approved, recurring lessons"] + lesson.reload + lesson.counter_slot.should be_nil + + counter1 = FactoryGirl.build(:lesson_booking_slot_recurring, update_all: true) + + lesson.counter({proposer: user, message: "crumble and bumble take 2", slot: counter1}) + lesson.errors.any?.should be_false + lesson.reload + lesson.status.should eql LessonSession::STATUS_COUNTERED + lesson.counter_slot.id.should eql counter1.id + lesson.lesson_booking.counter_slot.id.should eql counter1.id + + counter2 = FactoryGirl.build(:lesson_booking_slot_recurring, update_all: true) + + lesson.counter({proposer: teacher, message: "crumble and bumble take 3", slot: counter2}) + lesson.errors.any?.should be_false + lesson.reload + lesson.status.should eql LessonSession::STATUS_COUNTERED + lesson.counter_slot.id.should eql counter2.id + lesson.lesson_booking.counter_slot.id.should eql counter2.id + + lesson.accept({accepter: user, message: "burp", slot: counter2}) + lesson.errors.any?.should be_false + lesson.reload + lesson.status.should eql LessonSession::STATUS_APPROVED + + counter3 = FactoryGirl.build(:lesson_booking_slot_recurring, update_all: false) + + lesson.counter({proposer: user, message: "crumble and bumble take 4", slot: counter3}) + lesson.errors.any?.should be_false + lesson.reload + lesson.status.should eql LessonSession::STATUS_COUNTERED + lesson.counter_slot.id.should eql counter3.id + lesson.lesson_booking.counter_slot.id.should eql counter3.id + + counter4 = FactoryGirl.build(:lesson_booking_slot_recurring, update_all: true) + + lesson.counter({proposer: teacher, message: "crumble and bumble take 5", slot: counter4}) + lesson.errors.any?.should be_false + lesson.reload + lesson.status.should eql LessonSession::STATUS_COUNTERED + lesson.counter_slot.id.should eql counter4.id + lesson.lesson_booking.counter_slot.id.should eql counter4.id end end end diff --git a/ruby/spec/jam_ruby/models/teacher_spec.rb b/ruby/spec/jam_ruby/models/teacher_spec.rb index 32003f0cc..a5fd51e50 100644 --- a/ruby/spec/jam_ruby/models/teacher_spec.rb +++ b/ruby/spec/jam_ruby/models/teacher_spec.rb @@ -45,18 +45,20 @@ describe Teacher do it "instruments" do teacher = FactoryGirl.create(:teacher, ready_for_session_at: Time.now) - teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query] - teachers.length.should eq 0 + #teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query] + #teachers.length.should eq 0 teacher.instruments << Instrument.find('acoustic guitar') teacher.save! - teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query] - teachers.length.should eq 1 - teachers[0].should eq(teacher.user) + #teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query] + #teachers.length.should eq 1 + #teachers[0].should eq(teacher.user) - teacher.instruments << Instrument.find('electric guitar') - teacher.save! + #teacher.instruments << Instrument.find('electric guitar') + #teacher.save! + puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query] + puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!---" teachers.length.should eq 1 teachers[0].should eq(teacher.user) end diff --git a/ruby/spec/jam_ruby/resque/google_analytics_event_spec.rb b/ruby/spec/jam_ruby/resque/google_analytics_event_spec.rb index 587080fe5..a9ebd1a28 100644 --- a/ruby/spec/jam_ruby/resque/google_analytics_event_spec.rb +++ b/ruby/spec/jam_ruby/resque/google_analytics_event_spec.rb @@ -9,6 +9,7 @@ describe GoogleAnalyticsEvent do end describe "track band analytics" do + pending "job is commented out" it 'reports first recording' do ResqueSpec.reset! user = FactoryGirl.create(:user) @@ -26,6 +27,7 @@ describe GoogleAnalyticsEvent do end it 'reports first real session' do + pending "job is commented out" ResqueSpec.reset! JamRuby::GoogleAnalyticsEvent::BandSessionTracker.should have_schedule_size_of(0) user = FactoryGirl.create(:user) @@ -72,6 +74,7 @@ describe GoogleAnalyticsEvent do ResqueSpec.reset! end it 'reports size increment' do + pending "job is commented out" user = FactoryGirl.create(:user) music_session = FactoryGirl.create(:active_music_session, :creator => user, @@ -86,6 +89,7 @@ describe GoogleAnalyticsEvent do end it 'reports duration' do + pending "job is commented out" user = FactoryGirl.create(:user) JamRuby::GoogleAnalyticsEvent::SessionDurationTracker.should have_schedule_size_of(0) music_session = FactoryGirl.create(:active_music_session, diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb index 911452807..bc1163e38 100644 --- a/ruby/spec/spec_helper.rb +++ b/ruby/spec/spec_helper.rb @@ -38,7 +38,7 @@ require 'timecop' require 'resque_spec/scheduler' # uncomment this to see active record logs -# ActiveRecord::Base.logger = Logger.new(STDOUT) if defined?(ActiveRecord::Base) +ActiveRecord::Base.logger = Logger.new(STDOUT) if defined?(ActiveRecord::Base) include JamRuby diff --git a/web/app/assets/javascripts/helpBubbleHelper.js b/web/app/assets/javascripts/helpBubbleHelper.js index f6b7622f0..0c7c05f65 100644 --- a/web/app/assets/javascripts/helpBubbleHelper.js +++ b/web/app/assets/javascripts/helpBubbleHelper.js @@ -82,7 +82,7 @@ } function subtlePulse($element) { - $element.find('.bt-content').pulse({'background-color' : '#868686'}, {pulses: 3}, function() { $element.css('background-color', '#980006')}) + $element.find('.bt-content').pulse({'background-color' : '#868686'}, {pulses: 2, duration: 1000, interval:300}, function() { $element.css('background-color', '#980006')}) } helpBubble.rotateJamTrackLandingBubbles = function($preview, $video, $ctaButton, $alternativeCta) { @@ -203,7 +203,7 @@ helpBubble.showBuyTestDrive = function($element, $offsetParent, user, callback) { return context.JK.onceBubble($element, 'side-buy-test-drive', user, {offsetParent:$offsetParent, width:260, positions:['right'], postShow: function(container) { subtlePulse(container) - var $bookNow = container('a.book-now') + var $bookNow = container.find('a.book-now') $bookNow.off('click').on('click', function(e) { e.preventDefault() callback() diff --git a/web/app/assets/javascripts/react-components/JamClassSearchHeader.js.jsx.coffee b/web/app/assets/javascripts/react-components/JamClassSearchHeader.js.jsx.coffee new file mode 100644 index 000000000..23fc81cc7 --- /dev/null +++ b/web/app/assets/javascripts/react-components/JamClassSearchHeader.js.jsx.coffee @@ -0,0 +1,135 @@ +@JamClassSearchHeader = React.createClass({ + + mixins: [Reflux.listenTo(@UserStore, "onUserChanged"), Reflux.listenTo(@TeacherSearchStore, "onTeacherSearchStore")] + + onTeacherSearchStore: ()-> + + getInitialState: () -> + {user: null} + + + onUserChanged: (@user) -> + @setState({user: @user?.user}) + + + createSearchDescription: () -> + searchOptions = TeacherSearchStore.getState() + + summary = '' + + if searchOptions.onlyMySchool && @state.user?.school_id? + summary += "From My School Only" + + instruments = searchOptions.instruments + if instruments? && instruments.length > 0 + if instruments.length == 1 + bit = "Instrument = #{InstrumentStore.display(instruments[0])}" + else + instruments.length > 1 + bit = "Instruments = #{InstrumentStore.display(instruments[0])} ... " + if summary.length > 0 + summary += ', ' + summary += " #{bit}" + + subjects = searchOptions.subjects + if subjects? && subjects.length > 0 + if subjects.length == 1 + bit = "Subject = #{SubjectStore.display(subjects[0])}" + else + subjects.length > 1 + bit = "Subjects = #{SubjectStore.display(subjects[0])} ... " + if summary.length > 0 + summary += ', ' + summary += " #{bit}" + + genres = searchOptions.genres + if genres? && genres.length > 0 + if genres.length == 1 + bit = "Genre = #{GenreStore.display(genres[0])}" + else + genres.length > 1 + bit = "Genres = #{GenreStore.display(genres[0])} ... " + if summary.length > 0 + summary += ', ' + summary += " #{bit}" + + languages = searchOptions.languages + if languages? && languages.length > 0 + if languages.length == 1 + bit = "Language = #{LanguageStore.display(languages[0])}" + else + languages.length > 1 + bit = "Languages = #{LanguageStore.display(languages[0])} ... " + if summary.length > 0 + summary += ', ' + summary += " #{bit}" + + if searchOptions.teaches_beginner || searchOptions.teaches_intermediate || searchOptions.teaches_advanced + bit = "Teaches " + qualifier = '' + if searchOptions.teaches_beginner + qualifier += "Beginner" + if searchOptions.teaches_intermediate + if qualifier.length > 0 + qualifier += ", " + qualifier += "Intermediate" + if searchOptions.teaches_advanced + if qualifier.length > 0 + qualifier += ", " + qualifier += "Advanced" + if summary.length > 0 + summary += ', ' + summary += " #{bit}#{qualifier}" + + if searchOptions.student_age? + if summary.length > 0 + summary += ', ' + summary += "Student Age = #{searchOptions.student_age}" + + if searchOptions.years_teaching? + if summary.length > 0 + summary += ', ' + summary += "Years Teaching = #{searchOptions.years_teaching}" + + if searchOptions.location?.country? + if summary.length > 0 + summary += ', ' + summary += "Country = #{searchOptions.location.country}" + + if searchOptions.location?.region? + + if summary.length > 0 + summary += ', ' + summary += "Region = #{searchOptions.location.region}" + + + if summary.length == 0 + summary = 'all teachers' + + summary + + + render: () -> + searchDesc = @createSearchDescription() + + if @props.teacher + complete = ` + Search Results :  + {this.props.teacher.name} + ` + else + complete = + ` + + Search Results / + {searchDesc} + + ` + `
+ JamKazam Home :  + JamClass Home :  + Teachers Search : + {complete} +
` +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee index 9b60eb7d1..71c25bdd6 100644 --- a/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee @@ -704,14 +704,21 @@ proficiencyDescriptionMap = { render: () -> if @state.user? avatar = context.JK.resolveAvatarUrl(@state.user.photo_url); - if @state.user?.teacher? - mainContent = @mainContent() - profileLeft = @profileLeft() - else - if context.JK.currentUserId? && @state.user?.id == context.JK.currentUserId - noTeacherProfile = `
You have no teacher profile defined yet. Start making one now.
` + if @state.user? + if @state.user.teacher? + mainContent = @mainContent() + profileLeft = @profileLeft() else - noTeacherProfile = `
This user has no teacher profile.
` + if context.JK.currentUserId? && @state.user?.id == context.JK.currentUserId + noTeacherProfile = `
You have no teacher profile defined yet. Start making one now.
` + else + noTeacherProfile = `
This user has no teacher profile.
` + + mainContent = `
+ {noTeacherProfile} +
` + else + noTeacherProfile = `
Loading profile...
` mainContent = `
{noTeacherProfile}
` @@ -730,10 +737,7 @@ proficiencyDescriptionMap = {
` `
- -
-

-
+ {actionButtons}

diff --git a/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee index c5a4539e2..a668aa33d 100644 --- a/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee @@ -28,7 +28,7 @@ ProfileActions = @ProfileActions refreshing: false getInitialState: () -> - {searchOptions: {}, results: [], user: null} + {searchOptions: {}, results: [], user: null, searching: false} onAppInit: (@app) -> @app.bindScreen('teachers/search', {beforeShow: @beforeShow, afterShow: @afterShow, afterHide: @afterHide}) @@ -58,7 +58,7 @@ ProfileActions = @ProfileActions @needToSearch = true onTeacherSearchResultsStore: (results) -> - results.searching = false + #results.searching = false @refreshing = false @contentBodyScroller.find('.infinite-scroll-loader-2').remove() @setState(results) @@ -89,7 +89,7 @@ ProfileActions = @ProfileActions if $scroller.scrollTop() + $scroller.innerHeight() + 100 >= $scroller[0].scrollHeight $scroller.append('
... Loading more Teachers ...
') @refreshing = true - @setState({searching: true}) + #@setState({searching: true}) logger.debug("refreshing more teachers for infinite scroll") TeacherSearchResultsActions.nextPage() ) @@ -149,7 +149,9 @@ ProfileActions = @ProfileActions moreAboutTeacher: (user, e) -> e.preventDefault() - ProfileActions.viewTeacherProfile(user, '/client#/teachers/search', 'BACK TO TEACHER SEARCH') + context.location = "/client#/profile/teacher/#{user.id}" + + #ProfileActions.viewTeacherProfile(user, '/client#/teachers/search', 'BACK TO TEACHER SEARCH') bookTestDrive: (user, e) -> e.preventDefault() @@ -210,162 +212,60 @@ ProfileActions = @ProfileActions target.trigger( 'destroy.dot' ); teacherBio.css('height', 'auto') - createSearchDescription: () -> - searchOptions = TeacherSearchStore.getState() - summary = '' - - if searchOptions.onlyMySchool && @state.user?.school_id? - summary += "From My School Only" - - instruments = searchOptions.instruments - if instruments? && instruments.length > 0 - if instruments.length == 1 - bit = "Instrument = #{InstrumentStore.display(instruments[0])}" - else - instruments.length > 1 - bit = "Instruments = #{InstrumentStore.display(instruments[0])} ... " - if summary.length > 0 - summary += ', ' - summary += " #{bit}" - - subjects = searchOptions.subjects - if subjects? && subjects.length > 0 - if subjects.length == 1 - bit = "Subject = #{SubjectStore.display(subjects[0])}" - else - subjects.length > 1 - bit = "Subjects = #{SubjectStore.display(subjects[0])} ... " - if summary.length > 0 - summary += ', ' - summary += " #{bit}" - - genres = searchOptions.genres - if genres? && genres.length > 0 - if genres.length == 1 - bit = "Genre = #{GenreStore.display(genres[0])}" - else - genres.length > 1 - bit = "Genres = #{GenreStore.display(genres[0])} ... " - if summary.length > 0 - summary += ', ' - summary += " #{bit}" - - languages = searchOptions.languages - if languages? && languages.length > 0 - if languages.length == 1 - bit = "Language = #{LanguageStore.display(languages[0])}" - else - languages.length > 1 - bit = "Languages = #{LanguageStore.display(languages[0])} ... " - if summary.length > 0 - summary += ', ' - summary += " #{bit}" - - if searchOptions.teaches_beginner || searchOptions.teaches_intermediate || searchOptions.teaches_advanced - bit = "Teaches " - qualifier = '' - if searchOptions.teaches_beginner - qualifier += "Beginner" - if searchOptions.teaches_intermediate - if qualifier.length > 0 - qualifier += ", " - qualifier += "Intermediate" - if searchOptions.teaches_advanced - if qualifier.length > 0 - qualifier += ", " - qualifier += "Advanced" - if summary.length > 0 - summary += ', ' - summary += " #{bit}#{qualifier}" - - if searchOptions.student_age? - if summary.length > 0 - summary += ', ' - summary += "Student Age = #{searchOptions.student_age}" - - if searchOptions.years_teaching? - if summary.length > 0 - summary += ', ' - summary += "Years Teaching = #{searchOptions.years_teaching}" - - if searchOptions.location?.country? - if summary.length > 0 - summary += ', ' - summary += "Country = #{searchOptions.location.country}" - - if searchOptions.location?.region? - - if summary.length > 0 - summary += ', ' - summary += "Region = #{searchOptions.location.region}" - - - if summary.length == 0 - summary = 'all teachers' - - summary - render: () -> - searchDesc = @createSearchDescription() - resultsJsx = [] - for user in @state.results + if @state.currentPage == 1 && @state.searching + resultsJsx = `
` + else + for user in @state.results - photo_url = user.photo_url - if !photo_url? - photo_url = '/assets/shared/avatar_generic.png' + photo_url = user.photo_url + if !photo_url? + photo_url = '/assets/shared/avatar_generic.png' - bio = user.teacher.biography - if !bio? - bio = 'No bio' + bio = user.teacher.biography + if !bio? + bio = 'No bio' - school_on_school = user.teacher.school_id? && @state.user?.school_id? && user.teacher.school_id == @state.user.school_id + school_on_school = user.teacher.school_id? && @state.user?.school_id? && user.teacher.school_id == @state.user.school_id - bookSingleBtn = null - bookTestDriveBtn = null + bookSingleBtn = null + bookTestDriveBtn = null - if !school_on_school && (!@state.user? || @state.user.remaining_test_drives > 0 || @state.user['can_buy_test_drive?']) - bookTestDriveBtn = `BOOK TESTDRIVE LESSON` - else - bookSingleBtn = `BOOK LESSON` - resultsJsx.push(`
-
-
- + if !school_on_school && (!@state.user? || @state.user.remaining_test_drives > 0 || @state.user['can_buy_test_drive?']) + bookTestDriveBtn = `BOOK TESTDRIVE LESSON` + else + bookSingleBtn = `BOOK LESSON` + resultsJsx.push(`
+
+
+ +
+
+ {user.name} +
-
- {user.name} +
+
+ {bio} + more +
+
+ MORE ABOUT THIS TEACHER + {bookTestDriveBtn} + {bookSingleBtn} +
-
-
-
- {bio} - more -
-
- MORE ABOUT THIS TEACHER - {bookTestDriveBtn} - {bookSingleBtn} -
-
-
-
`) +
+
`) `
- JamKazam Home :  - JamClass Home :  - Teachers Search : - - - Search Results / - {searchDesc} - - +
{resultsJsx} diff --git a/web/app/assets/javascripts/react-components/landing/JamClassStudentLandingBottomPage.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/JamClassStudentLandingBottomPage.js.jsx.coffee index adeeebc59..0928947a9 100644 --- a/web/app/assets/javascripts/react-components/landing/JamClassStudentLandingBottomPage.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/landing/JamClassStudentLandingBottomPage.js.jsx.coffee @@ -45,8 +45,8 @@ rest = context.JK.Rest()

While you're getting this done, if you want to learn more about all the nifty features you can access in - JamClass and in JamKazam in general, you can check out our online JamClass + JamClass and in JamKazam in general, you can check out our online JamClass User Guide.

` `
diff --git a/web/app/assets/javascripts/react-components/stores/TeacherSearchResultsStore.js.coffee b/web/app/assets/javascripts/react-components/stores/TeacherSearchResultsStore.js.coffee index db6e5e22b..e81153fe6 100644 --- a/web/app/assets/javascripts/react-components/stores/TeacherSearchResultsStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/TeacherSearchResultsStore.js.coffee @@ -11,6 +11,7 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions results: [] page: 1 limit: 20 + searching: false init: -> # Register with the app store to get @app @@ -22,6 +23,7 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions onReset: () -> @results = [] @page = 1 + @searching = true @changed() query = @createQuery() @@ -29,11 +31,13 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions rest.searchTeachers(query) .done((response) => @next = response.next - + @searching = false @results.push.apply(@results, response.entries) @changed() ) .fail((jqXHR, textStatus, errorMessage) => + @searching = false + @changed() @app.ajaxError(jqXHR, textStatus, errorMessage) ) @@ -42,18 +46,22 @@ TeacherSearchResultsActions = @TeacherSearchResultsActions query = @createQuery() + @searching = true rest.searchTeachers(query) .done((response) => @next = response.next @results.push.apply(@results, response.entries) + @searching = false @changed() ) .fail((jqXHR, textStatus, errorMessage) => + @searching = false @app.ajaxError(jqXHR, textStatus, errorMessage) + @changed() ) getState: () -> - ({results: @results, next: @next, currentPage: @page}) + ({results: @results, next: @next, currentPage: @page, searching: @searching}) changed:() -> @trigger(@getState()) diff --git a/web/app/assets/javascripts/wizard/gear/step_select_gear.js b/web/app/assets/javascripts/wizard/gear/step_select_gear.js index 998be7c22..a73274ab4 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -190,7 +190,7 @@ logger.debug("marking all unassigned inputs length=(" + $allInputs.length + ")") - var maxTries = 20; + var maxTries = 12; // we only allow up to 6 tracks, 12 inputs for(var i = 0; i < maxTries; i++) { $unassignedInputs = $inputChannels.find('input[type="checkbox"]:not(:checked)'); @@ -1148,12 +1148,12 @@ } var $unassignedInputs = $inputChannels.find('input[type="checkbox"]:not(:checked)') $allInputs.eq(0).iCheck('check').attr('checked', 'checked') - var firstInputDomNode = $allInputs.get(0) - var maxTries = 20; + var maxTries = 36; for(var i = 0; i < maxTries; i++) { var $assignedInputs = $inputChannels.find('input[type="checkbox"]:checked') - + var $allInputs = $inputChannels.find('input[type="checkbox"]') + var firstInputDomNode = $allInputs.get(0) if ($assignedInputs.length == 1) { break; } @@ -1170,9 +1170,7 @@ } }) } - } - } function onFocus() { diff --git a/web/app/assets/stylesheets/client/react-components/TeacherProfile.css.scss b/web/app/assets/stylesheets/client/react-components/TeacherProfile.css.scss index 2f0412533..3a4618776 100644 --- a/web/app/assets/stylesheets/client/react-components/TeacherProfile.css.scss +++ b/web/app/assets/stylesheets/client/react-components/TeacherProfile.css.scss @@ -194,7 +194,22 @@ .years {float:right} } .profileNavActions { - margin-right: -3px; + right:20px; + top:10px; + position:absolute; + } + + + .spinner-large { + width:200px; + height:200px; + position:relative; + margin:0 auto; + } + + .loading-profile { + text-align:center; + color:white; } .ratings-block { diff --git a/web/app/assets/stylesheets/client/react-components/TeacherSearch.css.scss b/web/app/assets/stylesheets/client/react-components/TeacherSearch.css.scss index a2fec71e9..9353e2d20 100644 --- a/web/app/assets/stylesheets/client/react-components/TeacherSearch.css.scss +++ b/web/app/assets/stylesheets/client/react-components/TeacherSearch.css.scss @@ -1,5 +1,41 @@ @import "client/common"; + +.jamclass-search-header { + + + a { + font-size:16px; + text-decoration:underline; + margin-bottom:5px; + } + .search-results-options { + font-size:16px; + color:$ColorTextTypical; + } + + .search-summary { + font-size:11px; + } + .search-description { + white-space: nowrap; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + vertical-align:middle; + } + .search-summary { + line-height:16px; + vertical-align: middle; + } + .results-text { + &.link { + + } + } +} + + #teacherSearch { div[data-react-class="TeacherSearchScreen"] { height:100%; @@ -9,29 +45,21 @@ padding:20px; } - .header { + .jamclass-search-header { margin-bottom:10px; + } - - - a { - font-size:16px; - text-decoration:underline; - margin-bottom:5px; - } - .search-results-options { - font-size:16px; - color:$ColorTextTypical; - } + .spinner-large { + width:200px; + height:200px; + position:relative; + margin:0 auto; } a.readmore { display:none; } - .search-summary { - font-size:11px; - } .teacher-search-result { @include border_box_sizing; clear:both; @@ -108,17 +136,7 @@ padding-right: 31px; margin-bottom: 20px; } - .search-description { - white-space: nowrap; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - vertical-align:middle; - } - .search-summary { - line-height:16px; - vertical-align: middle; - } + .result-text { line-height:16px; } diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index ba420e5ab..3f0f547ec 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -955,10 +955,13 @@ class ApiUsersController < ApiController end def lookup_user + User.includes([{musician_instruments: :instrument}, {band_musicians: :user}, {genre_players: :genre}, - :bands, :instruments, :genres, :jam_track_rights, :affiliate_partner]) + :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]) end diff --git a/web/app/views/api_teachers/index.rabl b/web/app/views/api_teachers/index.rabl index 40fd86a60..9e3dd989a 100644 --- a/web/app/views/api_teachers/index.rabl +++ b/web/app/views/api_teachers/index.rabl @@ -3,7 +3,7 @@ node :next do |page| end node :entries do |page| - partial "api_users/show", object: @users + partial "api_users/show_teacher_index", object: @users end node :total_entries do |page| diff --git a/web/app/views/api_users/show_teacher_index.rabl b/web/app/views/api_users/show_teacher_index.rabl new file mode 100644 index 000000000..842126f58 --- /dev/null +++ b/web/app/views/api_users/show_teacher_index.rabl @@ -0,0 +1,8 @@ +object @user + + +attributes :id, :first_name, :last_name, :name, :photo_url + +child :teacher do |teacher| + attributes :id, :biography, :school_id +end diff --git a/web/spec/controllers/api_teachers_controller_spec.rb b/web/spec/controllers/api_teachers_controller_spec.rb index 5c0ae7348..37745af8b 100644 --- a/web/spec/controllers/api_teachers_controller_spec.rb +++ b/web/spec/controllers/api_teachers_controller_spec.rb @@ -23,6 +23,21 @@ describe ApiTeachersController do Teacher.destroy_all end + describe "index" do + it "simple" do + @teacher = Teacher.save_teacher( + user, + years_teaching: 21, + biography: BIO, + genres: [genre1, genre2], + instruments: [instrument1, instrument2], + languages: [language1, language2], + subjects: [subject1, subject2] + ) + + get :index + end + end describe "creates" do it "simple" do post :create, biography: BIO, format: 'json' diff --git a/web/spec/features/jamclass_screen_spec.rb b/web/spec/features/jamclass_screen_spec.rb index 18fa92794..51568cffb 100644 --- a/web/spec/features/jamclass_screen_spec.rb +++ b/web/spec/features/jamclass_screen_spec.rb @@ -171,4 +171,42 @@ describe "JamClassScreen", :js => true, :type => :feature, :capybara_feature => find('tr[data-lesson-session-id="' + lesson1.id + '"] td.displayStatusColumn', text: 'Canceled (Student)') find('tr[data-lesson-session-id="' + lesson2.id + '"] td.displayStatusColumn', text: 'Canceled (Student)') end + + describe "counter" do + it "test drive" do + lesson = testdrive_lesson(user, teacher_user, {accept: false}) + + fast_signin(teacher_user, "/client#/jamclass") + validate_status(lesson, 'Requested') + + jamclass_hover_option('reschedule', 'Reschedule Lesson') + + # no popup should show in this case, because it's not yet scheduled + + # we should be at lesson status page + find('h2', text: 'respond to lesson request') + + screenshot + + counter_day + + # switch to student + + switch_user(user, "/client#/jamclass") + validate_status(lesson, 'Requested') + jamclass_hover_option('status', 'View Status') + + find('h2', text: 'this lesson is coming up soon') + + screenshot + + approve_lesson(lesson) + + jamclass_hover_option('reschedule', 'Reschedule Lesson') + find('#banner h1', text: 'Lesson Change Requested') + find('#banner .close-btn').trigger(:click) + + counter_day + end + end end diff --git a/web/spec/support/lessons.rb b/web/spec/support/lessons.rb index ff4aec5a3..f262ffcb0 100644 --- a/web/spec/support/lessons.rb +++ b/web/spec/support/lessons.rb @@ -60,6 +60,33 @@ def fill_out_single_lesson end +def validate_status(lesson, expectedStatus) + find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: expectedStatus) +end + + +def jamclass_hover_option(lesson, option, text) + # open up hover + find('tr[data-lesson-session-id="' + lesson.id + '"] .lesson-session-actions-btn').trigger(:click) + find('li[data-lesson-option="' + option + '"] a', visible: false, text: text).trigger(:click) +end + +def counter_day + fill_in "alt-date-input", with: date_picker_format(Date.new(Date.today.year, Date.today.month + 1, 17)) + find('td a', text: '17').trigger(:click) + sleep 3 + find('.schedule.button-orange').trigger(:click) + find('#banner h1', text: 'Lesson Change Requested') + find('#banner .close-btn').trigger(:click) + find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Requested') +end + +def approve_lesson(lesson, slot = lesson.lesson_booking.default_slot) + find(".slot-decision-field[data-slot-id=\"#{slot.id}\"] ins", visible: false).trigger(:click) + find('.schedule.button-orange').trigger(:click) + find('tr[data-lesson-session-id="' + lesson.id + '"] td.displayStatusColumn', text: 'Scheduled') +end + def fill_out_payment(expected = nil) fill_in 'card-number', with: '4111111111111111'