diff --git a/db/up/profile_teacher.sql b/db/up/profile_teacher.sql index 5e1155dc0..e4d0bd0b1 100644 --- a/db/up/profile_teacher.sql +++ b/db/up/profile_teacher.sql @@ -1,6 +1,5 @@ CREATE TABLE teachers ( id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE, introductory_video VARCHAR(1024) NULL, years_teaching SMALLINT NOT NULL DEFAULT 0, years_playing SMALLINT NOT NULL DEFAULT 0, @@ -32,6 +31,8 @@ CREATE TABLE teachers ( updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); +ALTER TABLE users ADD COLUMN teacher_id VARCHAR(64) REFERENCES teachers(id) ON DELETE SET NULL; + CREATE TABLE subjects( id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), description VARCHAR(1024) NULL diff --git a/ruby/lib/jam_ruby/models/teacher.rb b/ruby/lib/jam_ruby/models/teacher.rb index 4d8374cbf..06ef35e1e 100644 --- a/ruby/lib/jam_ruby/models/teacher.rb +++ b/ruby/lib/jam_ruby/models/teacher.rb @@ -12,7 +12,7 @@ module JamRuby has_many :experiences_teaching, :class_name => "JamRuby::TeacherExperience", conditions: {experience_type: 'teaching'} has_many :experiences_education, :class_name => "JamRuby::TeacherExperience", conditions: {experience_type: 'education'} has_many :experiences_award, :class_name => "JamRuby::TeacherExperience", conditions: {experience_type: 'award'} - belongs_to :user, :class_name => 'JamRuby::User' + has_one :user, :class_name => 'JamRuby::User' validates :user, :presence => true validates :biography, length: {minimum: 5, maximum: 4096}, :if => :validate_introduction diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 53327a746..4767b6da9 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -183,6 +183,7 @@ module JamRuby has_one :musician_search, :class_name => 'JamRuby::MusicianSearch' has_one :band_search, :class_name => 'JamRuby::BandSearch' + belongs_to :teacher, :class_name => 'JamRuby::Teacher' before_save :default_anonymous_names before_save :create_remember_token, :if => :should_validate_password? diff --git a/web/app/assets/javascripts/accounts.js b/web/app/assets/javascripts/accounts.js index ac5d3fa18..aa14917cc 100644 --- a/web/app/assets/javascripts/accounts.js +++ b/web/app/assets/javascripts/accounts.js @@ -170,11 +170,11 @@ function navToEditProfile() { resetForm() - window.location = '/client#/account/profile' + window.ProfileActions.startProfileEdit(null, false) } function navToEditSubscriptions() { - window.location = '/client#/account/profile' + window.ProfileActions.startProfileEdit(null, false) } function navToEditPayments() { diff --git a/web/app/assets/javascripts/accounts_profile.js b/web/app/assets/javascripts/accounts_profile.js index 9938d4cf0..44834b353 100644 --- a/web/app/assets/javascripts/accounts_profile.js +++ b/web/app/assets/javascripts/accounts_profile.js @@ -220,7 +220,7 @@ function events() { $btnCancel.click(function(evt) { evt.stopPropagation(); - navToAccount(); + window.ProfileActions.cancelProfileEdit() return false; }); @@ -302,7 +302,7 @@ function postUpdateProfileSuccess(response) { $document.triggerHandler(EVENTS.USER_UPDATED, response); - context.location = "/client#/account/profile/experience"; + window.ProfileActions.editProfileNext('experience'); } function postUpdateProfileFailure(xhr, textStatus, errorMessage) { diff --git a/web/app/assets/javascripts/accounts_profile_experience.js b/web/app/assets/javascripts/accounts_profile_experience.js index de57e8695..46c44826a 100644 --- a/web/app/assets/javascripts/accounts_profile_experience.js +++ b/web/app/assets/javascripts/accounts_profile_experience.js @@ -21,6 +21,13 @@ } function afterShow(data) { + if (window.ProfileStore.solo) { + $btnBack.hide() + } + else { + $btnBack.show() + } + resetForm(); renderExperience(); } @@ -115,7 +122,7 @@ function events() { $btnCancel.click(function(evt) { evt.stopPropagation(); - navigateTo('/client#/profile/' + context.JK.currentUserId); + window.ProfileActions.cancelProfileEdit() return false; }); @@ -179,7 +186,7 @@ function postUpdateProfileSuccess(response) { $document.triggerHandler(EVENTS.USER_UPDATED, response); - context.location = "/client#/account/profile/interests"; + ProfileActions.editProfileNext('interests') } function postUpdateProfileFailure(xhr, textStatus, errorMessage) { diff --git a/web/app/assets/javascripts/accounts_profile_interests.js b/web/app/assets/javascripts/accounts_profile_interests.js index 48674efd6..768b62469 100644 --- a/web/app/assets/javascripts/accounts_profile_interests.js +++ b/web/app/assets/javascripts/accounts_profile_interests.js @@ -70,7 +70,14 @@ } function afterShow(data) { - renderInterests() + if (window.ProfileStore.solo) { + $btnBack.hide() + } + else { + $btnBack.show() + } + + renderInterests() } function resetForm() { @@ -187,7 +194,7 @@ $btnCancel.click(function(e) { e.stopPropagation() - navigateTo('/client#/profile/' + context.JK.currentUserId) + window.ProfileActions.cancelProfileEdit() return false }) @@ -310,7 +317,7 @@ function postUpdateProfileSuccess(response) { $document.triggerHandler(EVENTS.USER_UPDATED, response) - context.location = "/client#/account/profile/samples" + ProfileActions.editProfileNext('samples') } function postUpdateProfileFailure(xhr, textStatus, errorMessage) { diff --git a/web/app/assets/javascripts/accounts_profile_samples.js b/web/app/assets/javascripts/accounts_profile_samples.js index 0962d513b..1b18a1062 100644 --- a/web/app/assets/javascripts/accounts_profile_samples.js +++ b/web/app/assets/javascripts/accounts_profile_samples.js @@ -43,7 +43,6 @@ var $btnBack = parent.find('.account-edit-profile-back') var $btnSubmit = parent.find('.account-edit-profile-submit') - var urlValidator=null var soundCloudValidator=null var reverbNationValidator=null @@ -59,9 +58,16 @@ } function afterShow(data) { + if (window.ProfileStore.solo) { + $btnBack.hide() + } + else { + $btnBack.show() + } + $.when(loadFn()) .done(function(targetPlayer) { - if (targetPlayer && targetPlayer.keys && targetPlayer.keys.length > 0) { + if (targetPlayer) { renderPlayer(targetPlayer) } }) @@ -207,7 +213,9 @@ $btnCancel.click(function(evt) { evt.stopPropagation(); - navigateTo('/client#/profile/' + context.JK.currentUserId); + + window.ProfileActions.cancelProfileEdit() + return false; }); @@ -334,7 +342,7 @@ function postUpdateProfileSuccess(response) { $document.triggerHandler(EVENTS.USER_UPDATED, response); - context.location = "/client#/profile/" + context.JK.currentUserId; + ProfileActions.doneProfileEdit() } function postUpdateProfileFailure(xhr, textStatus, errorMessage) { diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js index 2153fb9c1..96870ab2d 100644 --- a/web/app/assets/javascripts/application.js +++ b/web/app/assets/javascripts/application.js @@ -51,6 +51,7 @@ //= require ga //= require utils //= require subscription_utils +//= require profile_utils //= require custom_controls //= require react //= require react_ujs diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 18e496512..56d3abd12 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -582,13 +582,16 @@ } function getUserDetail(options) { + if(!options) { + options = {} + } var id = getId(options); var detail = null; if (id != null && typeof(id) != 'undefined') { detail = $.ajax({ type: "GET", dataType: "json", - url: "/api/users/" + id, + url: "/api/users/" + id + '?'+ $.param(options), processData: false }); } diff --git a/web/app/assets/javascripts/profile.js b/web/app/assets/javascripts/profile.js index d99bb421f..dd24f9aca 100644 --- a/web/app/assets/javascripts/profile.js +++ b/web/app/assets/javascripts/profile.js @@ -103,6 +103,7 @@ var $btnAddRecordings = $screen.find('.add-recordings'); var $btnAddSites = $screen.find('.add-sites'); var $btnAddInterests = $screen.find('.add-interests'); + var $btnAddExperiences = $screen.find('.add-experiences') // social var $socialLeft = $screen.find('.profile-social-left'); @@ -251,6 +252,37 @@ // Hook up soundcloud player: $soundCloudSamples.off("click", "a.sound-cloud-playable") .on("click", "a.sound-cloud-playable", playSoundCloudFile) + + $btnEdit.click(function(e) { + e.preventDefault() + window.ProfileActions.startProfileEdit(null, false) + return false; + }) + $btnEditBio.click(function(e) { + e.preventDefault() + window.ProfileActions.startProfileEdit(null, true) + return false; + }) + $btnAddRecordings.click(function(e) { + e.preventDefault() + window.ProfileActions.startProfileEdit('samples', true) + return false; + }) + $btnAddSites.click(function(e) { + e.preventDefault() + window.ProfileActions.startProfileEdit('samples', true) + return false; + }) + $btnAddInterests.click(function(e) { + e.preventDefault() + window.ProfileActions.startProfileEdit('interests', true) + return false; + }); + $btnAddExperiences.click(function(e) { + e.preventDefault() + window.ProfileActions.startProfileEdit('experience', true) + return false; + }) } function playSoundCloudFile(e) { diff --git a/web/app/assets/javascripts/profile_utils.js b/web/app/assets/javascripts/profile_utils.js index 8629a738c..424c777dd 100644 --- a/web/app/assets/javascripts/profile_utils.js +++ b/web/app/assets/javascripts/profile_utils.js @@ -19,6 +19,8 @@ var NOT_SPECIFIED_TEXT = 'Not specified'; + profileUtils.NOT_SPECIFIED_TEXT = NOT_SPECIFIED_TEXT + var proficiencyDescriptionMap = { "1": "BEGINNER", "2": "INTERMEDIATE", diff --git a/web/app/assets/javascripts/react-components.js b/web/app/assets/javascripts/react-components.js index e0afa1c63..d02622469 100644 --- a/web/app/assets/javascripts/react-components.js +++ b/web/app/assets/javascripts/react-components.js @@ -5,6 +5,9 @@ //= require ./react-components/stores/AppStore //= require ./react-components/stores/InstrumentStore //= require ./react-components/stores/LanguageStore +//= require ./react-components/stores/GenreStore +//= require ./react-components/stores/SubjectStore +//= require ./react-components/stores/ProfileStore //= require ./react-components/stores/PlatformStore //= require ./react-components/stores/BrowserMediaStore //= require ./react-components/stores/RecordingStore 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 a080f7b21..4e877aca9 100644 --- a/web/app/assets/javascripts/react-components/GenreCheckBoxList.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/GenreCheckBoxList.js.jsx.coffee @@ -3,16 +3,18 @@ rest = window.JK.Rest() logger = context.JK.logger @GenreCheckBoxList = React.createClass({ - genres: [] - componentDidUnmount: () -> - @genres = [] - componentDidMount: () -> - rest.getGenres().done (genres) => - @genres = genres + mixins: [Reflux.listenTo(@GenreStore,"onGenresChanged")] + getInitialState:() -> + {genres: []} + + onGenresChanged: (genres) -> + @setState({genres: genres}) + + render: () -> `
- +
` }) \ 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 e3cf1fbe9..71e6d8406 100644 --- a/web/app/assets/javascripts/react-components/SubjectCheckBoxList.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SubjectCheckBoxList.js.jsx.coffee @@ -3,16 +3,17 @@ rest = window.JK.Rest() logger = context.JK.logger @SubjectCheckBoxList = React.createClass({ - subjects: [] - componentDidUnmount: () -> - @subjects = [] - componentDidMount: () -> - rest.getSubjects().done (subjects) => - @subjects = subjects + mixins: [Reflux.listenTo(@SubjectStore,"onSubjectsChanged")] + + getInitialState:() -> + {subjects: []} + + onSubjectsChanged: (subjects) -> + @setState({subjects: subjects}) render: () -> `
- +
` }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee index f1a1a9206..e78a954cc 100644 --- a/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/TeacherProfile.js.jsx.coffee @@ -1,32 +1,613 @@ - context = window MIX_MODES = context.JK.MIX_MODES rest = context.JK.Rest() +logger = context.JK.logger +SubjectStore = context.SubjectStore +InstrumentStore = context.InstrumentStore +LanguageStore = context.LanguageStore +GenreStore = context.GenreStore +UserStore = context.UserStore +AppStore = context.AppStore + +profileUtils = context.JK.ProfileUtils + +proficiencyCssMap = { + "1": "proficiency-beginner", + "2": "proficiency-intermediate", + "3": "proficiency-expert" +}; + +proficiencyDescriptionMap = { + "1": "BEGINNER", + "2": "INTERMEDIATE", + "3": "EXPERT" +}; @TeacherProfile = React.createClass({ - mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged")] + mixins: [ + Reflux.listenTo(AppStore, "onAppInit"), + Reflux.listenTo(UserStore, "onUserChanged"), + Reflux.listenTo(SubjectStore, "onSubjectsChanged"), + Reflux.listenTo(GenreStore, "onGenresChanged"), + Reflux.listenTo(InstrumentStore, "onInstrumentsChanged"), + Reflux.listenTo(LanguageStore, "onLanguagesChanged") + ] + + TILE_ABOUT: 'about' + TILE_EXPERIENCE: 'experience' + TILE_SAMPLES: 'samples' + TILE_RATINGS: 'ratings' + TILE_PRICES: 'prices' + + TILES: ['about', 'experience', 'samples', 'ratings', 'prices'] onAppInit: (@app) -> - @app.bindScreen('/teacher/profile', {beforeShow: @beforeShow, afterShow: @afterShow}) + @app.bindScreen('profile/teacher', {beforeShow: @beforeShow, afterShow: @afterShow}) + + onSubjectsChanged: () -> + @setState({subjects: true}) + + onInstrumentsChanged: () -> + @setState({instruments: true}) + + onGenresChanged: () -> + @setState({genres: true}) + + onLanguagesChanged: () -> + @setState({languages: true}) beforeShow: (e) -> - console.log(arguments) - rest.getUserDetail('') - + @setState({userId: e.id, user: null}) + rest.getUserDetail({ + id: e.id, + show_teacher: true, + show_profile: true + }).done((response) => @userDetailDone(response)).fail(@app.ajaxError) + + userDetailDone: (response) -> + if response.id == @state.userId + @setState({user: response, isSelf: response.id == context.JK.currentUserId}) + + afterShow: () -> - console.log(arguments) getInitialState: () -> - {user: null} + { + userId: null, + user: null, + selected: @TILE_ABOUT, + isSelf: false, + subjects: false, + instruments: false, + genres: false, + languages: false + } onUserChanged: (userState) -> @user = userState?.user - @onUser(userState.user) if userState.user + + editProfile: (selected, e) -> + e.preventDefault() + logger.debug("edit profile requested for state " + selected) + ProfileActions.startTeacherEdit(selected, true) + + editMusicProfile: (selected, e) -> + e.preventDefault() + logger.debug("edit music profile requested " + selected) + ProfileActions.startProfileEdit(selected, true) + + editProfileLink: (text, selected) -> + if @state.isSelf + `{text}` + + editMusicProfileLink: (text, selected) -> + if @state.isSelf + `{text}` + + + teacherBio: (user, teacher) -> + if teacher.biography? + biography = teacher.biography + else + biography = 'No bio defined yet' + + biography = biography.replace(/\n/g, "
") + + `
+

Teacher Profile {this.editProfileLink('edit profile', 'introduction')}

+
+
+
+
` + + sampleVideo: (user, teacher) -> + if teacher.introductory_video? + videoUrl = teacher.introductory_video + + if videoUrl.indexOf(window.location.protocol) != 0 + console.log("replacing video") + if window.location.protocol == 'http:' + console.log("replacing https: " + videoUrl) + videoUrl = videoUrl.replace('https://', 'http://') + console.log("replaced : " + videoUrl) + else + videoUrl = videoUrl.replace('http://', 'https://') + + videoUrl = videoUrl.replace("watch?v=", "v/") + + + return `
+

Intro Video

+ +
+
+
+