Cull recruiting views and translations

This commit is contained in:
Scott Erickson 2015-12-18 10:34:21 -08:00
parent 53f8bca26b
commit 079109145f
23 changed files with 4 additions and 2931 deletions

View file

@ -20,17 +20,13 @@ module.exports = class CocoRouter extends Backbone.Router
'account': go('account/MainAccountView')
'account/settings': go('account/AccountSettingsRootView')
'account/unsubscribe': go('account/UnsubscribeView')
#'account/profile': go('user/JobProfileView') # legacy URL, sent in emails
'account/profile': go('EmployersView') # Show the not-recruiting-now screen
'account/payments': go('account/PaymentsView')
'account/subscription': go('account/SubscriptionView')
'account/invoices': go('account/InvoicesView')
'account/prepaid': go('account/PrepaidView')
'admin': go('admin/MainAdminView')
'admin/candidates': go('admin/CandidatesView')
'admin/clas': go('admin/CLAsView')
'admin/employers': go('admin/EmployersListView')
'admin/files': go('admin/FilesView')
'admin/analytics': go('admin/AnalyticsView')
'admin/analytics/subscriptions': go('admin/AnalyticsSubscriptionsView')
@ -61,9 +57,6 @@ module.exports = class CocoRouter extends Backbone.Router
'contribute/diplomat': go('contribute/DiplomatView')
'contribute/scribe': go('contribute/ScribeView')
'courses/mock1': go('courses/mock1/CoursesView')
'courses/mock1/enroll/:courseID': go('courses/mock1/CourseEnrollView')
'courses/mock1/:courseID': go('courses/mock1/CourseDetailsView')
'courses': go('courses/CoursesView')
'Courses': go('courses/CoursesView')
'courses/students': go('courses/StudentCoursesView')
@ -92,9 +85,7 @@ module.exports = class CocoRouter extends Backbone.Router
'editor/campaign/:campaignID': go('editor/campaign/CampaignEditorView')
'editor/poll': go('editor/poll/PollSearchView')
'editor/poll/:articleID': go('editor/poll/PollEditView')
'employers': go('EmployersView')
'file/*path': 'routeToServer'
'github/*path': 'routeToServer'
@ -133,8 +124,6 @@ module.exports = class CocoRouter extends Backbone.Router
'test(/*subpath)': go('TestView')
'user/:slugOrID': go('user/MainUserView')
#'user/:slugOrID/profile': go('user/JobProfileView')
'user/:slugOrID/profile': go('EmployersView') # Show the not-recruiting-now screen
'*name/': 'removeTrailingSlash'
'*name': go('NotFoundView')

View file

@ -722,11 +722,6 @@
saved: "Changes Saved"
password_mismatch: "Password does not match."
password_repeat: "Please repeat your password."
job_profile: "Job Profile" # Rest of this section (the job profile stuff and wizard stuff) is deprecated
job_profile_approved: "Your job profile has been approved by CodeCombat. Employers will be able to see it until you either mark it inactive or it has not been changed for four weeks."
job_profile_explanation: "Hi! Fill this out, and we will get in touch about finding you a software developer job."
sample_profile: "See a sample profile"
view_profile: "View Your Profile"
keyboard_shortcuts:
keyboard_shortcuts: "Keyboard Shortcuts"
@ -1576,157 +1571,6 @@
license: "license"
oreilly: "ebook of your choice"
account_profile:
settings: "Settings" # We are not actively recruiting right now, so there's no need to add new translations for this section.
edit_profile: "Edit Profile"
done_editing: "Done Editing"
profile_for_prefix: "Profile for "
profile_for_suffix: ""
featured: "Featured"
not_featured: "Not Featured"
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: "write a short description."
next_long_description: "describe your desired position."
next_skills: "list at least five skills."
next_work: "chronicle your work history."
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: "Blog"
example_personal_site: "Personal Site"
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? (If you live in Canada or Australia, mark authorized.)"
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 tagline to help an employer quickly learn more about you."
short_description: "Tagline"
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: "Describe your desired position"
long_description_blurb: "Tell employers how awesome you are and what role you want."
long_description: "Self 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: "CodeCombat's Notes"
remarks: "Remarks"
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."
player_code: "Player Code"
employers:
deprecation_warning_title: "Sorry, CodeCombat is not recruiting right now."
deprecation_warning: "We are focusing on beginner levels instead of finding expert developers for the time being."
hire_developers_not_credentials: "Hire developers, not credentials." # We are not actively recruiting right now, so there's no need to add new translations for the rest of this section.
get_started: "Get Started"
already_screened: "We've already technically screened all our candidates"
filter_further: ", but you can also filter further:"
filter_visa: "Visa"
filter_visa_yes: "US Authorized"
filter_visa_no: "Not Authorized"
filter_education_top: "Top School"
filter_education_other: "Other"
filter_role_web_developer: "Web Developer"
filter_role_software_developer: "Software Developer"
filter_role_mobile_developer: "Mobile Developer"
filter_experience: "Experience"
filter_experience_senior: "Senior"
filter_experience_junior: "Junior"
filter_experience_recent_grad: "Recent Grad"
filter_experience_student: "College Student"
filter_results: "results"
start_hiring: "Start hiring."
reasons: "Three reasons you should hire through us:"
everyone_looking: "Everyone here is looking for their next opportunity."
everyone_looking_blurb: "Forget about 20% LinkedIn InMail response rates. Everyone that we list on this site wants to find their next position and will respond to your request for an introduction."
weeding: "Sit back; we've done the weeding for you."
weeding_blurb: "Every player that we list has been screened for technical ability. We also perform phone screens for select candidates and make notes on their profiles to save you time."
pass_screen: "They will pass your technical screen."
pass_screen_blurb: "Review each candidate's code before reaching out. One employer found that 5x as many of our devs passed their technical screen than hiring from Hacker News."
make_hiring_easier: "Make my hiring easier, please."
what: "What is CodeCombat?"
what_blurb: "CodeCombat is a multiplayer browser programming game. Players write code to control their forces in battle against other developers. Our players have experience with all major tech stacks."
cost: "How much do we charge?"
cost_blurb: "We charge 15% of first year's salary and offer a 100% money back guarantee for 90 days. We don't charge for candidates who are already actively being interviewed at your company."
candidate_name: "Name"
candidate_location: "Location"
candidate_looking_for: "Looking For"
candidate_role: "Role"
candidate_top_skills: "Top Skills"
candidate_years_experience: "Yrs Exp"
candidate_last_updated: "Last Updated"
candidate_who: "Who"
featured_developers: "Featured Developers"
other_developers: "Other Developers"
inactive_developers: "Inactive Developers"
admin:
av_espionage: "Espionage" # Really not important to translate /admin controls.
av_espionage_placeholder: "Email or username"

View file

@ -1,6 +0,0 @@
CocoModel = require './CocoModel'
module.exports = class UserRemark extends CocoModel
@className: 'UserRemark'
@schema: require 'schemas/models/user_remark'
urlRoot: '/db/user.remark'

View file

@ -127,6 +127,7 @@ _.extend UserSchema.properties,
simulatedBy: {type: 'integer', minimum: 0 }
simulatedFor: {type: 'integer', minimum: 0 }
# Deprecated. TODO: Figure out how to remove.
jobProfile: c.object {title: 'Job Profile', default: { active: false, lookingFor: 'Full-time', jobTitle: 'Software Developer', city: 'Defaultsville, CA', country: 'USA', skills: ['javascript'], shortDescription: 'Programmer seeking to build great software.', longDescription: '* I write great code.\n* You need great code?\n* Great!' }},
lookingFor: {title: 'Looking For', type: 'string', enum: ['Full-time', 'Part-time', 'Remote', 'Contracting', 'Internship'], description: 'What kind of developer position do you want?'}
jobTitle: {type: 'string', maxLength: 50, title: 'Desired Job Title', description: 'What role are you looking for? Ex.: "Full Stack Engineer", "Front-End Developer", "iOS Developer"' }

View file

@ -1,5 +1,7 @@
c = require './../schemas'
# Deprecated. TODO: Remove
UserRemarkSchema = c.object {
title: 'Remark'
description: 'Remarks on a user, point of contact, tasks.'

View file

@ -1,263 +0,0 @@
#employers-view
.deprecation-warning
text-align: center
margin-bottom: 50px
.deprecated
cursor: default
opacity: 0.25
.artisanal-claim
background: transparent url(/images/pages/employer/artisanal_claim.png) no-repeat center
margin-bottom: 5px
height: 24px
.employer-button
background: #fce232 /* Old browsers */
background: -moz-linear-gradient(top, #fce232 0%, #ea8e2b 100%)
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fce232), color-stop(100%,#ea8e2b))
background: -webkit-linear-gradient(top, #fce232 0%,#ea8e2b 100%)
background: -o-linear-gradient(top, #fce232 0%,#ea8e2b 100%)
background: -ms-linear-gradient(top, #fce232 0%,#ea8e2b 100%)
background: linear-gradient(to bottom, #fce232 0%,#ea8e2b 100%)
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fce232', endColorstr='#ea8e2b',GradientType=0 )
vertical-align: text-bottom
margin-left: 30px
#filter-column
margin-left: 0px
padding-right: 0
#candidates-column
padding-left: 5px
padding-right: 5px
//filter panels
#filter
border: 2px solid #CBCBCB
margin-bottom: 10px
.panel-heading
background-color: #D9D9D9
cursor: pointer
border-top-left-radius: 0px
border-top-right-radius: 0px
#folder-icon
margin-right: 5px
color: #919191
.panel-body
background-color: #D9D9D9
border-top-left-radius: 0px
border-top-right-radius: 0px
padding: 5px 10px 10px 10px
.panel
border: 1px red
border-radius: 0px
#filters
margin-bottom: 10px
.filter_section
min-width: 25%
margin-right: 10px
display: inline-block
vertical-align: top
h4
margin-bottom: 5px
label
display: block
font-weight: normal
margin-bottom: 0
cursor: pointer
input
margin-right: 5px
#saved-filter-table
display: none
width: 100%
tbody
tr:nth-child(even)
background-color: #CBCBCB
margin-bottom: 20px
.deletion-row
text-align: center
.get-started-button
vertical-align: text-bottom
margin-left: 10px
#filter-button, #create-alert-button
float: right
#login-link, #logout-link
position: absolute
right: 10px
top: -25px
color: black
display: inline-block
:visited
color: black
#logout-link:hover
cursor: pointer
#tagline, .hiring-call-to-action
width: 100%
text-align: center
display: inline-block
margin-top: 20px
h1, h2, h3
display: inline-block
button
display: inline-block
h1, h2, h3, h4
font-family: Arial, Helvetica, sans-serif
color: #333333
#tagline
margin-bottom: 20px
.see-candidates-header
margin: 30px
text-align: center
#see-candidates
cursor: pointer
.reasons
height: 275px
margin-bottom: 25px
width: 100%
.information_row
height: 150px
padding-right: 15px
.reason
width: 33%
padding-left: 3%
height: 150px
display: inline-block
vertical-align: top
.employer_icon
display: block
width: 125px
margin: 0px auto
.reason_long
width: 50%
padding-left: 3%
display: inline-block
.employer_icon
display: inline-block
width: 25%
max-width: 125px
vertical-align: text-bottom
.reason_text
display: inline-block
width: 75%
#bottom_row
height: auto
#candidate-table
background-color: #E7E7E7
table
cursor: pointer
width: 96%
margin-left: 2%
margin-right: 2%
margin-bottom: 30px
.tag_column, .location_column, .education_column, .work_column
width: 33%
display: inline-block
img
margin-right: 5px
vertical-align: top
tr
.candidate-picture
background-color: transparent
background-size: cover
background-repeat: no-repeat
background-position: center
width: 75px
height: 75px
border-radius: 5px
overflow: hidden
margin-right: 10px
&.anonymous
background-size: contain
.candidate-description
width: 100%
vertical-align: bottom
div
text-overflow: ellipsis
overflow: hidden
height: 17px
td.candidate-description
padding-bottom: 5px
td.candidate-name-cell
padding-top: 15px
padding-bottom: 5px
font-size: 18px
.border_row
border-bottom: 1px solid #d3d3d3
vertical-align: bottom
td
padding-top: 5px
padding-bottom: 15px
.teaser-profiles #candidate-table table
.tag_column, .location_column, .education_column, .work_column
width: 25%
tr
.candidate-description
padding-top: 15px
.candidate-picture
width: 50px
height: 50px
#results
display: inline-block
.tablesorter
//img
// display: none
.tablesorter-header
cursor: pointer
&:hover
color: black
&:first-child
// Make sure that "Developer #56" doesn't wrap onto second row
min-width: 110px
.tablesorter-headerAsc
background-color: #cfc
.tablesorter-headerDesc
background-color: #ccf
tr
cursor: pointer
tr.expired
opacity: 0.5
code
background-color: rgb(220, 220, 220)
color: #555
margin: 2px 0
display: inline-block
text-transform: lowercase
td:nth-child(3) select
min-width: 100px
td:nth-child(6) select
min-width: 50px
td:nth-child(7) select
min-width: 100px
#employers-view, #profile-view.viewed-by-employer
#outer-content-wrapper, #intermediate-content-wrapper, #inner-content-wrapper
background: #B4B4B4
.main-content-area
background-color: #EAEAEA

View file

@ -1,12 +0,0 @@
extends /templates/core/modal-base
block modal-header-content
h3 Applicant Code for
span.spl= view.session.get('levelName')
block modal-body-content
.level-session-code-view
block modal-footer
.modal-footer
button(data-dismiss="modal", data-i18n="modal.close").btn Close

View file

@ -1,28 +0,0 @@
h3(data-i18n="account_settings.job_profile") Job Profile
.row
.col-md-9
if me.get('jobProfileApproved')
p.lead(data-i18n="account_settings.job_profile_approved") Your job profile has been approved by CodeCombat. Employers will be able to see it until you either mark it inactive or it has not been changed for four weeks.
else
p.lead(data-i18n="account_settings.job_profile_explanation") Hi! Fill this out, and we will get in touch about finding you a software developer job.
.row
.col-md-9
.progress.profile-completion-progress
.progress-bar.progress-bar-success
.progress-next-item
.col-md-3
a.btn.btn-large.btn-primary.profile-preview-button.top-preview(href="/account/profile/#{me.id}", target="job_profile", data-i18n="account_settings.view_profile") View Your Profile
.col-md-3
.thumbnail.sample-profile-thumbnail
a(href="http://codecombat.com/images/pages/account/profile/sample_profile.png", target="_blank")
img(src="/images/pages/account/profile/sample_profile_thumb.png" alt="Sample Profile Thumbnail")
.caption
span(data-i18n="account_settings.sample_profile") See a sample profile
#job-profile-treema
a.btn.btn-large.btn-primary.profile-preview-button.bottom-preview(href="/account/profile/#{me.id}", target="job_profile", data-i18n="account_settings.view_profile") View Your Profile

View file

@ -1,495 +0,0 @@
extends /templates/base
block content
if !allowedToEditJobProfile && me.get('anonymous') == true
div(class="job-profile-container")
h1#login-message
|Please
a.login-button login
| to view this profile.
else if user.loaded
if allowedToEditJobProfile
.profile-control-bar
if editing
.progress.profile-completion-progress
.progress-bar.progress-bar-success(style="width: #{100 * progress}%")
.progress-text
a.btn(href="/account/settings")
i.icon-cog
span(data-i18n="account_profile.settings") Settings
if editing
button.btn.btn-success#toggle-editing
i.icon-ok
span(data-i18n="account_profile.done_editing") Done Editing
else
button.btn#toggle-editing
i.icon-cog
span(data-i18n="account_profile.edit_profile") Edit Profile
if profile && 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 profile && (profile.active || me.isAdmin())
if profileApproved
button.btn.btn-success#toggle-job-profile-approved(disabled=!me.isAdmin())
i.icon-eye-open
span(data-i18n='account_profile.featured') Featured
else if me.isAdmin()
button.btn#toggle-job-profile-approved
i.icon-eye-close
span(data-i18n='account_profile.not_featured') Not Featured
if me.isAdmin() && !myProfile
button.btn#enter-espionage-mode 007
if me.isAdmin()
button.btn#open-model-modal Raw
if profile && allowedToViewJobProfile
div(class="job-profile-container" + (editing ? " editable-profile" : ""))
.job-profile-row
.left-column.full-height-column
.sub-column
#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'
#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
ul.links
li.has-icon
a(href='http://example.com')
img(src='/images/pages/account/profile/icon_github.png', alt='GitHub')
li.has-icon
a(href='http://example.com')
img(src='/images/pages/account/profile/icon_facebook.png', alt='Facebook')
li.has-icon
a(href='http://example.com')
img(src='/images/pages/account/profile/icon_twitter.png', alt='Twitter')
li.has-icon
a(href='http://example.com/')
img(src='/images/pages/account/profile/icon_linkedin.png', alt='LinkedIn')
button.btn.btn-large.btn-inverse.flat-button.edit-example-button(data-i18n="account_profile.example_blog") Blog
button.btn.btn-large.btn-inverse.flat-button.edit-example-button(data-i18n="account_profile.example_personal_site") Personal Site
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 && !rawProfile.city
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? (If you live in Canada or Australia, mark authorized.)
.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
if !editing && !myProfile
button#contact-candidate.btn.btn-large.btn-inverse.flat-button
span(data-i18n="account_profile.contact") Contact
| #{(profile.name || user.get('name') || 'Anonymous').split(' ')[0]}
if me.isAdmin()
select#admin-contact.form-control
for contact in adminContacts
option(value=contact.id, selected=remark && remark.get('contact') == contact.id)= contact.name
if !editing && sessions.length
h3(data-i18n="account_profile.player_code") Player Code
ul.sessions
each session in sessions
li
a.session-link(data-session-id=session._id)
span= session.levelName
if session.team
span.spl - #{session.team}
if session.codeLanguage && session.codeLanguage != 'javascript'
span.spl - #{{coffeescript: 'CoffeeScript', python: 'Python', lua: 'Lua', io: 'Io', clojure: 'Clojure'}[session.codeLanguage]}
if !editing && !sessions.length
h3(data-i18n="account_profile.player_code") Player Code
p This player hasn't submitted code to our more recent tournaments.
.middle-column.full-height-column
.sub-column
#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 + (me.isAdmin() ? ' (' + user.get('name') + ')' : '')
else
h3
span(data-i18n="account_profile.name_anonymous") Anonymous Developer
if me.isAdmin()
span (#{user.get('name')})
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 tagline")
.editable-icon.glyphicon.glyphicon-pencil
if editing && !rawProfile.shortDescription
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 tagline to help an employer quickly learn more about you.
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") Tagline
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
- var modified = rawProfile.longDescription
if editing && !modified
h3.edit-label(data-i18n="account_profile.long_description_header") Describe your desired position
p.edit-example-text(data-i18n="account_profile.long_description_blurb") Tell employers how awesome you are and what role you want.
else if modified
div.long-description.editable-thinner!= marked(profile.longDescription)
form.editable-form
.editable-icon.glyphicon.glyphicon-remove
.form-group
label.control-label(data-i18n="account_profile.long_description") Self 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 Built a P2P streaming 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]', value=profile.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
div Anonymously solved problems in algebraic graph theory. Swept 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()
div(class="our-notes-section" + (editing ? " deemphasized" : ""))
h3.experience-header(data-i18n="account_profile.our_notes") CodeCombat's 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)
if me.isAdmin()
h3(data-i18n="account_profile.remarks") Remarks
#remark-treema
.right-column.full-height-column
.sub-column
#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('/images/pages/account/profile/sample_project.png')")
p Cog.js
div JS microlibrary to animate fixie bike cogs on ecommerce sites that sell bike hardware.
li.edit-example-text
.project-image(style="background-image: url('/images/pages/account/profile/sample_project.png')")
p Combjoy
div Ember.js project that schedules hair stylists to ride in Ubers with you to comb your hair on your way to work.
li.edit-example-text
.project-image(style="background-image: url('/images/pages/account/profile/sample_project.png')")
p Bass Drop
div Insert a few lines of JS into your site and get a "Drop the bass!" button for your website. Plays techno.
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/pages/account/profile/sample_project.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(data-i18n="common.loading") Loading...
else if user
.public-profile-container
h2
span(data-i18n="account_profile.profile_for_prefix") Profile for
span= user.get('name') || "Anonymous Wizard"
span(data-i18n="account_profile.profile_for_suffix")
if user.loaded
img.profile-photo(src=user.getPhotoURL(256))
p To see a private user profile, you may need to log in.
else
.public-profile-container
h2
span(data-i18n="account_profile.profile_for_prefix") Profile for
span= userID
span(data-i18n="account_profile.profile_for_suffix")
|
span(data-i18n="loading_error.not_found")

View file

@ -27,10 +27,6 @@ block content
ul
li
a(href="/admin/level-sessions", data-i18n="admin.av_entities_active_instances_url") Active Instances
li
a(href="/admin/candidates", data-i18n="admin.av_entities_candidates_list_url") Candidate List
li
a(href="/admin/employers", data-i18n="admin.av_entities_employer_list_url") Employer List
li
a(href="/admin/trial-requests") Trial Requests
li

View file

@ -1,110 +0,0 @@
extends /templates/base
block content
if !me.isAdmin()
h1 Admins Only
if me.isAdmin()
h1(data-i18n="employers.want_to_hire_our_players") Hire CodeCombat Players
if !isEmployer && !me.isAdmin()
div#info_wrapper
div#leftside
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon1.png")
h2(data-i18n="employers.what") What is CodeCombat?
p(data-i18n="employers.what_blurb") CodeCombat is a multiplayer browser programming game. Players write code to control their forces in battle against other developers. We support JavaScript, Python, Lua, Clojure, CoffeeScript, and Io.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon3.png")
h2(data-i18n="employers.who") Who Are the Players?
p(data-i18n="employers.who_blurb") CodeCombateers are CTOs, VPs of Engineering, and graduates of top 20 engineering schools. No junior developers here. Our players enjoy playing with code and solving problems.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon5.png")
h2(data-i18n="employers.cost") Who Are the Players?
p(data-i18n="employers.cost_blurb") CodeCombateers are CTOs, VPs of Engineering, and graduates of top 20 engineering schools. No junior developers here. Our players enjoy playing with code and solving problems.
div#rightside
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon2.png")
h2(data-i18n="employers.how") How Much Do we Charge?
p(data-i18n="employers.how_blurb") We charge 15% of first year's salary and offer a 100% money back guarantee for 90 days. We don't charge for candidates who are already actively being interviewed at your company.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon4.png")
h2(data-i18n="employers.why") Why Hire Through Us?
p
span(data-i18n="employers.why_blurb_1") We will save you time. Every CodeCombateer we feaure is
strong(data-i18n="employers.why_blurb_2") looking for work
span(data-i18n="employers.why_blurb_3") , has
strong(data-i18n="employers.why_blurb_4") demonstrated top notch technical skills
span(data-i18n="employers.why_blurb_5") , and has been
strong(data-i18n="employers.why_blurb_6") personally screened by us
span(data-i18n="employers.why_blurb_7") . Stop screening and start hiring.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon6.png")
h2(data-i18n="employers.response") What's the Response Rate?
p(data-i18n="employers.response_blurb") Almost every developer you contact on CodeCombat will respond to inquires whether or not they want to interivew. If you would like help finding a candidate for your role, we can make recommendations.
if candidates.length
ul.nav.nav-pills
li.active
a(href="#featured-candidates", data-toggle="tab")
span.spr(data-i18n="employers.featured_developers") Featured Developers
| (#{featuredCandidates.length})
if otherCandidates.length
li
a(href="#other-candidates", data-toggle="tab")
span.spr(data-i18n="employers.other_developers") Other Developers
| (#{otherCandidates.length})
if me.isAdmin() && inactiveCandidates.length
li
a(href="#inactive-candidates", data-toggle="tab")
span.spr(data-i18n="employers.inactive_developers") Inactive Developers
| (#{inactiveCandidates.length})
div.tab-content
for area, tabIndex in [{id: "featured-candidates", candidates: featuredCandidates}, {id: "other-candidates", candidates: otherCandidates}, {id: "inactive-candidates", candidates: inactiveCandidates}]
div(class="tab-pane well" + (tabIndex ? "" : " active"), id=area.id)
table.table.table-condensed.table-hover.table-responsive.tablesorter
thead
tr
th(data-i18n="general.name") Name
th(data-i18n="employers.candidate_location") Location
th(data-i18n="employers.candidate_looking_for") Looking For
th(data-i18n="employers.candidate_role") Role
th(data-i18n="employers.candidate_top_skills") Top Skills
th(data-i18n="employers.candidate_years_experience") Yrs Exp
th(data-i18n="employers.candidate_last_updated") Last Updated
if me.isAdmin()
th(data-i18n="employers.candidate_who") Who
if me.isAdmin() && area.id == 'inactive-candidates'
th ✓?
tbody
for candidate, index in area.candidates
- var profile = candidate.get('jobProfile', true);
- var authorized = candidate.id; // If we have the id, then we are authorized.
- var profileAge = (new Date() - new Date(profile.updated)) / 86400 / 1000;
- var expired = profileAge > 2 * 30.4;
tr(data-candidate-id=candidate.id, id=candidate.id, class=expired ? "expired" : "")
td
if authorized
img(src=candidate.getPhotoURL(50), alt=profile.name, title=profile.name, height=50)
if profile.name
p= profile.name
else if me.isAdmin()
p (#{candidate.get('name')})
else
img(src="/images/pages/contribute/archmage.png", alt="", title="Sign up as an employer to see our candidates", width=50)
p Developer ##{index + 1 + (area.id == 'featured-candidates' ? 0 : featuredCandidates.length)}
if profile.country == 'USA'
td= profile.city
else
td= profile.country
td= profile.lookingFor
td= profile.jobTitle
td
each skill in profile.skills
code= skill
span
td= profile.experience
td(data-profile-age=profileAge)= moment(profile.updated).fromNow()
if me.isAdmin()
td= remarks[candidate.id] ? remarks[candidate.id].get('contactName') : ''
if me.isAdmin() && area.id == 'inactive-candidates'
if candidate.get('jobProfileApproved')
td ✓
else
td ✗

View file

@ -1,70 +0,0 @@
extends /templates/base
block content
if !me.isAdmin()
h1 Admin Only
else
h1(data-i18n="admin.av_entities_employer_list_url") Employer List
p
| We currently have
if employers.length
| #{employers.length}
else
| ...
| employers in the system.
if employers.length
table.table.table-condensed.table-hover.table-responsive.tablesorter
thead
tr
th(data-i18n="general.name") Name
th Company
th(data-i18n="general.email") Email
th Logins
th Candidates Viewed
th Candidates Contacted
th Signed Up
tbody
for employer, index in employers
- var activity = employer.get('activity') || {};
- var hasLinkedIn = typeof employer.get('signedEmployerAgreement') !== "undefined";
- var emptyDate = {date: Date.now()};
- var linkedIn = hasLinkedIn ? employer.get('signedEmployerAgreement').data : emptyDate;
tr(data-employer-id=employer.id)
td
img(src=employer.getPhotoURL(50), height=50)
p
if employer.get('firstName')
span= employer.get('firstName') + ' ' + employer.get('lastName')
if employer.get('name')
| -
else if linkedIn.firstName
span= linkedIn.firstName + ' ' + linkedIn.lastName
if employer.get('name')
| -
if employer.get('name')
span= employer.get('name')
if !employer.get('firstName') && !linkedIn.firstName && !employer.get('name')
| Anoner
td
if employer.get('signedEmployerAgreement')
a(href=employer.get('signedEmployerAgreement').data.publicProfileUrl)= employer.get('employerAt')
else
| Unknown
td= employer.get('email')
for a in ['login', 'view_candidate', 'contact_candidate']
- var act = activity[a];
if act
td
strong= act.count
|
br
span= moment(act.first).fromNow()
br
span= moment(act.last).fromNow()
else
td 0
td(data-employer-age=(new Date() - new Date(linkedIn.date)) / 86400 / 1000)= moment(employer.get('signedEmployerAgreement') ? employer.get('signedEmployerAgreement').date : linkedIn.date).fromNow()

View file

@ -1,188 +0,0 @@
extends /templates/recruitment_base
block content
.deprecation-warning
h1(data-i18n="employers.deprecation_warning_title") Sorry, CodeCombat is not recruiting right now.
p(data-i18n="employers.deprecation_warning") We are focusing on beginner levels instead of finding expert developers for the time being.
//.deprecated
// .artisanal-claim
// if me.get('anonymous')
// a#login-link(data-i18n="login.log_in") Log In
// br
// if !isEmployer && !me.isAdmin()
// #tagline
// h1(data-i18n="employers.hire_developers_not_credentials") Hire developers, not credentials.
// button.btn.get-started-button.employer-button(data-i18n="employers.get_started") Get Started
// else
// if !me.get('anonymous')
// a#logout-link(data-i18n="login.log_out") Log Out
// br
// .row
// - var fullProfiles = isEmployer || me.isAdmin();
//
// if fullProfiles
// #filter-column.col-md-3
// #filter
// .panel-group#filter_panel
// a#filter-link(data-toggle="collapse" data-target="#collapseOne")
// .panel.panel-default
// .panel-heading
// h4.panel-title
// span.glyphicon.glyphicon-folder-open#folder-icon
// | Filter
// .panel-collapse.collapse.in#collapseOne
// .panel-body
// p
// strong(data-i18n="employers.already_screened") We've already technically screened all our candidates
// span(data-i18n="employers.filter_further") , but you can also filter further:
// form#filters
// .filter_section#visa_filter
// h4(data-i18n="employers.filter_visa") Visa
// label
// input(type="checkbox" name="visa" value="Authorized to work in the US")
// span(data-i18n="employers.filter_visa_yes") US Authorized
// | (#{candidatesInFilter("visa","Authorized to work in the US")})
// label
// input(type="checkbox" name="visa" value="Need visa sponsorship")
// span(data-i18n="employers.filter_visa_no") Not Authorized
// | (#{candidatesInFilter("visa","Need visa sponsorship")})
// .filter_section#school_filter
// h4(data-i18n="account_profile.education") Education
// label
// input(type="checkbox" name="schoolFilter" value="Top School")
// span(data-i18n="employers.filter_education_top") Top School
// | (#{candidatesInFilter("schoolFilter","Top School")})
// label
// input(type="checkbox" name="schoolFilter" value="Other")
// span(data-i18n="employers.filter_education_other") Other
// | (#{candidatesInFilter("schoolFilter","Other")})
// .filter_section#role_filter
// h4(data-i18n="employers.candidate_role") Role
// label
// input(type="checkbox" name="roleFilter" value="Web Developer")
// span(data-i18n="employers.filter_role_web_developer") Web Developer
// | (#{candidatesInFilter("roleFilter","Web Developer")})
// label
// input(type="checkbox" name="roleFilter" value="Software Developer")
// span(data-i18n="employers.filter_role_software_developer") Software Developer
// | (#{candidatesInFilter("roleFilter","Software Developer")})
// label
// input(type="checkbox" name="roleFilter" value="Mobile Developer")
// span(data-i18n="employers.filter_role_mobile_developer") Mobile Developer
// | (#{candidatesInFilter("roleFilter","Mobile Developer")})
// .filter_section#seniority_filter
// h4(data-i18n="employers.filter_experience") Experience
// label
// input(type="checkbox" name="seniorityFilter" value="Senior")
// span(data-i18n="employers.filter_experience_senior") Senior
// | (#{candidatesInFilter("seniorityFilter","Senior")})
// label
// input(type="checkbox" name="seniorityFilter" value="Junior")
// span(data-i18n="employers.filter_experience_junior") Junior
// | (#{candidatesInFilter("seniorityFilter","Junior")})
// label
// input(type="checkbox" name="seniorityFilter" value="Recent Grad")
// span(data-i18n="employers.filter_experience_recent_grad") Recent Grad
// | (#{candidatesInFilter("seniorityFilter","Recent Grad")})
// label
// input(type="checkbox" name="seniorityFilter" value="College Student")
// span(data-i18n="employers.filter_experience_student") College Student
// | (#{candidatesInFilter("seniorityFilter","College Student")})
//
// //input#select_all_checkbox(type="checkbox" name="select_all" checked)
// //| Select all
// p#results
// | #{numberOfCandidates}
// span(data-i18n="employers.results") results
// h4#filter-alerts-heading Filter Email Alerts
// p Get an email whenever a candidate meeting certain criteria enters the system.
// table#saved-filter-table
// thead
// tr
// th Filters
// th Remove
// tbody
// button.btn#create-alert-button Create Alert with Current Filters
//
// #candidates-column(class=fullProfiles ? "full-profiles col-md-9" : "teaser-profiles col-md-12")
// if candidates.length
// #candidate-table
// table
// tbody
// for candidate, index in featuredCandidates
// - var profile = candidate.get('jobProfile');
// - var authorized = candidate.id; // If we have the id, then we are authorized.
// - var profileAge = (new Date() - new Date(profile.updated)) / 86400 / 1000;
// - var expired = profileAge > 2 * 30.4;
// - var curated = profile.curated;
// - var photoSize = fullProfiles ? 75 : 50;
//
// tr.candidate-row(data-candidate-id=candidate.id, id=candidate.id, class=expired ? "expired" : "")
// td(rowspan=3)
// - var photoURL = candidate.getPhotoURL(photoSize, false, true);
// div(class="candidate-picture " + (/^\/file/.test(photoURL) ? "" : "anonymous"), style='background-image: url(' + encodeURI(photoURL) + ')')
// if fullProfiles
// td.candidate-name-cell
// strong= profile.name
// | -
// span= profile.jobTitle
// tr.description_row(data-candidate-id=candidate.id)
// if curated && curated.shortDescription
// td.candidate-description
// div #{curated.shortDescription}
// else
// td.candidate-description
// div #{profile.shortDescription}
// tr.border_row(data-candidate-id=candidate.id)
// if curated
// - var workHistory = curated.workHistory.join(",");
// if !fullProfiles
// td.tag_column
// img(src="/images/pages/employer/tag.png")
// | #{profile.jobTitle}
// td.location_column
// img(src="/images/pages/employer/location.png")
// | #{curated.location}
// td.education_column
// img(src="/images/pages/employer/education.png")
// | #{curated.education}
// td.work_column
// if workHistory
// img(src="/images/pages/employer/briefcase.png")
// | #{workHistory}
//
// if !fullProfiles
// div#info_wrapper
// span.hiring-call-to-action
// h2#start-hiring(data-i18n="employers.start_hiring") Start hiring.
// button.btn.get-started-button.employer-button(data-i18n="employers.get_started") Get Started
//
// h2#hiring-reasons.hiring-call-to-action(data-i18n="employers.reasons") Three reasons you should hire through us:
// .reasons#top_row
// .reason
// img.employer_icon(src="/images/pages/employer/employer_icon2.png")
// h3(data-i18n="employers.everyone_looking") Everyone here is looking for their next opportunity.
// p(data-i18n="employers.everyone_looking_blurb") Forget about 20% LinkedIn InMail response rates. Everyone that we list on this site wants to find their next position and will respond to your request for an introduction.
// .reason
// img.employer_icon(src="/images/pages/employer/employer_icon6.png")
// h3(data-i18n="employers.weeding") Sit back; we've done the weeding for you.
// p(data-i18n="employers.weeding_blurb") Every player that we list has been screened for technical ability. We also perform phone screens for select candidates and make notes on their profiles to save you time.
// .reason
// img(class="employer_icon" src="/images/pages/employer/employer_icon3.png")
// h3(data-i18n="employers.pass_screen") They will pass your technical screen.
// p(data-i18n="employers.pass_screen_blurb") Review each candidate's code before reaching out. One employer found that 5x as many of our devs passed their technical screen than hiring from Hacker News.
// span.hiring-call-to-action
// h2(data-i18n="employers.make_hiring_easier") Make my hiring easier, please.
// button.btn.get-started-button.employer-button(data-i18n="employers.get_started") Get Started
// .reasons#bottom_row
// .reason_long
// img.employer_icon(src="/images/pages/employer/employer_icon1.png")
// .reason_text
// h3(data-i18n="employers.what") What is CodeCombat?
// p(data-i18n="employers.what_blurb") CodeCombat is a multiplayer browser programming game. Players write code to control their forces in battle against other developers. We support JavaScript, Python, Lua, Clojure, CoffeeScript, and Io.
// .reason_long
// img.employer_icon(src="/images/pages/employer/employer_icon5.png")
// .reason_text
// h3(data-i18n="employers.cost") How much do we charge?
// p(data-i18n="employers.cost_blurb") We charge 15% of first year's salary and offer a 100% money back guarantee for 90 days. We don't charge for candidates who are already actively being interviewed at your company.

View file

@ -1,61 +0,0 @@
extends /templates/core/modal-base
block modal-header-content
if userIsAnonymous || !userIsAuthorized
h3(data-i18n="employer_signup.title") Sign Up to Hire CodeCombat Players
else
h3 CodeCombat Placement Agreement
block modal-body-content
if userIsAnonymous
if userIsAuthorized
| You appear to be authorized on CodeCombat with LinkedIn.
else if sentMoreInfoEmail
| Thanks! You should receive an email from George shortly.
else
.form#signup-form
.form-group
input#signup-email.form-control.input-large(name="email",type="email" placeholder="Email")
.form-group
input#signup-password.input-large.form-control(name="password", type="password" placeholder="Password")
.modal-footer.linkedin
button.btn.btn-primary(id="create-account-button") Create Account
br
br
| Already have a CodeCombat account?
a.login-link(data-toggle="coco-modal", data-target="core/AuthModal") Log in to continue!
else if !userIsAuthorized
.modal-footer.linkedin
p Please sign into your LinkedIn account to verify your identity.
script(type="in/Login" id="linkedInAuthButton" data-onAuth="contractCallback")
else
| Please agree to our terms before accessing our candidates.
br
br
b Who we are:
| CodeCombat is a programming game that both teaches and vets programmers. If you accept this agreement, we will let you hire the most talented developers on our platform.
br
br
b Placement fee:
| If you hire any of our players, you agree to pay us 15% of the candidate's first year annualized starting base salary. The fee is due on the first day that the candidate is employed and is 100% refundable for up to 90 day if the candidate doesn't remain employed at the company for any reason.
br
br
b Interns are free:
| We will not bill you for interns and part time hires (remote or onsite) hired through this site, provided they do not become full time hires within 1 year of their start date. If they do become full time hires within 1 year of their start date, we will invoice you 15% of their first year's annualized starting base salary on their first day of full time employment. For these hires, the 90 day guarantee does not apply.
br
br
| By clicking Agree, you are agreeing to CodeCombat's Placement Agreement on behalf of your company. You also consent to CodeCombat storing basic LinkedIn profile data for verification purposes, including your name, email, public profile URL, and work history.
block modal-footer
if userIsAnonymous
if userIsAuthorized
.modal-footer.linkedin
a.login-link(data-toggle="coco-modal", data-target="core/AuthModal") Please log in to continue.
else if !userIsAnonymous && !userIsAuthorized
.modal-footer.linkedin
else if userIsAuthorized && !userHasSignedContract
.modal-footer.linkedin
button.btn.btn-primary(id="contract-agreement-button") I agree
else
.modal-footer.linkedin
| Thanks! You've already agreed to the contract.

View file

@ -1,22 +0,0 @@
extends /templates/core/contact
block modal-header-content
h3(data-i18n="contact.contact_candidate") Contact Candidate
block modal-body-content
p(data-i18n="contact.recruitment_reminder") Use this form to reach out to candidates you are interested in interviewing. Remember that CodeCombat charges 15% of first-year salary. The fee is due upon hiring the employee and is refundable for 90 days if the employee does not remain employed. Part time, remote, and contract employees are free, as are interns.
.form
.form-group
label.control-label(for="contact-email", data-i18n="general.email") Email
input#contact-email.form-control(name="email", type="email", value=me.get('email'), placeholder="Where should the candidate reply?")
.form-group
label.control-label(for="contact-subject", data-i18n="general.subject") Subject
input#contact-subject.form-control(name="subject", type="text", value="Job interest", placeholder="Subject of the email the candidate will receive.")
.form-group
label.control-label(for="contact-message", data-i18n="general.message") Message
textarea#contact-message.form-control(name="message", rows=8)
block modal-footer-content
span.sending-indicator.pull-left.secret(data-i18n="common.sending") Sending...
a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="common.cancel").btn Cancel
button.btn.btn-primary#contact-submit-button(data-i18n="common.send") Send

View file

@ -1,352 +0,0 @@
RootView = require 'views/core/RootView'
template = require 'templates/employers'
User = require 'models/User'
{me} = require 'core/auth'
CocoCollection = require 'collections/CocoCollection'
EmployerSignupModal = require 'views/modal/EmployerSignupModal'
class CandidatesCollection extends CocoCollection
url: '/db/user/x/candidates'
model: User
module.exports = class EmployersView extends RootView
id: 'employers-view'
template: template
events:
'click #candidate-table': 'onCandidateClicked'
'click #logout-link': 'logoutAccount'
'change #filters input': 'onFilterChanged'
'change #select_all_checkbox': 'handleSelectAllChange'
'click .get-started-button': 'openSignupModal'
'click .navbar-brand': 'restoreBodyColor'
'click #login-link': 'onClickAuthButton'
'click #filter-link': 'swapFolderIcon'
'click #create-alert-button': 'createFilterAlert'
'click .deletion-col': 'deleteFilterAlert'
constructor: (options) ->
super options
return
@candidates = @supermodel.loadCollection(new CandidatesCollection(), 'candidates').model
@setFilterDefaults()
onLoaded: ->
super()
return
@setUpScrolling()
afterRender: ->
super()
return
@sortTable() if @candidates.models.length
@renderSavedFilters()
afterInsert: ->
super()
return
_.delay @checkForEmployerSignupHash, 500
#fairly hacky, change this in the future
@originalBackgroundColor = $('body').css 'background-color'
$('body').css 'background-color', '#B4B4B4'
restoreBodyColor: ->
$('body').css 'background-color', @originalBackgroundColor
swapFolderIcon: ->
$('#folder-icon').toggleClass('glyphicon-folder-close').toggleClass('glyphicon-folder-open')
onFilterChanged: ->
@resetFilters()
that = @
$('#filters :input').each ->
input = $(this)
checked = input.prop 'checked'
name = input.attr 'name'
value = input.val()
if name is 'phoneScreenFilter'
value = JSON.parse(input.prop 'value')
if checked
that.filters[name] = _.union that.filters[name], [value]
else
that.filters[name] = _.difference that.filters[name], [value]
for filterName, filterValues of @filters
if filterValues.length is 0
@filters[filterName] = @defaultFilters[filterName]
@applyFilters()
openSignupModal: ->
@openModalView new EmployerSignupModal
handleSelectAllChange: (e) ->
checkedState = e.currentTarget.checked
$('#filters :input').each ->
$(this).prop 'checked', checkedState
@onFilterChanged()
resetFilters: ->
for filterName, filterValues of @filters
@filters[filterName] = []
applyFilters: ->
candidateList = _.sortBy @candidates.models, (c) -> c.get('jobProfile').updated
candidateList = _.filter candidateList, (c) -> c.get('jobProfileApproved')
filteredCandidates = candidateList
for filterName, filterValues of @filters
if filterName is 'visa'
filteredCandidates = _.difference filteredCandidates, _.filter(filteredCandidates, (c) ->
fieldValue = c.get('jobProfile').visa
return not (_.contains filterValues, fieldValue)
)
else
filteredCandidates = _.difference filteredCandidates, _.filter(filteredCandidates, (c) ->
unless c.get('jobProfile').curated then return true
fieldValue = c.get('jobProfile').curated?[filterName]
return not (_.contains filterValues, fieldValue)
)
candidateIDsToShow = _.pluck filteredCandidates, 'id'
$('#candidate-table tr').each -> $(this).hide()
candidateIDsToShow.forEach (id) ->
$("[data-candidate-id=#{id}]").show()
$('#results').text(candidateIDsToShow.length + ' results')
return filteredCandidates
setFilterDefaults: ->
@filters =
phoneScreenFilter: [true, false]
visa: ['Authorized to work in the US', 'Need visa sponsorship']
schoolFilter: ['Top School', 'Other']
locationFilter: ['Bay Area', 'New York', 'Other US', 'International']
roleFilter: ['Web Developer', 'Software Developer', 'Mobile Developer']
seniorityFilter: ['College Student', 'Recent Grad', 'Junior', 'Senior']
@defaultFilters = _.cloneDeep @filters
candidatesInFilter: (filterName, filterValue) =>
candidates = @getActiveAndApprovedCandidates()
if filterName and filterValue
if filterName is 'visa'
return (_.filter candidates, (c) -> c.get('jobProfile').visa is filterValue).length
else
return (_.filter candidates, (c) -> c.get('jobProfile').curated?[filterName] is filterValue).length
else
return Math.floor(Math.random() * 500)
createFilterAlert: ->
currentFilterSet = _.cloneDeep @filters
currentSavedFilters = _.cloneDeep me.get('savedEmployerFilterAlerts') ? []
currentSavedFilters.push currentFilterSet
@patchEmployerFilterAlerts currentSavedFilters, @renderSavedFilters
deleteFilterAlert: (e) ->
index = $(e.target).closest('tbody tr').data('filter-index')
currentSavedFilters = me.get('savedEmployerFilterAlerts')
currentSavedFilters.splice(index,1)
@patchEmployerFilterAlerts currentSavedFilters, @renderSavedFilters
patchEmployerFilterAlerts: (newFilters, cb) ->
me.set('savedEmployerFilterAlerts',newFilters)
unless me.isValid()
alert("There was an error setting the filter(me.isValid() returned false.) Reverting! Please notify team@codecombat.com.")
me.set 'savedEmployerFilterAlerts', me.previous('savedEmployerFilterAlerts')
else
triggerErrorAlert = -> alert("There was an error saving your filter alert! Please notify team@codecombat.com.")
res = me.save {"savedEmployerFilterAlerts": newFilters}, {patch: true, type: 'PUT', success: cb, error: triggerErrorAlert}
renderSavedFilters: =>
savedFilters = me.get('savedEmployerFilterAlerts')
unless savedFilters?.length then return $("#saved-filter-table").hide()
$("#saved-filter-table").show()
$("#saved-filter-table").find("tbody tr").remove()
for filter, index in savedFilters
$("#saved-filter-table tbody").append("""<tr data-filter-index="#{index}"><td>#{@generateFilterAlertDescription(filter)}</td><td class="deletion-col"><a>✗</a></td></tr> """)
generateFilterAlertDescription: (filter) =>
descriptionString = ""
for key in _.keys(filter).sort()
value = filter[key]
if key is "filterActive" or _.without(@defaultFilters[key],value...).length is 0 then continue
if descriptionString.length then descriptionString += ", "
descriptionString += value.join(", ")
if descriptionString.length is 0 then descriptionString = "Any new candidate"
return descriptionString
getActiveAndApprovedCandidates: =>
candidates = _.filter @candidates.models, (c) -> c.get('jobProfile').active
return _.filter candidates, (c) -> c.get('jobProfileApproved')
getRenderData: ->
ctx = super()
return ctx
ctx.isEmployer = @isEmployer()
#If you change the candidates displayed, change candidatesInFilter()
ctx.candidates = _.sortBy @candidates.models, (c) -> -1 * c.get('jobProfile').experience
ctx.candidates = _.sortBy ctx.candidates, (c) -> not c.get('jobProfile').curated?
ctx.candidates = _.sortBy ctx.candidates, (c) -> c.get('jobProfile').curated?.featured
ctx.activeCandidates = _.filter ctx.candidates, (c) -> c.get('jobProfile').active
ctx.inactiveCandidates = _.reject ctx.candidates, (c) -> c.get('jobProfile').active
ctx.featuredCandidates = _.filter ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
unless @isEmployer() or me.isAdmin()
ctx.featuredCandidates = _.filter ctx.featuredCandidates, (c) -> c.get('jobProfile').curated
ctx.featuredCandidates = ctx.featuredCandidates.slice(0,7)
if me.isAdmin()
ctx.featuredCandidates = ctx.candidates
ctx.candidatesInFilter = @candidatesInFilter
ctx.otherCandidates = _.reject ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
ctx.moment = moment
ctx._ = _
ctx.numberOfCandidates = ctx.featuredCandidates.length
ctx
isEmployer: -> 'employer' in me.get('permissions', true)
setUpScrolling: =>
$('.nano').nanoScroller()
#if window.history?.state?.lastViewedCandidateID
# $('.nano').nanoScroller({scrollTo: $('#' + window.history.state.lastViewedCandidateID)})
#else if window.location.hash.length is 25
# $('.nano').nanoScroller({scrollTo: $(window.location.hash)})
checkForEmployerSignupHash: =>
if window.location.hash is '#employerSignupLoggingIn' and not ('employer' in me.get('permissions', true)) and not me.isAdmin()
@openModalView new EmployerSignupModal
window.location.hash = ''
sortTable: ->
# http://mottie.github.io/tablesorter/docs/example-widget-bootstrap-theme.html
$.extend $.tablesorter.themes.bootstrap,
# these classes are added to the table. To see other table classes available,
# look here: http://twitter.github.com/bootstrap/base-css.html#tables
table: 'table table-bordered'
caption: 'caption'
header: 'bootstrap-header' # give the header a gradient background
footerRow: ''
footerCells: ''
icons: '' # add 'icon-white' to make them white; this icon class is added to the <i> in the header
sortNone: 'bootstrap-icon-unsorted'
sortAsc: 'icon-chevron-up' # glyphicon glyphicon-chevron-up' # we are still using v2 icons
sortDesc: 'icon-chevron-down' # glyphicon-chevron-down' # we are still using v2 icons
active: '' # applied when column is sorted
hover: '' # use custom css here - bootstrap class may not override it
filterRow: '' # filter row class
even: '' # odd row zebra striping
odd: '' # even row zebra striping
# e = exact text from cell
# n = normalized value returned by the column parser
# f = search filter input value
# i = column index
# $r = ???
filterSelectExactMatch = (e, n, f, i, $r) -> e is f
# call the tablesorter plugin and apply the uitheme widget
@$el.find('.tablesorter').tablesorter
theme: 'bootstrap'
widthFixed: true
headerTemplate: '{content} {icon}'
textSorter:
6: (a, b, direction, column, table) ->
days = []
for s in [a, b]
n = parseInt s
n = 0 unless _.isNumber n
n = 1 if /^a/.test s
for [duration, factor] in [
[/second/i, 1/(86400*1000)]
[/minute/i, 1/1440]
[/hour/i, 1/24]
[/week/i, 7]
[/month/i, 30.42]
[/year/i, 365.2425]
]
if duration.test s
n *= factor
break
if /^in /i.test s
n *= -1
days.push n
days[0] - days[1]
sortList: if @isEmployer() or me.isAdmin() then [[6, 0]] else [[0, 1]]
# widget code contained in the jquery.tablesorter.widgets.js file
# use the zebra stripe widget if you plan on hiding any rows (filter widget)
widgets: ['uitheme', 'zebra', 'filter']
widgetOptions:
# using the default zebra striping class name, so it actually isn't included in the theme variable above
# this is ONLY needed for bootstrap theming if you are using the filter widget, because rows are hidden
zebra: ['even', 'odd']
# extra css class applied to the table row containing the filters & the inputs within that row
filter_cssFilter: ''
# If there are child rows in the table (rows with class name from 'cssChildRow' option)
# and this option is true and a match is found anywhere in the child row, then it will make that row
# visible; default is false
filter_childRows: false
# if true, filters are collapsed initially, but can be revealed by hovering over the grey bar immediately
# below the header row. Additionally, tabbing through the document will open the filter row when an input gets focus
filter_hideFilters: false
# Set this option to false to make the searches case sensitive
filter_ignoreCase: true
# jQuery selector string of an element used to reset the filters
filter_reset: '.reset'
# Use the $.tablesorter.storage utility to save the most recent filters
filter_saveFilters: true
# Delay in milliseconds before the filter widget starts searching; This option prevents searching for
# every character while typing and should make searching large tables faster.
filter_searchDelay: 150
# Set this option to true to use the filter to find text from the start of the column
# So typing in 'a' will find 'albert' but not 'frank', both have a's; default is false
filter_startsWith: false
filter_functions:
2:
'Full-time': filterSelectExactMatch
'Part-time': filterSelectExactMatch
'Contracting': filterSelectExactMatch
'Remote': filterSelectExactMatch
'Internship': filterSelectExactMatch
5:
'0-1': (e, n, f, i, $r) -> n <= 1
'2-5': (e, n, f, i, $r) -> 2 <= n <= 5
'6+': (e, n, f, i, $r) -> 6 <= n
6:
'Last day': (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 1
'Last week': (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 7
'Last 4 weeks': (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 28
8:
'': filterSelectExactMatch
'': filterSelectExactMatch
logoutAccount: ->
window.location.hash = ''
super()
onCandidateClicked: (e) ->
id = $(e.target).closest('tr').data('candidate-id')
if id and (@isEmployer() or me.isAdmin())
if window.history
oldState = _.cloneDeep window.history.state ? {}
oldState['lastViewedCandidateID'] = id
window.history.replaceState(oldState, '')
else
window.location.hash = id
url = "/account/profile/#{id}"
window.open url, '_blank'
else
@openModalView new EmployerSignupModal

File diff suppressed because one or more lines are too long

View file

@ -1,200 +0,0 @@
RootView = require 'views/core/RootView'
template = require 'templates/admin/candidates'
User = require 'models/User'
UserRemark = require 'models/UserRemark'
{me} = require 'core/auth'
CocoCollection = require 'collections/CocoCollection'
EmployerSignupModal = require 'views/modal/EmployerSignupModal'
class CandidatesCollection extends CocoCollection
url: '/db/user/x/candidates'
model: User
class UserRemarksCollection extends CocoCollection
url: '/db/user.remark?project=contact,contactName,user'
model: UserRemark
module.exports = class CandidatesView extends RootView
id: "admin-candidates-view"
template: template
events:
'click tbody tr': 'onCandidateClicked'
constructor: (options) ->
super options
#@candidates = @supermodel.loadCollection(new CandidatesCollection(), 'candidates').model
@candidates = models: [] # Disabling, since we got rid of the index that fetches these.
@remarks = @supermodel.loadCollection(new UserRemarksCollection(), 'user_remarks').model
onLoaded: ->
super()
@setUpScrolling()
afterRender: ->
super()
@sortTable() if @candidates.models.length
afterInsert: ->
super()
_.delay @checkForEmployerSignupHash, 500
getRenderData: ->
ctx = super()
ctx.isEmployer = @isEmployer()
ctx.candidates = _.sortBy @candidates.models, (c) -> c.get('jobProfile').updated
ctx.activeCandidates = _.filter ctx.candidates, (c) -> c.get('jobProfile').active
ctx.inactiveCandidates = _.reject ctx.candidates, (c) -> c.get('jobProfile').active
ctx.featuredCandidates = _.filter ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
ctx.otherCandidates = _.reject ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
ctx.remarks = {}
ctx.remarks[remark.get('user')] = remark for remark in @remarks.models
ctx.moment = moment
ctx._ = _
ctx
isEmployer: -> 'employer' in me.get('permissions', true)
setUpScrolling: ->
$(".nano").nanoScroller()
if window.history?.state?.lastViewedCandidateID
$(".nano").nanoScroller({scrollTo:$("#" + window.history.state.lastViewedCandidateID)})
else if window.location.hash.length is 25
$(".nano").nanoScroller({scrollTo:$(window.location.hash)})
checkForEmployerSignupHash: =>
if window.location.hash is "#employerSignupLoggingIn" and not ("employer" in me.get('permissions', true))
@openModalView new EmployerSignupModal
window.location.hash = ''
sortTable: ->
# http://mottie.github.io/tablesorter/docs/example-widget-bootstrap-theme.html
$.extend $.tablesorter.themes.bootstrap,
# these classes are added to the table. To see other table classes available,
# look here: http://twitter.github.com/bootstrap/base-css.html#tables
table: "table table-bordered"
caption: "caption"
header: "bootstrap-header" # give the header a gradient background
footerRow: ""
footerCells: ""
icons: "" # add "icon-white" to make them white; this icon class is added to the <i> in the header
sortNone: "bootstrap-icon-unsorted"
sortAsc: "icon-chevron-up" # glyphicon glyphicon-chevron-up" # we are still using v2 icons
sortDesc: "icon-chevron-down" # glyphicon-chevron-down" # we are still using v2 icons
active: "" # applied when column is sorted
hover: "" # use custom css here - bootstrap class may not override it
filterRow: "" # filter row class
even: "" # odd row zebra striping
odd: "" # even row zebra striping
# e = exact text from cell
# n = normalized value returned by the column parser
# f = search filter input value
# i = column index
# $r = ???
filterSelectExactMatch = (e, n, f, i, $r) -> e is f
# call the tablesorter plugin and apply the uitheme widget
@$el.find(".tablesorter").tablesorter
theme: "bootstrap"
widthFixed: true
headerTemplate: "{content} {icon}"
textSorter:
6: (a, b, direction, column, table) ->
days = []
for s in [a, b]
n = parseInt s
n = 0 unless _.isNumber n
n = 1 if /^a/.test s
for [duration, factor] in [
[/second/i, 1 / (86400 * 1000)]
[/minute/i, 1 / 1440]
[/hour/i, 1 / 24]
[/week/i, 7]
[/month/i, 30.42]
[/year/i, 365.2425]
]
if duration.test s
n *= factor
break
if /^in /i.test s
n *= -1
days.push n
days[0] - days[1]
sortList: if @isEmployer() or me.isAdmin() then [[6, 0]] else [[0, 1]]
# widget code contained in the jquery.tablesorter.widgets.js file
# use the zebra stripe widget if you plan on hiding any rows (filter widget)
widgets: ["uitheme", "zebra", "filter"]
widgetOptions:
# using the default zebra striping class name, so it actually isn't included in the theme variable above
# this is ONLY needed for bootstrap theming if you are using the filter widget, because rows are hidden
zebra: ["even", "odd"]
# extra css class applied to the table row containing the filters & the inputs within that row
filter_cssFilter: ""
# If there are child rows in the table (rows with class name from "cssChildRow" option)
# and this option is true and a match is found anywhere in the child row, then it will make that row
# visible; default is false
filter_childRows: false
# if true, filters are collapsed initially, but can be revealed by hovering over the grey bar immediately
# below the header row. Additionally, tabbing through the document will open the filter row when an input gets focus
filter_hideFilters: false
# Set this option to false to make the searches case sensitive
filter_ignoreCase: true
# jQuery selector string of an element used to reset the filters
filter_reset: ".reset"
# Use the $.tablesorter.storage utility to save the most recent filters
filter_saveFilters: true
# Delay in milliseconds before the filter widget starts searching; This option prevents searching for
# every character while typing and should make searching large tables faster.
filter_searchDelay: 150
# Set this option to true to use the filter to find text from the start of the column
# So typing in "a" will find "albert" but not "frank", both have a's; default is false
filter_startsWith: false
filter_functions:
2:
"Full-time": filterSelectExactMatch
"Part-time": filterSelectExactMatch
"Contracting": filterSelectExactMatch
"Remote": filterSelectExactMatch
"Internship": filterSelectExactMatch
5:
"0-1": (e, n, f, i, $r) -> n <= 1
"2-5": (e, n, f, i, $r) -> 2 <= n <= 5
"6+": (e, n, f, i, $r) -> 6 <= n
6:
"Last day": (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 1
"Last week": (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 7
"Last 4 weeks": (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 28
8:
"": filterSelectExactMatch
"": filterSelectExactMatch
onCandidateClicked: (e) ->
id = $(e.target).closest('tr').data('candidate-id')
if id
if window.history
oldState = _.cloneDeep window.history.state ? {}
oldState["lastViewedCandidateID"] = id
window.history.replaceState(oldState,"")
else
window.location.hash = id
url = "/account/profile/#{id}"
window.open url,"_blank"
else
@openModalView new EmployerSignupModal

View file

@ -1,152 +0,0 @@
RootView = require 'views/core/RootView'
template = require 'templates/admin/employer_list'
User = require 'models/User'
{me} = require 'core/auth'
CocoCollection = require 'collections/CocoCollection'
ModelModal = require 'views/modal/ModelModal'
class EmployersCollection extends CocoCollection
url: '/db/user/x/employers'
model: User
module.exports = class EmployersListView extends RootView
id: 'employers-view'
template: template
events:
'click tbody tr td:first-child': 'onEmployerClicked'
constructor: (options) ->
super options
@employers = @supermodel.loadCollection(new EmployersCollection(), 'employers').model
afterRender: ->
super()
@sortTable() if @employers.models.length
getRenderData: ->
ctx = super()
ctx.employers = @employers.models
ctx.moment = moment
ctx
sortTable: ->
# http://mottie.github.io/tablesorter/docs/example-widget-bootstrap-theme.html
$.extend $.tablesorter.themes.bootstrap,
# these classes are added to the table. To see other table classes available,
# look here: http://twitter.github.com/bootstrap/base-css.html#tables
table: 'table table-bordered'
caption: 'caption'
header: 'bootstrap-header' # give the header a gradient background
footerRow: ''
footerCells: ''
icons: '' # add 'icon-white' to make them white; this icon class is added to the <i> in the header
sortNone: 'bootstrap-icon-unsorted'
sortAsc: 'icon-chevron-up' # glyphicon glyphicon-chevron-up' # we are still using v2 icons
sortDesc: 'icon-chevron-down' # glyphicon-chevron-down' # we are still using v2 icons
active: '' # applied when column is sorted
hover: '' # use custom css here - bootstrap class may not override it
filterRow: '' # filter row class
even: '' # odd row zebra striping
odd: '' # even row zebra striping
# e = exact text from cell
# n = normalized value returned by the column parser
# f = search filter input value
# i = column index
# $r = ???
filterSelectExactMatch = (e, n, f, i, $r) -> e is f
# call the tablesorter plugin and apply the uitheme widget
@$el.find('.tablesorter').tablesorter
theme: 'bootstrap'
widthFixed: true
headerTemplate: '{content} {icon}'
textSorter:
6: (a, b, direction, column, table) ->
days = []
for s in [a, b]
n = parseInt s
n = 0 unless _.isNumber n
n = 1 if /^a/.test s
for [duration, factor] in [
[/second/i, 1 / (86400 * 1000)]
[/minute/i, 1 / 1440]
[/hour/i, 1 / 24]
[/week/i, 7]
[/month/i, 30.42]
[/year/i, 365.2425]
]
if duration.test s
n *= factor
break
if /^in /i.test s
n *= -1
days.push n
days[0] - days[1]
sortList: [[6, 0]]
# widget code contained in the jquery.tablesorter.widgets.js file
# use the zebra stripe widget if you plan on hiding any rows (filter widget)
widgets: ['uitheme', 'zebra', 'filter']
widgetOptions:
# using the default zebra striping class name, so it actually isn't included in the theme variable above
# this is ONLY needed for bootstrap theming if you are using the filter widget, because rows are hidden
zebra: ['even', 'odd']
# extra css class applied to the table row containing the filters & the inputs within that row
filter_cssFilter: ''
# If there are child rows in the table (rows with class name from 'cssChildRow' option)
# and this option is true and a match is found anywhere in the child row, then it will make that row
# visible; default is false
filter_childRows: false
# if true, filters are collapsed initially, but can be revealed by hovering over the grey bar immediately
# below the header row. Additionally, tabbing through the document will open the filter row when an input gets focus
filter_hideFilters: false
# Set this option to false to make the searches case sensitive
filter_ignoreCase: true
# jQuery selector string of an element used to reset the filters
filter_reset: '.reset'
# Use the $.tablesorter.storage utility to save the most recent filters
filter_saveFilters: true
# Delay in milliseconds before the filter widget starts searching; This option prevents searching for
# every character while typing and should make searching large tables faster.
filter_searchDelay: 150
# Set this option to true to use the filter to find text from the start of the column
# So typing in 'a' will find 'albert' but not 'frank', both have a's; default is false
filter_startsWith: false
filter_functions:
3:
'0-1': (e, n, f, i, $r) -> parseInt(e) <= 1
'2-5': (e, n, f, i, $r) -> 2 <= parseInt(e) <= 5
'6+': (e, n, f, i, $r) -> 6 <= parseInt(e)
4:
'0-1': (e, n, f, i, $r) -> parseInt(e) <= 1
'2-5': (e, n, f, i, $r) -> 2 <= parseInt(e) <= 5
'6+': (e, n, f, i, $r) -> 6 <= parseInt(e)
5:
'0-1': (e, n, f, i, $r) -> parseInt(e) <= 1
'2-5': (e, n, f, i, $r) -> 2 <= parseInt(e) <= 5
'6+': (e, n, f, i, $r) -> 6 <= parseInt(e)
6:
'Last day': (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('employer-age')
days <= 1
'Last week': (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('employer-age')
days <= 7
'Last 4 weeks': (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('employer-age')
days <= 28
onEmployerClicked: (e) ->
return unless id = $(e.target).closest('tr').data('employer-id')
employer = new User _id: id
@openModalView new ModelModal models: [employer]

View file

@ -1,139 +0,0 @@
ModalView = require 'views/core/ModalView'
template = require 'templates/modal/employer_signup_modal'
forms = require 'core/forms'
User = require 'models/User'
auth = require 'core/auth'
me = auth.me
module.exports = class EmployerSignupModal extends ModalView
id: 'employer-signup'
template: template
closeButton: true
subscriptions:
'errors:server-error': 'onServerError'
'auth:linkedin-api-loaded': 'onLinkedInLoaded'
'created-user-without-reload': 'createdAccount'
events:
'click #contract-agreement-button': 'agreeToContract'
'click #create-account-button': 'createAccount'
'click #more-info-button': 'submitMoreInfoEmail'
'click .login-link': 'setHashToOpenModalAutomatically'
'keydown': 'checkForFormSubmissionEnterPress'
constructor: (options) ->
super(options)
return # Removed LinkedIn, so if we want to resurrect the EmployerSignupModal, we'll want to redo it without LinkedIn auth.
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
window.tracker?.trackEvent 'Started Employer Signup'
@reloadWhenClosed = false
@linkedinLoaded = Boolean(IN.parse)
@waitingForLinkedIn = false
@sentMoreInfoEmail = false
window.contractCallback = =>
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
@render()
onLinkedInLoaded: =>
@linkedinLoaded = true
if @waitingForLinkedIn
@renderLinkedInButton()
renderLinkedInButton: =>
IN.parse()
onServerError: (e) ->
@disableModalInProgress(@$el)
afterInsert: ->
super()
linkedInButtonParentElement = document.getElementById('linkedInAuthButton')
if linkedInButtonParentElement
if @linkedinLoaded
@renderLinkedInButton()
else
@waitingForLinkedIn = true
getRenderData: ->
context = super()
context.userIsAuthorized = @authorizedWithLinkedIn
context.userHasSignedContract = 'employer' in me.get('permissions', true)
context.userIsAnonymous = context.me.get('anonymous')
context.sentMoreInfoEmail = @sentMoreInfoEmail
context
agreeToContract: ->
application.linkedinHandler.constructEmployerAgreementObject (err, profileData) =>
if err? then return handleAgreementFailure err
$.ajax
url: "/db/user/#{me.id}/agreeToEmployerAgreement"
data: profileData
type: 'POST'
success: @handleAgreementSuccess
error: @handleAgreementFailure
handleAgreementSuccess: (result) ->
window.tracker?.trackEvent 'Employer Agreed to Contract'
me.fetch cache: false
window.location.reload()
handleAgreementFailure: (error) ->
alert "There was an error signing the contract. Please contact team@codecombat.com with this error: #{error.responseText}"
checkForFormSubmissionEnterPress: (e) ->
if e.which is 13
if $('#signup-email').val() isnt '' and $('#signup-password').val() isnt ''
@createAccount(e)
else if $('#more-info-email').val() isnt ''
@submitMoreInfoEmail e
createAccount: (e) =>
window.tracker?.trackEvent 'Finished Employer Signup'
el = $('#signup-form')
e.stopPropagation()
forms.clearFormAlerts(el)
userObject = forms.formToObject el
delete userObject.subscribe
for key, val of me.attributes when key in ['preferredLanguage', 'testGroupNumber', 'dateCreated', 'wizardColor1', 'name', 'music', 'volume', 'emails']
userObject[key] ?= val
userObject.emails ?= {}
userObject.emails.employerNotes = {enabled: true}
res = tv4.validateMultiple userObject, User.schema
return forms.applyErrorsToForm(el, res.errors) unless res.valid
@enableModalInProgress(el)
auth.createUserWithoutReload userObject, null
submitMoreInfoEmail: (e) =>
emailAddress = $('#more-info-email').val()
window.tracker?.trackEvent 'Employer requested more information.'
successFunc = =>
@sentMoreInfoEmail = true
@render()
errorFunc = =>
alert('Something went wrong! Please contact team@codecombat.com for more information and inform them of this error.')
$.ajax
type: 'POST'
url: '/contact'
data:
email: emailAddress
message: "THIS IS AN AUTOMATED MESSAGE FROM THE EMPLOYER SIGNUP FORM \n Please send me more info about hiring CodeCombat players."
success: successFunc
error: errorFunc
$.post '/stacklead', email: emailAddress
setHashToOpenModalAutomatically: (e) ->
window.location.hash = 'employerSignupLoggingIn'
createdAccount: ->
@reloadWhenClosed = true
@listenTo me, 'sync', =>
@render()
IN.parse()
me.fetch cache: false
destroy: ->
reloadWhenClosed = @reloadWhenClosed
super()
if reloadWhenClosed
window.location.reload()

View file

@ -1,43 +0,0 @@
ContactModal = require 'views/core/ContactModal'
template = require 'templates/modal/job_profile_contact'
forms = require 'core/forms'
{sendContactMessage} = require 'core/contact'
contactSchema =
additionalProperties: false
required: ['email', 'message']
properties:
email:
type: 'string'
maxLength: 100
minLength: 1
format: 'email'
subject:
type: 'string'
minLength: 1
message:
type: 'string'
minLength: 1
recipientID:
type: 'string'
minLength: 1
module.exports = class JobProfileContactModal extends ContactModal
id: 'job-profile-contact-modal'
template: template
contact: ->
forms.clearFormAlerts @$el
contactMessage = forms.formToObject @$el
contactMessage.recipientID = @options.recipientID
res = tv4.validateMultiple contactMessage, contactSchema
return forms.applyErrorsToForm @$el, res.errors unless res.valid
contactMessage.message += "\n\n\n\n[For reference, the recipient's CodeCombat username is #{@options.recipientUserName}!]"
window.tracker?.trackEvent 'Sent Job Profile Message', message: contactMessage
sendContactMessage contactMessage, @$el
$.post "/db/user/#{me.id}/track/contact_candidate"
$.post "/db/user/#{@options.recipientID}/track/contacted_by_employer" unless me.isAdmin()

View file

@ -1,19 +0,0 @@
ModalView = require 'views/core/ModalView'
template = require 'templates/account/job-profile-code-modal'
LevelSessionCodeView = require 'views/common/LevelSessionCodeView'
module.exports = class JobProfileCodeModal extends ModalView
id: 'job-profile-code-modal'
template: template
modalWidthPercent: 90
plain: true
constructor: (options) ->
super(arguments...)
@session = options.session
afterRender: ->
super()
codeView = new LevelSessionCodeView({session:@session})
@insertSubView(codeView, @$el.find('.level-session-code-view'))

View file

@ -1,463 +0,0 @@
UserView = require 'views/common/UserView'
template = require 'templates/account/job-profile-view'
User = require 'models/User'
LevelSession = require 'models/LevelSession'
CocoCollection = require 'collections/CocoCollection'
{me} = require 'core/auth'
JobProfileContactModal = require 'views/modal/JobProfileContactModal'
JobProfileTreemaView = require 'views/account/JobProfileTreemaView'
UserRemark = require 'models/UserRemark'
forms = require 'core/forms'
ModelModal = require 'views/modal/ModelModal'
JobProfileCodeModal = require './JobProfileCodeModal'
require 'vendor/treema'
class LevelSessionsCollection extends CocoCollection
url: -> "/db/user/#{@userID}/level.sessions/employer"
model: LevelSession
constructor: (@userID) ->
super()
adminContacts = [
{id: '', name: 'Assign a Contact'}
{id: '512ef4805a67a8c507000001', name: 'Nick'}
{id: '5162fab9c92b4c751e000274', name: 'Scott'}
{id: '51eb2714fa058cb20d0006ef', name: 'Michael'}
{id: '51538fdb812dd9af02000001', name: 'George'}
{id: '52a57252a89409700d0000d9', name: 'Ignore'}
]
module.exports = class JobProfileView extends UserView
id: 'profile-view'
template: template
showBackground: false
usesSocialMedia: true
subscriptions: {}
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 #open-model-modal': 'openModelModal'
'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'
'change #admin-contact': 'onAdminContactChanged'
'click .session-link': 'onSessionLinkPressed'
constructor: (options, userID) ->
@onJobProfileNotesChanged = _.debounce @onJobProfileNotesChanged, 1000
@onRemarkChanged = _.debounce @onRemarkChanged, 1000
require('core/services/filepicker')() # Initialize if needed
super userID, options
onLoaded: ->
@finishInit() unless @destroyed
super()
finishInit: ->
return unless @userID
@uploadFilePath = "db/user/#{@userID}"
if @user?.get('firstName')
jobProfile = @user.get('jobProfile')
jobProfile ?= {}
if not jobProfile.name
jobProfile.name = (@user.get('firstName') + ' ' + @user.get('lastName')).trim()
@user.set('jobProfile', jobProfile)
@highlightedContainers = []
if me.isAdmin() or 'employer' in me.get('permissions', true)
$.post "/db/user/#{me.id}/track/view_candidate"
$.post "/db/user/#{@userID}/track/viewed_by_employer" unless me.isAdmin()
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(@user.id), 'candidate_sessions').model
@listenToOnce @sessions, 'sync', => @render?()
if me.isAdmin()
# Mimicking how the VictoryModal fetches LevelFeedback
@remark = new UserRemark()
@remark.setURL "/db/user/#{@userID}/remark"
@remark.fetch cache: false
@listenToOnce @remark, 'sync', @onRemarkLoaded
@listenToOnce @remark, 'error', @onRemarkNotFound
onRemarkLoaded: ->
@remark.setURL "/db/user.remark/#{@remark.id}"
@render()
onRemarkNotFound: ->
@remark = new UserRemark() # hmm, why do we create a new one here?
@remark.set 'user', @userID
@remark.set 'userName', name if name = @user.get('name')
jobProfileSchema: -> @user.schema().properties.jobProfile.properties
getRenderData: ->
context = super()
context.userID = @userID
context.profile = @user.get('jobProfile', true)
context.rawProfile = @user.get('jobProfile') or {}
context.user = @user
context.myProfile = @isMe()
context.allowedToViewJobProfile = @user and (me.isAdmin() or 'employer' in me.get('permissions', true) or (context.myProfile && !me.get('anonymous')))
context.allowedToEditJobProfile = @user and (me.isAdmin() or (context.myProfile && !me.get('anonymous')))
context.profileApproved = @user?.get 'jobProfileApproved'
context.progress = @progress ? @updateProgress()
@editing ?= context.myProfile and context.progress < 0.8
context.editing = @editing
context.marked = marked
context.moment = moment
context.iconForLink = @iconForLink
if links = context.profile.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
if @sessions
context.sessions = (s.attributes for s in @sessions.models when (s.get('submitted') or (s.get('levelID') is 'gridmancer') and s.get('code')?.thoktar?.plan?.length isnt 942)) # no default code
context.sessions.sort (a, b) -> (b.playtime ? 0) - (a.playtime ? 0)
else
context.sessions = []
context.adminContacts = adminContacts
context.remark = @remark
context
afterRender: ->
super()
if me.get('employerAt')
@$el.addClass 'viewed-by-employer'
return unless @user
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 ? true
justSavedSection = @$el.find('#' + @justSavedSectionID).addClass 'just-saved'
_.defer =>
@progress = @updateProgress highlightNext
_.delay ->
justSavedSection.removeClass 'just-saved', duration: 1500, easing: 'easeOutQuad'
, 500
if me.isAdmin() and @user and @remark
visibleSettings = ['history', 'tasks']
data = _.pick (@remark.attributes), (value, key) -> key in visibleSettings
data.history ?= []
data.tasks ?= []
schema = _.cloneDeep @remark.schema()
schema.properties = _.pick schema.properties, (value, key) => key in visibleSettings
schema.required = _.intersection (schema.required ? []), visibleSettings
treemaOptions =
filePath: "db/user/#{@userID}"
schema: schema
data: data
aceUseWrapMode: true
callbacks: {change: @onRemarkChanged}
@remarkTreema = @$el.find('#remark-treema').treema treemaOptions
@remarkTreema?.build()
@remarkTreema?.open(3)
onRemarkChanged: (e) =>
return unless @remarkTreema.isValid()
for key in ['history', 'tasks']
val = _.filter(@remarkTreema.get(key), (entry) -> entry?.content or entry?.action)
entry.date ?= (new Date()).toISOString() for entry in val if key is 'history'
@remark.set key, val
@saveRemark()
initializeAutocomplete: (container) ->
(container ? @$el).find('input[data-autocomplete]').each ->
$(@).autocomplete(source: JobProfileTreemaView[$(@).data('autocomplete')], minLength: parseInt($(@).data('autocomplete-min-length')), delay: 0, autoFocus: true)
toggleEditing: ->
@editing = not @editing
@render()
@saveEdits()
toggleJobProfileApproved: ->
return unless me.isAdmin()
approved = not @user.get 'jobProfileApproved'
@user.set 'jobProfileApproved', approved
res = @user.patch()
res.success (model, response, options) => @render()
toggleJobProfileActive: ->
active = not @user.get('jobProfile').active
@user.get('jobProfile').active = active
@saveEdits()
if active and not (me.isAdmin() or @stackLed)
$.post '/stacklead'
@stackLed = true
enterEspionageMode: ->
postData = emailLower: @user.get('email').toLowerCase(), usernameLower: @user.get('name').toLowerCase()
$.ajax
type: 'POST',
url: '/auth/spy'
data: postData
success: @espionageSuccess
espionageSuccess: (model) ->
window.location.reload()
openModelModal: (e) ->
@openModalView new ModelModal models: [@user]
onJobProfileNotesChanged: (e) =>
notes = @$el.find('#job-profile-notes').val()
@user.set 'jobProfileNotes', notes
@user.save {jobProfileNotes: notes}, {patch: true, type: 'PUT'}
iconForLink: (link) ->
icons = [
{icon: 'facebook', name: 'Facebook', domain: /facebook\.com/, match: /facebook/i}
{icon: 'twitter', name: 'Twitter', domain: /twitter\.com/, match: /twitter/i}
{icon: 'github', name: 'GitHub', domain: /github\.(com|io)/, match: /github/i}
{icon: 'gplus', name: 'Google Plus', domain: /plus\.google\.com/, match: /(google|^g).?(\+|plus)/i}
{icon: 'linkedin', name: 'LinkedIn', domain: /linkedin\.com/, match: /(google|^g).?(\+|plus)/i}
]
for icon in icons
if (link.name.search(icon.match) isnt -1) or (link.link.search(icon.domain) isnt -1)
icon.url = "/images/pages/account/profile/icon_#{icon.icon}.png"
return icon
null
onContactCandidate: (e) ->
@openModalView new JobProfileContactModal recipientID: @user.id, recipientUserName: @user.get('name')
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() if @user is me
@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) ->
@$el.find('.emphasized').removeClass('emphasized')
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') or {}
jobProfile[prop] ?= [] for prop in ['links', 'skills', 'work', 'education', 'projects']
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
parent[key] ?= {}
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'
@user.set('jobProfile', jobProfile)
@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()
onAdminContactChanged: (e) ->
newContact = @$el.find('#admin-contact').val()
newContactName = if newContact then _.find(adminContacts, id: newContact).name else ''
@remark.set 'contact', newContact
@remark.set 'contactName', newContactName
@saveRemark()
saveRemark: ->
@remark.set 'user', @user.id
@remark.set 'userName', @user.get('name')
if errors = @remark.validate()
return console.error 'UserRemark', @remark, 'failed validation with errors:', errors
res = @remark.save()
res.error =>
return if @destroyed
console.error 'UserRemark', @remark, 'failed to save with error:', res.responseText
res.success (model, response, options) =>
return if @destroyed
console.log 'Saved UserRemark', @remark, 'with response', response
updateProgress: (highlightNext) ->
return unless @user?.loaded and @sessions?.loaded
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}%"
if next
text = ''
t = $.i18n.t
text = "#{progress}% #{t 'account_profile.complete'}. #{t 'account_profile.next'}: #{next.name}"
bar.parent().show().find('.progress-text').text text
if highlightNext and next?.container and not (next.container in @highlightedContainers)
@highlightedContainers.push next.container
@$el.find(next.container).addClass 'emphasized'
#@onEditSection target: next.container
#$('#page-container').scrollTop 0
else
bar.parent().hide()
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_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_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_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'}
]
onSessionLinkPressed: (e) ->
sessionID = $(e.target).closest('.session-link').data('session-id')
session = _.find @sessions.models, (session) -> session.id is sessionID
modal = new JobProfileCodeModal({session:session})
@openModalView modal
destroy: ->
@remarkTreema?.destroy()
super()