diff --git a/admin/app/admin/monthly_stats.rb b/admin/app/admin/monthly_stats.rb index 1789197aa..0cd599456 100644 --- a/admin/app/admin/monthly_stats.rb +++ b/admin/app/admin/monthly_stats.rb @@ -1,40 +1,35 @@ ActiveAdmin.register_page "Monthly Stats" do menu :parent => 'Reports' - distinct_users = MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', msuh.created_at)::date as month, count(distinct(user_id)) from music_sessions_user_history msuh group by month order by month desc;") - total_session = MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', ms.created_at)::date as month, count(id) from music_sessions ms where started_at is not null group by month order by month desc;") - total_jamtrack_sessions = MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', jts.created_at)::date as month, count(distinct(music_session_id)) from jam_track_sessions jts where session_type = 'session' group by month order by month desc;") - total_webplayer_sessions = MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', jts.created_at)::date as month, count(id) from jam_track_sessions jts where session_type = 'browser' group by month order by month desc;") - distinct_jamtrack_users = MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', jts.created_at)::date as month, count(distinct(user_id)) from jam_track_sessions jts group by month order by month desc;") - content :title => "Month Stats" do + content :title => "Monthly Stats" do h2 "Distinct Users Playing in Sessions" - table_for distinct_users do + table_for MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', msuh.created_at)::date as month, count(distinct(user_id)) from music_sessions_user_history msuh group by month order by month desc;") do column "Month", Proc.new { |row| Date.parse(row.month).strftime('%B %Y') } column "Users", :count - end + endF h2 "Music Sessions" - table_for total_session do + table_for MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', ms.created_at)::date as month, count(id) from music_sessions ms where started_at is not null group by month order by month desc;") do column "Month", Proc.new { |row| Date.parse(row.month).strftime('%B %Y') } column "Sessions", :count end h2 "Distinct Users Who Played with a JamTrack" - table_for distinct_jamtrack_users do + table_for MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', jts.created_at)::date as month, count(distinct(user_id)) from jam_track_sessions jts group by month order by month desc;") do column "Month", Proc.new { |row| Date.parse(row.month).strftime('%B %Y') } column "Users", :count end h2 "Music Sessions with JamTracks Played" - table_for total_jamtrack_sessions do + table_for MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', jts.created_at)::date as month, count(distinct(music_session_id)) from jam_track_sessions jts where session_type = 'session' group by month order by month desc;") do column "Month", Proc.new { |row| Date.parse(row.month).strftime('%B %Y') } column "Sessions", :count end h2 "JamTrack Web Player Sessions" - table_for total_webplayer_sessions do + table_for MusicSession.select([:month, :count]).find_by_sql("select date_trunc('month', jts.created_at)::date as month, count(id) from jam_track_sessions jts where session_type = 'browser' group by month order by month desc;") do column "Month", Proc.new { |row| Date.parse(row.month).strftime('%B %Y') } column "Sessions", :count end diff --git a/ruby/lib/jam_ruby/models/teacher.rb b/ruby/lib/jam_ruby/models/teacher.rb index 8306c60ad..690c6cce2 100644 --- a/ruby/lib/jam_ruby/models/teacher.rb +++ b/ruby/lib/jam_ruby/models/teacher.rb @@ -33,110 +33,201 @@ module JamRuby default_scope { includes(:genres) } - class << self - def save_teacher(user, params) - teacher = build_teacher(user, params) - teacher.save - teacher + + def self.index(user, params = {}) + limit = params[:per_page] + limit ||= 20 + limit = limit.to_i + + query = User.joins(:teacher) + + instruments = params[:instruments] + if instruments && !instruments.blank? && instruments.length > 0 + query = query.joins("inner JOIN teachers_instruments AS tinst ON tinst.teacher_id = teachers.id") + .where("tinst.instrument_id IN (?)", instruments) end - def build_teacher(user, params) - # ensure person creating this Teacher is a Musician - unless user && user.musician? - raise JamPermissionError, "must be a musician" - end - - teacher = user.teacher - teacher ||= user.build_teacher() - teacher.user = user - - teacher.website = params[:website] if params.key?(:website) - teacher.biography = params[:biography] if params.key?(:biography) - teacher.introductory_video = params[:introductory_video] if params.key?(:introductory_video) - - teacher.introductory_video = params[:introductory_video] if params.key?(:introductory_video) - teacher.years_teaching = params[:years_teaching] if params.key?(:years_teaching) - teacher.years_playing = params[:years_playing] if params.key?(:years_playing) - teacher.teaches_age_lower = params[:teaches_age_lower] if params.key?(:teaches_age_lower) - teacher.teaches_age_upper = params[:teaches_age_upper] if params.key?(:teaches_age_upper) - teacher.website = params[:website] if params.key?(:website) - teacher.biography = params[:biography] if params.key?(:biography) - teacher.teaches_beginner = params[:teaches_beginner] if params.key?(:teaches_beginner) - teacher.teaches_intermediate = params[:teaches_intermediate] if params.key?(:teaches_intermediate) - teacher.teaches_advanced = params[:teaches_advanced] if params.key?(:teaches_advanced) - teacher.prices_per_lesson = params[:prices_per_lesson] if params.key?(:prices_per_lesson) - teacher.prices_per_month = params[:prices_per_month] if params.key?(:prices_per_month) - teacher.lesson_duration_30 = params[:lesson_duration_30] if params.key?(:lesson_duration_30) - teacher.lesson_duration_45 = params[:lesson_duration_45] if params.key?(:lesson_duration_45) - teacher.lesson_duration_60 = params[:lesson_duration_60] if params.key?(:lesson_duration_60) - teacher.lesson_duration_90 = params[:lesson_duration_90] if params.key?(:lesson_duration_90) - teacher.lesson_duration_120 = params[:lesson_duration_120] if params.key?(:lesson_duration_120) - teacher.price_per_lesson_30_cents = params[:price_per_lesson_30_cents] if params.key?(:price_per_lesson_30_cents) - teacher.price_per_lesson_45_cents = params[:price_per_lesson_45_cents] if params.key?(:price_per_lesson_45_cents) - teacher.price_per_lesson_60_cents = params[:price_per_lesson_60_cents] if params.key?(:price_per_lesson_60_cents) - teacher.price_per_lesson_90_cents = params[:price_per_lesson_90_cents] if params.key?(:price_per_lesson_90_cents) - teacher.price_per_lesson_120_cents = params[:price_per_lesson_120_cents] if params.key?(:price_per_lesson_120_cents) - teacher.price_per_month_30_cents = params[:price_per_month_30_cents] if params.key?(:price_per_month_30_cents) - teacher.price_per_month_45_cents = params[:price_per_month_45_cents] if params.key?(:price_per_month_45_cents) - teacher.price_per_month_60_cents = params[:price_per_month_60_cents] if params.key?(:price_per_month_60_cents) - teacher.price_per_month_90_cents = params[:price_per_month_90_cents] if params.key?(:price_per_month_90_cents) - teacher.price_per_month_120_cents = params[:price_per_month_120_cents] if params.key?(:price_per_month_120_cents) - - # Many-to-many relations: - if params.key?(:genres) - genres = params[:genres] - genres = [] if genres.nil? - teacher.genres = genres.collect{|genre_id| Genre.find(genre_id)} - end - if params.key?(:instruments) - instruments = params[:instruments] - instruments = [] if instruments.nil? - teacher.instruments = instruments.collect{|instrument_id| Instrument.find(instrument_id)} - end - if params.key?(:subjects) - subjects = params[:subjects] - subjects = [] if subjects.nil? - teacher.subjects = subjects.collect{|subject_id| Subject.find(subject_id)} - end - if params.key?(:languages) - languages = params[:languages] - languages = [] if languages.nil? - teacher.languages = languages.collect{|language_id| Language.find(language_id)} - end - - # Experience: - [:teaching, :education, :award].each do |experience_type| - key = "experiences_#{experience_type}".to_sym - if params.key?(key) - list = params[key] - list = [] if list.nil? - experiences = list.collect do |exp| - TeacherExperience.new( - name: exp[:name], - experience_type: experience_type, - organization: exp[:organization], - start_year: exp[:start_year], - end_year: exp[:end_year] - ) - end # collect - - # we blindly destroy/recreate on every resubmit - previous = teacher.send("#{key.to_s}") - previous.destroy_all - - # Dynamically call the appropriate method (just setting the - # value doesn't result in the behavior we need) - teacher.send("#{key.to_s}=", experiences) - end # if - end # do - - # How to validate: - teacher.validate_introduction = !!params[:validate_introduction] - teacher.validate_basics = !!params[:validate_basics] - teacher.validate_pricing = !!params[:validate_pricing] - - teacher + subjects = params[:subjects] + if subjects && !subjects.blank? && subjects.length > 0 + query = query.joins("inner JOIN teachers_subjects AS tsubjs ON tsubjs.teacher_id = teachers.id") + .where('tsubjs.subject_id IN (?)', subjects) end + + genres = params[:genres] + if genres && !genres.blank? && genres.length > 0 + query = query.joins("inner JOIN teachers_genres AS tgenres ON tgenres.teacher_id = teachers.id") + .where('tgenres.genre_id IN (?)', genres) + end + + country = params[:country] + if country && country.length > 0 + query = query.where(country: country) + end + + region = params[:region] + if region && region.length > 0 + query = query.where(state: region) + end + + languages = params[:languages] + if languages && !languages.blank? && languages.length > 0 + query= query.joins("inner JOIN teachers_languages AS tlang ON tlang.teacher_id = teachers.id") + .where('tlang.language_id IN (?)', languages) + end + + years_teaching = params[:years_teaching].to_i + if years_teaching && years_teaching > 0 + query = query.where('years_teaching >= ?', years_teaching) + end + + teaches_beginner = params[:teaches_beginner] + teaches_intermediate = params[:teaches_intermediate] + teaches_advanced = params[:teaches_advanced] + + if teaches_beginner || teaches_intermediate || teaches_advanced + + clause = '' + + if teaches_beginner + clause << 'teaches_beginner = true' + end + if teaches_intermediate + if clause.length > 0 + clause << ' OR ' + end + clause << 'teaches_intermediate = true' + end + if teaches_advanced + if clause.length > 0 + clause << ' OR ' + end + clause << 'teaches_advanced = true' + end + query = query.where(clause) + end + + student_age = params[:student_age].to_i + if student_age && student_age > 0 + query = query.where("teaches_age_lower <= ? AND (CASE WHEN teaches_age_upper = 0 THEN true ELSE teaches_age_upper >= ? END)", student_age, student_age) + end + + current_page = params[:page].nil? ? 1 : params[:page].to_i + next_page = current_page + 1 + + # will_paginate gem + query = query.paginate(:page => current_page, :per_page => limit) + + if query.length == 0 # no more results + { query: query, next_page: nil} + elsif query.length < limit # no more results + { query: query, next_page: nil} + else + { query: query, next_page: next_page } + end + end + + def self.save_teacher(user, params) + teacher = build_teacher(user, params) + teacher.save + teacher + end + + def self.build_teacher(user, params) + # ensure person creating this Teacher is a Musician + unless user && user.musician? + raise JamPermissionError, "must be a musician" + end + + teacher = user.teacher + teacher ||= user.build_teacher() + teacher.user = user + + teacher.website = params[:website] if params.key?(:website) + teacher.biography = params[:biography] if params.key?(:biography) + teacher.introductory_video = params[:introductory_video] if params.key?(:introductory_video) + + teacher.introductory_video = params[:introductory_video] if params.key?(:introductory_video) + teacher.years_teaching = params[:years_teaching] if params.key?(:years_teaching) + teacher.years_playing = params[:years_playing] if params.key?(:years_playing) + teacher.teaches_age_lower = params[:teaches_age_lower] if params.key?(:teaches_age_lower) + teacher.teaches_age_upper = params[:teaches_age_upper] if params.key?(:teaches_age_upper) + teacher.website = params[:website] if params.key?(:website) + teacher.biography = params[:biography] if params.key?(:biography) + teacher.teaches_beginner = params[:teaches_beginner] if params.key?(:teaches_beginner) + teacher.teaches_intermediate = params[:teaches_intermediate] if params.key?(:teaches_intermediate) + teacher.teaches_advanced = params[:teaches_advanced] if params.key?(:teaches_advanced) + teacher.prices_per_lesson = params[:prices_per_lesson] if params.key?(:prices_per_lesson) + teacher.prices_per_month = params[:prices_per_month] if params.key?(:prices_per_month) + teacher.lesson_duration_30 = params[:lesson_duration_30] if params.key?(:lesson_duration_30) + teacher.lesson_duration_45 = params[:lesson_duration_45] if params.key?(:lesson_duration_45) + teacher.lesson_duration_60 = params[:lesson_duration_60] if params.key?(:lesson_duration_60) + teacher.lesson_duration_90 = params[:lesson_duration_90] if params.key?(:lesson_duration_90) + teacher.lesson_duration_120 = params[:lesson_duration_120] if params.key?(:lesson_duration_120) + teacher.price_per_lesson_30_cents = params[:price_per_lesson_30_cents] if params.key?(:price_per_lesson_30_cents) + teacher.price_per_lesson_45_cents = params[:price_per_lesson_45_cents] if params.key?(:price_per_lesson_45_cents) + teacher.price_per_lesson_60_cents = params[:price_per_lesson_60_cents] if params.key?(:price_per_lesson_60_cents) + teacher.price_per_lesson_90_cents = params[:price_per_lesson_90_cents] if params.key?(:price_per_lesson_90_cents) + teacher.price_per_lesson_120_cents = params[:price_per_lesson_120_cents] if params.key?(:price_per_lesson_120_cents) + teacher.price_per_month_30_cents = params[:price_per_month_30_cents] if params.key?(:price_per_month_30_cents) + teacher.price_per_month_45_cents = params[:price_per_month_45_cents] if params.key?(:price_per_month_45_cents) + teacher.price_per_month_60_cents = params[:price_per_month_60_cents] if params.key?(:price_per_month_60_cents) + teacher.price_per_month_90_cents = params[:price_per_month_90_cents] if params.key?(:price_per_month_90_cents) + teacher.price_per_month_120_cents = params[:price_per_month_120_cents] if params.key?(:price_per_month_120_cents) + + # Many-to-many relations: + if params.key?(:genres) + genres = params[:genres] + genres = [] if genres.nil? + teacher.genres = genres.collect{|genre_id| Genre.find(genre_id)} + end + if params.key?(:instruments) + instruments = params[:instruments] + instruments = [] if instruments.nil? + teacher.instruments = instruments.collect{|instrument_id| Instrument.find(instrument_id)} + end + if params.key?(:subjects) + subjects = params[:subjects] + subjects = [] if subjects.nil? + teacher.subjects = subjects.collect{|subject_id| Subject.find(subject_id)} + end + if params.key?(:languages) + languages = params[:languages] + languages = [] if languages.nil? + teacher.languages = languages.collect{|language_id| Language.find(language_id)} + end + + # Experience: + [:teaching, :education, :award].each do |experience_type| + key = "experiences_#{experience_type}".to_sym + if params.key?(key) + list = params[key] + list = [] if list.nil? + experiences = list.collect do |exp| + TeacherExperience.new( + name: exp[:name], + experience_type: experience_type, + organization: exp[:organization], + start_year: exp[:start_year], + end_year: exp[:end_year] + ) + end # collect + + # we blindly destroy/recreate on every resubmit + previous = teacher.send("#{key.to_s}") + previous.destroy_all + + # Dynamically call the appropriate method (just setting the + # value doesn't result in the behavior we need) + teacher.send("#{key.to_s}=", experiences) + end # if + end # do + + # How to validate: + teacher.validate_introduction = !!params[:validate_introduction] + teacher.validate_basics = !!params[:validate_basics] + teacher.validate_pricing = !!params[:validate_pricing] + + teacher end def offer_pricing diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 27435b204..dd4781571 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -94,6 +94,10 @@ FactoryGirl.define do end end + factory :teacher, :class => JamRuby::Teacher do + association :user, factory: :user + end + factory :musician_instrument, :class => JamRuby::MusicianInstrument do instrument { JamRuby::Instrument.find('electric guitar') } proficiency_level 1 diff --git a/ruby/spec/jam_ruby/models/teacher_spec.rb b/ruby/spec/jam_ruby/models/teacher_spec.rb index 061f7fe3a..7ec2cf2e5 100644 --- a/ruby/spec/jam_ruby/models/teacher_spec.rb +++ b/ruby/spec/jam_ruby/models/teacher_spec.rb @@ -12,12 +12,177 @@ describe Teacher do let(:instrument1) { FactoryGirl.create(:instrument, :description => 'a great instrument')} let(:instrument2) { FactoryGirl.create(:instrument, :description => 'an ok instrument')} + describe "index" do + it "no params" do + teacher = FactoryGirl.create(:teacher) + teachers = Teacher.index(nil)[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + end + + it "instruments" do + teacher = FactoryGirl.create(:teacher) + 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) + + teacher.instruments << Instrument.find('electric guitar') + teacher.save! + teachers = Teacher.index(nil, {instruments: ['acoustic guitar']})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + end + + it "subjects" do + teacher = FactoryGirl.create(:teacher) + teachers = Teacher.index(nil, {subjects: ['music-theory']})[:query] + teachers.length.should eq 0 + + teacher.subjects << Subject.find('music-theory') + teacher.save! + teachers = Teacher.index(nil, {subjects: ['music-theory']})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + end + + it "genres" do + teacher = FactoryGirl.create(:teacher) + teachers = Teacher.index(nil, {genres: ['ambient']})[:query] + teachers.length.should eq 0 + + teacher.genres << Genre.find('ambient') + teacher.save! + teachers = Teacher.index(nil, {genres: ['ambient']})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + end + + + it "languages" do + teacher = FactoryGirl.create(:teacher) + teachers = Teacher.index(nil, {languages: ['EN']})[:query] + teachers.length.should eq 0 + + teacher.languages << Language.find('EN') + teacher.save! + teachers = Teacher.index(nil, {languages: ['EN']})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + end + + it "country" do + teacher = FactoryGirl.create(:teacher) + teachers = Teacher.index(nil, {country: 'DO'})[:query] + teachers.length.should eq 0 + + teacher.user.country = 'DO' + teacher.user.save! + teachers = Teacher.index(nil, {country: 'DO'})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + end + + it "region" do + teacher = FactoryGirl.create(:teacher) + teachers = Teacher.index(nil, {region: 'HE'})[:query] + teachers.length.should eq 0 + + teacher.user.state = 'HE' + teacher.user.save! + teachers = Teacher.index(nil, {region: 'HE'})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + end + + it "years_teaching" do + teacher = FactoryGirl.create(:teacher, years_teaching: 5) + teachers = Teacher.index(nil, {years_teaching: 10})[:query] + teachers.length.should eq 0 + + teachers = Teacher.index(nil, {years_teaching: 2})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + end + + it "teaches beginner/intermediate/advanced" do + teacher = FactoryGirl.create(:teacher) + teachers = Teacher.index(nil, {teaches_beginner: true})[:query] + teachers.length.should eq 0 + + teacher.teaches_beginner = true + teacher.save! + teachers = Teacher.index(nil, {teaches_beginner: true})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + + teachers = Teacher.index(nil, {teaches_intermediate: true})[:query] + teachers.length.should eq 0 + + teachers = Teacher.index(nil, {teaches_beginner: true, teaches_intermediate: true})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + + teachers = Teacher.index(nil, {teaches_beginner: true, teaches_intermediate: true, teaches_advanced: true})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + end + + it "student_age" do + teacher = FactoryGirl.create(:teacher) + teachers = Teacher.index(nil, {student_age: 5})[:query] + teachers.length.should eq 1 + + teacher.teaches_age_lower = 5 + teacher.save! + teachers = Teacher.index(nil, {student_age: 5})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + + teacher.teaches_age_lower = 6 + teacher.save! + teachers = Teacher.index(nil, {student_age: 5})[:query] + teachers.length.should eq 0 + + teacher.teaches_age_lower = 4 + teacher.teaches_age_upper = 6 + teacher.save! + teachers = Teacher.index(nil, {student_age: 5})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + + teacher.teaches_age_lower = 0 + teacher.teaches_age_upper = 5 + teacher.save! + teachers = Teacher.index(nil, {student_age: 5})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + + teacher.teaches_age_lower = 0 + teacher.teaches_age_upper = 6 + teacher.save! + teachers = Teacher.index(nil, {student_age: 5})[:query] + teachers.length.should eq 1 + teachers[0].should eq(teacher.user) + + teacher.teaches_age_lower = 0 + teacher.teaches_age_upper = 4 + teacher.save! + teachers = Teacher.index(nil, {student_age: 5})[:query] + teachers.length.should eq 0 + end + end + BIO = "Once a man learned a guitar." GOOD_YOUTUBE_URL = "http://youtube.com/watch?v=1234567890" describe "can create" do it "a simple teacher" do teacher = Teacher.new - teacher.user = user; + teacher.user = user teacher.biography = BIO teacher.introductory_video = GOOD_YOUTUBE_URL teacher.save.should be_true diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb index 7f189e159..7a4ab16e6 100644 --- a/ruby/spec/spec_helper.rb +++ b/ruby/spec/spec_helper.rb @@ -95,13 +95,13 @@ end config.before(:suite) do DatabaseCleaner.strategy = :transaction - DatabaseCleaner.clean_with(:deletion, {pre_count: true, reset_ids:false, :except => %w[gift_card_types instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] }) + DatabaseCleaner.clean_with(:deletion, {pre_count: true, reset_ids:false, :except => %w[gift_card_types instruments languages subjects genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] }) end config.around(:each) do |example| # set no_transaction: true as metadata on your test to use deletion strategy instead if example.metadata[:no_transaction] - DatabaseCleaner.strategy = :deletion, {pre_count: true, reset_ids:false, :except => %w[gift_card_types instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] } + DatabaseCleaner.strategy = :deletion, {pre_count: true, reset_ids:false, :except => %w[gift_card_types instruments languages subjects genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] } else DatabaseCleaner.strategy = :transaction end diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 219df4f2c..953b9e0a7 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1974,6 +1974,15 @@ }); } + function searchTeachers(query) { + return $.ajax({ + type: "GET", + url: "/api/teachers?" + $.param(query), + dataType: "json", + contentType: 'application/json' + }); + } + function getMusicianSearchFilter(query) { var qarg = query === undefined ? '' : query; return $.get("/api/search/musicians.json?"+qarg); @@ -2286,6 +2295,7 @@ this.updateBillingInfo = updateBillingInfo; this.placeOrder = placeOrder; this.searchMusicians = searchMusicians; + this.searchTeachers = searchTeachers; this.resendBandInvitation = resendBandInvitation; this.getMount = getMount; this.createSourceChange = createSourceChange; diff --git a/web/app/assets/javascripts/react-components.js b/web/app/assets/javascripts/react-components.js index 9e82ac707..8c4e9b8db 100644 --- a/web/app/assets/javascripts/react-components.js +++ b/web/app/assets/javascripts/react-components.js @@ -18,6 +18,9 @@ //= require ./react-components/stores/MixerStore //= require ./react-components/stores/ConfigureTracksStore //= require ./react-components/stores/JamTrackStore +//= require ./react-components/stores/LocationStore +//= require ./react-components/stores/TeacherSearchStore +//= require ./react-components/stores/TeacherSearchResultsStore //= require ./react-components/stores/SessionNotificationStore //= require ./react-components/stores/MediaPlaybackStore //= require ./react-components/stores/BrowserMediaPlaybackStore diff --git a/web/app/assets/javascripts/react-components/AgeRangeList.js.jsx.coffee b/web/app/assets/javascripts/react-components/AgeRangeList.js.jsx.coffee index 615a13514..84037a885 100644 --- a/web/app/assets/javascripts/react-components/AgeRangeList.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/AgeRangeList.js.jsx.coffee @@ -4,11 +4,15 @@ logger = context.JK.logger @AgeRangeList = React.createClass({ ages: [0, 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100] + agesJsx: [] + propTypes: { + onItemChanged: React.PropTypes.func.isRequired + } - componentDidMount: () -> + componentWillMount: () -> @agesJsx = [] for age in @ages - @agesJsx.push(``) + @agesJsx.push(``) getInitialState: () -> {selectedAge:@props.selectedAge} diff --git a/web/app/assets/javascripts/react-components/GenreCheckBoxList.js.jsx.coffee b/web/app/assets/javascripts/react-components/GenreCheckBoxList.js.jsx.coffee index 4e877aca9..3f641618f 100644 --- a/web/app/assets/javascripts/react-components/GenreCheckBoxList.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/GenreCheckBoxList.js.jsx.coffee @@ -6,6 +6,13 @@ logger = context.JK.logger mixins: [Reflux.listenTo(@GenreStore,"onGenresChanged")] + propTypes: { + onItemChanged: React.PropTypes.func.isRequired + } + + getDefaultProps: () -> + selectedGenres: [] + getInitialState:() -> {genres: []} diff --git a/web/app/assets/javascripts/react-components/InstrumentCheckBoxList.js.jsx.coffee b/web/app/assets/javascripts/react-components/InstrumentCheckBoxList.js.jsx.coffee index 9078d5b4d..996e9c2d5 100644 --- a/web/app/assets/javascripts/react-components/InstrumentCheckBoxList.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/InstrumentCheckBoxList.js.jsx.coffee @@ -6,6 +6,13 @@ logger = context.JK.logger mixins: [Reflux.listenTo(@InstrumentStore,"onInstrumentsChanged")] + propTypes: { + onItemChanged: React.PropTypes.func.isRequired + } + + getDefaultProps: () -> + selectedInstruments: [] + getInitialState: () -> {instruments: []} diff --git a/web/app/assets/javascripts/react-components/LanguageCheckBoxList.js.jsx.coffee b/web/app/assets/javascripts/react-components/LanguageCheckBoxList.js.jsx.coffee index 59fa3140f..6f2660678 100644 --- a/web/app/assets/javascripts/react-components/LanguageCheckBoxList.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/LanguageCheckBoxList.js.jsx.coffee @@ -6,6 +6,13 @@ logger = context.JK.logger mixins: [Reflux.listenTo(@LanguageStore,"onLanguagesChanged")] + propTypes: { + onItemChanged: React.PropTypes.func.isRequired + } + + getDefaultProps: () -> + selectedLanguages: [] + getInitialState: () -> {languages: []} diff --git a/web/app/assets/javascripts/react-components/SelectLocation.js.jsx.coffee b/web/app/assets/javascripts/react-components/SelectLocation.js.jsx.coffee new file mode 100644 index 000000000..e6a097dcd --- /dev/null +++ b/web/app/assets/javascripts/react-components/SelectLocation.js.jsx.coffee @@ -0,0 +1,62 @@ +context = window +rest = window.JK.Rest() +logger = context.JK.logger + +@SelectLocation = React.createClass({ + + mixins: [Reflux.listenTo(@LocationStore,"onLocationsChanged")] + + propTypes: { + onItemChanged: React.PropTypes.func.isRequired + } + + getInitialState:() -> + {selectedCountry: null, countries:{US: {name: 'United States', region: null}}} + + onLocationsChanged: (countries) -> + @setState({countries: countries}) + + onCountryChanged: (e) -> + val = $(e.target).val() + @changed(val, null) + @setState({selectedCountry: val, selectedRegion: null }) + + if val? + LocationActions.selectCountry(val) + + onRegionChanged: (e) -> + val = $(e.target).val() + @changed(@state.selectedCountry, val) + @setState({selectedRegion: val }) + + changed: (country, region) -> + if country == '' + country = null + + if region == '' + region = null + + @props.onItemChanged(country, region) + + render: () -> + countries = [``] + for countryId, countryInfo of @state.countries + countries.push(``) + + country = @state.countries[@state.selectedCountry] + + regions = [``] + + + if country? && country.regions + for region in country.regions + regions.push(``) + + disabled = regions.length == 1 + `
+

Country:

+ +

State/Region:

+ +
` +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/SubjectCheckBoxList.js.jsx.coffee b/web/app/assets/javascripts/react-components/SubjectCheckBoxList.js.jsx.coffee index 71e6d8406..ec22270ef 100644 --- a/web/app/assets/javascripts/react-components/SubjectCheckBoxList.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SubjectCheckBoxList.js.jsx.coffee @@ -6,6 +6,13 @@ logger = context.JK.logger mixins: [Reflux.listenTo(@SubjectStore,"onSubjectsChanged")] + propTypes: { + onItemChanged: React.PropTypes.func.isRequired + } + + getDefaultProps: () -> + selectedSubjects: [] + getInitialState:() -> {subjects: []} diff --git a/web/app/assets/javascripts/react-components/TeacherExperienceEditableList.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherExperienceEditableList.js.jsx.coffee index ebebdd62f..0177cb7ed 100644 --- a/web/app/assets/javascripts/react-components/TeacherExperienceEditableList.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/TeacherExperienceEditableList.js.jsx.coffee @@ -78,7 +78,7 @@ logger = context.JK.logger errors = [] if this.state.errors? for error in this.state.errors - errors.push(error) + errors.push(`{error}`) `
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 90de1c5d5..ca93b873e 100644 --- a/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee @@ -9,6 +9,7 @@ LanguageStore = context.LanguageStore GenreStore = context.GenreStore UserStore = context.UserStore AppStore = context.AppStore +ProfileActions = context.ProfileActions profileUtils = context.JK.ProfileUtils @@ -44,7 +45,7 @@ proficiencyDescriptionMap = { TILES: ['about', 'experience', 'samples', 'ratings', 'prices'] onAppInit: (@app) -> - @app.bindScreen('profile/teacher', {beforeShow: @beforeShow, afterShow: @afterShow}) + @app.bindScreen('profile/teacher', {beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide}) onSubjectsChanged: () -> @setState({subjects: true}) @@ -83,15 +84,21 @@ proficiencyDescriptionMap = { ) - beforeShow: (e) -> - userDetailDone: (response) -> if response.id == @state.userId @setState({user: response, isSelf: response.id == context.JK.currentUserId}) else logger.debug("ignoring userDetailDone", response.id, @state.userId) + beforeHide: (e) -> + logger.debug("TeacherProfile: beforeHide") + ProfileActions.viewTeacherProfileDone() + + beforeShow: (e) -> + logger.debug("TeacherProfile: beforeShow") + afterShow: (e) -> + logger.debug("TeacherProfile: afterShow") @setState({userId: e.id, user: null}) rest.getUserDetail({ id: e.id, @@ -619,6 +626,10 @@ proficiencyDescriptionMap = { return `
{tile}
` + onCustomBack: (customBack, e) -> + e.preventDefault() + context.location = customBack + render: () -> if @state.user? avatar = context.JK.resolveAvatarUrl(@state.user.photo_url); @@ -634,12 +645,10 @@ proficiencyDescriptionMap = { {noTeacherProfile}
` - editButton = `EDIT PROFILE` - actionButtons = `
- ADD FRIEND - FOLLOW - MESSAGE -
` + if ProfileStore.customBack + actionButtons = `
+ {ProfileStore.customBackDisplay} +
` profileSelections = [] for tile, i in @TILES @@ -653,7 +662,6 @@ proficiencyDescriptionMap = {

- {editButton}
{actionButtons} diff --git a/web/app/assets/javascripts/react-components/TeacherSearchOptionsScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherSearchOptionsScreen.js.jsx.coffee new file mode 100644 index 000000000..f1c3e09ce --- /dev/null +++ b/web/app/assets/javascripts/react-components/TeacherSearchOptionsScreen.js.jsx.coffee @@ -0,0 +1,137 @@ +context = window +rest = context.JK.Rest() +TeacherSearchActions = @TeacherSearchActions +LocationActions = @LocationActions + +@TeacherSearchOptionsScreen = React.createClass({ + + mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged"), + Reflux.listenTo(@TeacherSearchStore, "onTeacherSearchChanged")] + + LIMIT: 20 + + getInitialState: () -> + {options: {}} + + onAppInit: (@app) -> + @app.bindScreen('jamclass/searchOptions', {beforeShow: @beforeShow, afterShow: @afterShow}) + + beforeShow: (e) -> + LocationActions.load() + + afterShow: (e) -> + + onUserChanged: (@user) -> + + onTeacherSearchChanged: (options) -> + @setState({options: options}) + + handleListChange: (listName, selectedObjects)-> + logger.debug("handleListChange:", listName, selectedObjects) + options = @state.options + options[listName] = selectedObjects + @setState({options: options}) + + handleLocationChange: (country, region) -> + logger.debug("handleLocationChange #{country} #{region}") + options = @state.options + options['location'] = {country:country, region:region} + @setState({options: options}) + + onCancel: (e) -> + e.preventDefault() + window.history.go(-1) + + onSearch: (e) -> + e.preventDefault() + TeacherSearchActions.search({searchOptions: @state.options}) + + + levelChanged: (e) -> + purpose = $(e.target).attr('data-purpose') + checked = $(e.target).is(":checked") + logger.debug("levelChanged:", purpose, checked) + options = @state.options + options[purpose] = checked + @setState({options: options}) + + handleYearsTeaching: (e) -> + yearsTeaching = $(e.target).val() + + logger.debug("years teaching:", yearsTeaching) + options = @state.options + options['years-teaching'] = yearsTeaching + @setState({options: options}) + + render: () -> + selectedAge = null + yearsTeaching = [] + for yr in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 40, 45, 50] + yearsTeaching.push(``) + `
+
+

search teachers

+ +
+

Instruments Taught:

+ +
+ +
+

Music Subjects Taught:

+ +
+ +
+

Genres Taught:

+ +
+ +
+

Languages Spoken:

+ +
+ +
+ +
+
+

Student Levels Taught:

+ +
+ + +
+
+ + +
+
+ + +
+
+
+ +
+

Student Ages Taught:

+ + + +

Years Teaching Experience:

+ +
+ +
+ +
+
+
+ CANCEL + SEARCH +
+
` +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee new file mode 100644 index 000000000..ab918bd0f --- /dev/null +++ b/web/app/assets/javascripts/react-components/TeacherSearchScreen.js.jsx.coffee @@ -0,0 +1,184 @@ +context = window +rest = context.JK.Rest() +TeacherSearchStore = @TeacherSearchStore +TeacherSearchActions = @TeacherSearchActions +TeacherSearchResultsActions = @TeacherSearchResultsActions +LocationActions = @LocationActions +InstrumentStore = @InstrumentStore +SubjectStore = @SubjectStore +GenreStore = @GenreStore +LanguageStore = @LanguageStore +ProfileActions = @ProfileActions + +@TeacherSearchScreen = React.createClass({ + + # Reflux.listenTo(@TeacherSearchStore, "onTeacherSearchChanged"), + + mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged"), + Reflux.listenTo(@TeacherSearchResultsStore, "onTeacherSearchResultsStore")] + + LIMIT: 20 + visible: false + + getInitialState: () -> + {searchOptions: {}, results: []} + + onAppInit: (@app) -> + @app.bindScreen('teachers/search', {beforeShow: @beforeShow, afterShow: @afterShow, afterHide: @afterHide}) + + beforeShow: (e) -> + @visible = true + + afterShow: (e) -> + @visible = false + #@setState(TeacherSearchStore.getState()) + TeacherSearchResultsActions.reset() + + afterHide: (e) -> + + + onUserChanged: (@user) -> + + #onTeacherSearchChanged: (options) -> + # if @visible + # @setState(options) + + componentDidMount: () -> + @root = $(@getDOMNode()) + @resultsNode = @root.find('.results') + + componentDidUpdate: () -> + @resultsNode.find('.teacher-bio').each((index, element) => ( + $this = $(element) + if !$this.data('dotdotdot') + $this.dotdotdot({ + after: "a.readmore" + }) + )) + onTeacherSearchResultsStore: (results) -> + @setState(results) + + moreAboutTeacher: (user, e) -> + e.preventDefault() + + ProfileActions.viewTeacherProfile(user, '/client#/teachers/search', 'BACK TO TEACHER SEARCH') + + bookTestDrive: (e) -> + e.preventDefault() + + bookNormalLesson: (e) -> + e.preventDefault() + + bookFreeLesson: (e) -> + e.preventDefault() + + readMore: (e) -> + e.preventDefault() + + target = $(e.target) + teacherBio = target.closest('.teacher-bio') + teacherBio.css('height', 'auto') + target.trigger( 'destroy.dot' ); + teacherBio.css('height', 'auto') + + createSearchDescription: () -> + searchOptions = TeacherSearchStore.getState().searchOptions + + summary = '' + 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])}..." + 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])}..." + 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])}..." + summary += " #{bit}" + + languages = searchOptions.languages + if languages? && languages.length > 0 + if languages.length == 1 + bit = "Genre = #{LanguageStore.display(languages[0])}" + else + languages.length > 1 + bit = "Genres = #{LanguageStore.display(languages[0])}..." + summary += " #{bit}" + + if summary.length == 0 + summary = 'all teachers' + + summary + + render: () -> + + searchDesc = @createSearchDescription() + + resultsJsx = [] + + for user in @state.results + + photo_url = user.photo_url + if !photo_url? + photo_url = '/assets/shared/avatar_generic.png' + + bio = user.teacher.biography + if !bio? + bio = 'No bio' + + resultsJsx.push(`
+
+
+ +
+
+ {user.name} +
+
+
+
+ {bio} + more +
+
+ MORE ABOUT THIS TEACHER + BOOK TESTDRIVE LESSON + BOOK FREE LESSON + BOOK NORMAL LESSON +
+
+
+
`) + + `
+
+
+ LESSONS HOME :  + TEACHERS SEARCH :  + + SEARCH RESULTS / + {searchDesc} + +
+
+ {resultsJsx} +
+
+
` +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/actions/LocationActions.js.coffee b/web/app/assets/javascripts/react-components/actions/LocationActions.js.coffee new file mode 100644 index 000000000..21b2953bd --- /dev/null +++ b/web/app/assets/javascripts/react-components/actions/LocationActions.js.coffee @@ -0,0 +1,7 @@ +context = window + +@LocationActions = Reflux.createActions({ + + load: {} + selectCountry: {} +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/actions/ProfileActions.js.coffee b/web/app/assets/javascripts/react-components/actions/ProfileActions.js.coffee index e5746e4d3..0351ee476 100644 --- a/web/app/assets/javascripts/react-components/actions/ProfileActions.js.coffee +++ b/web/app/assets/javascripts/react-components/actions/ProfileActions.js.coffee @@ -9,4 +9,6 @@ context = window cancelProfileEdit: {} doneProfileEdit: {} editProfileNext: {} + viewTeacherProfile: {} + viewTeacherProfileDone: {} }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/actions/TeacherSearchActions.js.coffee b/web/app/assets/javascripts/react-components/actions/TeacherSearchActions.js.coffee new file mode 100644 index 000000000..b9c8681a8 --- /dev/null +++ b/web/app/assets/javascripts/react-components/actions/TeacherSearchActions.js.coffee @@ -0,0 +1,7 @@ +context = window + +@TeacherSearchActions = Reflux.createActions({ + updateOptions: {} + search: {} +}) + diff --git a/web/app/assets/javascripts/react-components/actions/TeacherSearchResultsActions.js.coffee b/web/app/assets/javascripts/react-components/actions/TeacherSearchResultsActions.js.coffee new file mode 100644 index 000000000..924187040 --- /dev/null +++ b/web/app/assets/javascripts/react-components/actions/TeacherSearchResultsActions.js.coffee @@ -0,0 +1,6 @@ +context = window + +@TeacherSearchResultsActions = Reflux.createActions({ + reset: {} +}) + diff --git a/web/app/assets/javascripts/react-components/stores/LocationStore.js.coffee b/web/app/assets/javascripts/react-components/stores/LocationStore.js.coffee new file mode 100644 index 000000000..de24a2f17 --- /dev/null +++ b/web/app/assets/javascripts/react-components/stores/LocationStore.js.coffee @@ -0,0 +1,74 @@ +$ = jQuery +context = window +rest = window.JK.Rest() +logger = context.JK.logger + +@LocationStore = Reflux.createStore( + { + listenables: @LocationActions + countries: {} + + init: -> + # Register with the app store to get @app + this.listenTo(context.AppStore, this.onAppInit) + + changed: () -> + @trigger(@countries) + + onSelectCountry: (country) -> + @loadRegions(country) + + onLoad: () -> + + # avoid double-loads + if Object.keys(@countries).length == 0 + rest.getCountries().done ((response) => + countries = response.countriesx + if Object.keys(@countries).length == 0 + for country in countries + name = country.countryname + if !name? + name = country.countrycode + @countries[country.countrycode] = {name: name, regions:null} + + @loadRegions('US') + ) + + loadRegions: (loadForCountry) -> + if loadForCountry == null + return + + country = @countries[loadForCountry] + + if !country? + logger.warn("country is null in searching for: " + loadForCountry) + return + + regions = country.regions + + # avoid double-loads + if regions == null + rest.getRegions({country: loadForCountry}).done ((countriesRegions) => + regions = country.regions + if regions == null + regions = [] + country.regions = regions + + if regions.length > 0 + return + + for region in countriesRegions.regions + + id = region.region + name = region.name + if !name? + name = region.region + + regions.push({id: id, name: name}) + + @changed() + ) + onAppInit: (@app) -> + + } +) diff --git a/web/app/assets/javascripts/react-components/stores/ProfileStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ProfileStore.js.coffee index b0bf622ca..6a801e115 100644 --- a/web/app/assets/javascripts/react-components/stores/ProfileStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/ProfileStore.js.coffee @@ -8,6 +8,8 @@ ProfileActions = @ProfileActions { listenables: ProfileActions + customBack: null + customBackDisplay: null returnNav: null solo: false @@ -95,6 +97,16 @@ ProfileActions = @ProfileActions @solo = false else context.location = "/client#/account/profile/" + step + + onViewTeacherProfile: (user, customBack, customBackDisplay) -> + + @customBack = customBack + @customBackDisplay = customBackDisplay + context.location = "/client#/profile/teacher/#{user.id}" + + onViewTeacherProfileDone: () -> + @customBack = null + @customBackDisplay = null } ) diff --git a/web/app/assets/javascripts/react-components/stores/TeacherSearchResultsStore.js.coffee b/web/app/assets/javascripts/react-components/stores/TeacherSearchResultsStore.js.coffee new file mode 100644 index 000000000..86b963048 --- /dev/null +++ b/web/app/assets/javascripts/react-components/stores/TeacherSearchResultsStore.js.coffee @@ -0,0 +1,66 @@ +$ = jQuery +context = window +logger = context.JK.logger +rest = context.JK.Rest() + +TeacherSearchResultsActions = @TeacherSearchResultsActions + +@TeacherSearchResultsStore = Reflux.createStore( + { + listenables: TeacherSearchResultsActions + results: [] + page: 1 + limit: 20 + + init: -> + # Register with the app store to get @app + this.listenTo(context.AppStore, this.onAppInit) + + + onAppInit: (app) -> + @app = app + + onReset: () -> + @results = [] + @changed() + + query = @createQuery() + + rest.searchTeachers(query) + .done((response) => + @next = response.next + + @results.push.apply(@results, response.entries) + @changed() + ) + .fail((jqXHR, textStatus, errorMessage) => + @app.ajaxError(jqXHR, textStatus, errorMessage) + ) + + getState: () -> + ({results: @results}) + + changed:() -> + @trigger(@getState()) + + createQuery: () -> + + searchOptions = context.TeacherSearchStore.getState().searchOptions + + query = {} + query.page = @page + query.limit = @limit + query.instruments = searchOptions.instruments + query.subjects = searchOptions.subjects + query.genres = searchOptions.genres + query.languages = searchOptions.languages + query.teaches_beginner = searchOptions.teaches_beginner + query.teaches_intermediate = searchOptions.teaches_intermediate + query.teaches_advanced = searchOptions.teaches_advanced + query.student_age = searchOptions['ages-taught'] + query.years_teaching = searchOptions['years-teaching'] + query.country = searchOptions.location?.country + query.region = searchOptions.location?.region + query + } +) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/stores/TeacherSearchStore.js.coffee b/web/app/assets/javascripts/react-components/stores/TeacherSearchStore.js.coffee new file mode 100644 index 000000000..c8ff60f63 --- /dev/null +++ b/web/app/assets/javascripts/react-components/stores/TeacherSearchStore.js.coffee @@ -0,0 +1,38 @@ +$ = jQuery +context = window +logger = context.JK.logger +rest = context.JK.Rest() + +TeacherSearchActions = @TeacherSearchActions + +@TeacherSearchStore = Reflux.createStore( + { + listenables: TeacherSearchActions + searchOptions: {} + viewingTeacher: null + + init: -> + # Register with the app store to get @app + this.listenTo(context.AppStore, this.onAppInit) + + + onAppInit: (app) -> + @app = app + + onUpdateOptions: (options) -> + @searchOptions = options + + onSearch: (searchOptions) -> + @searchOptions = searchOptions + + @changed() + + window.location = "/client#/teachers/search" + + getState: () -> + ({searchOptions: @searchOptions}) + + changed:() -> + @trigger(@getState()) + } +) \ No newline at end of file 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 a789fde9a..b0f8975c0 100644 --- a/web/app/assets/stylesheets/client/react-components/TeacherProfile.css.scss +++ b/web/app/assets/stylesheets/client/react-components/TeacherProfile.css.scss @@ -172,6 +172,10 @@ } .years {float:right} } + .profileNavActions { + margin-right: -3px; + } + .ratings-block { h3 { diff --git a/web/app/assets/stylesheets/client/react-components/TeacherSearch.css.scss b/web/app/assets/stylesheets/client/react-components/TeacherSearch.css.scss new file mode 100644 index 000000000..e84079a3f --- /dev/null +++ b/web/app/assets/stylesheets/client/react-components/TeacherSearch.css.scss @@ -0,0 +1,109 @@ +@import "client/common"; + +#teacherSearch { + div[data-react-class="TeacherSearchScreen"] { + height:100%; + } + + .screen-content { + padding:20px; + } + + .header { + height:20px; + margin-bottom:10px; + + a { + font-size:16px; + text-decoration:underline; + } + .search-results-options { + font-size:16px; + color:$ColorTextTypical; + } + } + + a.readmore { + display:none; + } + + .search-summary { + font-size:11px; + } + .teacher-search-result { + @include border_box_sizing; + clear:both; + position:relative; + margin-bottom:10px; + width:100%; + min-width:600px; + background-color:#242323; + color:$ColorTextTypical; + //height:110px; + padding:10px; + } + + .user-info { + @include border_box_sizing; + width: calc(100% - 150px); + //padding:12px 10px 10px; + min-width:450px; + float:right; + } + + .teacher-bio { + @include border_box_sizing; + height:50px; + width:100%; + margin-bottom:16px; + font-size:12px; + } + .teacher-actions { + a { + font-size:12px; + + &:nth-of-type(1) { + margin-left:2px; + } + } + } + + .user-avatar { + text-align:center; + float:left; + width:150px; + } + .avatar { + display:inline-block; + padding:1px; + width:48px; + height:48px; + background-color:#ed4818; + margin:10px 0 0 0; + -webkit-border-radius:24px; + -moz-border-radius:24px; + border-radius:24px; + float:none; + } + .avatar img { + width: 48px; + height: 48px; + -webkit-border-radius:24px; + -moz-border-radius:24px; + border-radius:24px; + } + .user-name { + text-align:center; + display:block; + margin-top:8px; + clear:both; + } + + .actions { + float: right; + width: 100%; + text-align: right; + padding-right: 31px; + margin-bottom: 20px; + } +} diff --git a/web/app/assets/stylesheets/client/react-components/TeacherSearchOptions.css.scss b/web/app/assets/stylesheets/client/react-components/TeacherSearchOptions.css.scss new file mode 100644 index 000000000..c2f808c0e --- /dev/null +++ b/web/app/assets/stylesheets/client/react-components/TeacherSearchOptions.css.scss @@ -0,0 +1,93 @@ +@import "client/common"; + +#teacherSearchOptions { + div[data-react-class="TeacherSearchOptionsScreen"] { + height:100%; + } + + .search-criteria { + .checkbox-scroller { + height:100px; + margin-top:0; + } + min-width:200px; + width:25%; + @include border_box_sizing; + float:left; + padding: 0 10px; + margin-bottom:30px; + + &:first-child { + padding-left:0; + } + + &:last-child { + padding-right:0; + } + + h3 { + color: #FFFFFF; + font-size: 14px; + font-weight: 400; + margin-bottom: 8px; + } + } + + .screen-content { + padding:20px; + } + + .actions { + float: right; + width: 100%; + text-align: right; + padding-right: 31px; + margin-bottom: 20px; + } + h2 { + color: #FFFFFF; + font-size: 23px; + font-weight: 400; + margin-bottom:20px; + padding-left:10px; + } + + select { + @include border_box_sizing; + width: 100%; + max-width:200px; + } + + .SelectLocation { + + select[name="countries"] { + //margin-top: 6px; + margin-bottom: 30px; + } + } + + select[name="years-teaching"] { + //margin-top: 6px; + margin-bottom: 30px; + } + + .student-levels-taught { + .teaching-level { + margin-bottom:10px; + } + label { + display:inline-block; + } + input { + display:inline-block; + margin-right:10px; + } + .beginner-level { + margin-top:10px; + } + } + + .years-teaching-header { + margin-top:30px; + } +} diff --git a/web/app/controllers/api_teachers_controller.rb b/web/app/controllers/api_teachers_controller.rb index 72cba581e..90cae056f 100644 --- a/web/app/controllers/api_teachers_controller.rb +++ b/web/app/controllers/api_teachers_controller.rb @@ -1,13 +1,21 @@ class ApiTeachersController < ApiController - before_filter :api_signed_in_user, :except => [:index, :detail] + before_filter :api_signed_in_user, :except => [:index, :detail, :search] before_filter :auth_teacher, :only => [:update, :delete] before_filter :auth_user, :only => [:create, :update] respond_to :json def index - @teachers = Teacher.paginate(page: params[:page]) + data = Teacher.index(current_user, params) + + @show_profile = true + @show_teacher = true + + @users = data[:query] + + @next = data[:next_page] + render "api_teachers/index", :layout => nil end def detail diff --git a/web/app/views/api_teachers/index.rabl b/web/app/views/api_teachers/index.rabl new file mode 100644 index 000000000..40fd86a60 --- /dev/null +++ b/web/app/views/api_teachers/index.rabl @@ -0,0 +1,11 @@ +node :next do |page| + @next +end + +node :entries do |page| + partial "api_users/show", object: @users +end + +node :total_entries do |page| + @users.total_entries +end diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl index c6fe9a56c..4fcd67106 100644 --- a/web/app/views/api_users/show.rabl +++ b/web/app/views/api_users/show.rabl @@ -3,19 +3,25 @@ object @user attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level, :reuse_card, :email_needs_verification -if @user.musician? - node :location do @user.location end -else - node :location do @user.online ? 'Online' : 'Offline' end -end - -if @show_teacher && @user.teacher - node :teacher do - partial("api_teachers/detail", :object => @user.teacher) +node :location do |user| + if user.musician? + user.location + else + user.online ? 'Online' : 'Offline' end end + +node :teacher do |user| + if @show_teacher && user.teacher + partial("api_teachers/detail", :object => user.teacher) + end +end + + + + if @show_profile node :profile do partial("api_users/profile_show", :object => @user) @@ -23,7 +29,8 @@ if @show_profile end # give back more info if the user being fetched is yourself -if @user == current_user + +if current_user && @user == current_user attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications, :sales_count, :reuse_card, :purchased_jamtracks_count, :first_downloaded_client_at, :created_at, :first_opened_jamtrack_web_player, :gifted_jamtracks, :has_redeemable_jamtrack node :geoiplocation do |user| @@ -43,34 +50,34 @@ if @user == current_user user.mods_json end - node :has_recurly_account do - @user.recurly_code == @user.id + node :has_recurly_account do |user| + user.recurly_code == user.id end - node :is_affiliate_partner do - @user.affiliate_partner.present? + node :is_affiliate_partner do |user| + user.affiliate_partner.present? end - node :affiliate_referral_count do - @user.affiliate_partner.try(:referral_user_count) + node :affiliate_referral_count do |user| + user.affiliate_partner.try(:referral_user_count) end - node :affiliate_earnings do - @user.affiliate_partner.try(:cumulative_earnings_in_cents) + node :affiliate_earnings do |user| + user.affiliate_partner.try(:cumulative_earnings_in_cents) end elsif current_user node :is_friend do |uu| - current_user.friends?(@user) + current_user.friends?(uu) end node :is_following do |uu| - current_user.following?(@user) + current_user.following?(uu) end node :is_liking do |uu| - current_user.likes?(@user) + current_user.likes?(uu) end node :pending_friend_request do |uu| - current_user.pending_friend_request?(@user) + current_user.pending_friend_request?(uu) end node :my_audio_latency do |user| current_user.last_jam_audio_latency.round if current_user.last_jam_audio_latency diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 89336faba..b284a44ec 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -43,6 +43,8 @@ <%= render "clients/teachers/setup/experience" %> <%= render "clients/teachers/setup/pricing" %> <%= render "clients/teachers/profile/profile" %> +<%= render "clients/teachers/search/search_options" %> +<%= render "clients/teachers/search/search_results" %> <%= render "users/feed_music_session_ajax" %> <%= render "users/feed_recording_ajax" %> <%= render "jamtrack_search" %> diff --git a/web/app/views/clients/teachers/search/_search_options.html.slim b/web/app/views/clients/teachers/search/_search_options.html.slim new file mode 100644 index 000000000..739ebcaec --- /dev/null +++ b/web/app/views/clients/teachers/search/_search_options.html.slim @@ -0,0 +1,8 @@ +#teacherSearchOptions.screen.secondary.no-login-required layout='screen' layout-id='jamclass/searchOptions' + .content + .content-head + .content-icon=image_tag("content/icon_account.png", height:20, width:27) + h1 jamclass + = render "screen_navigation" + .content-body + = react_component 'TeacherSearchOptionsScreen', {} diff --git a/web/app/views/clients/teachers/search/_search_results.html.slim b/web/app/views/clients/teachers/search/_search_results.html.slim new file mode 100644 index 000000000..533d41548 --- /dev/null +++ b/web/app/views/clients/teachers/search/_search_results.html.slim @@ -0,0 +1,8 @@ +#teacherSearch.screen.secondary.no-login-required layout='screen' layout-id='teachers/search' + .content + .content-head + .content-icon=image_tag("content/icon_musicians.png", height:20, width:20) + h1 jamclass + = render "screen_navigation" + .content-body + = react_component 'TeacherSearchScreen', {}