mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-14 07:00:01 -04:00
Cull recruiting views and translations
This commit is contained in:
parent
53f8bca26b
commit
079109145f
23 changed files with 4 additions and 2931 deletions
|
@ -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')
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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'
|
|
@ -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"' }
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
c = require './../schemas'
|
||||
|
||||
# Deprecated. TODO: Remove
|
||||
|
||||
UserRemarkSchema = c.object {
|
||||
title: 'Remark'
|
||||
description: 'Remarks on a user, point of contact, tasks.'
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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")
|
|
@ -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
|
||||
|
|
|
@ -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 ✗
|
|
@ -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()
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
@ -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
|
|
@ -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]
|
|
@ -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()
|
|
@ -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()
|
|
@ -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'))
|
||||
|
|
@ -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()
|
Loading…
Reference in a new issue