mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-27 06:23:41 -04:00
Merge branch 'inline-profile'
This commit is contained in:
commit
f3d5b724de
10 changed files with 903 additions and 146 deletions
app
locale
models
schemas/models
styles
templates/account
views
|
@ -26,6 +26,14 @@
|
|||
minutes: "minutes"
|
||||
hour: "hour"
|
||||
hours: "hours"
|
||||
day: "day"
|
||||
days: "days"
|
||||
week: "week"
|
||||
weeks: "weeks"
|
||||
month: "month"
|
||||
months: "months"
|
||||
year: "year"
|
||||
years: "years"
|
||||
|
||||
modal:
|
||||
close: "Close"
|
||||
|
@ -186,6 +194,7 @@
|
|||
|
||||
account_profile:
|
||||
edit_settings: "Edit Settings"
|
||||
done_editing_settings: "Done Editing"
|
||||
profile_for_prefix: "Profile for "
|
||||
profile_for_suffix: ""
|
||||
approved: "Approved"
|
||||
|
@ -193,10 +202,99 @@
|
|||
looking_for: "Looking for:"
|
||||
last_updated: "Last updated:"
|
||||
contact: "Contact"
|
||||
active: "Looking for interview offers now"
|
||||
inactive: "Not looking for offers right now"
|
||||
complete: "complete"
|
||||
next: "Next"
|
||||
next_city: "city?"
|
||||
next_country: "pick your country."
|
||||
next_name: "name?"
|
||||
next_short_description: "summarize yourself at a glance."
|
||||
next_long_description: "describe the work you're looking for."
|
||||
next_skills: "list at least five skills."
|
||||
next_work: "list your work experience."
|
||||
next_education: "recount your educational ordeals."
|
||||
next_projects: "show off up to three projects you've worked on."
|
||||
next_links: "add any personal or social links."
|
||||
next_photo: "add an optional professional photo."
|
||||
next_active: "mark yourself open to offers to show up in searches."
|
||||
example_blog: "Your Blog"
|
||||
example_github: "Your GitHub"
|
||||
links_header: "Personal Links"
|
||||
links_blurb: "Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog."
|
||||
links_name: "Link Name"
|
||||
links_name_help: "What are you linking to?"
|
||||
links_link_blurb: "Link URL"
|
||||
basics_header: "Update basic info"
|
||||
basics_active: "Open to Offers"
|
||||
basics_active_help: "Want interview offers right now?"
|
||||
basics_job_title: "Desired Job Title"
|
||||
basics_job_title_help: "What role are you looking for?"
|
||||
basics_city: "City"
|
||||
basics_city_help: "City you want to work in (or live in now)."
|
||||
basics_country: "Country"
|
||||
basics_country_help: "Country you want to work in (or live in now)."
|
||||
basics_visa: "US Work Status"
|
||||
basics_visa_help: "Are you authorized to work in the US, or do you need visa sponsorship?"
|
||||
basics_looking_for: "Looking For"
|
||||
basics_looking_for_full_time: "Full-time"
|
||||
basics_looking_for_part_time: "Part-time"
|
||||
basics_looking_for_remote: "Remote"
|
||||
basics_looking_for_contracting: "Contracting"
|
||||
basics_looking_for_internship: "Internship"
|
||||
basics_looking_for_help: "What kind of developer position do you want?"
|
||||
name_header: "Fill in your name"
|
||||
name_anonymous: "Anonymous Developer"
|
||||
name_help: "Name you want employers to see, like 'Nick Winter'."
|
||||
short_description_header: "Write a short description of yourself"
|
||||
short_description_blurb: "Add a blurb here that will show, at a glance, whether you might be just the developer that an employer is looking for."
|
||||
short_description: "Short Description"
|
||||
short_description_help: "Who are you, and what are you looking for? 140 characters max."
|
||||
skills_header: "Skills"
|
||||
skills_help: "Tag relevant developer skills in order of proficiency."
|
||||
long_description_header: "Detail your desired position"
|
||||
long_description_blurb_1: "Write a little longer section here to describe the role you would like to pursue next."
|
||||
long_description_blurb_2: "Talk about how awesome you are and why it would be a good idea to hire you."
|
||||
long_description: "Description"
|
||||
long_description_help: "Describe yourself to potential employers. Keep it short and to the point. We recommend outlining the position that would most interest you. Tasteful markdown okay; 600 characters max."
|
||||
work_experience: "Work Experience"
|
||||
work_header: "Chronicle your work history"
|
||||
work_years: "Years of Experience"
|
||||
work_years_help: "How many years of professional experience (getting paid) developing software do you have?"
|
||||
work_blurb: "List your relevant work experience, most recent first."
|
||||
work_employer: "Employer"
|
||||
work_employer_help: "Name of your employer."
|
||||
work_role: "Job Title"
|
||||
work_role_help: "What was your job title or role?"
|
||||
work_duration: "Duration"
|
||||
work_duration_help: "When did you hold this gig?"
|
||||
work_description: "Description"
|
||||
work_description_help: "What did you do there? (140 chars; optional)"
|
||||
education: "Education"
|
||||
education_header: "Recount your academic ordeals"
|
||||
education_blurb: "List your academic ordeals."
|
||||
education_school: "School"
|
||||
education_school_help: "Name of your school."
|
||||
education_degree: "Degree"
|
||||
education_degree_help: "What was your degree and field of study?"
|
||||
education_duration: "Dates"
|
||||
education_duration_help: "When?"
|
||||
education_description: "Description"
|
||||
education_description_help: "Highlight anything about this educational experience. (140 chars; optional)"
|
||||
our_notes: "Our Notes"
|
||||
projects: "Projects"
|
||||
projects_header: "Add 3 projects"
|
||||
projects_header_2: "Projects (Top 3)"
|
||||
projects_blurb: "Highlight your projects to amaze employers."
|
||||
project_name: "Project Name"
|
||||
project_name_help: "What was the project called?"
|
||||
project_description: "Description"
|
||||
project_description_help: "Briefly describe the project."
|
||||
project_picture: "Picture"
|
||||
project_picture_help: "Upload a 230x115px or larger image showing off the project."
|
||||
project_link: "Link"
|
||||
project_link_help: "Link to the project."
|
||||
|
||||
|
||||
employers:
|
||||
want_to_hire_our_players: "Want to hire expert CodeCombat players?"
|
||||
|
|
|
@ -16,6 +16,13 @@ module.exports = class ThangType extends CocoModel
|
|||
@on 'sync', @setDefaults
|
||||
@spriteSheets = {}
|
||||
|
||||
## Testing memory clearing
|
||||
#f = =>
|
||||
# console.info 'resetting raw data'
|
||||
# @unset 'raw'
|
||||
# @_previousAttributes.raw = null
|
||||
#setTimeout f, 40000
|
||||
|
||||
setDefaults: ->
|
||||
@resetRawData() unless @get('raw')
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ UserSchema = c.object {},
|
|||
name: c.shortString {title: 'Name', description: 'Name you want employers to see, like "Nick Winter".'}
|
||||
city: c.shortString {title: 'City', description: 'City you want to work in (or live in now), like "San Francisco" or "Lubbock, TX".', default: 'Defaultsville, CA', format: 'city'}
|
||||
country: c.shortString {title: 'Country', description: 'Country you want to work in (or live in now), like "USA" or "France".', default: 'USA', format: 'country'}
|
||||
skills: c.array {title: 'Skills', description: 'Tag relevant developer skills in order of proficiency. Employers will see the first five at a glance.', default: ['javascript'], minItems: 1, maxItems: 30, uniqueItems: true},
|
||||
skills: c.array {title: 'Skills', description: 'Tag relevant developer skills in order of proficiency.', default: ['javascript'], minItems: 1, maxItems: 30, uniqueItems: true},
|
||||
{type: 'string', minLength: 1, maxLength: 20, description: 'Ex.: "objective-c", "mongodb", "rails", "android", "javascript"', format: 'skill'}
|
||||
experience: {type: 'integer', title: 'Years of Experience', minimum: 0, description: 'How many years of professional experience (getting paid) developing software do you have?'}
|
||||
shortDescription: {type: 'string', maxLength: 140, title: 'Short Description', description: 'Who are you, and what are you looking for? 140 characters max.', default: 'Programmer seeking to build great software.'}
|
||||
|
@ -103,7 +103,7 @@ UserSchema = c.object {},
|
|||
link: c.url {title: 'Link', description: 'Link to the project.', default: 'http://example.com'}
|
||||
links: c.array {title: 'Personal and Social Links', description: 'Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog.'},
|
||||
c.object {title: 'Link', description: 'A link to another site you want to highlight, like your GitHub, your LinkedIn, or your blog.', required: ['name', 'link']},
|
||||
name: {type: 'string', maxLength: 30, title: 'Link Name', description: 'What are you linking to? Ex: "Personal Website", "Twitter"', format: 'link-name'}
|
||||
name: {type: 'string', maxLength: 30, title: 'Link Name', description: 'What are you linking to? Ex: "Personal Website", "GitHub"', format: 'link-name'}
|
||||
link: c.url {title: 'Link', description: 'The URL.', default: 'http://example.com'}
|
||||
photoURL: {type: 'string', format: 'image-file', title: 'Profile Picture', description: 'Upload a 256x256px or larger image if you want to show a different profile picture to employers than your normal avatar.'}
|
||||
|
||||
|
|
|
@ -1,19 +1,41 @@
|
|||
@import "app/styles/bootstrap/variables"
|
||||
|
||||
#profile-view
|
||||
$sideBackground: rgb(220, 220, 220)
|
||||
|
||||
.profile-control-bar
|
||||
background-color: rgb(78, 78, 78)
|
||||
width: 100%
|
||||
text-align: center
|
||||
max-height: 70px
|
||||
|
||||
button.edit-settings-button
|
||||
button
|
||||
margin: 2px
|
||||
i
|
||||
margin-right: 5px
|
||||
|
||||
.approved, .not-approved
|
||||
display: none
|
||||
|
||||
.sample-profile
|
||||
position: absolute
|
||||
right: 5px
|
||||
|
||||
.profile-completion-progress
|
||||
width: 100%
|
||||
display: inline-block
|
||||
height: 33px
|
||||
margin-bottom: 0
|
||||
border-radius: 0
|
||||
background-color: $sideBackground
|
||||
|
||||
.progress-bar
|
||||
line-height: 33px
|
||||
font-size: 16px
|
||||
text-overflow: ellipsis
|
||||
overflow: hidden
|
||||
white-space: nowrap
|
||||
|
||||
.main-content-area
|
||||
padding: 0
|
||||
background-color: white
|
||||
|
||||
.flat-button
|
||||
width: 100%
|
||||
|
@ -68,20 +90,20 @@
|
|||
.left-column
|
||||
width: $side-width - 2 * $side-padding
|
||||
padding: $side-padding
|
||||
background-color: rgb(220, 220, 220)
|
||||
background-color: $sideBackground
|
||||
|
||||
.sub-column
|
||||
width: $side-width - 2 * $side-padding
|
||||
overflow-wrap: break-word
|
||||
|
||||
.profile-photo-container
|
||||
#profile-photo-container
|
||||
position: relative
|
||||
margin-bottom: 10px
|
||||
|
||||
img.profile-photo
|
||||
width: $side-width - 2 * $side-padding
|
||||
border-radius: 6px
|
||||
|
||||
|
||||
.profile-caption
|
||||
background-color: rgba(0, 0, 0, 0.5)
|
||||
color: white
|
||||
|
@ -128,7 +150,7 @@
|
|||
overflow-wrap: break-word
|
||||
|
||||
code
|
||||
background-color: rgb(220, 220, 220)
|
||||
background-color: $sideBackground
|
||||
color: #555
|
||||
margin: 2px 0
|
||||
display: inline-block
|
||||
|
@ -161,7 +183,7 @@
|
|||
|
||||
.right-column
|
||||
width: $side-width
|
||||
background-color: rgb(220, 220, 220)
|
||||
background-color: $sideBackground
|
||||
|
||||
.sub-column
|
||||
width: $side-width - 2 * $side-padding
|
||||
|
@ -176,7 +198,7 @@
|
|||
li
|
||||
margin-bottom: 10px
|
||||
padding: 5px 3px
|
||||
border: 2px solid rgb(220, 220, 220)
|
||||
border: 2px solid $sideBackground
|
||||
transition: .5s ease-in-out
|
||||
position: relative
|
||||
background-color: white
|
||||
|
@ -196,25 +218,101 @@
|
|||
left: 0
|
||||
z-index: 1
|
||||
|
||||
.project-image
|
||||
width: 230px
|
||||
height: 115px
|
||||
background-size: cover
|
||||
background-repeat: no-repeat
|
||||
background-position: center
|
||||
.project-image
|
||||
width: 230px
|
||||
height: 115px
|
||||
background-size: cover
|
||||
background-repeat: no-repeat
|
||||
background-position: center
|
||||
|
||||
-webkit-filter: grayscale(100%)
|
||||
-webkit-transition: .5s ease-in-out
|
||||
-moz-filter: grayscale(100%)
|
||||
-moz-transition: .5s ease-in-out
|
||||
-o-filter: grayscale(100%)
|
||||
-o-transition: .5s ease-in-out
|
||||
filter: grayscale(100%)
|
||||
transition: .5s ease-in-out
|
||||
-webkit-filter: grayscale(100%)
|
||||
-webkit-transition: .5s ease-in-out
|
||||
-moz-filter: grayscale(100%)
|
||||
-moz-transition: .5s ease-in-out
|
||||
-o-filter: grayscale(100%)
|
||||
-o-transition: .5s ease-in-out
|
||||
filter: grayscale(100%)
|
||||
transition: .5s ease-in-out
|
||||
|
||||
li:hover
|
||||
.project-image
|
||||
-webkit-filter: grayscale(0%)
|
||||
-moz-filter: grayscale(0%)
|
||||
-o-filter: grayscale(0%)
|
||||
filter: grayscale(0%)
|
||||
ul.projects li:hover .project-image, .project-image:hover
|
||||
-webkit-filter: grayscale(0%)
|
||||
-moz-filter: grayscale(0%)
|
||||
-o-filter: grayscale(0%)
|
||||
filter: grayscale(0%)
|
||||
|
||||
.main-content-area
|
||||
|
||||
.job-profile-container
|
||||
.editable-section
|
||||
position: relative
|
||||
transition: box-shadow 0.5s easeInOutQuad
|
||||
|
||||
&.just-saved
|
||||
box-shadow: 0px 0px 80px 0px #080
|
||||
z-index: 1
|
||||
|
||||
.editable-form
|
||||
display: none
|
||||
background-color: white
|
||||
padding: 5px 5px 5px 5px
|
||||
|
||||
.skill-array-item
|
||||
display: inline-block
|
||||
|
||||
input
|
||||
width: 120px
|
||||
margin: 5px
|
||||
|
||||
.project-image
|
||||
width: 210px
|
||||
height: 105px
|
||||
cursor: pointer
|
||||
|
||||
.editable-icon
|
||||
display: none
|
||||
|
||||
.job-profile-container.editable-profile
|
||||
|
||||
.full-height-column.deemphasized
|
||||
background-color: $sideBackground
|
||||
|
||||
.saving
|
||||
opacity: 0.75
|
||||
|
||||
.editable-thinner
|
||||
padding-right: 30px
|
||||
|
||||
.editable-icon
|
||||
display: block
|
||||
position: absolute
|
||||
right: 5px
|
||||
top: 5px
|
||||
font-size: 20px
|
||||
color: $blue
|
||||
opacity: 0.5
|
||||
|
||||
.edit-label
|
||||
color: $blue
|
||||
|
||||
.edit-example-button
|
||||
background-color: transparentize($blue, 0.25)
|
||||
|
||||
.edit-example-text
|
||||
color: $blue
|
||||
|
||||
code.edit-example-tag
|
||||
color: $blue
|
||||
|
||||
.editable-section.deemphasized:not(.just-saved), .our-notes-section.deemphasized
|
||||
opacity: 0.5
|
||||
|
||||
.editable-section:hover
|
||||
cursor: pointer
|
||||
outline: 1px solid $blue
|
||||
|
||||
.editable-icon
|
||||
opacity: 1.0
|
||||
cursor: pointer
|
||||
|
||||
.editable-form
|
||||
cursor: default
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
@import "bootstrap/variables"
|
||||
@import "bootstrap/mixins"
|
||||
@import "bootstrap/variables"
|
||||
|
||||
html
|
||||
background-color: #2f261d
|
||||
|
|
|
@ -1,121 +1,429 @@
|
|||
extends /templates/base
|
||||
|
||||
block content
|
||||
|
||||
if myProfile || (me.isAdmin() && user.get('jobProfile'))
|
||||
if allowedToEditJobProfile
|
||||
.profile-control-bar
|
||||
if myProfile
|
||||
a(href=user.get('jobProfile') ? "/account/settings#job-profile" : "/account/settings")
|
||||
button.btn.edit-settings-button
|
||||
i.icon-cog
|
||||
span(data-i18n="account_profile.edit_settings") Edit Settings
|
||||
if me.isAdmin() && user.get('jobProfile')
|
||||
button.btn.edit-settings-button#toggle-job-profile-approved
|
||||
if editing
|
||||
button.btn.btn-success#toggle-editing
|
||||
i.icon-ok
|
||||
span(data-i18n="account_profile.done_editing_settings") Done Editing
|
||||
else
|
||||
button.btn#toggle-editing
|
||||
i.icon-cog
|
||||
span(data-i18n='account_profile.approved').approved Approved
|
||||
span(data-i18n='account_profile.not_approved').not-approved Not Approved
|
||||
if user.id != me.id
|
||||
button.btn.edit-settings-button#enter-espionage-mode 007
|
||||
span(data-i18n="account_profile.edit_settings") Edit Settings
|
||||
if profile.active
|
||||
button.btn.btn-success#toggle-job-profile-active
|
||||
i.icon-eye-open
|
||||
span(data-i18n="account_profile.active") Looking for interview offers now
|
||||
else
|
||||
button.btn#toggle-job-profile-active
|
||||
i.icon-eye-close
|
||||
span(data-i18n="account_profile.inactive") Not looking for offers right now
|
||||
if profileApproved
|
||||
button.btn.btn-success#toggle-job-profile-approved
|
||||
i.icon-eye-open
|
||||
span(data-i18n='account_profile.approved') Approved
|
||||
else if me.isAdmin()
|
||||
button.btn#toggle-job-profile-approved
|
||||
i.icon-eye-close
|
||||
span(data-i18n='account_profile.not_approved') Not Approved
|
||||
if me.isAdmin() && !myProfile
|
||||
button.btn.edit-settings-button#enter-espionage-mode 007
|
||||
if editing && myProfile
|
||||
a.sample-profile(href="http://codecombat.com/images/pages/account/profile/sample_profile.png", target="_blank")
|
||||
button.btn
|
||||
i.icon-user
|
||||
span(data-i18n="account_settings.sample_profile") See a sample profile
|
||||
if editing
|
||||
.progress.profile-completion-progress
|
||||
.progress-bar.progress-bar-success(style="width: #{100 * progress}%")
|
||||
|
||||
if user.get('jobProfile') && allowedToViewJobProfile
|
||||
- var profile = user.get('jobProfile');
|
||||
.job-profile-container
|
||||
if profile && allowedToViewJobProfile
|
||||
div(class="job-profile-container" + (editing ? " editable-profile" : ""))
|
||||
.job-profile-row
|
||||
.left-column.full-height-column
|
||||
.sub-column
|
||||
.profile-photo-container
|
||||
#profile-photo-container.editable-section(title="Click to change your photo")
|
||||
.editable-icon.glyphicon.glyphicon-pencil
|
||||
img.profile-photo(src=user.getPhotoURL(240, true))
|
||||
.profile-caption= profile.jobTitle || 'Software Developer'
|
||||
|
||||
if profileLinks.length
|
||||
ul.links
|
||||
each link in profileLinks
|
||||
if link.link && link.name
|
||||
li(title=profile.name + " on " + link.name, class=link.icon ? "has-icon" : "")
|
||||
a(href=link.link)
|
||||
if link.icon
|
||||
img(src=link.icon.url, alt=link.icon.name)
|
||||
else
|
||||
button.btn.btn-large.btn-inverse.flat-button= link.name
|
||||
#links-container.editable-section
|
||||
.editable-display(title="Click to add social and personal links")
|
||||
.editable-icon.glyphicon.glyphicon-pencil
|
||||
if profileLinks && profileLinks.length
|
||||
ul.links.editable-thinner
|
||||
each link in profileLinks
|
||||
if link.link && link.name
|
||||
li(title=profile.name + " on " + link.name, class=link.icon ? "has-icon" : "")
|
||||
a(href=link.link)
|
||||
if link.icon
|
||||
img(src=link.icon.url, alt=link.icon.name)
|
||||
else
|
||||
button.btn.btn-large.btn-inverse.flat-button= link.name
|
||||
else if editing
|
||||
h3.edit-label(data-i18n="account_profile.add_links") Add some links
|
||||
button.btn.btn-large.btn-inverse.flat-button.edit-example-button(data-i18n="account_profile.example_blog") Your Blog
|
||||
button.btn.btn-large.btn-inverse.flat-button.edit-example-button(data-i18n="account_profile.example_githu") Your GitHub
|
||||
|
||||
form.editable-form
|
||||
.editable-icon.glyphicon.glyphicon-remove
|
||||
h3(data-i18n="account_profile.links_header") Personal Links
|
||||
p.help-block(data-i18n="account_profile.links_blurb") Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog.
|
||||
.editable-array(data-property='links')
|
||||
for link, index in (profile.links || []).concat({})
|
||||
.array-item.link-container.well.well-sm
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.links_name") Link Name
|
||||
input.form-control(type='link-name', maxlength='30', data-schemaformat='link-name', name="root[links][#{index}][name]", value=link.name, data-autocomplete="commonLinkNames", data-autocomplete-min-length=0)
|
||||
if !index
|
||||
p.help-block
|
||||
span(data-i18n="account_profile.links_name_help") What are you linking to?
|
||||
| Ex.: 'Personal Website', 'GitHub'
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.links_link") Link URL
|
||||
input.form-control(type='url', pattern='^(ht|f)tp(s?)://[0-9a-zA-Z]([-.w]*[0-9a-zA-Z])*(:(0-9)*)*(/?)([a-zA-Z0-9-.?,\'/\+&%$#_=]*)?$', data-schemaformat='url', name="root[links][#{index}][link]", value=link.link)
|
||||
if !index
|
||||
p.help-block Ex.: "https://github.com/nwinter"
|
||||
button.btn.btn-success.btn-block.save-section(data-i18n="common.save") Save
|
||||
|
||||
.editable-section.basic-info-container
|
||||
- var editableDefaults = editing && profile.city == jobProfileSchema.properties.city.default
|
||||
div(class="editable-display" + (editableDefaults ? " edit-example-text" : ""), title="Click to edit your basic info")
|
||||
.editable-icon.glyphicon.glyphicon-pencil
|
||||
if editableDefaults
|
||||
h3.edit-label(data-i18n="account_profile.basics_header") Update basic info
|
||||
div= profile.city + ', ' + profile.country
|
||||
div= profile.visa
|
||||
div
|
||||
span(data-i18n="account_profile.looking_for") Looking for:
|
||||
| #{profile.lookingFor}
|
||||
div
|
||||
span(data-i18n="account_profile.last_updated") Last updated:
|
||||
| #{moment(profile.updated).fromNow()}
|
||||
|
||||
form.editable-form
|
||||
.editable-icon.glyphicon.glyphicon-remove
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.basics_active") Open to Offers
|
||||
select.form-control(name='root[active]')
|
||||
option(value='1', selected=profile.active, data-i18n="account_profile.active") Looking for interview offers now
|
||||
option(value='', selected=!profile.active, data-i18n="account_profile.inactive") Not looking for offers right now
|
||||
p.help-block(data-i18n="account_profile.basics_active_help") Want interview offers right now?
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.basics_job_title") Desired Job Title
|
||||
input.form-control(type='text', maxlength='50', name='root[jobTitle]', value=profile.jobTitle)
|
||||
p.help-block
|
||||
span(data-i18n="account_profile.basics_job_title_help") What role are you looking for?
|
||||
| Ex.: "Full Stack Engineer", "Front-End Developer", "iOS Developer"
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.basics_city") City
|
||||
input.form-control(type='city', maxlength='100', data-schemaformat='city', name='root[city]', value=profile.city, data-autocomplete="commonCities", data-autocomplete-min-length=1)
|
||||
p.help-block
|
||||
span(data-i18n="account_profile.basics_city_help") City you want to work in (or live in now).
|
||||
| Ex.: "San Francisco", "Lubbock, TX"
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.basics_country") Country
|
||||
input.form-control(type='country', maxlength='100', data-schemaformat='country', name='root[country]', value=profile.country, data-autocomplete="commonCountries", data-autocomplete-min-length=1)
|
||||
p.help-block
|
||||
span(data-i18n="account_profile.basics_country_help") Country you want to work in (or live in now).
|
||||
| Ex.: "USA", "France"
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.basics_visa") US Work Status
|
||||
select.form-control(name='root[visa]')
|
||||
option(value='Authorized to work in the US', selected=profile.visa == 'Authorized to work in the US') Authorized to work in the US
|
||||
option(value='Need visa sponsorship', selected=profile.visa == 'Need visa sponsorship') Need visa sponsorship
|
||||
p.help-block(data-i18n="account_profile.basics_visa_help") Are you authorized to work in the US, or do you need visa sponsorship?
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.basics_looking_for") Looking For
|
||||
select.form-control(name='root[lookingFor]')
|
||||
option(value='Full-time', selected=profile.lookingFor == "Full-time", data-i18n="account_profile.basics_looking_for_full_time") Full-time
|
||||
option(value='Part-time', selected=profile.lookingFor == "Part-time", data-i18n="account_profile.basics_looking_for_part_time") Part-time
|
||||
option(value='Remote', selected=profile.lookingFor == "Remote", data-i18n="account_profile.basics_looking_for_remote") Remote
|
||||
option(value='Contracting', selected=profile.lookingFor == "Contracting", data-i18n="account_profile.basics_looking_for_contracting") Contracting
|
||||
option(value='Internship', selected=profile.lookingFor == "Internship", data-i18n="account_profile.basics_looking_for_internship") Internship
|
||||
p.help-block(data-i18n="account_profile.basics_looking_for_help") What kind of developer position do you want?
|
||||
button.btn.btn-success.btn-block.save-section(data-i18n="common.save") Save
|
||||
|
||||
div= profile.city + ', ' + profile.country
|
||||
div= profile.visa
|
||||
div
|
||||
span(data-i18n="account_profile.looking_for") Looking for:
|
||||
| #{profile.lookingFor}
|
||||
div
|
||||
span(data-i18n="account_profile.last_updated") Last updated:
|
||||
| #{moment(profile.updated).fromNow()}
|
||||
|
||||
button#contact-candidate.btn.btn-large.btn-inverse.flat-button
|
||||
span(data-i18n="account_profile.contact") Contact
|
||||
| #{profile.name.split(' ')[0]}
|
||||
if !editing
|
||||
button#contact-candidate.btn.btn-large.btn-inverse.flat-button
|
||||
span(data-i18n="account_profile.contact") Contact
|
||||
| #{profile.name.split(' ')[0]}
|
||||
|
||||
.middle-column.full-height-column
|
||||
.sub-column
|
||||
h3= profile.name || "Anonymous Developer"
|
||||
if profile.shortDescription
|
||||
p= profile.shortDescription
|
||||
#name-container.editable-section
|
||||
.editable-display(title="Click to fill in your name")
|
||||
.editable-icon.glyphicon.glyphicon-pencil
|
||||
if editing && !profile.name
|
||||
h3.edit-label(data-i18n="account_profile.name_header") Fill in your name
|
||||
else if profile.name
|
||||
h3= profile.name
|
||||
else
|
||||
h3(data-i18n="account_profile.name_anonymous") Anonymous Developer
|
||||
|
||||
form.editable-form
|
||||
.editable-icon.glyphicon.glyphicon-remove
|
||||
.form-group
|
||||
label.control-label(data-i18n="general.name") Name
|
||||
input.form-control(type='text', maxlength='100', name='root[name]', value=profile.name)
|
||||
p.help-block(data-i18n="account_profile.name_help") Name you want employers to see, like 'Nick Winter'.
|
||||
button.btn.btn-success.btn-block.save-section(data-i18n="common.save") Save
|
||||
|
||||
|
||||
#short-description-container.editable-section
|
||||
.editable-display(title="Click to write your short description")
|
||||
.editable-icon.glyphicon.glyphicon-pencil
|
||||
if editing && (!profile.shortDescription || profile.shortDescription == jobProfileSchema.properties.shortDescription.default)
|
||||
h3.edit-label(data-i18n="account_profile.short_description_header") Write a short description of yourself
|
||||
p.edit-example-text(data-i18n="account_profile.short_description_blurb") Add a blurb here that will show, at a glance, whether you might be just the developer that an employer is looking for.
|
||||
|
||||
else if profile.shortDescription
|
||||
p.editable-thinner= profile.shortDescription
|
||||
|
||||
form.editable-form
|
||||
.editable-icon.glyphicon.glyphicon-remove
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.short_description") Short Description
|
||||
textarea.form-control(rows=3, maxlength='140', name='root[shortDescription]')= profile.shortDescription
|
||||
p.help-block(data-i18n="account_profile.short_description_help") Who are you, and what are you looking for? 140 characters max.
|
||||
button.btn.btn-success.btn-block.save-section(data-i18n="common.save") Save
|
||||
|
||||
|
||||
#skills-container.editable-section
|
||||
.editable-display.editable-thinner(title="Click to tag your programming skills")
|
||||
.editable-icon.glyphicon.glyphicon-pencil
|
||||
if editing && (!profile.skills || !profile.skills.length || (profile.skills.length == 1 && profile.skills[0] == 'javascript'))
|
||||
h3.edit-label Tag your programming skills
|
||||
each skill in ["python", "coffeescript", "node", "ios", "objective-c", "javascript", "app-engine", "mongodb", "web dev", "django", "backbone"]
|
||||
code.edit-example-tag= skill
|
||||
span
|
||||
else
|
||||
each skill in profile.skills
|
||||
code= skill
|
||||
span
|
||||
|
||||
form.editable-form
|
||||
.editable-icon.glyphicon.glyphicon-remove
|
||||
h3(data-i18n="account_profile.skills_header") Skills
|
||||
p.help-block
|
||||
span(data-i18n="account_profile.skills_help") Tag relevant developer skills in order of proficiency.
|
||||
| Ex.: "objective-c", "mongodb", "rails", "android", "javascript"
|
||||
.editable-array(data-property='skills')
|
||||
for skill, index in (profile.skills || []).concat('')
|
||||
.array-item.skill-array-item
|
||||
input.form-control(type='skill', maxlength='20', pattern='.{1,}', data-schemaformat='skill', name="root[skills][#{index}]", value=skill, data-autocomplete="commonSkills", data-autocomplete-min-length=1)
|
||||
button.btn.btn-success.btn-block.save-section(data-i18n="common.save") Save
|
||||
|
||||
|
||||
#long-description-container.editable-section
|
||||
.editable-display(title="Click to start writing your longer description")
|
||||
.editable-icon.glyphicon.glyphicon-pencil
|
||||
if editing && (!profile.longDescription || profile.longDescription == jobProfileSchema.properties.longDescription.default)
|
||||
h3.edit-label(data-i18n="account_profile.long_description_header") Detail your desired position
|
||||
p.edit-example-text(data-i18n="account_profile.long_description_blurb_1") Write a little longer section here to describe the role you would like to pursue next.
|
||||
p.edit-example-text(data-i18n="account_profile.long_description_blurb_2") Talk about how awesome you are and why it would be a good idea to hire you.
|
||||
else if profile.longDescription
|
||||
div.long-description.editable-thinner!= marked(profile.longDescription)
|
||||
|
||||
each skill in profile.skills
|
||||
code= skill
|
||||
span
|
||||
if profile.longDescription
|
||||
div.long-description!= marked(profile.longDescription)
|
||||
|
||||
if profile.work.length
|
||||
h3.experience-header
|
||||
img.header-icon(src="/images/pages/account/profile/work.png", alt="")
|
||||
span(data-i18n="account_profile.work_experience") Work Experience
|
||||
each job in profile.work
|
||||
if job.role && job.employer
|
||||
div.experience-entry
|
||||
div.duration.pull-right= job.duration
|
||||
| #{job.role} at #{job.employer}
|
||||
form.editable-form
|
||||
.editable-icon.glyphicon.glyphicon-remove
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.long_description") Description
|
||||
textarea.form-control(rows=20, maxlength='600', data-schemaformat='markdown', name='root[longDescription]')= profile.longDescription
|
||||
p.help-block(data-i18n="account_profile.long_description_help") Describe yourself to potential employers. Keep it short and to the point. We recommend outlining the position that would most interest you. Tasteful markdown okay; 600 characters max.
|
||||
button.btn.btn-success.btn-block.save-section(data-i18n="common.save") Save
|
||||
|
||||
#work-container.editable-section
|
||||
.editable-display(title="Click to add work experience")
|
||||
.editable-icon.glyphicon.glyphicon-pencil
|
||||
if profile.work && profile.work.length
|
||||
h3.experience-header
|
||||
img.header-icon(src="/images/pages/account/profile/work.png", alt="")
|
||||
span(data-i18n="account_profile.work_experience") Work Experience
|
||||
| - #{profile.experience}
|
||||
span(data-i18n=profile.experience == 1 ? "units.year" : "units.years")
|
||||
each job in profile.work
|
||||
if job.role && job.employer
|
||||
div.experience-entry
|
||||
div.duration.pull-right= job.duration
|
||||
| #{job.role} at #{job.employer}
|
||||
.clearfix
|
||||
if job.description
|
||||
div!= marked(job.description)
|
||||
else if editing
|
||||
h3.experience-header.edit-label(data-i18n="account_profile.work_header") Chronicle your work history
|
||||
|
||||
div.experience-entry.edit-example-text
|
||||
div.duration.pull-right June, 2012 - present
|
||||
| UX Designer at Hooli
|
||||
.clearfix
|
||||
div Revolutionized CSS, refactored flattening, and designed all the things.
|
||||
|
||||
div.experience-entry.edit-example-text
|
||||
div.duration.pull-right 1999 - 2012
|
||||
| Software Engineer at Initrode
|
||||
.clearfix
|
||||
div Architected a peer-to-peer streaming automatic TPS report fulfillment system.
|
||||
|
||||
form.editable-form
|
||||
.editable-icon.glyphicon.glyphicon-remove
|
||||
h3(data-i18n="account_profile.work_experience") Work Experience
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.work_years") Years of Experience
|
||||
input.form-control(type='text', name='root[experience]')
|
||||
p.help-block(data-i18n="account_profile.work_years_help") How many years of professional experience (getting paid) developing software do you have?
|
||||
p(data-i18n="account_profile.work_blurb") List your relevant work experience, most recent first.
|
||||
.editable-array(data-property='work')
|
||||
for job, index in (profile.work || []).concat({})
|
||||
.array-item.well.well-sm
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.work_employer") Employer
|
||||
input.form-control(type='text', maxlength='100', name="root[work][#{index}][employer]", value=job.employer)
|
||||
p.help-block(data-i18n="account_profile.work_employer_help") Name of your employer.
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.work_role") Job Title
|
||||
input.form-control(type='text', maxlength='100', name="root[work][#{index}][role]", value=job.role)
|
||||
p.help-block(data-i18n="account_profile.work_role_help") What was your job title or role?
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.work_duration") Duration
|
||||
input.form-control(type='text', maxlength='100', name="root[work][#{index}][duration]", value=job.duration)
|
||||
p.help-block
|
||||
span(data-i18n="account_profile.work_duration_help") When did you hold this gig?
|
||||
| Ex.: "Feb 2013 - present".
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.work_description") Description
|
||||
textarea.form-control(rows=3, maxlength='140', name="root[work][#{index}][description]")= job.description
|
||||
p.help-block(data-i18n="account_profile.work_description_help") What did you do there? (140 chars; optional)
|
||||
button.btn.btn-success.btn-block.save-section(data-i18n="common.save") Save
|
||||
|
||||
|
||||
#education-container.editable-section
|
||||
.editable-display(title="Click to add academic experience")
|
||||
.editable-icon.glyphicon.glyphicon-pencil
|
||||
if profile.education && profile.education.length
|
||||
h3.experience-header
|
||||
img.header-icon(src="/images/pages/account/profile/education.png", alt="")
|
||||
span(data-i18n="account_profile.education") Education
|
||||
each school in profile.education
|
||||
if school.degree && school.school
|
||||
div.experience-entry
|
||||
div.duration.pull-right= school.duration
|
||||
| #{school.degree} at #{school.school}
|
||||
.clearfix
|
||||
if school.description
|
||||
div!= marked(school.description)
|
||||
else if editing
|
||||
h3.experience-header.edit-label(data-i18n="account_profile.education_header") Recount your academic ordeals
|
||||
|
||||
div.experience-entry.edit-example-text
|
||||
div.duration.pull-right 1995 - 1997
|
||||
| Ph.D. Janitorial Science at MIT
|
||||
.clearfix
|
||||
if job.description
|
||||
div!= marked(job.description)
|
||||
|
||||
if profile.education.length
|
||||
h3.experience-header
|
||||
img.header-icon(src="/images/pages/account/profile/education.png", alt="")
|
||||
span(data-i18n="account_profile.education") Education
|
||||
each school in profile.education
|
||||
if school.degree && school.school
|
||||
div.experience-entry
|
||||
div.duration.pull-right= school.duration
|
||||
| #{school.degree} at #{school.school}
|
||||
.clearfix
|
||||
if school.description
|
||||
div!= marked(school.description)
|
||||
div Anonymously solved difficult problems in algebraic graph theory. Swept the floors.
|
||||
|
||||
form.editable-form
|
||||
.editable-icon.glyphicon.glyphicon-remove
|
||||
h3(data-i18n="accont_profile.education") Education
|
||||
p(data-i18n="account_profile.education_blurb") List your academic ordeals.
|
||||
.editable-array(data-property='education')
|
||||
for school, index in (profile.education || []).concat({})
|
||||
.array-item.well.well-sm
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.education_school") School
|
||||
input.form-control(type='text', maxlength='100', name="root[education][#{index}][school]", value=school.school)
|
||||
p.help-block(data-i18n="account_profile.education_school_help") Name of your school.
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.education_degree") Degree
|
||||
input.form-control(type='text', maxlength='100', name="root[education][#{index}][degree]", value=school.degree)
|
||||
p.help-block
|
||||
span(data-i18n="account_profile.education_degree_help") What was your degree and field of study?
|
||||
| Ex.: "Ph.D. Human-Computer Interaction (incomplete)"
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.education_duration") Dates
|
||||
input.form-control(type='text', maxlength='100', name="root[education][#{index}][duration]", value=school.duration)
|
||||
p.help-block
|
||||
span(data-i18n="account_profile.education_duration_help") When?
|
||||
| Ex.: "Aug 2004 - May 2008".
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.education_description") Description
|
||||
textarea.form-control(rows=3, maxlength='140', name="root[education][#{index}][description]")= school.description
|
||||
p.help-block(data-i18n="account_profile.education_description_help") Highlight anything about this educational experience. (140 chars; optional)
|
||||
button.btn.btn-success.btn-block.save-section(data-i18n="common.save") Save
|
||||
|
||||
|
||||
if user.get('jobProfileNotes') || me.isAdmin()
|
||||
h3.experience-header(data-i18n="account_profile.our_notes") Our Notes
|
||||
- var notes = user.get('jobProfileNotes') || '';
|
||||
if me.isAdmin()
|
||||
textarea#job-profile-notes!= notes
|
||||
button.btn.btn-primary#save-notes-button Save Notes
|
||||
else
|
||||
div!= marked(notes)
|
||||
div(class="our-notes-section" + (editing ? " deemphasized" : ""))
|
||||
h3.experience-header(data-i18n="account_profile.our_notes") Our Notes
|
||||
- var notes = user.get('jobProfileNotes') || '';
|
||||
if me.isAdmin()
|
||||
textarea#job-profile-notes!= notes
|
||||
button.btn.btn-primary#save-notes-button(data-i18n="common.save") Save
|
||||
else
|
||||
div!= marked(notes)
|
||||
|
||||
.right-column.full-height-column
|
||||
.sub-column
|
||||
if profile.projects.length
|
||||
h3(data-i18n="account_profile.projects") Projects
|
||||
ul.projects
|
||||
each project in profile.projects
|
||||
if project.name
|
||||
li
|
||||
if project.link && project.link.length && project.link != 'http://example.com'
|
||||
a(href=project.link)
|
||||
if project.picture
|
||||
.project-image(style="background-image: url('/file/" + project.picture + "')")
|
||||
p= project.name
|
||||
div!= marked(project.description)
|
||||
#projects-container.editable-section
|
||||
.editable-display(title="Click to add your projects")
|
||||
.editable-icon.glyphicon.glyphicon-pencil
|
||||
if profile.projects && profile.projects.length
|
||||
h3(data-i18n="account_profile.projects") Projects
|
||||
ul.projects
|
||||
each project in profile.projects
|
||||
if project.name
|
||||
li
|
||||
if project.link && project.link.length && project.link != 'http://example.com'
|
||||
a(href=project.link)
|
||||
if project.picture
|
||||
.project-image(style="background-image: url('/file/" + project.picture + "')")
|
||||
p= project.name
|
||||
div!= marked(project.description)
|
||||
else if editing
|
||||
h3.edit-label(data-i18n="account_profile.projects_header") Add 3 projects
|
||||
ul.projects
|
||||
li.edit-example-text
|
||||
.project-image(style="background-image: url('/file/db/user/52a57252a89409700d0000d9/godzilla_babies.jpg')")
|
||||
p HTML5 Godzilla Babies
|
||||
div I orchestrated a combat between a young Godzilla and nine thousand young human baby infants, filmed for fun and profit, using Shadow DOM.
|
||||
li.edit-example-text
|
||||
.project-image(style="background-image: url('/images/level/hud_center.png')")
|
||||
p Node.js Framework Framework
|
||||
div Yo dawg, I heard you like web frameworks, so I made a web framework framework...
|
||||
li.edit-example-text
|
||||
.project-image(style="background-image: url('/images/level/popover_background.png')")
|
||||
p Unfinished 2.5D MMORPGFPS
|
||||
div A real-time multiplayer game engine with cross-platform streaming shader support and hand-drawn characters.
|
||||
|
||||
form.editable-form
|
||||
.editable-icon.glyphicon.glyphicon-remove
|
||||
h3(data-i18n="account_profile.projects_header_2") Projects (Top 3)
|
||||
p(data-i18n="account_profile.projects_blurb") Highlight your projects to amaze employers.
|
||||
for index in [0, 1, 2]
|
||||
- var project = (profile.projects || [])[index] || {};
|
||||
.array-item.well.well-sm
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.projects_name") Project Name
|
||||
input.form-control(type='text', maxlength='100', name="root[projects][#{index}][name]", value=project.name)
|
||||
p.help-block(data-i18n="account_profile.projects_name_help") What was the project called?
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.projects_description") Description
|
||||
textarea.form-control(rows=6, maxlength='400', data-schemaformat='markdown', name="root[projects][#{index}][description]")= project.description
|
||||
p.help-block(data-i18n="account_profile.projects_description_help") Briefly describe the project.
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.projects_picture") Picture
|
||||
.project-image(style="background-image: url('" + (src=project.picture ? "/file/" + project.picture : "/images/jquery.minicolors.png") + "')")
|
||||
input(type="hidden", name="root[projects][#{index}][picture]", value=project.picture)
|
||||
p.help-block(data-i18n="account_profile.projects_picture_help") Upload a 230x115px or larger image showing off the project.
|
||||
.form-group
|
||||
label.control-label(data-i18n="account_profile.projects_link") Link
|
||||
input.form-control(type='url', pattern='^(ht|f)tp(s?)://[0-9a-zA-Z]([-.w]*[0-9a-zA-Z])*(:(0-9)*)*(/?)([a-zA-Z0-9-.?,\'/\+&%$#_=]*)?$', data-schemaformat='url', name="root[projects][#{index}][link]", value=project.link)
|
||||
p.help-block(data-i18n="account_profile.projects_link_help") Link to the project.
|
||||
button.btn.btn-success.btn-block.save-section(data-i18n="common.save") Save
|
||||
|
||||
else if allowedToViewJobProfile
|
||||
.public-profile-container
|
||||
h2 Loading...
|
||||
|
||||
h2(data-i18n="common.loading") Loading...
|
||||
|
||||
else
|
||||
.public-profile-container
|
||||
|
@ -127,4 +435,5 @@ block content
|
|||
img.profile-photo(src=user.getPhotoURL(256))
|
||||
|
||||
h2 TODO
|
||||
p Public user profiles are not ready yet. If you are seeing this, we probably have a bug leading to a broken link.
|
||||
p Public user profiles are not ready yet. If you are seeing this, we probably have a bug leading to a broken link.
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,20 +2,35 @@ View = require 'views/kinds/RootView'
|
|||
template = require 'templates/account/profile'
|
||||
User = require 'models/User'
|
||||
JobProfileContactView = require 'views/modal/job_profile_contact_modal'
|
||||
JobProfileView = require 'views/account/job_profile_view'
|
||||
forms = require 'lib/forms'
|
||||
|
||||
module.exports = class ProfileView extends View
|
||||
id: "profile-view"
|
||||
template: template
|
||||
|
||||
events:
|
||||
'click #toggle-editing': 'toggleEditing'
|
||||
'click #toggle-job-profile-active': 'toggleJobProfileActive'
|
||||
'click #toggle-job-profile-approved': 'toggleJobProfileApproved'
|
||||
'click save-notes-button': 'onJobProfileNotesChanged'
|
||||
'click #contact-candidate': 'onContactCandidate'
|
||||
'click #enter-espionage-mode': 'enterEspionageMode'
|
||||
'click .editable-profile .profile-photo': 'onEditProfilePhoto'
|
||||
'click .editable-profile .project-image': 'onEditProjectImage'
|
||||
'click .editable-profile .editable-display': 'onEditSection'
|
||||
'click .editable-profile .save-section': 'onSaveSection'
|
||||
'click .editable-profile .glyphicon-remove': 'onCancelSectionEdit'
|
||||
'change .editable-profile .editable-array input': 'onEditArray'
|
||||
'keyup .editable-profile .editable-array input': 'onEditArray'
|
||||
'click .editable-profile a': 'onClickLinkWhileEditing'
|
||||
|
||||
constructor: (options, @userID) ->
|
||||
@userID ?= me.id
|
||||
@onJobProfileNotesChanged = _.debounce @onJobProfileNotesChanged, 1000
|
||||
super options
|
||||
@uploadFilePath = "db/user/#{@userID}"
|
||||
@highlightedContainers = []
|
||||
if @userID is me.id
|
||||
@user = me
|
||||
else if me.isAdmin() or "employer" in me.get('permissions')
|
||||
|
@ -23,16 +38,31 @@ module.exports = class ProfileView extends View
|
|||
@user.fetch()
|
||||
@listenTo @user, "sync", =>
|
||||
@render()
|
||||
else
|
||||
@user = User.getByID(@userID)
|
||||
|
||||
getRenderData: ->
|
||||
context = super()
|
||||
context.jobProfileSchema = me.schema().properties.jobProfile
|
||||
unless jobProfile = @user.get 'jobProfile'
|
||||
jobProfile = {}
|
||||
for prop, schema of context.jobProfileSchema.properties
|
||||
jobProfile[prop] = _.clone schema.default if schema.default?
|
||||
@user.set 'jobProfile', jobProfile
|
||||
jobProfile.name ?= (@user.get('firstName') + ' ' + @user.get('lastName')).trim() if @user.get('firstName')
|
||||
context.profile = jobProfile
|
||||
context.user = @user
|
||||
context.allowedToViewJobProfile = me.isAdmin() or "employer" in me.get('permissions')
|
||||
context.myProfile = @user.id is context.me.id
|
||||
context.allowedToViewJobProfile = me.isAdmin() or "employer" in me.get('permissions') or context.myProfile
|
||||
context.allowedToEditJobProfile = me.isAdmin() or context.myProfile
|
||||
context.profileApproved = @user.get 'jobProfileApproved'
|
||||
context.progress = @progress ? @updateProgress()
|
||||
@editing ?= context.progress < 0.8
|
||||
context.editing = @editing
|
||||
context.marked = marked
|
||||
context.moment = moment
|
||||
context.iconForLink = @iconForLink
|
||||
if links = @user.get('jobProfile')?.links
|
||||
if links = jobProfile.links
|
||||
links = ($.extend(true, {}, link) for link in links)
|
||||
link.icon = @iconForLink link for link in links
|
||||
context.profileLinks = _.sortBy links, (link) -> not link.icon # icons first
|
||||
|
@ -40,21 +70,38 @@ module.exports = class ProfileView extends View
|
|||
|
||||
afterRender: ->
|
||||
super()
|
||||
@updateProfileApproval() if me.isAdmin()
|
||||
unless @user.get('jobProfile')?.projects?.length
|
||||
unless @user.get('jobProfile')?.projects?.length or @editing
|
||||
@$el.find('.right-column').hide()
|
||||
@$el.find('.middle-column').addClass('double-column')
|
||||
unless @editing
|
||||
@$el.find('.editable-display').attr('title', '')
|
||||
@initializeAutocomplete()
|
||||
highlightNext = @highlightNext
|
||||
justSavedSection = @$el.find('#' + @justSavedSectionID).addClass "just-saved"
|
||||
_.defer =>
|
||||
@progress = @updateProgress highlightNext
|
||||
_.delay ->
|
||||
justSavedSection.removeClass "just-saved", duration: 1500, easing: 'easeOutQuad'
|
||||
, 500
|
||||
|
||||
updateProfileApproval: ->
|
||||
approved = @user.get 'jobProfileApproved'
|
||||
@$el.find('.approved').toggle Boolean(approved)
|
||||
@$el.find('.not-approved').toggle not approved
|
||||
initializeAutocomplete: (container) ->
|
||||
(container ? @$el).find('input[data-autocomplete]').each ->
|
||||
$(@).autocomplete(source: JobProfileView[$(@).data('autocomplete')], minLength: parseInt($(@).data('autocomplete-min-length')), delay: 0, autoFocus: true)
|
||||
|
||||
toggleEditing: ->
|
||||
@editing = not @editing
|
||||
@render()
|
||||
|
||||
toggleJobProfileApproved: ->
|
||||
approved = not @user.get 'jobProfileApproved'
|
||||
@user.set 'jobProfileApproved', approved
|
||||
@user.save()
|
||||
@updateProfileApproval()
|
||||
res = @user.save {jobProfileApproved: approved}, {patch: true}
|
||||
res.success (model, response, options) => @render()
|
||||
|
||||
toggleJobProfileActive: ->
|
||||
active = not @user.get('jobProfile').active
|
||||
@user.get('jobProfile').active = active
|
||||
@saveEdits()
|
||||
|
||||
enterEspionageMode: ->
|
||||
postData = emailLower: @user.get('email').toLowerCase(), usernameLower: @user.get('name').toLowerCase()
|
||||
|
@ -70,7 +117,7 @@ module.exports = class ProfileView extends View
|
|||
onJobProfileNotesChanged: (e) =>
|
||||
notes = @$el.find("#job-profile-notes").val()
|
||||
@user.set 'jobProfileNotes', notes
|
||||
@user.save()
|
||||
@user.save {jobProfileNotes: notes}, {patch: true}
|
||||
|
||||
iconForLink: (link) ->
|
||||
icons = [
|
||||
|
@ -88,3 +135,201 @@ module.exports = class ProfileView extends View
|
|||
|
||||
onContactCandidate: (e) ->
|
||||
@openModalView new JobProfileContactView recipientID: @user.id
|
||||
|
||||
showErrors: (errors) ->
|
||||
section = @$el.find '.saving'
|
||||
console.error "Couldn't save because of validation errors:", errors
|
||||
section.removeClass 'saving'
|
||||
forms.clearFormAlerts section
|
||||
# This is pretty lame, since we don't easily match which field had the error like forms.applyErrorsToForm can.
|
||||
section.find('form').addClass('has-error').find('.save-section').before($("<span class='help-block error-help-block'>#{errors[0].message}</span>"))
|
||||
|
||||
saveEdits: (highlightNext) ->
|
||||
errors = @user.validate()
|
||||
return @showErrors errors if errors
|
||||
jobProfile = @user.get('jobProfile')
|
||||
jobProfile.updated = (new Date()).toISOString()
|
||||
@user.set 'jobProfile', jobProfile
|
||||
return unless res = @user.save()
|
||||
res.error =>
|
||||
return if @destroyed
|
||||
@showErrors [message: res.responseText]
|
||||
res.success (model, response, options) =>
|
||||
return if @destroyed
|
||||
@justSavedSectionID = @$el.find('.editable-section.saving').removeClass('saving').attr('id')
|
||||
@highlightNext = highlightNext
|
||||
@render()
|
||||
@highlightNext = false
|
||||
@justSavedSectionID = null
|
||||
|
||||
onEditProfilePhoto: (e) ->
|
||||
onSaving = =>
|
||||
@$el.find('.profile-photo').addClass('saving')
|
||||
onSaved = (uploadingPath) =>
|
||||
@user.get('jobProfile').photoURL = uploadingPath
|
||||
@saveEdits()
|
||||
filepicker.pick {mimetypes: 'image/*'}, @onImageChosen(onSaving, onSaved)
|
||||
|
||||
onEditProjectImage: (e) ->
|
||||
img = $(e.target)
|
||||
onSaving = =>
|
||||
img.addClass('saving')
|
||||
onSaved = (uploadingPath) =>
|
||||
img.parent().find('input').val(uploadingPath)
|
||||
img.css('background-image', "url('/file/#{uploadingPath}')")
|
||||
img.removeClass('saving')
|
||||
filepicker.pick {mimetypes: 'image/*'}, @onImageChosen(onSaving, onSaved)
|
||||
|
||||
formatImagePostData: (inkBlob) ->
|
||||
url: inkBlob.url, filename: inkBlob.filename, mimetype: inkBlob.mimetype, path: @uploadFilePath, force: true
|
||||
|
||||
onImageChosen: (onSaving, onSaved) ->
|
||||
(inkBlob) =>
|
||||
onSaving()
|
||||
uploadingPath = [@uploadFilePath, inkBlob.filename].join('/')
|
||||
$.ajax '/file', type: 'POST', data: @formatImagePostData(inkBlob), success: @onImageUploaded(onSaved, uploadingPath)
|
||||
|
||||
onImageUploaded: (onSaved, uploadingPath) ->
|
||||
(e) =>
|
||||
onSaved uploadingPath
|
||||
|
||||
onEditSection: (e) ->
|
||||
section = $(e.target).closest('.editable-section').removeClass 'deemphasized'
|
||||
section.find('.editable-form').show().find('select, input, textarea').first().focus()
|
||||
section.find('.editable-display').hide()
|
||||
@$el.find('.editable-section').not(section).addClass 'deemphasized'
|
||||
column = section.closest('.full-height-column')
|
||||
@$el.find('.full-height-column').not(column).addClass 'deemphasized'
|
||||
|
||||
onCancelSectionEdit: (e) ->
|
||||
@render()
|
||||
|
||||
onSaveSection: (e) ->
|
||||
e.preventDefault()
|
||||
section = $(e.target).closest('.editable-section')
|
||||
form = $(e.target).closest('form')
|
||||
isEmpty = @arrayItemIsEmpty
|
||||
section.find('.array-item').each ->
|
||||
$(@).remove() if isEmpty @
|
||||
resetOnce = false # We have to clear out arrays if we're going to redo them
|
||||
serialized = form.serializeArray()
|
||||
jobProfile = @user.get 'jobProfile'
|
||||
rootPropertiesSeen = {}
|
||||
for field in serialized
|
||||
keyChain = @extractFieldKeyChain field.name
|
||||
value = @extractFieldValue keyChain[0], field.value
|
||||
parent = jobProfile
|
||||
for key, i in keyChain
|
||||
rootPropertiesSeen[key] = true unless i
|
||||
break if i is keyChain.length - 1
|
||||
child = parent[key]
|
||||
if _.isArray(child) and not resetOnce
|
||||
child = parent[key] = []
|
||||
resetOnce = true
|
||||
else unless child?
|
||||
child = parent[key] = {}
|
||||
parent = child
|
||||
if key is 'link' and keyChain[0] is 'projects' and not value
|
||||
delete parent[key]
|
||||
else
|
||||
parent[key] = value
|
||||
form.find('.editable-array').each ->
|
||||
key = $(@).data('property')
|
||||
unless rootPropertiesSeen[key]
|
||||
jobProfile[key] = []
|
||||
if section.hasClass('projects-container') and not section.find('.array-item').length
|
||||
jobProfile.projects = []
|
||||
section.addClass 'saving'
|
||||
@saveEdits true
|
||||
|
||||
extractFieldKeyChain: (key) ->
|
||||
# "root[projects][0][name]" -> ["projects", "0", "name"]
|
||||
key.replace(/^root/, '').replace(/\[(.*?)\]/g, '.$1').replace(/^\./, '').split(/\./)
|
||||
|
||||
extractFieldValue: (key, value) ->
|
||||
switch key
|
||||
when 'active' then Boolean value
|
||||
when 'experience' then parseInt value or '0'
|
||||
else value
|
||||
|
||||
arrayItemIsEmpty: (arrayItem) ->
|
||||
for input in $(arrayItem).find('input[type!=hidden], textarea')
|
||||
return false if $(input).val().trim()
|
||||
true
|
||||
|
||||
onEditArray: (e) ->
|
||||
# We make sure there's always an empty array item at the end for the user to add to, deleting interstitial empties.
|
||||
array = $(e.target).closest('.editable-array')
|
||||
arrayItems = array.find('.array-item')
|
||||
toRemove = []
|
||||
for arrayItem, index in arrayItems
|
||||
empty = @arrayItemIsEmpty arrayItem
|
||||
if index is arrayItems.length - 1
|
||||
lastEmpty = empty
|
||||
else if empty and not $(arrayItem).find('input:focus, textarea:focus').length
|
||||
toRemove.unshift index
|
||||
$(arrayItems[emptyIndex]).remove() for emptyIndex in toRemove
|
||||
unless lastEmpty
|
||||
clone = $(arrayItem).clone(false)
|
||||
clone.find('input').each -> $(@).val('')
|
||||
clone.find('textarea').each -> $(@).text('')
|
||||
array.append clone
|
||||
@initializeAutocomplete clone
|
||||
for arrayItem, index in array.find('.array-item')
|
||||
for input in $(arrayItem).find('input, textarea')
|
||||
$(input).attr('name', $(input).attr('name').replace(/\[\d+\]/, "[#{index}]"))
|
||||
|
||||
onClickLinkWhileEditing: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
updateProgress: (highlightNext) ->
|
||||
completed = 0
|
||||
totalWeight = 0
|
||||
next = null
|
||||
for metric in metrics = @getProgressMetrics()
|
||||
done = metric.fn()
|
||||
completed += metric.weight if done
|
||||
totalWeight += metric.weight
|
||||
next = metric unless next or done
|
||||
progress = Math.round 100 * completed / totalWeight
|
||||
bar = @$el.find('.profile-completion-progress .progress-bar')
|
||||
bar.css 'width', "#{progress}%"
|
||||
text = ""
|
||||
t = $.i18n.t
|
||||
if next and progress > 40
|
||||
text = "#{progress}% #{t 'account_profile.complete'}. #{t 'account_profile.next'}: #{next.name}"
|
||||
else if next and progress > 30
|
||||
text = "#{progress}%. #{t 'account_profile.next'}: #{next.name}"
|
||||
else if next and progress > 11
|
||||
text = "#{progress}%: #{next.name}"
|
||||
else if progress > 3
|
||||
text = "#{progress}%"
|
||||
bar.text text
|
||||
bar.parent().toggle Boolean progress
|
||||
if highlightNext and next?.container and not (next.container in @highlightedContainers)
|
||||
@onEditSection target: next.container
|
||||
@highlightedContainers.push next.container
|
||||
$('#page-container').scrollTop 0
|
||||
completed / totalWeight
|
||||
|
||||
getProgressMetrics: ->
|
||||
schema = me.schema().properties.jobProfile
|
||||
jobProfile = @user.get('jobProfile') ? {}
|
||||
exists = (field) -> -> jobProfile[field]
|
||||
modified = (field) -> -> jobProfile[field] and jobProfile[field] isnt schema.properties[field].default
|
||||
listStarted = (field, subfields) -> -> jobProfile[field]?.length and _.every subfields, (subfield) -> jobProfile[field][0][subfield]
|
||||
t = $.i18n.t
|
||||
@progressMetrics = [
|
||||
{name: t('account_profile.next_city'), weight: 1, container: '#basic-info-container', fn: modified 'city'}
|
||||
{name: t('account_profile.next_country'), weight: 0, container: '#basic-info-container', fn: exists 'country'}
|
||||
{name: t('account_profile.next_name'), weight: 1, container: '#name-container', fn: modified 'name'}
|
||||
{name: t('account_profile.next_short_description'), weight: 2, container: '#short-description-container', fn: modified 'shortDescription'}
|
||||
{name: t('account_profile.next_skills'), weight: 2, container: '#skills-container', fn: -> jobProfile.skills?.length >= 5}
|
||||
{name: t('account_profile.next_long_description'), weight: 3, container: '#long-description-container', fn: modified 'longDescription'}
|
||||
{name: t('account_profile.next_work'), weight: 3, container: '#work-container', fn: listStarted 'work', ['role', 'employer']}
|
||||
{name: t('account_profile.next_education'), weight: 3, container: '#education-container', fn: listStarted 'education', ['degree', 'school']}
|
||||
{name: t('account_profile.next_projects'), weight: 3, container: '#projects-container', fn: listStarted 'projects', ['name']}
|
||||
{name: t('account_profile.next_links'), weight: 2, container: '#links-container', fn: listStarted 'links', ['link', 'name']}
|
||||
{name: t('account_profile.next_photo'), weight: 2, container: '#profile-photo-container', fn: modified 'photoURL'}
|
||||
{name: t('account_profile.next_active'), weight: 1, fn: modified 'active'}
|
||||
]
|
||||
|
|
|
@ -107,6 +107,7 @@ module.exports = class SettingsView extends View
|
|||
@grabData()
|
||||
res = me.validate()
|
||||
if res?
|
||||
console.error "Couldn't save because of validation errors:", res
|
||||
forms.applyErrorsToForm(@$el, res)
|
||||
return
|
||||
|
||||
|
@ -144,7 +145,7 @@ module.exports = class SettingsView extends View
|
|||
me.set 'name', $('#name', @$el).val()
|
||||
me.set 'email', $('#email', @$el).val()
|
||||
for emailName, enabled of @getSubscriptions()
|
||||
me.setEmailSubscription emailName, enabled
|
||||
me.setEmailSubscription emailName, enabled
|
||||
me.set 'photoURL', @pictureTreema.get('/photoURL')
|
||||
|
||||
adminCheckbox = @$el.find('#admin')
|
||||
|
|
|
@ -32,7 +32,7 @@ module.exports = class LadderView extends RootView
|
|||
|
||||
events:
|
||||
'click .play-button': 'onClickPlayButton'
|
||||
'click a': 'onClickedLink'
|
||||
'click a:not([data-toggle])': 'onClickedLink'
|
||||
|
||||
constructor: (options, @levelID) ->
|
||||
super(options)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue