VRFS-4041 - side bubble for teacher help reach out

This commit is contained in:
Seth Call 2016-05-17 20:29:56 -05:00
parent 22bd49b16b
commit 0c0e22df8c
23 changed files with 272 additions and 28 deletions

View File

@ -30,7 +30,7 @@ module JamRuby
def social(options)
mail(to: APP_CONFIG.email_social_alias,
from: APP_CONFIG.email_generic_from,
from: options[:from] || APP_CONFIG.email_generic_from,
body: options[:body],
content_type: "text/plain",
subject: options[:subject])

View File

@ -58,7 +58,7 @@ module JamRuby
# only show teachers with ready for session set to true
query = query.where('teachers.ready_for_session_at IS NOT NULL')
if params[:onlyMySchool] && params[:onlyMySchool] != 'false' && user.school_id
if user && params[:onlyMySchool] && params[:onlyMySchool] != 'false' && user.school_id
query = query.where("teachers.school_id = ?", user.school_id)
end

View File

@ -2113,19 +2113,9 @@ module JamRuby
end
def has_rated_teacher(teacher)
teacher_rating(teacher)
end
def has_rated_student(student)
Review.where(target_id: student.id).where(target_type: "JamRuby::User").count > 0
teacher_rating(teacher).count > 0
end
def teacher_rating(teacher)
if teacher.is_a?(JamRuby::User)
teacher = teacher.teacher
end
Review.where(target_id: teacher.id).where(target_type: teacher.class.to_s)
end
def teacher_rating(teacher)
if teacher.is_a?(JamRuby::User)
teacher = teacher.teacher
@ -2134,13 +2124,21 @@ module JamRuby
end
def has_rated_student(student)
Review.where(target_id: student.id).where(target_type: "JamRuby::User").count > 0
student_rating(student).count > 0
end
def student_rating(student)
Review.where(target_id: student.id).where(target_type: "JamRuby::User")
end
def teacher_profile_url
"#{APP_CONFIG.external_root_url}/client#/profile/teacher/#{id}"
end
def profile_url
"#{APP_CONFIG.external_root_url}/client#/profile/#{id}"
end
def ratings_url
"#{APP_CONFIG.external_root_url}/client?tile=ratings#/profile/teacher/#{id}"
end

View File

@ -261,7 +261,11 @@
return;
}
var activate = ["jamtrack/search", "jamtrack/filter", "shoppingCart", "checkoutPayment", "checkoutOrder", "redeemComplete", "checkoutComplete", "teachers/setup/introduction", "teachers/setup/basics", "teachers/setup/experience", "teachers/setup/pricing", "account/profile", "account/profile/experience", "account/profile/interests", "account/profile/samples"]
var activate = ["jamtrack/search", "jamtrack/filter",
"shoppingCart", "checkoutPayment", "checkoutOrder", "redeemComplete", "checkoutComplete",
"teachers/setup/introduction", "teachers/setup/basics", "teachers/setup/experience", "teachers/setup/pricing",
"account/profile", "account/profile/experience", "account/profile/interests", "account/profile/samples",
"jamclass"]
$(document).on(context.JK.EVENTS.SCREEN_CHANGED, function(e, data) {
var show = false;

View File

@ -186,7 +186,7 @@
helpBubble.showBuyNormalLesson = function($element, $offsetParent, user, callback) {
return context.JK.onceBubble($element, 'side-buy-normal-lesson', user, {offsetParent:$offsetParent, width:260, positions:['right'], postShow: function(container) {
var $bookNow = $('a.book-now')
var $bookNow = container.find('a.book-now')
$bookNow.off('click').on('click', function(e) {
e.preventDefault()
callback()
@ -194,4 +194,17 @@
})
}})
}
helpBubble.didntFindTeacher = function($element, $offsetParent, user, callback) {
return context.JK.onceBubble($element, 'side-didnt-find-teacher', user, {offsetParent:$offsetParent, width:260, positions:['right'], postShow: function(container) {
var $bookNow = container.find('a.post-help')
console.log("container", $bookNow)
$bookNow.off('click').on('click', function(e) {
e.preventDefault()
callback(container.find('.note').val(), container.find('.email').val(), container.find('.phonenumber').val())
return false;
})
console.log("hehelllo!")
}})
}
})(window, jQuery);

View File

@ -2491,6 +2491,17 @@
});
}
function askSearchHelp(options) {
return $.ajax({
type: "POST",
url: '/api/teachers/search_help',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(options),
})
}
function initialize() {
return self;
}
@ -2714,7 +2725,7 @@
this.listTeacherDistributions = listTeacherDistributions;
this.lessonStartTime = lessonStartTime;
this.createReview = createReview;
this.askSearchHelp = askSearchHelp;
return this;
};
})(window,jQuery);

View File

@ -12,7 +12,9 @@ BroadcastHolder = React.createClass(
notification = []
if this.state.notification
if this.state.notification.isLesson
if this.state.notification.isJamClass
result = `<JamClassPhone key="jamclassphone" />`
else if this.state.notification.isLesson
result = `<InLessonBroadcast key={'lesson'} lessonSession={this.state.notification}/>`
else
result = `<Broadcast key={this.state.notification.id} notification={this.state.notification}/>`

View File

@ -0,0 +1,24 @@
context = window
@JamClassPhone = React.createClass(
{
displayName: 'JamClassPhone',
render: ->
`<div className="jamclass-phone">
<img src="/assets/content/phone-icon.png" width="32" height="32"/>
<div className="callwrapper">
<div className="call-num">
Call&nbsp;
<span className="phonenumber">
877-37-MUSIC
</span>
</div>
<div className="call-prompt">
Have questions? Call now!
</div>
</div>
</div>`
}
)

View File

@ -22,6 +22,7 @@ ProfileActions = @ProfileActions
visible: false
needToSearch: true
root: null
screen: null
endOfList: null
contentBodyScroller: null
refreshing: false
@ -36,7 +37,6 @@ ProfileActions = @ProfileActions
@visible = true
afterShow: (e) ->
@visible = false
#@setState(TeacherSearchStore.getState())
#if @state.results.length == 0
# don't issue a new search every time someone comes to the screen, to preserve location from previous browsing
@ -48,7 +48,9 @@ ProfileActions = @ProfileActions
@needToSearch = false
afterHide: (e) ->
@visible = false
@contentBodyScroller.off('scroll')
@hideSideBubble()
onTeacherSearchStore: (storeChanged) ->
@needToSearch = true
@ -68,10 +70,12 @@ ProfileActions = @ProfileActions
componentDidMount: () ->
@root = $(@getDOMNode())
@screen = $('#teacherSearch')
@resultsNode = @root.find('.results')
@endOfList = @root.find('.end-of-teacher-list')
@contentBodyScroller = @root
registerInfiniteScroll:() ->
$scroller = @contentBodyScroller
logger.debug("registering infinite scroll")
@ -87,6 +91,33 @@ ProfileActions = @ProfileActions
logger.debug("refreshing more teachers for infinite scroll")
TeacherSearchResultsActions.nextPage()
)
showSideBubble: () ->
setTimeout(
(() => (
if @visible
context.JK.HelpBubbleHelper.didntFindTeacher(@screen, null, @state.user || {}, ((note, email, phone) => @postHelp(note, email, phone)))
)), 3000)
hideSideBubble: () ->
if @screen.btOff
@screen.btOff()
postHelp: (value, email, phone) ->
if !value? || value == ''
@app.layout.notify({title: 'note required', text: 'Please enter something about what you are looking for.'})
return
if !context.JK.currentUserId? && (!email? || email == '')
@app.layout.notify({title: 'email required', text: 'Please enter your email.'})
return
rest.askSearchHelp({note: value, email: email, phone: phone}).done((response) => @postHelpDone()).fail(@app.ajaxError)
postHelpDone: () ->
console.log("show notice")
context.JK.Banner.showNotice("request received", "We got your note. We will reach back shortly!")
@hideSideBubble()
componentDidUpdate: () ->
@resultsNode.find('.teacher-bio').each((index, element) => (
@ -99,6 +130,9 @@ ProfileActions = @ProfileActions
if @visible
if @state.currentPage > 1
@showSideBubble()
if @state.next == null
@contentBodyScroller.off('scroll')
if @state.currentPage == 1 and @state.results.length == 0

View File

@ -51,6 +51,7 @@ rest = context.JK.Rest()
<h2 className="original-artist">Do you own or operate a music store or school?</h2>
<div className="clearall"/>
</div>
<JamClassPhone/>
<div className="preview-and-action-box jamclass">
<img src="/assets/landing/arrow-1-student.png" className="arrow1-jamclass" />
<div className="preview-jamtrack-header">

View File

@ -51,6 +51,7 @@ rest = context.JK.Rest()
<h2 className="original-artist">Do you own/operate a music school?</h2>
<div className="clearall"/>
</div>
<JamClassPhone/>
<div className="preview-and-action-box jamclass">
<img src="/assets/landing/arrow-1-student.png" className="arrow1-jamclass" />
<div className="preview-jamtrack-header">

View File

@ -51,6 +51,7 @@ rest = context.JK.Rest()
<h2 className="original-artist">Finally, online music lessons<br/>that really work!</h2>
<div className="clearall"/>
</div>
<JamClassPhone/>
<div className="preview-and-action-box jamclass">
<img src="/assets/landing/arrow-1-student.png" className="arrow1-jamclass" />
<div className="preview-jamtrack-header">

View File

@ -51,6 +51,7 @@ rest = context.JK.Rest()
<h2 className="original-artist">Finally, online music lessons<br/>that really work!</h2>
<div className="clearall"/>
</div>
<JamClassPhone/>
<div className="preview-and-action-box jamclass">
<img src="/assets/landing/arrow-1-student.png" className="arrow1-jamclass" />
<div className="preview-jamtrack-header">

View File

@ -16,9 +16,11 @@ BroadcastStore = Reflux.createStore(
broadcast: null
currentLessonTimer: null
teacherFault: false
isJamClass: false
init: ->
this.listenTo(context.AppStore, this.onAppInit);
this.listenTo(context.AppStore, this.onAppInit)
this.listenTo(context.SessionStore, this.onSessionChange)
this.listenTo(context.NavStore, this.onNavChange)
onAppInit: (@app) ->
@ -104,6 +106,12 @@ BroadcastStore = Reflux.createStore(
if @currentLessonTimer?
clearInterval(@currentLessonTimer)
@currentLessonTimer = null
onNavChange: (nav) ->
path = nav.screenPath.toLowerCase()
@isJamClass = path.indexOf('jamclass') > -1 || path.indexOf('teacher') > -1
@changed()
onSessionChange: (session) ->
@session = session
@ -158,6 +166,8 @@ BroadcastStore = Reflux.createStore(
this.trigger(null)
else
this.trigger(@currentLesson)
else if @isJamClass
this.trigger({isJamClass: true})
else
this.trigger(@broadcast)
}

View File

@ -41,6 +41,6 @@ rest = new context.JK.Rest()
@changed()
changed:() ->
@trigger({currentScreen: @currentScreen, currentSection: @currentSection, currentScreenName: @currentScreenName})
@trigger({screenPath: @screenPath, currentScreen: @currentScreen, currentSection: @currentSection, currentScreenName: @currentScreenName})
}
)

View File

@ -148,7 +148,7 @@
context.JK.popExternalLinks($(container))
if (originalPostShow) {
originalPostShow(container);
originalPostShow($(container));
}
}

View File

@ -1,7 +1,11 @@
@import "client/common";
body.jam, body.web, .dialog{
//body.jam, body.web, .dialog{
html {
.bt-wrapper {
font-size: 14px;
font-family: 'Raleway', Arial, Helvetica, sans-serif;
font-weight: 300;
.bt-content {
color:#cccccc;
@ -91,7 +95,7 @@ body.jam, body.web, .dialog{
}
.side-remaining-test-drives, .side-buy-test-drive, .side-buy-normal-lesson {
.side-remaining-test-drives, .side-buy-test-drive, .side-buy-normal-lesson, .side-didnt-find-teacher {
h2 {
font-size:20px;
color:white;
@ -106,6 +110,40 @@ body.jam, body.web, .dialog{
display:block;
margin:30px auto;
}
.post-help {
width:100px;
display:block;
margin:30px auto;
}
.textarea-wrapper {
padding: 0 1em;
width:100%;
@include border_box_sizing;
}
textarea {
height:70px;
@include border_box_sizing;
width:100%;
font-size:14px;
}
.field {
margin: 0 1em;
label {
width:50px;
display:inline-block;
font-size:14px;
}
input {
font-size:14px;
display:inline-block;
margin-bottom:20px;
width:178px;
}
}
}

View File

@ -750,4 +750,30 @@ button.stripe-connect {
.site-nav {
margin-bottom:10px;
}
.jamclass-phone {
background-color:#262626;
float:right;
height:50px;
font-size:16px;
img {
display:inline-block;
margin-right:7px;
vertical-align:baseline;
}
.callwrapper {
display:inline-block;
}
.call-num {
white-space:nowrap;
margin-bottom:4px;
text-align:right;
}
.call-prompt {
white-space:nowrap;
}
.phonenumber {
font-size:20px;
}
}

View File

@ -57,6 +57,12 @@ body.web.individual_jamtrack {
}
}
.jamclass-phone {
position: relative;
top: -150px;
right: 107px;
background-color:black;
}
.name-and-artist {
padding-top: 60px;

View File

@ -1,6 +1,6 @@
class ApiTeachersController < ApiController
before_filter :api_signed_in_user, :except => [:index, :detail, :search]
before_filter :api_signed_in_user, :except => [:index, :detail, :search, :search_help]
before_filter :auth_teacher, :only => [:update, :delete]
before_filter :auth_user, :only => [:create, :update]
@ -46,7 +46,54 @@ class ApiTeachersController < ApiController
respond_with_model(@intent)
end
private
def search_help
email = params[:email]
if current_user && email.blank?
email = current_user.email
end
if current_user
subject = "#{current_user.name} wants help searching for a teacher"
body = "#{current_user.name} (#{email}) needs help locating a teacher.\n\n"
if params[:phone].present?
body << "Phone Number: #{params[:phone]}\n\n"
else
body << "Phone Number: None Entered\n\n"
end
if params[:note].present?
body << "Here's what they wrote: \n\n\n"
body << params[:note]
else
body << "...They didn't write anything..."
end
body << "\n\nAdmin: #{current_user.admin_url}"
body << "\nProfile: #{current_user.profile_url}"
else
subject = "#{email} wants help searching for a teacher"
body = "#{email} needs help locating a teacher.\n\n"
if params[:phone].present?
body << "Phone Number: #{params[:phone]}\n\n"
else
body << "Phone Number: None Entered\n\n"
end
if params[:note].present?
body << "Here's what they wrote: \n\n\n"
body << params[:note]
else
body << "...They didn't write anything..."
end
end
AdminMailer.social({from: email, body: body, subject: subject}).deliver
render json: { success: true }, :status => 200
end
private
def auth_teacher
@teacher = Teacher.find(params[:id])
@ -71,6 +118,6 @@ private
else
@user=current_user
end
end
end

View File

@ -423,4 +423,21 @@ script type="text/template" id="template-help-side-buy-normal-lesson"
a.book-now.button-orange BOOK NOW!
p Or call us at
p 877-376-8742 (877-37-MUSIC)
p And we can answer any questions and help set you up over the phone.
p And we can answer any questions and help set you up over the phone.
script type="text/template" id="template-help-side-didnt-find-teacher"
.side-didnt-find-teacher
h2 Let us help!
p
| Didn't find the teacher you want? Tell us what you're looking for,
| and our concierge team will find one or more teachers who meet your requirements in about a week. We are 100% committed to helping you connect to your ideal teacher!
.field
label Email
input.email type="text" placeholder="Email address" value="{{data.email}}"
.field
label Phone
input.phonenumber type="text" placeholder="Callback number"
.textarea-wrapper
textarea.note placeholder="Write a note here explaining what you need in your teacher..."
a.post-help.button-orange SEND NOTE

View File

@ -0,0 +1,9 @@
.jamclass-phone
= image_tag("content/phone_icon.png", {width:32, height:32})
.call-num
| Call
span.phonenumber 877-37-MUSIC
.call-prompt
| Have questions? Call now!

View File

@ -497,6 +497,7 @@ SampleApp::Application.routes.draw do
# teachers
match '/teachers' => 'api_teachers#index', :via => :get
match '/teachers/detail' => 'api_teachers#detail', :via => :get, :as => 'api_teacher_detail'
match '/teachers/search_help' => 'api_teachers#search_help', :via => :post
match '/teachers' => 'api_teachers#create', :via => :post
match '/teachers/:id' => 'api_teachers#update', :via => :post
match '/teachers/:id' => 'api_teachers#delete', :via => :delete