* working minus ratings

This commit is contained in:
Seth Call 2016-01-12 20:37:00 -06:00
parent 48bd7ae2da
commit 14643531dc
36 changed files with 1107 additions and 69 deletions

View File

@ -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

View File

@ -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

View File

@ -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?

View File

@ -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() {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -51,6 +51,7 @@
//= require ga
//= require utils
//= require subscription_utils
//= require profile_utils
//= require custom_controls
//= require react
//= require react_ujs

View File

@ -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
});
}

View File

@ -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) {

View File

@ -19,6 +19,8 @@
var NOT_SPECIFIED_TEXT = 'Not specified';
profileUtils.NOT_SPECIFIED_TEXT = NOT_SPECIFIED_TEXT
var proficiencyDescriptionMap = {
"1": "BEGINNER",
"2": "INTERMEDIATE",

View File

@ -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

View File

@ -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: () ->
`<div className="GenreCheckBoxList react-component">
<CheckBoxList objectName='genres' onItemChanged={this.props.onItemChanged} sourceObjects={this.genres} selectedObjects={this.props.selectedGenres}/>
<CheckBoxList objectName='genres' onItemChanged={this.props.onItemChanged} sourceObjects={this.state.genres} selectedObjects={this.props.selectedGenres}/>
</div>`
})

View File

@ -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: () ->
`<div className="SubjectCheckBoxList react-component">
<CheckBoxList objectName='subjects' onItemChanged={this.props.onItemChanged} sourceObjects={this.subjects} selectedObjects={this.props.selectedSubjects}/>
<CheckBoxList objectName='subjects' onItemChanged={this.props.onItemChanged} sourceObjects={this.state.subjects} selectedObjects={this.props.selectedSubjects}/>
</div>`
})

View File

@ -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
`<a className="edit-link" onClick={this.editProfile.bind(this, selected)}>{text}</a>`
editMusicProfileLink: (text, selected) ->
if @state.isSelf
`<a className="edit-link" onClick={this.editMusicProfile.bind(this, selected)}>{text}</a>`
teacherBio: (user, teacher) ->
if teacher.biography?
biography = teacher.biography
else
biography = 'No bio defined yet'
biography = biography.replace(/\n/g, "<br/>")
`<div className="section bio">
<h3>Teacher Profile {this.editProfileLink('edit profile', 'introduction')}</h3>
<div className="section-content">
<div dangerouslySetInnerHTML={{__html: biography}}></div>
</div>
</div>`
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 `<div className="section introductory-video">
<h3>Intro Video</h3>
<div className="section-content">
<div className="video-wrapper">
<div className="video-container">
<iframe src={videoUrl} frameborder="0" allowfullscreen="allowfullscreen"/>
</div>
</div>
</div>
</div>`
teachesInfo: (user, teacher) ->
teachesInfo = []
instruments = teacher.instruments.map((id) -> InstrumentStore.display(id))
instrumentData = instruments.join(', ')
teachesInfo.push(`<tr>
<td>Instruments</td>
<td>{instrumentData}</td>
</tr>`)
subjects = teacher.subjects.map((id) -> SubjectStore.display(id))
subjectsData = subjects.join(', ')
teachesInfo.push(`<tr>
<td>Subjects</td>
<td>{subjectsData}</td>
</tr>`)
genres = teacher.genres.map((id) -> GenreStore.display(id))
genresData = genres.join(', ')
teachesInfo.push(`<tr>
<td>Genres</td>
<td>{genresData}</td>
</tr>`)
levels = []
if teacher.teaches_beginner
levels.push('Beginner')
if teacher.teaches_intermediate
levels.push('Intermediate')
if teacher.teaches_advanced
levels.push('Advanced')
levelsData = levels.join(', ')
teachesInfo.push(`<tr>
<td>Levels</td>
<td>{levelsData}</td>
</tr>`)
ageData = '?'
if teacher.teaches_age_lower == 0 && teacher.teaches_age_upper == 0
ageData = 'Any'
else if teacher.teaches_age_lower != 0 && teacher.teaches_age_upper != 0
ageData = "#{teacher.teaches_age_lower} to #{teacher.teaches_age_upper}"
else if teacher.teaches_age_lower == 0
ageData = "Age #{teacher.teaches_age_upper} and younger"
else if teacher.teaches_age_upper == 0
ageData = "Ages #{teacher.teaches_age_lower} and up"
teachesInfo.push(`<tr>
<td>Ages</td>
<td>{ageData}</td>
</tr>`)
languages = teacher.languages.map((id) -> LanguageStore.display(id))
languagesData = languages.join(', ')
teachesInfo.push(`<tr>
<td>Languages</td>
<td>{languagesData}</td>
</tr>`)
`<div className="section teachers">
<h3>{this.state.user.first_name} Teaches {this.editProfileLink('edit teaching', 'basics')}</h3>
<div className="section-content">
<table className="jamtable" cellspacing="0" cellpadding="0" border="0">
<tbody>
{teachesInfo}
</tbody>
</table>
</div>
</div>`
musicianBio: (user, teacher) ->
if user.biography?
musicianBio = user.biography
else
musicianBio = 'None specified.'
musicianBio = musicianBio.replace(/\n/g, "<br/>")
`<div className="section musician-bio">
<h3>Musical Profile {this.editMusicProfileLink('update bio', '')}</h3>
<div className="section-content" dangerouslySetInnerHTML={{__html: musicianBio}}></div>
</div>`
plays: (user, teacher) ->
if user.profile.concert_count > 0
gigs = "Has played #{profileUtils.gigMap[user.profile.concert_count]} concert gigs"
else
gigs = "Has played an unknown # of concert gigs"
if user.profile.studio_session_count > 0
studioSessions = "Has played #{profileUtils.gigMap[user.profile.studio_session_count]} studio sessions"
else
studioSessions = "Has played an unknown # of studio sessions"
gigTable = `<table className="jamtable giginfo">
<tbody>
<tr>
<td>{gigs}</td>
</tr>
<tr>
<td>{studioSessions}</td>
</tr>
</tbody>
</table>`
display_instruments = []
for instrument in user.instruments
description = InstrumentStore.display(instrument.instrument_id)
proficiency = instrument.proficiency_level;
instrument_icon_url = context.JK.getInstrumentIcon256(instrument.instrument_id);
display_instruments.push(`<div className="profile-instrument">
<img src={instrument_icon_url} width="70" height="70"/><br />
<span>{description}</span><br />
<span className={proficiencyCssMap[proficiency]}>{proficiencyDescriptionMap[proficiency]}</span>
</div>`)
`<div className="section plays">
<h3>{this.state.user.first_name} Plays {this.editMusicProfileLink('update instruments', 'experience')}</h3>
<div className="section-content">
{display_instruments}
{gigTable}
</div>
<br className="clearall"/>
</div>`
createPresence: (className, url, img) ->
`<div className={className + "-presence logo online-presence-option"}>
<a href={url}><img className="logo" src={img}/></a>
</div>`
onlinePresences: (user, teacher) ->
online_presences = user.profile.online_presences
presences = []
if user.profile.website
# make sure website is rooted
website = user.profile.website
if website.indexOf('http') == -1
website = 'http://' + website
presences.push(@createPresence("user-website", website, "/assets/content/website-logo.png"))
matches = profileUtils.soundCloudPresences(online_presences)
if matches.length > 0
presences.push(@createPresence("soundcloud", "http://www.soundcloud.com/#{matches[0].username}",
"/assets/content/soundcloud-logo.png"))
matches = profileUtils.reverbNationPresences(online_presences)
if matches.length > 0
presences.push(@createPresence("reverbnation", "http://www.reverbnation.com/#{matches[0].username}",
"/assets/content/reverbnation-logo.png"))
matches = profileUtils.bandCampPresences(online_presences)
if matches.length > 0
presences.push(@createPresence("bandcamp", 'http://' + matches[0].username + '.bandcamp.com/',
"/assets/content/bandcamp-logo.png"))
matches = profileUtils.fandalismPresences(online_presences)
if matches.length > 0
presences.push(@createPresence("fandalism", 'http://www.fandalism.com/' + matches[0].username,
"/assets/content/fandalism-logo.png"))
matches = profileUtils.youTubePresences(online_presences)
if matches.length > 0
presences.push(@createPresence("youtube", 'http://www.youtube.com/' + matches[0].username,
"/assets/content/youtube-logo.png"))
matches = profileUtils.facebookPresences(online_presences)
if matches.length > 0
presences.push(@createPresence("facebook", 'http://www.facebook.com/' + matches[0].username,
"/assets/content/facebook-logo.png"))
matches = profileUtils.twitterPresences(online_presences)
if matches.length > 0
presences.push(@createPresence("twitter", 'http://www.twitter.com/' + matches[0].username,
"/assets/content/twitter-logo.png"))
if presences.length == 0
presences = `<div className="no-online-presence">None specified</div>`
`<div className="section plays">
<h3>Online Presence {this.editMusicProfileLink('update presence', 'samples')}</h3>
<div className="section-content">
{presences}
</div>
</div>`
about: () ->
user = @state.user
teacher = user.teacher
`<div className="about-block info-block">
{this.sampleVideo(user, teacher)}
{this.teacherBio(user, teacher)}
{this.teachesInfo(user, teacher)}
{this.musicianBio(user, teacher)}
{this.plays(user, teacher)}
{this.onlinePresences(user, teacher)}
</div>`
experiences: (title, attr, update, user, teacher) ->
teachingExperiences = []
for teachingExperience in teacher['experiences_' + attr]
years = ''
if teachingExperience.start_year > 0
if teachingExperience.end_year && teachingExperience.end_year > 0
years = "#{teachingExperience.start_year} - #{teachingExperience.end_year}"
else
years = "#{teachingExperience.start_year} - Present"
teachingExperiences.push(`<div className="experience">
<div className="years">{years}</div>
<h4>{teachingExperience.name}</h4>
<div className="org">{teachingExperience.organization}</div>
</div>`)
if update?
updateLink = this.editProfileLink('update ' + update, 'experience')
if teachingExperiences.length == 0
teachingExperiences = `<div>None specified</div>`
`<div className="section teaching-experience">
<h3>{title} {updateLink}</h3>
<div className="section-content">
{teachingExperiences}
</div>
</div>`
sampleClicked: (e) ->
e.preventDefault()
context.JK.popExternalLink($(e.target).attr('href'))
musicSamples: (user, teacher) ->
performance_samples = user.profile.performance_samples
jamkazamSamples = []
samples = profileUtils.jamkazamSamples(performance_samples);
for sample in samples
jamkazamSamples.push(`<a className="jamkazam-playable playable" href={"/recordings/" + sample.claimed_recording.id} onClick={this.sampleClicked}>{sample.claimed_recording.name}</a>`)
soundCloudSamples= []
samples = profileUtils.soundCloudSamples(performance_samples);
for sample in samples
soundCloudSamples.push(`<a className="sound-cloud-playable playable" href={sample.url} onClick={this.sampleClicked}>{sample.description}</a>`)
youTubeSamples = []
samples = profileUtils.youTubeSamples(performance_samples);
for sample in samples
youTubeSamples.push(`<a className="youtube-playable playable" href={sample.url} onClick={this.sampleClicked}>{sample.description}</a>`)
sampleJsx = []
if jamkazamSamples.length > 0
sampleJsx.push(`<div className="jamkazam-samples logo performance-sample-option">
<img className="logo" src="/assets/header/logo.png" />
{jamkazamSamples}
</div>`)
if soundCloudSamples.length > 0
sampleJsx.push(`<div className="soundcloud-samples logo performance-sample-option">
<img className="logo" src="/assets/content/soundcloud-logo.png" />
{soundCloudSamples}
</div>`)
if youTubeSamples.length > 0
sampleJsx.push(`<div className="youtube-samples logo performance-sample-option">
<img className="logo" src="/assets/content/youtube-logo.png" />
{youTubeSamples}
</div>`)
`<div className="section samples">
<h3>Performance Samples {this.editMusicProfileLink('update samples', 'samples')}</h3>
<div className="section-content">
{sampleJsx}
</div>
</div>`
experience: () ->
user = @state.user
teacher = user.teacher
`<div className="experience-block info-block">
{this.experiences('Teaching Experience', 'teaching', 'experience', user, teacher)}
{this.experiences('Music Education', 'education', null, user, teacher)}
{this.experiences('Music Awards', 'award', null, user, teacher)}
</div>`
samples: () ->
user = @state.user
teacher = user.teacher
`<div className="samples-block info-block">
{this.musicSamples(user, teacher)}
</div>`
ratings: () ->
user = @state.user
teacher = user.teacher
`<div className="ratings-block info-block">
Coming Soon!
</div>`
prices: () ->
user = @state.user
teacher = user.teacher
rows = []
for minutes in [30, 45, 60, 90, 120]
lesson_price = teacher["price_per_lesson_#{minutes}_cents"]
monthly_price = teacher["price_per_month_#{minutes}_cents"]
duration_enabled = teacher["lesson_duration_#{minutes}"]
console.log("lesson_price", lesson_price)
console.log("monthly_price", monthly_price)
console.log("duration neabled", duration_enabled)
if duration_enabled && teacher.prices_per_lesson && lesson_price? && lesson_price > 0
lessonPriceFormatted = '$ ' + (lesson_price / 100).toFixed(2)
else
lessonPriceFormatted = 'N/A'
if duration_enabled && teacher.prices_per_month && monthly_price? && monthly_price > 0
monthlyPriceFormatted = '$ ' + (monthly_price / 100).toFixed(2)
else
monthlyPriceFormatted = 'N/A'
rows.push(`<tr><td>{minutes + " Minutes"}</td><td>{lessonPriceFormatted}</td><td>{monthlyPriceFormatted}</td></tr>`)
`<div className="prices-block info-block">
<h3>Lesson Prices {this.editProfileLink('update prices', 'pricing')}</h3>
<div className="section-content">
<table className="jamtable price-table">
<thead>
<tr><th>LESSON LENGTH</th><th>PRICE PER LESSON</th><th>PRICE PER MONTH</th></tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
</div>
</div>`
mainContent: () ->
if @state.selected == @TILE_ABOUT
@about()
else if @state.selected == @TILE_EXPERIENCE
@experience()
else if @state.selected == @TILE_SAMPLES
@samples()
else if @state.selected == @TILE_RATINGS
@ratings()
else if @state.selected == @TILE_PRICES
@prices()
profileLeft: () ->
`<div className="profile-about-left">
<div className="left-content">
<div className="location">
<div className="city">{this.state.user.city}</div>
<div className="state">{this.state.user.state}</div>
<div className="country">{this.state.user.country}</div>
<div className="age">{this.state.user.age} years old</div>
</div>
<div className="activity">
<div className="last-signed-in">Last Signed In:</div>
<div>{"very recently"}</div>
</div>
<div className="backgroundCheck">
<div className="background-check">Background Check:</div>
<div className="last-verified">last verified</div>
<div className="last-verified-time">3 months ago</div>
</div>
</div>
</div>`
selectionMade: (selection, e) ->
e.preventDefault()
@setState({selected: selection})
render: () ->
`<div className="content-body-scroller">
if @state.user?
avatar = context.JK.resolveAvatarUrl(@state.user.photo_url);
if @state.user?.teacher?
mainContent = @mainContent()
profileLeft = @profileLeft()
editButton = `<a href="/client#/account/profile" className="button-orange edit-profile-btn hidden">EDIT PROFILE</a>`
actionButtons = `<div className="right hidden">
<a id="btn-add-friend" className="button-orange">ADD FRIEND</a>
<a id="btn-follow-user" className="button-orange">FOLLOW</a>
<a id="btn-message-user" className="button-orange">MESSAGE</a>
</div>`
profileSelections = []
for tile, i in @TILES
classes = classNames({last: i == @TILES.length - 1, active: @state.selected == tile})
profileSelections.push(`<div className="profile-tile"><a className={classes}
onClick={this.selectionMade.bind(this, tile)}>{tile}</a>
</div>`)
`<div className="content-body-scroller">
<div className="profile-header profile-head">
<div className="user-header">
<h2 id="username"></h2>
{editButton}
</div>
{actionButtons}
<br clear="all"/><br />
<div className="profile-photo">
<div className="avatar-profile">
<img width="200" height="200" src={avatar}/>
</div>
</div>
<div className="profile-nav">
{profileSelections}
</div>
<div className="clearall"></div>
</div>
<div className="profile-body">
<div className="profile-wrapper">
{profileLeft}
<div className="main-content">
{mainContent}
</div>
</div>
</div>
</div>`
})

View File

@ -70,7 +70,7 @@ rest = window.JK.Rest()
<input className="years-playing-experience" name="years_playing" ref="years_playing_experience" type="number" min="0" max="99" value={this.state.years_playing} onChange={this.handleTextChange} placeholder="Select" />
</div>
</div>
<TeacherSetupNav handleNav={this.handleNav}/>
<TeacherSetupNav hideBack={true} handleNav={this.handleNav}/>
</div>`
})

View File

@ -1,6 +1,7 @@
context = window
teacherActions = window.JK.Actions.Teacher
SessionActions = @SessionActions
ProfileActions = @ProfileActions
@TeacherSetupNav = React.createClass({
@ -9,19 +10,32 @@ SessionActions = @SessionActions
console.log("navBack this.props", this.state, this.props)
this.props.handleNav({direction: "back"})
navCancel: (e) ->
ProfileActions.cancelTeacherEdit()
navNext: (e) ->
e.preventDefault()
console.log("navNext this.props", this.state, this.props)
this.props.handleNav({direction: "next"})
render: () ->
#console.log("SetupNav: this.props", this.state, this.ref, this.props.handleNav)
if window.ProfileStore.solo
saveText = 'SAVE'
else
saveText = 'SAVE & NEXT'
if !this.props.hideBack && !window.ProfileStore.solo
back = `<a className="nav-button button-grey" onClick={this.navBack}>
BACK
</a>`
`<div className="TeacherSetupNav right">
<a className="nav-button button-grey" onClick={this.navBack}>
BACK
<a className="nav-button button-grey" onClick={this.navCancel}>
CANCEL
</a>
{back}
<a className="nav-button button-orange" onClick={this.navNext}>
NEXT
{saveText}
</a>
</div>`
})

View File

@ -0,0 +1,5 @@
context = window
@GenreActions = Reflux.createActions({
})

View File

@ -0,0 +1,12 @@
context = window
@ProfileActions = Reflux.createActions({
startTeacherEdit: {}
cancelTeacherEdit: {}
doneTeacherEdit: {}
startProfileEdit: {}
cancelProfileEdit: {}
doneProfileEdit: {}
editProfileNext: {}
})

View File

@ -0,0 +1,5 @@
context = window
@SubjectActions = Reflux.createActions({
})

View File

@ -0,0 +1,26 @@
$ = jQuery
context = window
logger = context.JK.logger
@GenreStore = Reflux.createStore(
{
listenables: @GenreActions
genres: []
genresLookup: {}
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (@app) ->
rest.getGenres().done (genres) =>
@genres = genres
for genre in genres
@genresLookup[genre.id] = genre.description
@trigger(@genres)
display: (id) ->
@genresLookup[id]
}
)

View File

@ -6,6 +6,7 @@ logger = context.JK.logger
{
listenables: @InstrumentActions
instruments: []
instrumentLookup: {}
init: ->
# Register with the app store to get @app
@ -14,7 +15,12 @@ logger = context.JK.logger
onAppInit: (@app) ->
rest.getInstruments().done (instruments) =>
@instruments = instruments
for instrument in instruments
@instrumentLookup[instrument.id] = instrument.description
@trigger(@instruments)
display: (id) ->
@instrumentLookup[id]
}
)

View File

@ -6,6 +6,7 @@ logger = context.JK.logger
{
listenables: @LanguageActions
languages: []
languageLookup: {}
init: ->
# Register with the app store to get @app
@ -14,7 +15,11 @@ logger = context.JK.logger
onAppInit: (@app) ->
rest.getLanguages().done (languages) =>
@languages = languages
for language in @languages
@languageLookup[language.id] = language.description
@trigger(@languages)
display: (id) ->
@languageLookup[id]
}
)

View File

@ -0,0 +1,101 @@
$ = jQuery
context = window
logger = context.JK.logger
ProfileActions = @ProfileActions
@ProfileStore = Reflux.createStore(
{
listenables: ProfileActions
returnNav: null
solo: false
# step can be:
# introduction
# basics
# pricing
# experience
onStartProfileEdit: (step, solo) ->
if !step?
step = ''
if step != '' && step != 'samples' && step != 'interests' && step != 'experience'
alert("invalid step: " + step)
return
@solo = solo
@returnNav = window.location.href
window.location = '/client#/account/profile/' + step
onDoneProfileEdit: () ->
if @returnNav
window.location = @returnNav
@returnNav = null
else
window.location = "/client#/profile/" + context.JK.currentUserId;
@solo = false
onCancelProfileEdit: () ->
if @returnNav
window.location = @returnNav
@returnNav = null
else
window.location = '/client#/profile/' + context.JK.currentUserId
@solo = false
onStartTeacherEdit: (step, solo) ->
if !step?
step = 'introduction'
if step != 'introduction' && step != 'basics' && step != 'pricing' && step != 'experience'
alert("invalid step: " + step)
return
@solo = solo
@returnNav = window.location.href
window.location = '/client#/teachers/setup/' + step
onDoneTeacherEdit: () ->
if @solo
if @returnNav
window.location = @returnNav
@returnNav = null
else
window.location = '/client#/home'
@solo = false
onCancelTeacherEdit: () ->
if @returnNav
window.location = @returnNav
@returnNav = null
else
window.location = '/client#/home'
@solo = false
onEditProfileNext: (step) ->
if @solo
if @returnNav
window.location = @returnNav
@returnNav = null
else
window.location = '/client#/home'
@solo = false
else
context.location = "/client#/account/profile/" + step
}
)

View File

@ -0,0 +1,27 @@
$ = jQuery
context = window
logger = context.JK.logger
@SubjectStore = Reflux.createStore(
{
listenables: @SubjectActions
subjects: []
subjectLookup: {}
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (@app) ->
rest.getSubjects().done (subjects) =>
@subjects = subjects
for subject in subjects
@subjectLookup[subject.id] = subject.description
@trigger(@subjects)
display: (id) ->
@subjectLookup[id]
}
)

View File

@ -56,9 +56,12 @@ EVENTS = context.JK.EVENTS
.done((savedTeacher) =>
logger.debug("SAVED TEACHER",savedTeacher)
this.trigger({teacher: savedTeacher})
if instructions.navTo?
logger.debug("NAVIGATING TO",instructions.navTo)
window.location = instructions.navTo
if ProfileStore.solo
ProfileActions.doneTeacherEdit()
else
if instructions.navTo?
logger.debug("NAVIGATING TO",instructions.navTo)
window.location = instructions.navTo
).fail((jqXHR, textStatus, errorMessage) =>
logger.debug("FAILED",jqXHR, textStatus, errorMessage)
#errors = JSON.parse(jqXHR.responseText)

View File

@ -205,7 +205,7 @@ mixins.push(Reflux.listenTo(VideoStore, 'onVideoStateChanged'))
componentWillUpdate: (nextProps, nextState) ->
# protect against non-video clients pointed at video-enabled server from getting into a session
@logger.debug("webcam devices", nextState.deviceNames, @state.deviceNames)
#@logger.debug("webcam devices", nextState.deviceNames, @state.deviceNames)
if !@initialScan?
@initialScan = true
@ -352,7 +352,7 @@ mixins.push(Reflux.listenTo(VideoStore, 'onVideoStateChanged'))
webcamName = null
# protect against non-video clients pointed at video-enabled server from getting into a session
webcam = state.currentDevice
@logger.debug("currently selected video device", webcam)
#@logger.debug("currently selected video device", webcam)
if (webcam? && Object.keys(webcam).length>0)
webcamName = Object.keys(webcam)[0]

View File

@ -0,0 +1,165 @@
@import "client/common";
#teacher-profile {
div[data-react-class="TeacherProfile"] {
height:100%;
}
.profile-nav a {
position: absolute;
text-align: center;
height: 100%;
width: 98%;
margin: 0 auto;
padding: 57px 0 0 0;
@include border-box_sizing;
}
.profile-tile {
width:20%;
float:left;
@include border-box_sizing;
height:87px;
position:relative;
}
.profile-about-left {
@include border-box_sizing;
width:16%;
}
.profile-body {
padding-top:159px;
}
.left-content {
width:88px;
text-align:center;
}
.profile-photo {
width:16%;
@include border-box_sizing;
}
.profile-nav {
margin:0;
width:84%;
}
.profile-wrapper {
padding:10px 20px
}
.main-content {
float:left;
@include border-box_sizing;
width:84%;
}
.edit-link {
font-size:12px;
margin-left:11px;
font-weight:normal;
}
.activity {
margin-top:20px;
}
.backgroundCheck {
margin-top:20px;
}
.introductory-video {
float:right;
width:40%;
position:relative;
}
.info-block {
h3 {
font-weight:bold;
font-size:18px;
margin-bottom:10px;
}
.section {
margin-bottom:40px;
&.teachers {
clear:both;
}
}
table.jamtable {
font-size:14px;
width:100%;
&.price-table {
max-width:600px;
th:nth-child(2) {
text-align:right;
}
th:nth-child(3) {
text-align:right;
}
td:nth-child(2) {
text-align:right;
}
td:nth-child(3) {
text-align:right;
}
}
&.giginfo {
top: 10px;
position: relative;
min-width: 200px;
width:auto;
}
}
}
.online-presence-option, .performance-sample-option {
display:block;
vertical-align:middle;
&.no-online-presence {
display:block;
}
float:left;
margin-right:20px;
margin-top:20px;
}
.performance-sample-option {
margin-right:40px;
a {
display:block;
}
img {
margin-bottom:5px;
}
}
.video-container {
width: 100%;
padding-bottom: 53.33%;
}
.profile-instrument {
float: left;
margin-right: 15px;
text-align: center;
}
.experience {
width:600px;
margin-bottom:20px;
h4 {
font-size:14px;
font-weight:bold;
margin-bottom:6px;
color:white;
}
.org {
font-size:14px;
}
.years {float:right}
}
}

View File

@ -77,6 +77,10 @@ body.web.individual_jamtrack {
}
}
.edit-link {
font-size:12px;
}
.video-container {
width: 420px;
padding-bottom: 53.33%;

View File

@ -1,6 +1,6 @@
class ApiTeachersController < ApiController
before_filter :api_signed_in_user, :except => [:index, :show]
before_filter :api_signed_in_user, :except => [:index, :detail]
before_filter :auth_teacher, :only => [:update, :delete]
before_filter :auth_user, :only => [:create, :update]

View File

@ -34,6 +34,9 @@ ApiUsersController < ApiController
def show
@user=lookup_user
@show_teacher = params[:show_teacher]
@show_profile = params[:show_profile]
respond_with @user, responder: ApiResponder, :status => 200
end

View File

@ -35,24 +35,24 @@ attributes :id,
:errors
node :instruments do
@teacher.instruments.collect{|o|o.id}
node :instruments do |teacher|
teacher.instruments.collect{|o|o.id}
end
node :subjects do
@teacher.subjects.collect{|o|o.id}
node :subjects do |teacher|
teacher.subjects.collect{|o|o.id}
end
node :genres do
@teacher.genres.collect{|o|o.id}
node :genres do |teacher|
teacher.genres.collect{|o|o.id}
end
node :languages do
@teacher.languages.collect{|o|o.id}
node :languages do |teacher|
teacher.languages.collect{|o|o.id}
end
node :experiences_teaching do
@teacher.experiences_teaching.collect do |o|
node :experiences_teaching do |teacher|
teacher.experiences_teaching.collect do |o|
{
name: o.name,
experience_type: o.experience_type,
@ -63,8 +63,8 @@ node :experiences_teaching do
end # collect
end
node :experiences_education do
@teacher.experiences_education.collect do |o|
node :experiences_education do |teacher|
teacher.experiences_education.collect do |o|
{
name: o.name,
experience_type: o.experience_type,
@ -75,8 +75,8 @@ node :experiences_education do
end # collect
end
node :experiences_award do
@teacher.experiences_award.collect do |o|
node :experiences_award do |teacher|
teacher.experiences_award.collect do |o|
{
name: o.name,
experience_type: o.experience_type,

View File

@ -9,6 +9,19 @@ 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)
end
end
if @show_profile
node :profile do
partial("api_users/profile_show", :object => @user)
end
end
# give back more info if the user being fetched is yourself
if @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
@ -115,3 +128,4 @@ end
node :last_jam_audio_latency do |user|
user.last_jam_audio_latency.round if user.last_jam_audio_latency
end

View File

@ -250,7 +250,7 @@
var accountProfileInterests = new JK.AccountProfileInterests(JK.app);
accountProfileInterests.initialize();
var accountProfileSamples = new JK.AccountProfileSamples(JK.app, $(".account-profile-samples"), api.getUserProfile, api.updateUser)
var accountProfileSamples = new JK.AccountProfileSamples(JK.app, $(".screen.account-profile-samples"), api.getUserProfile, api.updateUser)
accountProfileSamples.initialize();
var accountAudioProfile = new JK.AccountAudioProfile(JK.app);

View File

@ -1,4 +1,4 @@
#teacher-profile.screen.secondary layout="screen" layout-id="teachers/profile" layout-arg="id"
#teacher-profile.screen.secondary.no-login-required layout="screen" layout-id="profile/teacher" layout-arg="id"
.content-head
.content-icon
= image_tag "content/icon_profile.png", :size => "19x19"
@ -6,6 +6,5 @@
| teacher profile
= render "screen_navigation"
.content-body
.content-body-scroller
= react_component 'TeacherProfile', {}
= react_component 'TeacherProfile', {}