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