diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js index 96870ab2d..4b5b54a1d 100644 --- a/web/app/assets/javascripts/application.js +++ b/web/app/assets/javascripts/application.js @@ -38,6 +38,7 @@ //= require jquery.exists //= require jquery.payment //= require jquery.visible +//= require jquery.jstarbox //= require classnames //= require reflux //= require howler.core.js 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 e78a954cc..6caebaca9 100644 --- a/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee @@ -58,6 +58,31 @@ proficiencyDescriptionMap = { onLanguagesChanged: () -> @setState({languages: true}) + componentDidMount: () -> + @root = $(@getDOMNode()) + @starbox() + + componentDidUpdate:() -> + @starbox() + + starbox:() -> + $ratings = @root.find('.ratings-box') + $ratings.each((i, value) => + $element = $(value) + rating = $element.attr('data-ratings') + rating = parseFloat(rating) + + #$element.starbox('destroy') + + $element.starbox({ + average: rating, + changeable: false, + autoUpdateAverage: false, + ghosting: false + }).show() + ) + + beforeShow: (e) -> @setState({userId: e.id, user: null}) rest.getUserDetail({ @@ -470,8 +495,37 @@ proficiencyDescriptionMap = { user = @state.user teacher = user.teacher + summary = teacher.review_summary || {avg_rating: 0, review_count: 0} + + if summary.review_count == 1 + reviewCount = '1 review' + else + reviewCount = sumarry.review_count + ' reviews' + reviews = [] + + for review in teacher.recent_reviews + photo_url = review.user.photo_url + if !photo_url? + photo_url = '/assets/shared/avatar_generic.png' + + name = `
{review.user.name}
` + reviews.push(`
+
+
+ +
+ {name} +
+
{context.JK.formatDateShort(review.created_at)}
+
+
+
`) `
- Coming Soon! +

Ratings & Reviews

+ +

{user.first_name} Summary Rating:
({reviewCount})

+ + {reviews}
` prices: () -> diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index c9157ae67..807b1640d 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -714,6 +714,11 @@ return context.JK.padString(date.getMonth() + 1, 2) + "/" + context.JK.padString(date.getDate(), 2) + "/" + date.getFullYear() + " - " + date.toLocaleTimeString(); } + context.JK.formatDateShort = function (dateString) { + var date = dateString instanceof Date ? dateString : new Date(dateString); + return months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear(); + } + // returns Fri May 20, 2013 context.JK.formatDate = function (dateString, suppressDay) { var date = new Date(dateString); diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css index 79735d3eb..f7a854e58 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -11,6 +11,7 @@ *= require_self *= require web/Raleway *= require jquery.ui.datepicker + *= require jquery.jstarbox *= require ./ie *= require jquery.bt *= require easydropdown 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 7e95012ad..9a14450be 100644 --- a/web/app/assets/stylesheets/client/react-components/TeacherProfile.css.scss +++ b/web/app/assets/stylesheets/client/react-components/TeacherProfile.css.scss @@ -162,4 +162,85 @@ } .years {float:right} } + .ratings-block { + + h3 { + margin-bottom:30px; + } + h4 { + margin-top:20px; + font-weight:bold; + margin-bottom:20px; + color:white; + } + .ratings-box { + display:inline-block; + } + .stars { + position: relative; + top: 3px; + left: 20px; + } + + .review-count { + font-weight:normal; + display: inline-block; + margin-left: 40px; + font-size:12px; + color:$ColorTextTypical; + } + + .review { + border-width:1px 0 0 0; + border-color:$ColorTextTypical; + border-style:solid; + padding:20px 3px; + + .review-header { + margin-bottom:20px; + } + .avatar { + display:inline-block; + padding:1px; + width:36px; + height:36px; + background-color:#ed3618; + margin:0 20px 0 0; + -webkit-border-radius:18px; + -moz-border-radius:18px; + border-radius:18px; + } + + .avatar-small { + float:left; + + } + + .avatar img { + width: 36px; + height: 36px; + -webkit-border-radius:18px; + -moz-border-radius:18px; + border-radius:18px; + } + .stars { + top:4px; + } + + .review-time { + float:right; + } + + .reviewer-name { + height:40px; + display:inline-block; + line-height:40px; + vertical-align: middle; + } + + .reviewer-content { + + } + } + } } diff --git a/web/app/views/api_teachers/detail.rabl b/web/app/views/api_teachers/detail.rabl index 1d6a3a617..9071bc3df 100644 --- a/web/app/views/api_teachers/detail.rabl +++ b/web/app/views/api_teachers/detail.rabl @@ -40,7 +40,7 @@ child :review_summary => :review_summary do end child :recent_reviews => :recent_reviews do - attributes :description, :rating + attributes :description, :rating, :created_at child(:user => :user) { attributes :id, :first_name, :last_name, :name, :photo_url diff --git a/web/lib/tasks/sample_data.rake b/web/lib/tasks/sample_data.rake index bb8e704e0..80339c749 100644 --- a/web/lib/tasks/sample_data.rake +++ b/web/lib/tasks/sample_data.rake @@ -64,6 +64,18 @@ namespace :db do make_recording end + task populate_reviews: :environment do + Teacher.all.each do |teacher| + @review = Review.new + @review.target_id = teacher.id + @review.user = User.last + @review.rating = 5 + @review.description = 'Omg This teacher was so good. It was like whoa. Crazy whoa.' + @review.target_type = 'JamRuby::Teacher' + @review.save + end + end + task populate_jam_track_genres: :environment do genres = Genre.all genres = genres.sample(genres.count * 0.75) diff --git a/web/vendor/assets/javascripts/jquery.jstarbox.js b/web/vendor/assets/javascripts/jquery.jstarbox.js new file mode 100644 index 000000000..b5186e859 --- /dev/null +++ b/web/vendor/assets/javascripts/jquery.jstarbox.js @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2011 Raphael Schweikert, http://sabberworm.com/ + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +(function() { + var dataKey = 'jstarbox-data'; + var eventNamespace = '.jstarbox'; + var defaultOptions = { + average: 0.5, + stars: 5, + buttons: 5, //false will allow any value between 0 and 1 to be set + ghosting: false, + changeable: true, // true, false, or "once" + autoUpdateAverage: false + }; + var methods = { + destroy: function() { + this.removeData(dataKey); + this.unbind(eventNamespace).find('*').unbind(eventNamespace); + this.removeClass('starbox'); + this.empty(); + }, + + getValue: function() { + var data = this.data(dataKey); + return data.opts.currentValue; + }, + + setValue: function(val) { + var data = this.data(dataKey); + var size = arguments[1] || data.positioner.width(); + var include_ghost = arguments[2]; + if(include_ghost) { + data.ghost.css({width: ""+(val*size)+"px"}); + } + data.colorbar.css({width: ""+(val*size)+"px"}); + data.opts.currentValue = val; + }, + + getOption: function(option) { + var data = this.data(dataKey); + return data.opts[option]; + }, + + setOption: function(option, value) { + var data = this.data(dataKey); + + if(option === 'changeable' && value === false) { + data.positioner.triggerHandler('mouseleave'); + } + + data.opts[option] = value; + + if(option === 'stars') { + data.methods.update_stars(); + } else if(option === 'average') { + this.starbox('setValue', value, null, true); + } + }, + + markAsRated: function() { + var data = this.data(dataKey); + data.positioner.addClass('rated'); + } + }; + jQuery.fn.extend({ + starbox: function(options) { + if(options.constructor === String && methods[options]) { + return methods[options].apply(this, Array.prototype.slice.call(arguments, 1)) || this; + } + options = jQuery.extend({}, defaultOptions, options); + this.each(function(count) { + var element = jQuery(this); + + var opts = jQuery.extend({}, options); + var data = { + opts: opts, + methods: {} + }; + element.data(dataKey, data); + + var positioner = data.positioner = jQuery('
').addClass('positioner'); + + var stars = data.stars = jQuery('
').addClass('stars').appendTo(positioner); + var ghost = data.ghost = jQuery('
').addClass('ghost').hide().appendTo(stars); + var colorbar = data.colorbar = jQuery('
').addClass('colorbar').appendTo(stars); + var star_holder = data.star_holder = jQuery('
').addClass('star_holder').appendTo(stars); + + element.empty().addClass('starbox').append(positioner); + data.methods.update_stars = function() { + star_holder.empty(); + for(var i=0;i').addClass('star').addClass('star-'+i).appendTo(star_holder); + } + // (Re-)Set initial value + methods.setOption.call(element, 'average', opts.average); + }; + data.methods.update_stars(); + + positioner.bind('mousemove'+eventNamespace, function(event) { + if(!opts.changeable) return; + if(opts.ghosting) { + ghost.show(); + } + var size = positioner.width(); + var x = event.layerX; + if(x === undefined) { + x = (event.pageX-positioner.offset().left); + } + var val = x/size; + if(opts.buttons) { + val *= opts.buttons; + val = Math.floor(val); + val += 1; + val /= opts.buttons; + } + positioner.addClass('hover'); + methods.setValue.call(element, val, size); + element.starbox('setValue', val, size); + element.triggerHandler('starbox-value-moved', val); + }); + + positioner.bind('mouseleave'+eventNamespace, function(event) { + if(!opts.changeable) return; + ghost.hide(); + positioner.removeClass('hover'); + methods.setValue.call(element, opts.average); + }); + + positioner.bind('click'+eventNamespace, function(event) { + if(!opts.changeable) return; + + if(opts.autoUpdateAverage) { + methods.markAsRated.call(element); + methods.setOption.call(element, 'average', opts.currentValue); + } + + var new_average = element.triggerHandler('starbox-value-changed', opts.currentValue); + if(!isNaN(parseFloat(new_average)) && isFinite(new_average)) { + methods.setOption.call(element, 'average', new_average); + } + + if(opts.changeable === 'once') { + methods.setOption.call(element, 'changeable', false); + } + }); + + }); + return this; + } + }); +})(); \ No newline at end of file diff --git a/web/vendor/assets/stylesheets/jquery.jstarbox.css b/web/vendor/assets/stylesheets/jquery.jstarbox.css new file mode 100644 index 000000000..20229bd5f --- /dev/null +++ b/web/vendor/assets/stylesheets/jquery.jstarbox.css @@ -0,0 +1,48 @@ +.positioner { + position: relative; + display: inline-block; + line-height: 0; +} + +.starbox .colorbar, +.starbox .ghost { + z-index: 0; + position: absolute; + top: 0; + bottom: 0; + left: 0; +} + +.starbox .stars { + display: inline-block; +} + +.starbox .stars .star_holder { + position: relative; + z-index: 1; +} + +.starbox .stars .star_holder .star { + display: inline-block; + vertical-align: baseline; + background-repeat: no-repeat; +} + + +/* Override with your own image and size… */ +.starbox .stars .star_holder .star { + background-image: url('/assets/jstarbox-5-large.png'); + background-size:cover; + width: 20px; + height: 20px; +} + +/* Override with your own colours… */ +.starbox .stars { background: #ccc; } +.starbox .rated .stars { background: #dcdcdc; } +.starbox .rated.hover .stars { background: transparent; } +.starbox .colorbar { background: #ed3618; } +.starbox .hover .colorbar { background: #ffcc1c; } +.starbox .rated .colorbar { background: #64b2ff; } +.starbox .rated.hover .colorbar { background: #1e90ff; } +.starbox .ghost { background: #a1a1a1; } \ No newline at end of file