From 628a784ac4370efa04d215a9ff63396043963740 Mon Sep 17 00:00:00 2001 From: Michael Schmatz Date: Wed, 23 Apr 2014 11:25:36 -0700 Subject: [PATCH 1/5] Progress towards LinkedIn integration --- app/application.coffee | 3 ++- app/assets/main.html | 14 +++++++++- app/lib/LinkedInHandler.coffee | 27 +++++++++++++++++++ app/styles/base.sass | 3 +++ app/templates/account/job_profile.jade | 2 ++ .../modal/employer_signup_modal.jade | 21 ++++++++++++--- app/views/modal/employer_signup_modal.coffee | 24 +++++++++++++++++ 7 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 app/lib/LinkedInHandler.coffee diff --git a/app/application.coffee b/app/application.coffee index f44287599..a6ca8987a 100644 --- a/app/application.coffee +++ b/app/application.coffee @@ -1,5 +1,6 @@ FacebookHandler = require 'lib/FacebookHandler' GPlusHandler = require 'lib/GPlusHandler' +LinkedInHandler = require 'lib/LinkedInHandler' locale = require 'locale/locale' {me} = require 'lib/auth' Tracker = require 'lib/Tracker' @@ -33,7 +34,7 @@ Application = initialize: -> @facebookHandler = new FacebookHandler() @gplusHandler = new GPlusHandler() $(document).bind 'keydown', preventBackspace - + @linkedinHandler = new LinkedInHandler() preload(COMMON_FILES) $.i18n.init { lng: me?.lang() ? 'en' diff --git a/app/assets/main.html b/app/assets/main.html index 761eac10c..0521041b5 100644 --- a/app/assets/main.html +++ b/app/assets/main.html @@ -42,7 +42,18 @@ - + + + + + diff --git a/app/lib/LinkedInHandler.coffee b/app/lib/LinkedInHandler.coffee new file mode 100644 index 000000000..6bb9f8fa2 --- /dev/null +++ b/app/lib/LinkedInHandler.coffee @@ -0,0 +1,27 @@ +CocoClass = require 'lib/CocoClass' +{me} = require 'lib/auth' +{backboneFailure} = require 'lib/errors' +storage = require 'lib/storage' + + +module.exports = LinkedInHandler = class LinkedInHandler extends CocoClass + constructor: -> + super() + @linkedInData = {} + @loaded = false + subscriptions: + 'linkedin-loaded':'onLinkedInLoaded' + + onLinkedInLoaded: (e) => + console.log "Loaded LinkedIn!" + IN.Event.on IN, "auth", @onLinkedInAuth + + onLinkedInAuth: (e) => IN.API.Profile("me").result(@cacheProfileInformation) + + cacheProfileInformation: (profiles) => + @linkedInData = profiles.values[0] + console.log "LinkedIn data is #{@linkedInData}" + + + destroy: -> + super() diff --git a/app/styles/base.sass b/app/styles/base.sass index afa9fbcc9..aa491c842 100644 --- a/app/styles/base.sass +++ b/app/styles/base.sass @@ -133,6 +133,9 @@ a[data-toggle="modal"] background-color: transparent margin: 0 14px border-bottom-color: #ccc + .modal-footer.linkedin + text-align: center + // Bigger versions of some Bootstrap icons // TODO: make the non-white versions of these if we ever need them diff --git a/app/templates/account/job_profile.jade b/app/templates/account/job_profile.jade index 6ec836343..3e0e4a932 100644 --- a/app/templates/account/job_profile.jade +++ b/app/templates/account/job_profile.jade @@ -1,4 +1,6 @@ h3(data-i18n="account_settings.job_profile") Job Profile +script(type="in/Login") + Hello, . .row .col-md-9 diff --git a/app/templates/modal/employer_signup_modal.jade b/app/templates/modal/employer_signup_modal.jade index 809b26452..ae027d0aa 100644 --- a/app/templates/modal/employer_signup_modal.jade +++ b/app/templates/modal/employer_signup_modal.jade @@ -4,6 +4,21 @@ block modal-header-content h3(data-i18n="employer_signup.title") Hire CodeCombat Players block modal-body-content - h4(data-i18n="employer_signup.sub_heading") Let us find your next brilliant developers. - - p(data-i18n="employer_signup.pitch_body") When you hire one of our players, you will pay CodeCombat 18% of her first-year salary, payable within 30 days of when she starts working. We will fully refund our placement fee if she leaves or is fired within 90 days. Cool? Email george@codecombat.com to get set up with employer permissions to see our candidates. + if userIsAuthorized + h4 Contract + + p Check out this fancy-schmancy contract. You should sign it. + + else + h4(data-i18n="employer_signup.sub_heading") Let us find your next brilliant developers. + + p(data-i18n="employer_signup.pitch_body") When you hire one of our players, you will pay CodeCombat 18% of her first-year salary, payable within 30 days of when she starts working. We will fully refund our placement fee if she leaves or is fired within 90 days. Cool? Email george@codecombat.com to get set up with employer permissions to see our candidates. +block modal-footer + if userIsAuthorized + .modal-footer.linkedin + | Thanks #{firstName}! You've already agreed to the contract. + else + .modal-footer.linkedin + script(type="in/Login" id="linkedInAuthButton" data-onAuth="contractCallback") + | Thanks, ! You've agreed to the contract. + \ No newline at end of file diff --git a/app/views/modal/employer_signup_modal.coffee b/app/views/modal/employer_signup_modal.coffee index de66c007d..6ecb4bb8d 100644 --- a/app/views/modal/employer_signup_modal.coffee +++ b/app/views/modal/employer_signup_modal.coffee @@ -5,3 +5,27 @@ module.exports = class EmployerSignupView extends View id: "employer-signup" template: template closeButton: true + + subscriptions: + 'employer-linkedin-auth': 'showContractScreen' + + constructor: (options) -> + super(options) + @authorizedWithLinkedIn = IN?.User?.isAuthorized() + window.contractCallback = -> window.Backbone.Mediator.publish("employer-linkedin-auth") + + afterInsert: -> + super() + unless @authorizedWithLinkedIn + linkedInButtonParentElement = document.getElementById("linkedInAuthButton").parentNode + IN.parse(linkedInButtonParentElement) if linkedInButtonParentElement + + showContractScreen: => + @render() + getRenderData: -> + context = super() + context.userIsAuthorized = @authorizedWithLinkedIn + if @authorizedWithLinkedIn + context.firstName = application.linkedinHandler.linkedInData.firstName + context + \ No newline at end of file From 6644c6a9eeeecf6e8dfcf835f908d7444d8a45c3 Mon Sep 17 00:00:00 2001 From: Michael Schmatz Date: Thu, 24 Apr 2014 15:27:37 -0700 Subject: [PATCH 2/5] Push for george --- app/lib/LinkedInHandler.coffee | 1 + app/lib/auth.coffee | 8 ++ app/styles/base.sass | 5 ++ .../modal/employer_signup_modal.jade | 66 +++++++++++---- app/views/kinds/CocoView.coffee | 2 +- app/views/modal/employer_signup_modal.coffee | 84 +++++++++++++++++-- app/views/modal/login_modal.coffee | 1 + server/routes/db.coffee | 1 + 8 files changed, 144 insertions(+), 24 deletions(-) diff --git a/app/lib/LinkedInHandler.coffee b/app/lib/LinkedInHandler.coffee index 6bb9f8fa2..a206e48d8 100644 --- a/app/lib/LinkedInHandler.coffee +++ b/app/lib/LinkedInHandler.coffee @@ -20,6 +20,7 @@ module.exports = LinkedInHandler = class LinkedInHandler extends CocoClass cacheProfileInformation: (profiles) => @linkedInData = profiles.values[0] + me.set("linkedIn", @linkedInData) console.log "LinkedIn data is #{@linkedInData}" diff --git a/app/lib/auth.coffee b/app/lib/auth.coffee index 4fa94e61f..9a75af184 100644 --- a/app/lib/auth.coffee +++ b/app/lib/auth.coffee @@ -19,6 +19,14 @@ module.exports.createUser = (userObject, failure=backboneFailure, nextURL=null) error: failure, success: -> if nextURL then window.location.href = nextURL else window.location.reload() }) + +module.exports.createUserWithoutReload = (userObject, failure=backboneFailure) -> + user = new User(userObject) + user.save({}, { + error: failure + success: -> + Backbone.Mediator.publish("created-user-without-reload") + }) module.exports.loginUser = (userObject, failure=genericFailure) -> jqxhr = $.post('/auth/login', diff --git a/app/styles/base.sass b/app/styles/base.sass index aa491c842..c6d89d273 100644 --- a/app/styles/base.sass +++ b/app/styles/base.sass @@ -135,6 +135,11 @@ a[data-toggle="modal"] border-bottom-color: #ccc .modal-footer.linkedin text-align: center + .signin-text + font-size: 15px + padding-bottom: 10px + .login-link + cursor: pointer // Bigger versions of some Bootstrap icons diff --git a/app/templates/modal/employer_signup_modal.jade b/app/templates/modal/employer_signup_modal.jade index ae027d0aa..d3d13c5c4 100644 --- a/app/templates/modal/employer_signup_modal.jade +++ b/app/templates/modal/employer_signup_modal.jade @@ -1,24 +1,60 @@ extends /templates/modal/modal_base block modal-header-content - h3(data-i18n="employer_signup.title") Hire CodeCombat Players + if userIsAnonymous || !userIsAuthorized + h3(data-i18n="employer_signup.title") Sign up to hire CodeCombat players! + else + h3 Hiring Contract block modal-body-content - if userIsAuthorized - h4 Contract - - p Check out this fancy-schmancy contract. You should sign it. - - else + if userIsAnonymous h4(data-i18n="employer_signup.sub_heading") Let us find your next brilliant developers. - - p(data-i18n="employer_signup.pitch_body") When you hire one of our players, you will pay CodeCombat 18% of her first-year salary, payable within 30 days of when she starts working. We will fully refund our placement fee if she leaves or is fired within 90 days. Cool? Email george@codecombat.com to get set up with employer permissions to see our candidates. + + p Create an account to get started! + .form + .form-group + label.control-label(for="signup-email", data-i18n="general.email") Email + input#signup-email.form-control.input-large(name="email",type="email") + .form-group + label.control-label(for="signup-password", data-i18n="general.password") Password + input#signup-password.input-large.form-control(name="password", type="password") + 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 + | We (CodeCombat) are providing you (the Company) access to information about our best players. In exchange, you agree to pay us 15% of the first year's annualized starting base salary for any person that you hire on a full time basis through this site. That 15% is due on the first date that our candidate is employed and is 100% refundable for 90 days after that date if the candidate doesn't remain employed at the company for any reason. + br + br + | 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 + | You must keep all of the information you access on this site confidential. That means you cannot share it with third parties and will only use it for recruiting. + br + br + | We will invoice this account via email and you agree to pay within 30 days of that email being sent. + br + br + | Neither you or CodeCombat will be liable for any damages that result from this contract. + block modal-footer - if userIsAuthorized + if userIsAnonymous && !userisAuthorized + .modal-footer.linkedin + b.signin-text Sign in with LinkedIn to complete the registration process. + script(type="in/Login" id="linkedInAuthButton" data-onAuth="contractCallback") + br + br + | Already have a CodeCombat account? + a.login-link(data-toggle="coco-modal", data-target="modal/login") Log in to continue! + else if !userIsAnonymous && !userIsAuthorized + .modal-footer.linkedin + | We will record your name and work history for verification purposes. + else if userIsAuthorized && !userHasSignedContract + .modal-footer.linkedin + button.btn.btn-primary(id="contract-agreement-button") I agree + else .modal-footer.linkedin | Thanks #{firstName}! You've already agreed to the contract. - else - .modal-footer.linkedin - script(type="in/Login" id="linkedInAuthButton" data-onAuth="contractCallback") - | Thanks, ! You've agreed to the contract. - \ No newline at end of file + + \ No newline at end of file diff --git a/app/views/kinds/CocoView.coffee b/app/views/kinds/CocoView.coffee index 5e9275ca3..a45609ca7 100644 --- a/app/views/kinds/CocoView.coffee +++ b/app/views/kinds/CocoView.coffee @@ -207,7 +207,7 @@ class CocoView extends Backbone.View # Modals toggleModal: (e) -> - return if visibleModal + #return if visibleModal if $(e.currentTarget).prop('target') is '_blank' return true # special handler for opening modals that are dynamically loaded, rather than static in the page. It works (or should work) like Bootstrap's modals, except use coco-modal for the data-toggle value. diff --git a/app/views/modal/employer_signup_modal.coffee b/app/views/modal/employer_signup_modal.coffee index 6ecb4bb8d..541a290dd 100644 --- a/app/views/modal/employer_signup_modal.coffee +++ b/app/views/modal/employer_signup_modal.coffee @@ -1,31 +1,99 @@ View = require 'views/kinds/ModalView' template = require 'templates/modal/employer_signup_modal' +forms = require('lib/forms') +User = require 'models/User' +auth = require('lib/auth') +me = auth.me module.exports = class EmployerSignupView extends View id: "employer-signup" template: template closeButton: true + - subscriptions: - 'employer-linkedin-auth': 'showContractScreen' + subscriptions: + "server-error": "onServerError" + "created-user-without-reload": "linkedInAuth" + + events: + "click #contract-agreement-button": "agreeToContract" + constructor: (options) -> super(options) @authorizedWithLinkedIn = IN?.User?.isAuthorized() - window.contractCallback = -> window.Backbone.Mediator.publish("employer-linkedin-auth") + #TODO: If IN.User.logout is called after authorizing, then the modal is reopened + # and the user reauths, there will be a javascript error due to the + # contract callback context not finding @render + #window.tracker?.trackEvent 'Started Employer Signup' + @reloadWhenClosed = false + window.contractCallback = => + @authorizedWithLinkedIn = IN?.User?.isAuthorized() + @render() + + onServerError: (e) -> # TODO: work error handling into a separate forms system + @disableModalInProgress(@$el) afterInsert: -> super() - unless @authorizedWithLinkedIn - linkedInButtonParentElement = document.getElementById("linkedInAuthButton").parentNode - IN.parse(linkedInButtonParentElement) if linkedInButtonParentElement + linkedInButtonParentElement = document.getElementById("linkedInAuthButton")?.parentNode + if linkedInButtonParentElement + IN.parse() + if me.get('anonymous') + $(".IN-widget").get(0).addEventListener('click', @createAccount, true) + console.log "Parsed linkedin button element!" + console.log linkedInButtonParentElement - showContractScreen: => - @render() getRenderData: -> context = super() context.userIsAuthorized = @authorizedWithLinkedIn + context.userHasSignedContract = false + context.userIsAnonymous = context.me.get('anonymous') if @authorizedWithLinkedIn context.firstName = application.linkedinHandler.linkedInData.firstName context + + agreeToContract: -> + + createAccount: (e) => + console.log "Tried to create account!" + 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 + subscribe = true + #TODO: Enable all email subscriptions + + userObject.emails ?= {} + userObject.emails.generalNews ?= {} + userObject.emails.generalNews.enabled = subscribe + res = tv4.validateMultiple userObject, User.schema + return forms.applyErrorsToForm(@$el, res.errors) unless res.valid + window.tracker?.trackEvent 'Finished Signup' + @enableModalInProgress(@$el) + auth.createUserWithoutReload userObject, null + + linkedInAuth: (e) => + console.log "Authorizing with linkedin" + @listenTo me,"sync", -> + IN.User.authorize(@recordUserDetails, @) + me.fetch() + @reloadWhenClosed = true + + + recordUserDetails: (e) => + console.log "Record user details here!" + @render() + + destroy: -> + reloadWhenClosed = @reloadWhenClosed + super() + if reloadWhenClosed + window.location.reload() + + + + \ No newline at end of file diff --git a/app/views/modal/login_modal.coffee b/app/views/modal/login_modal.coffee index 68306a03e..56497fa51 100644 --- a/app/views/modal/login_modal.coffee +++ b/app/views/modal/login_modal.coffee @@ -17,6 +17,7 @@ module.exports = class LoginModalView extends View events: "click #login-button": "loginAccount" "keydown #login-password": "loginAccount" + "click #link-to-signup": "switchToSignup" subscriptions: 'server-error': 'onServerError' diff --git a/server/routes/db.coffee b/server/routes/db.coffee index beb120573..9b31e2537 100644 --- a/server/routes/db.coffee +++ b/server/routes/db.coffee @@ -6,6 +6,7 @@ mongoose = require 'mongoose' module.exports.setup = (app) -> # This is hacky and should probably get moved somewhere else, I dunno app.get '/db/cla.submissions', (req, res) -> + return errors.unauthorized(res, "You must be an admin to view that information") unless req.user?.isAdmin() res.setHeader('Content-Type', 'application/json') collection = mongoose.connection.db.collection 'cla.submissions', (err, collection) -> return log.error "Couldn't fetch CLA submissions because #{err}" if err From 4f6f845c2d612d78c38d786692acfbd603a656ec Mon Sep 17 00:00:00 2001 From: Michael Schmatz Date: Thu, 24 Apr 2014 17:36:07 -0700 Subject: [PATCH 3/5] Employer signup works --- app/lib/LinkedInHandler.coffee | 7 ++ app/schemas/models/user.coffee | 7 +- app/styles/base.sass | 4 +- app/templates/employers.jade | 6 +- .../modal/employer_signup_modal.jade | 64 +++++++++++-------- app/views/employers_view.coffee | 4 ++ app/views/modal/employer_signup_modal.coffee | 18 +++++- server/users/user_handler.coffee | 29 ++++++++- 8 files changed, 102 insertions(+), 37 deletions(-) diff --git a/app/lib/LinkedInHandler.coffee b/app/lib/LinkedInHandler.coffee index a206e48d8..bc3728131 100644 --- a/app/lib/LinkedInHandler.coffee +++ b/app/lib/LinkedInHandler.coffee @@ -23,6 +23,13 @@ module.exports = LinkedInHandler = class LinkedInHandler extends CocoClass me.set("linkedIn", @linkedInData) console.log "LinkedIn data is #{@linkedInData}" + constructEmployerAgreementObject: (cb) => + IN.API.Profile("me") + .fields(["positions","public-profile-url","id","first-name","last-name","email-address"]) + .error(cb) + .result (profiles) => + cb null, profiles.values[0] + destroy: -> super() diff --git a/app/schemas/models/user.coffee b/app/schemas/models/user.coffee index 4ec00f8c4..b19535d1d 100644 --- a/app/schemas/models/user.coffee +++ b/app/schemas/models/user.coffee @@ -53,7 +53,7 @@ UserSchema = c.object {}, #Internationalization stuff preferredLanguage: {type: 'string', default: 'en', 'enum': c.getLanguageCodeArray()} - + signedCLA: c.date({title: 'Date Signed the CLA'}) wizard: c.object {}, colorConfig: c.object {additionalProperties: c.colorConfig()} @@ -109,6 +109,11 @@ UserSchema = c.object {}, jobProfileApproved: {title: 'Job Profile Approved', type: 'boolean', description: 'Whether your profile has been approved by CodeCombat.'} jobProfileNotes: {type: 'string', maxLength: 1000, title: 'Our Notes', description: "CodeCombat's notes on the candidate.", format: 'markdown', default: ''} employerAt: c.shortString {description: "If given employer permissions to view job candidates, for which employer?"} + signedEmployerAgreement: c.object {}, + linkedinID: c.shortString {title:"LinkedInID", description: "The user's LinkedIn ID when they signed the contract."} + date: c.date {title: "Date signed employer agreement"} + data: c.object + c.extendBasicProperties UserSchema, 'user' diff --git a/app/styles/base.sass b/app/styles/base.sass index c6d89d273..d16ce64d9 100644 --- a/app/styles/base.sass +++ b/app/styles/base.sass @@ -138,8 +138,8 @@ a[data-toggle="modal"] .signin-text font-size: 15px padding-bottom: 10px - .login-link - cursor: pointer + .login-link + cursor: pointer // Bigger versions of some Bootstrap icons diff --git a/app/templates/employers.jade b/app/templates/employers.jade index efeb59429..c79a47b47 100644 --- a/app/templates/employers.jade +++ b/app/templates/employers.jade @@ -12,9 +12,11 @@ block content span(data-i18n="employers.candidates_count_many") many | span(data-i18n="employers.candidates_count_suffix") highly skilled and vetted developers looking for work. - h3 - a(title='Contact', tabindex=-1, data-toggle="coco-modal", data-target="modal/employer_signup", data-i18n="employers.contact_george") Contact George to see our candidates + if !isEmployer + h3 + a(title='Contact', tabindex=-1, data-toggle="coco-modal", data-target="modal/employer_signup") Click here to see candidates + if candidates.length table.table.table-condensed.table-hover.table-responsive.tablesorter thead diff --git a/app/templates/modal/employer_signup_modal.jade b/app/templates/modal/employer_signup_modal.jade index d3d13c5c4..7bee4aabb 100644 --- a/app/templates/modal/employer_signup_modal.jade +++ b/app/templates/modal/employer_signup_modal.jade @@ -4,49 +4,57 @@ block modal-header-content if userIsAnonymous || !userIsAuthorized h3(data-i18n="employer_signup.title") Sign up to hire CodeCombat players! else - h3 Hiring Contract + h3 CodeCombat Placement Agreement block modal-body-content if userIsAnonymous - h4(data-i18n="employer_signup.sub_heading") Let us find your next brilliant developers. - - p Create an account to get started! - .form - .form-group - label.control-label(for="signup-email", data-i18n="general.email") Email - input#signup-email.form-control.input-large(name="email",type="email") - .form-group - label.control-label(for="signup-password", data-i18n="general.password") Password - input#signup-password.input-large.form-control(name="password", type="password") + if userIsAuthorized + | You appear to be authorized on CodeCombat with LinkedIn. + else + h4(data-i18n="employer_signup.sub_heading") Let us find your next brilliant developers. + p Create an account to get started! + .form + .form-group + label.control-label(for="signup-email", data-i18n="general.email") Email + input#signup-email.form-control.input-large(name="email",type="email") + .form-group + label.control-label(for="signup-password", data-i18n="general.password") Password + input#signup-password.input-large.form-control(name="password", type="password") 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 - | We (CodeCombat) are providing you (the Company) access to information about our best players. In exchange, you agree to pay us 15% of the first year's annualized starting base salary for any person that you hire on a full time basis through this site. That 15% is due on the first date that our candidate is employed and is 100% refundable for 90 days after that date if the candidate doesn't remain employed at the company for any reason. + | 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 our 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 - | You must keep all of the information you access on this site confidential. That means you cannot share it with third parties and will only use it for recruiting. - br - br - | We will invoice this account via email and you agree to pay within 30 days of that email being sent. - br - br - | Neither you or CodeCombat will be liable for any damages that result from this contract. - + | 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 && !userisAuthorized - .modal-footer.linkedin - b.signin-text Sign in with LinkedIn to complete the registration process. - script(type="in/Login" id="linkedInAuthButton" data-onAuth="contractCallback") - br - br - | Already have a CodeCombat account? - a.login-link(data-toggle="coco-modal", data-target="modal/login") Log in to continue! + if userIsAnonymous + if !userIsAuthorized + .modal-footer.linkedin + b.signin-text Sign in with LinkedIn to complete the registration process. + script(type="in/Login" id="linkedInAuthButton" data-onAuth="contractCallback") + br + br + | Already have a CodeCombat account? + a.login-link(data-toggle="coco-modal", data-target="modal/login") Log in to continue! + else + .modal-footer.linkedin + a.login-link(data-toggle="coco-modal", data-target="modal/login") Please log in to continue. else if !userIsAnonymous && !userIsAuthorized .modal-footer.linkedin | We will record your name and work history for verification purposes. diff --git a/app/views/employers_view.coffee b/app/views/employers_view.coffee index cdbf8f074..3872f955f 100644 --- a/app/views/employers_view.coffee +++ b/app/views/employers_view.coffee @@ -2,6 +2,7 @@ View = require 'views/kinds/RootView' template = require 'templates/employers' app = require 'application' User = require 'models/User' +{me} = require 'lib/auth' CocoCollection = require 'models/CocoCollection' EmployerSignupView = require 'views/modal/employer_signup_modal' @@ -27,6 +28,9 @@ module.exports = class EmployersView extends View getRenderData: -> c = super() c.candidates = @candidates.models + userPermissions = me.get('permissions') || [] + + c.isEmployer = _.contains userPermissions, "employer" c.moment = moment c diff --git a/app/views/modal/employer_signup_modal.coffee b/app/views/modal/employer_signup_modal.coffee index 541a290dd..3022290e0 100644 --- a/app/views/modal/employer_signup_modal.coffee +++ b/app/views/modal/employer_signup_modal.coffee @@ -54,7 +54,21 @@ module.exports = class EmployerSignupView extends View 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) -> + me.fetch() + window.location.reload() + + handleAgreementFailure: (error) -> + createAccount: (e) => console.log "Tried to create account!" e.stopPropagation() @@ -84,7 +98,7 @@ module.exports = class EmployerSignupView extends View recordUserDetails: (e) => - console.log "Record user details here!" + #TODO: refactor this out @render() destroy: -> diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index 212ed7685..9c97dcb95 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -14,7 +14,7 @@ LevelSessionHandler = require '../levels/sessions/level_session_handler' serverProperties = ['passwordHash', 'emailLower', 'nameLower', 'passwordReset'] privateProperties = [ 'permissions', 'email', 'firstName', 'lastName', 'gender', 'facebookID', - 'gplusID', 'music', 'volume', 'aceConfig', 'employerAt' + 'gplusID', 'music', 'volume', 'aceConfig', 'employerAt', 'signedEmployerAgreement' ] candidateProperties = [ 'jobProfile', 'jobProfileApproved', 'jobProfileNotes' @@ -182,6 +182,7 @@ UserHandler = class UserHandler extends Handler getByRelationship: (req, res, args...) -> return @agreeToCLA(req, res) if args[1] is 'agreeToCLA' + return @agreeToEmployerAgreement(req,res) if args[1] is 'agreeToEmployerAgreement' return @avatar(req, res, args[0]) if args[1] is 'avatar' return @getNamesByIDs(req, res) if args[1] is 'names' return @nameToID(req, res, args[0]) if args[1] is 'nameToID' @@ -231,7 +232,31 @@ UserHandler = class UserHandler extends Handler return @sendDatabaseError(res, err) if err documents = (LevelSessionHandler.formatEntity(req, doc) for doc in documents) @sendSuccess(res, documents) - + agreeToEmployerAgreement: (req, res) -> + userIsAnonymous = req.user?.get('anonymous') + if userIsAnonymous then return errors.unauthorized(res, "You need to be logged in to agree to the employer agreeement.") + profileData = req.body + #TODO: refactor this bit to make it more elegant + if not profileData.id or not profileData.positions or not profileData.emailAddress or not profileData.firstName or not profileData.lastName + return errors.badInput(res, "You need to have a more complete profile to sign up for this service.") + @modelClass.findById(req.user.id).exec (err, user) => + if user.get('employerAt') or user.get('signedEmployerAgreement') or "employer" in user.get('permissions') + return errors.conflict(res, "You already have signed the agreement!") + #TODO: Search for the current position + employerAt = profileData.positions.values[0].company.name + signedEmployerAgreement = + linkedinID: profileData.id + date: new Date() + data: profileData + updateObject = + "employerAt": employerAt + "signedEmployerAgreement": signedEmployerAgreement + $push: "permissions":'employer' + + User.update {"_id": req.user.id}, updateObject, (err, result) => + if err? then return errors.serverError(res, "There was an issue updating the user object to reflect employer status: #{err}") + res.send({"message": "The agreement was successful."}) + res.end() getCandidates: (req, res) -> authorized = req.user.isAdmin() or ('employer' in req.user.get('permissions')) since = (new Date((new Date()) - 2 * 30.4 * 86400 * 1000)).toISOString() From 0dbcc861bf740d3c1db70df7bf4a7933cc56e199 Mon Sep 17 00:00:00 2001 From: Michael Schmatz Date: Fri, 25 Apr 2014 07:48:59 -0700 Subject: [PATCH 4/5] Various tweaks to employer signup --- app/lib/LinkedInHandler.coffee | 11 ++--------- app/styles/employers.sass | 2 ++ app/templates/account/job_profile.jade | 3 +-- app/templates/employers.jade | 2 +- app/views/modal/employer_signup_modal.coffee | 14 ++++++-------- app/views/modal/login_modal.coffee | 1 - server/users/user_handler.coffee | 2 +- 7 files changed, 13 insertions(+), 22 deletions(-) diff --git a/app/lib/LinkedInHandler.coffee b/app/lib/LinkedInHandler.coffee index bc3728131..415e142ac 100644 --- a/app/lib/LinkedInHandler.coffee +++ b/app/lib/LinkedInHandler.coffee @@ -7,21 +7,14 @@ storage = require 'lib/storage' module.exports = LinkedInHandler = class LinkedInHandler extends CocoClass constructor: -> super() - @linkedInData = {} - @loaded = false + subscriptions: 'linkedin-loaded':'onLinkedInLoaded' onLinkedInLoaded: (e) => - console.log "Loaded LinkedIn!" IN.Event.on IN, "auth", @onLinkedInAuth - onLinkedInAuth: (e) => IN.API.Profile("me").result(@cacheProfileInformation) - - cacheProfileInformation: (profiles) => - @linkedInData = profiles.values[0] - me.set("linkedIn", @linkedInData) - console.log "LinkedIn data is #{@linkedInData}" + onLinkedInAuth: (e) => console.log "Authorized with LinkedIn" constructEmployerAgreementObject: (cb) => IN.API.Profile("me") diff --git a/app/styles/employers.sass b/app/styles/employers.sass index 6e64b44a0..86a641b8f 100644 --- a/app/styles/employers.sass +++ b/app/styles/employers.sass @@ -1,4 +1,6 @@ #employers-view + #see-candidates + cursor: pointer .tablesorter //img // display: none diff --git a/app/templates/account/job_profile.jade b/app/templates/account/job_profile.jade index 3e0e4a932..220509e8c 100644 --- a/app/templates/account/job_profile.jade +++ b/app/templates/account/job_profile.jade @@ -1,6 +1,5 @@ h3(data-i18n="account_settings.job_profile") Job Profile -script(type="in/Login") - Hello, . + .row .col-md-9 diff --git a/app/templates/employers.jade b/app/templates/employers.jade index c79a47b47..74fe65f62 100644 --- a/app/templates/employers.jade +++ b/app/templates/employers.jade @@ -15,7 +15,7 @@ block content if !isEmployer h3 - a(title='Contact', tabindex=-1, data-toggle="coco-modal", data-target="modal/employer_signup") Click here to see candidates + a#see-candidates(title='Contact', tabindex=-1, data-toggle="coco-modal", data-target="modal/employer_signup") Click here to see candidates if candidates.length table.table.table-condensed.table-hover.table-responsive.tablesorter diff --git a/app/views/modal/employer_signup_modal.coffee b/app/views/modal/employer_signup_modal.coffee index 3022290e0..050abaebc 100644 --- a/app/views/modal/employer_signup_modal.coffee +++ b/app/views/modal/employer_signup_modal.coffee @@ -22,16 +22,13 @@ module.exports = class EmployerSignupView extends View constructor: (options) -> super(options) @authorizedWithLinkedIn = IN?.User?.isAuthorized() - #TODO: If IN.User.logout is called after authorizing, then the modal is reopened - # and the user reauths, there will be a javascript error due to the - # contract callback context not finding @render - #window.tracker?.trackEvent 'Started Employer Signup' + window.tracker?.trackEvent 'Started Employer Signup' @reloadWhenClosed = false window.contractCallback = => @authorizedWithLinkedIn = IN?.User?.isAuthorized() @render() - onServerError: (e) -> # TODO: work error handling into a separate forms system + onServerError: (e) -> @disableModalInProgress(@$el) afterInsert: -> @@ -47,10 +44,8 @@ module.exports = class EmployerSignupView extends View getRenderData: -> context = super() context.userIsAuthorized = @authorizedWithLinkedIn - context.userHasSignedContract = false + context.userHasSignedContract = "employer" in me.get("permissions") context.userIsAnonymous = context.me.get('anonymous') - if @authorizedWithLinkedIn - context.firstName = application.linkedinHandler.linkedInData.firstName context agreeToContract: -> @@ -64,12 +59,15 @@ module.exports = class EmployerSignupView extends View error: @handleAgreementFailure handleAgreementSuccess: (result) -> + window.tracker?.trackEvent 'Employer Agreed to Contract' me.fetch() window.location.reload() handleAgreementFailure: (error) -> + alert "There was an error signing the contract. Please contact support with this error: #{error}" createAccount: (e) => + window.tracker?.trackEvent 'Finished Employer Signup' console.log "Tried to create account!" e.stopPropagation() forms.clearFormAlerts(@$el) diff --git a/app/views/modal/login_modal.coffee b/app/views/modal/login_modal.coffee index 56497fa51..68306a03e 100644 --- a/app/views/modal/login_modal.coffee +++ b/app/views/modal/login_modal.coffee @@ -17,7 +17,6 @@ module.exports = class LoginModalView extends View events: "click #login-button": "loginAccount" "keydown #login-password": "loginAccount" - "click #link-to-signup": "switchToSignup" subscriptions: 'server-error': 'onServerError' diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index 9c97dcb95..7f63852e4 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -243,7 +243,7 @@ UserHandler = class UserHandler extends Handler if user.get('employerAt') or user.get('signedEmployerAgreement') or "employer" in user.get('permissions') return errors.conflict(res, "You already have signed the agreement!") #TODO: Search for the current position - employerAt = profileData.positions.values[0].company.name + employerAt = _.filter(profileData.positions.values,"isCurrent")[0]?.company.name ? "Not available" signedEmployerAgreement = linkedinID: profileData.id date: new Date() From 61458c97d69d49340945749ee744eeeed12807f9 Mon Sep 17 00:00:00 2001 From: Michael Schmatz Date: Fri, 25 Apr 2014 07:50:29 -0700 Subject: [PATCH 5/5] Fixed jqxhr error display --- app/views/modal/employer_signup_modal.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/modal/employer_signup_modal.coffee b/app/views/modal/employer_signup_modal.coffee index 050abaebc..9ff3932a6 100644 --- a/app/views/modal/employer_signup_modal.coffee +++ b/app/views/modal/employer_signup_modal.coffee @@ -64,7 +64,7 @@ module.exports = class EmployerSignupView extends View window.location.reload() handleAgreementFailure: (error) -> - alert "There was an error signing the contract. Please contact support with this error: #{error}" + alert "There was an error signing the contract. Please contact team@codecombat.com with this error: #{error.responseText}" createAccount: (e) => window.tracker?.trackEvent 'Finished Employer Signup'