mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-05 18:01:01 +00:00
Merge pull request #735 from sharelatex/pr-affiliations-ui-adjustments
Affiliations UI, second round
This commit is contained in:
commit
b150a7b4ae
15 changed files with 506 additions and 195 deletions
|
@ -61,6 +61,19 @@ module.exports = UserEmailsController =
|
|||
return next(error) if error?
|
||||
res.sendStatus 204
|
||||
|
||||
resendConfirmation: (req, res, next) ->
|
||||
userId = AuthenticationController.getLoggedInUserId(req)
|
||||
email = EmailHelper.parseEmail(req.body.email)
|
||||
return res.sendStatus 422 unless email?
|
||||
UserGetter.getUserByAnyEmail email, {_id:1}, (error, user) ->
|
||||
return next(error) if error?
|
||||
if !user? or user?._id?.toString() != userId
|
||||
logger.log {userId, email, foundUserId: user?._id}, "email doesn't match logged in user"
|
||||
return res.sendStatus 422
|
||||
logger.log {userId, email}, 'resending email confirmation token'
|
||||
UserEmailsConfirmationHandler.sendConfirmationEmail userId, email, (error) ->
|
||||
return next(error) if error?
|
||||
res.sendStatus 200
|
||||
|
||||
showConfirm: (req, res, next) ->
|
||||
res.render 'user/confirm_email', {
|
||||
|
|
|
@ -20,5 +20,7 @@ module.exports = Features =
|
|||
return Settings.overleaf?
|
||||
when 'templates'
|
||||
return !Settings.overleaf?
|
||||
when 'affiliations'
|
||||
return Settings?.apis?.v1?.url?
|
||||
else
|
||||
throw new Error("unknown feature: #{feature}")
|
||||
|
|
|
@ -115,8 +115,11 @@ module.exports = class Router
|
|||
UserEmailsController.showConfirm
|
||||
webRouter.post '/user/emails/confirm',
|
||||
UserEmailsController.confirm
|
||||
webRouter.post '/user/emails/resend_confirmation',
|
||||
AuthenticationController.requireLogin(),
|
||||
UserEmailsController.resendConfirmation
|
||||
|
||||
unless Features.externalAuthenticationSystemUsed()
|
||||
if Features.hasFeature 'affiliations'
|
||||
webRouter.post '/user/emails',
|
||||
AuthenticationController.requireLogin(),
|
||||
UserEmailsController.add
|
||||
|
|
|
@ -9,7 +9,7 @@ block content
|
|||
.page-header
|
||||
h1 #{translate("account_settings")}
|
||||
.account-settings(ng-controller="AccountSettingsController", ng-cloak)
|
||||
if locals.showAffiliationsUI
|
||||
if locals.showAffiliationsUI && hasFeature('affiliations')
|
||||
include settings/user-affiliations
|
||||
|
||||
form-messages(for="settingsForm")
|
||||
|
@ -22,25 +22,26 @@ block content
|
|||
h3 #{translate("update_account_info")}
|
||||
form(async-form="settings", name="settingsForm", method="POST", action="/user/settings", novalidate)
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
if !externalAuthenticationSystemUsed()
|
||||
.form-group
|
||||
label(for='email') #{translate("email")}
|
||||
input.form-control(
|
||||
type='email',
|
||||
name='email',
|
||||
placeholder="email@example.com"
|
||||
required,
|
||||
ng-model="email",
|
||||
ng-init="email = "+JSON.stringify(user.email),
|
||||
ng-model-options="{ updateOn: 'blur' }"
|
||||
)
|
||||
span.small.text-primary(ng-show="settingsForm.email.$invalid && settingsForm.email.$dirty")
|
||||
| #{translate("must_be_email_address")}
|
||||
else
|
||||
// show the email, non-editable
|
||||
.form-group
|
||||
label.control-label #{translate("email")}
|
||||
div.form-control(readonly="true") #{user.email}
|
||||
if !(locals.showAffiliationsUI && hasFeature('affiliations'))
|
||||
if !externalAuthenticationSystemUsed()
|
||||
.form-group
|
||||
label(for='email') #{translate("email")}
|
||||
input.form-control(
|
||||
type='email',
|
||||
name='email',
|
||||
placeholder="email@example.com"
|
||||
required,
|
||||
ng-model="email",
|
||||
ng-init="email = "+JSON.stringify(user.email),
|
||||
ng-model-options="{ updateOn: 'blur' }"
|
||||
)
|
||||
span.small.text-primary(ng-show="settingsForm.email.$invalid && settingsForm.email.$dirty")
|
||||
| #{translate("must_be_email_address")}
|
||||
else
|
||||
// show the email, non-editable
|
||||
.form-group
|
||||
label.control-label #{translate("email")}
|
||||
div.form-control(readonly="true") #{user.email}
|
||||
|
||||
if shouldAllowEditingDetails
|
||||
.form-group
|
||||
|
|
|
@ -3,44 +3,96 @@ form.row(
|
|||
name="affiliationsForm"
|
||||
)
|
||||
.col-md-12
|
||||
h3 Emails and Affiliations
|
||||
p.small Add additional email addresses to your account to access any upgrades your university or institution has, to make it easier for collaborators to find you, and to make sure you can recover your account.
|
||||
h3 #{translate("emails_and_affiliations_title")}
|
||||
p.small #{translate("emails_and_affiliations_explanation")}
|
||||
table.table.affiliations-table
|
||||
thead
|
||||
tr
|
||||
th.affiliations-table-email Email
|
||||
th.affiliations-table-institution Institution and role
|
||||
th.affiliations-table-email #{translate("email")}
|
||||
th.affiliations-table-institution #{translate("institution_and_role")}
|
||||
th.affiliations-table-inline-actions
|
||||
tbody
|
||||
tbody(
|
||||
ng-if="!ui.hasError"
|
||||
)
|
||||
tr(
|
||||
ng-repeat="userEmail in userEmails"
|
||||
)
|
||||
td {{ userEmail.email + (userEmail.default ? ' (default)' : '') }}
|
||||
td
|
||||
| {{ userEmail.email + (userEmail.default ? ' (default)' : '') }}
|
||||
div(ng-if="!userEmail.confirmedAt").small
|
||||
strong #{translate('unconfirmed')}.
|
||||
|
|
||||
| #{translate('please_check_your_inbox')}.
|
||||
br
|
||||
a(
|
||||
href,
|
||||
ng-click="resendConfirmationEmail(userEmail)"
|
||||
) #{translate('resend_confirmation_email')}
|
||||
td
|
||||
div(ng-if="userEmail.affiliation.institution") {{ userEmail.affiliation.institution.name }}
|
||||
div(ng-if="userEmail.affiliation.role || userEmail.affiliation.department")
|
||||
span(ng-if="userEmail.affiliation.role") {{ userEmail.affiliation.role }}
|
||||
span(ng-if="userEmail.affiliation.role && userEmail.affiliation.department") ,
|
||||
span(ng-if="userEmail.affiliation.department") {{ userEmail.affiliation.department }}
|
||||
td
|
||||
a(
|
||||
href
|
||||
div(ng-if="userEmail.affiliation.institution")
|
||||
div {{ userEmail.affiliation.institution.name }}
|
||||
span.small
|
||||
a(
|
||||
href
|
||||
ng-if="!isChangingAffiliation(userEmail.email) && !userEmail.affiliation.role && !userEmail.affiliation.department"
|
||||
ng-click="changeAffiliation(userEmail);"
|
||||
) #{translate("add_role_and_department")}
|
||||
div.small(
|
||||
ng-if="!isChangingAffiliation(userEmail.email) && (userEmail.affiliation.role || userEmail.affiliation.department)"
|
||||
)
|
||||
span(ng-if="userEmail.affiliation.role") {{ userEmail.affiliation.role }}
|
||||
span(ng-if="userEmail.affiliation.role && userEmail.affiliation.department") ,
|
||||
span(ng-if="userEmail.affiliation.department") {{ userEmail.affiliation.department }}
|
||||
br
|
||||
a(
|
||||
href
|
||||
ng-click="changeAffiliation(userEmail);"
|
||||
) #{translate("change")}
|
||||
.affiliation-change-container(
|
||||
ng-if="isChangingAffiliation(userEmail.email)"
|
||||
)
|
||||
affiliation-form(
|
||||
affiliation-data="affiliationToChange"
|
||||
show-university-and-country="false"
|
||||
show-role-and-department="true"
|
||||
)
|
||||
.affiliation-change-actions.small
|
||||
a(
|
||||
href
|
||||
ng-click="saveAffiliationChange();"
|
||||
) #{translate("save_or_cancel-save")}
|
||||
| #{translate("save_or_cancel-or" )}
|
||||
a(
|
||||
href
|
||||
ng-click="cancelAffiliationChange();"
|
||||
) #{translate("save_or_cancel-cancel")}
|
||||
td.affiliations-table-inline-actions
|
||||
// Disabled buttons don't work with tooltips, due to pointer-events: none,
|
||||
// so create a wrapper for the tooltip
|
||||
div.affiliations-table-inline-action-disabled-wrapper(
|
||||
tooltip=translate("please_confirm_your_email_before_making_it_default")
|
||||
ng-if="!userEmail.default && !userEmail.confirmedAt"
|
||||
)
|
||||
button.btn.btn-sm.btn-success.affiliations-table-inline-action(
|
||||
disabled
|
||||
) #{translate("make_default")}
|
||||
button.btn.btn-sm.btn-success.affiliations-table-inline-action(
|
||||
ng-if="!userEmail.default && userEmail.confirmedAt"
|
||||
ng-click="setDefaultUserEmail(userEmail)"
|
||||
) #{translate("make_default")}
|
||||
|
|
||||
button.btn.btn-sm.btn-danger.affiliations-table-inline-action(
|
||||
ng-if="!userEmail.default"
|
||||
ng-click="setDefaultUserEmail(userEmail.email)"
|
||||
) Make default
|
||||
br
|
||||
a(
|
||||
href
|
||||
ng-if="!userEmail.default"
|
||||
ng-click="removeUserEmail(userEmail.email)"
|
||||
) Remove
|
||||
ng-click="removeUserEmail(userEmail)"
|
||||
tooltip=translate("remove")
|
||||
)
|
||||
i.fa.fa-fw.fa-trash
|
||||
tr.affiliations-table-highlighted-row(
|
||||
ng-if="ui.isLoadingEmails"
|
||||
)
|
||||
td.text-center(colspan="3")
|
||||
i.fa.fa-fw.fa-spin.fa-refresh
|
||||
| Loading...
|
||||
|
||||
| #{translate("loading")}...
|
||||
tr.affiliations-table-highlighted-row(
|
||||
ng-if="!ui.showAddEmailUI && !ui.isLoadingEmails"
|
||||
)
|
||||
|
@ -48,7 +100,7 @@ form.row(
|
|||
a(
|
||||
href
|
||||
ng-click="showAddEmailForm()"
|
||||
) Add another email
|
||||
) #{translate("add_another_email")}
|
||||
|
||||
tr.affiliations-table-highlighted-row(
|
||||
ng-if="ui.showAddEmailUI"
|
||||
|
@ -67,106 +119,130 @@ form.row(
|
|||
input-required="true"
|
||||
)
|
||||
td
|
||||
.affiliations-table-label(
|
||||
p.affiliations-table-label(
|
||||
ng-if="newAffiliation.university && !ui.showManualUniversitySelectionUI"
|
||||
)
|
||||
| {{ newAffiliation.university.name }} (
|
||||
a(
|
||||
href
|
||||
ng-click="selectUniversityManually();"
|
||||
) change
|
||||
| )
|
||||
| {{ newAffiliation.university.name }}
|
||||
span.small
|
||||
| (
|
||||
a(
|
||||
href
|
||||
ng-click="selectUniversityManually();"
|
||||
) #{translate("change")}
|
||||
| )
|
||||
.affiliations-table-label(
|
||||
ng-if="!newAffiliation.university && !ui.isValidEmail && !ui.showManualUniversitySelectionUI"
|
||||
) Start by adding your email address.
|
||||
) #{translate("start_by_adding_your_email")}
|
||||
.affiliations-table-label(
|
||||
ng-if="!newAffiliation.university && ui.isValidEmail && !ui.isBlacklistedEmail && !ui.showManualUniversitySelectionUI"
|
||||
)
|
||||
| Is your email affiliated with an institution?
|
||||
| #{translate("is_email_affiliated")}
|
||||
br
|
||||
a(
|
||||
href
|
||||
ng-click="selectUniversityManually();"
|
||||
) Let us know
|
||||
.affiliations-form-group(
|
||||
ng-if="ui.showManualUniversitySelectionUI"
|
||||
) #{translate("let_us_know")}
|
||||
affiliation-form(
|
||||
affiliation-data="newAffiliation"
|
||||
show-university-and-country="ui.showManualUniversitySelectionUI"
|
||||
show-role-and-department="ui.isValidEmail && newAffiliation.university"
|
||||
)
|
||||
ui-select(
|
||||
ng-model="newAffiliation.country"
|
||||
)
|
||||
ui-select-match(
|
||||
placeholder="Country"
|
||||
) {{ $select.selected.name }}
|
||||
ui-select-choices(
|
||||
repeat="country in countries | filter: $select.search"
|
||||
)
|
||||
span(
|
||||
ng-bind="country.name"
|
||||
s)
|
||||
.affiliations-form-group(
|
||||
ng-if="ui.showManualUniversitySelectionUI"
|
||||
)
|
||||
ui-select(
|
||||
ng-model="newAffiliation.university"
|
||||
ng-disabled="!newAffiliation.country"
|
||||
tagging="addUniversityToSelection"
|
||||
tagging-label="false"
|
||||
)
|
||||
ui-select-match(
|
||||
placeholder="Institution"
|
||||
) {{ $select.selected.name }}
|
||||
ui-select-choices(
|
||||
repeat="university in universities | filter: $select.search"
|
||||
)
|
||||
span(
|
||||
ng-bind="university.name"
|
||||
)
|
||||
.affiliations-form-group(
|
||||
ng-if="ui.isValidEmail && newAffiliation.university"
|
||||
)
|
||||
ui-select(
|
||||
ng-model="newAffiliation.role"
|
||||
tagging
|
||||
tagging-label="false"
|
||||
)
|
||||
ui-select-match(
|
||||
placeholder="Role"
|
||||
) {{ $select.selected }}
|
||||
ui-select-choices(
|
||||
repeat="role in roles | filter: $select.search"
|
||||
)
|
||||
span(
|
||||
ng-bind="role"
|
||||
)
|
||||
|
||||
.affiliations-form-group(
|
||||
ng-if="ui.isValidEmail && newAffiliation.university"
|
||||
)
|
||||
ui-select(
|
||||
ng-model="newAffiliation.department"
|
||||
tagging
|
||||
tagging-label="false"
|
||||
)
|
||||
ui-select-match(
|
||||
placeholder="Department"
|
||||
) {{ $select.selected }}
|
||||
ui-select-choices(
|
||||
repeat="department in departments | filter: $select.search"
|
||||
)
|
||||
span(
|
||||
ng-bind="department"
|
||||
)
|
||||
td
|
||||
button.btn.btn-primary(
|
||||
button.btn.btn-sm.btn-primary(
|
||||
ng-disabled="affiliationsForm.$invalid || ui.isAddingNewEmail"
|
||||
ng-click="addNewEmail()"
|
||||
)
|
||||
span(
|
||||
ng-if="!ui.isAddingNewEmail"
|
||||
) Add new email
|
||||
) #{translate("add_new_email")}
|
||||
span(
|
||||
ng-if="ui.isAddingNewEmail"
|
||||
)
|
||||
i.fa.fa-fw.fa-spin.fa-refresh
|
||||
| Adding...
|
||||
hr
|
||||
| #{translate("adding")}...
|
||||
tbody(
|
||||
ng-if="ui.hasError"
|
||||
)
|
||||
tr.affiliations-table-error-row(
|
||||
ng-if="true"
|
||||
)
|
||||
td.text-center(colspan="3")
|
||||
div
|
||||
i.fa.fa-fw.fa-exclamation-triangle
|
||||
| #{translate("error_performing_request")}
|
||||
div
|
||||
button.btn.btn-xs(
|
||||
ng-click="acknowledgeError();"
|
||||
) #{translate("reload_emails_and_affiliations")}
|
||||
|
||||
hr
|
||||
|
||||
script(type="text/ng-template", id="affiliationFormTpl")
|
||||
.affiliations-form-group(
|
||||
ng-if="$ctrl.showUniversityAndCountry"
|
||||
)
|
||||
ui-select(
|
||||
ng-model="$ctrl.affiliationData.country"
|
||||
)
|
||||
ui-select-match(
|
||||
placeholder="Country"
|
||||
) {{ $select.selected.name }}
|
||||
ui-select-choices(
|
||||
repeat="country in $ctrl.countries | filter: $select.search"
|
||||
)
|
||||
span(
|
||||
ng-bind="country.name"
|
||||
)
|
||||
.affiliations-form-group(
|
||||
ng-if="$ctrl.showUniversityAndCountry"
|
||||
)
|
||||
ui-select(
|
||||
ng-model="$ctrl.affiliationData.university"
|
||||
ng-disabled="!$ctrl.affiliationData.country"
|
||||
tagging="$ctrladdUniversityToSelection"
|
||||
tagging-label="false"
|
||||
)
|
||||
ui-select-match(
|
||||
placeholder="Institution"
|
||||
) {{ $select.selected.name }}
|
||||
ui-select-choices(
|
||||
repeat="university in $ctrl.universities | filter: $select.search"
|
||||
)
|
||||
span(
|
||||
ng-bind="university.name"
|
||||
)
|
||||
.affiliations-form-group(
|
||||
ng-if="$ctrl.showRoleAndDepartment"
|
||||
)
|
||||
ui-select(
|
||||
ng-model="$ctrl.affiliationData.role"
|
||||
tagging
|
||||
tagging-label="false"
|
||||
)
|
||||
ui-select-match(
|
||||
placeholder="Role"
|
||||
) {{ $select.selected }}
|
||||
ui-select-choices(
|
||||
repeat="role in $ctrl.roles | filter: $select.search"
|
||||
)
|
||||
span(
|
||||
ng-bind="role"
|
||||
)
|
||||
|
||||
.affiliations-form-group(
|
||||
ng-if="$ctrl.showRoleAndDepartment"
|
||||
)
|
||||
ui-select(
|
||||
ng-model="$ctrl.affiliationData.department"
|
||||
tagging
|
||||
tagging-label="false"
|
||||
)
|
||||
ui-select-match(
|
||||
placeholder="Department"
|
||||
) {{ $select.selected }}
|
||||
ui-select-choices(
|
||||
repeat="department in $ctrl.departments | filter: $select.search"
|
||||
)
|
||||
span(
|
||||
ng-bind="department"
|
||||
)
|
||||
|
|
|
@ -20,6 +20,7 @@ define [
|
|||
"main/subscription/team-invite-controller"
|
||||
"main/contact-us"
|
||||
"main/learn"
|
||||
"main/affiliations/components/affiliationForm"
|
||||
"main/affiliations/controllers/UserAffiliationsController"
|
||||
"main/affiliations/factories/UserAffiliationsDataService"
|
||||
"main/keys"
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
affiliationFormController = ($scope, $element, $attrs, UserAffiliationsDataService) ->
|
||||
ctrl = @
|
||||
ctrl.roles = []
|
||||
ctrl.departments = []
|
||||
ctrl.countries = []
|
||||
ctrl.universities = []
|
||||
_defaultDepartments = []
|
||||
|
||||
ctrl.addUniversityToSelection = (universityName) ->
|
||||
{ name: universityName, isUserSuggested: true }
|
||||
# Populates the countries dropdown
|
||||
UserAffiliationsDataService
|
||||
.getCountries()
|
||||
.then (countries) -> ctrl.countries = countries
|
||||
# Populates the roles dropdown
|
||||
UserAffiliationsDataService
|
||||
.getDefaultRoleHints()
|
||||
.then (roles) -> ctrl.roles = roles
|
||||
# Fetches the default department hints
|
||||
UserAffiliationsDataService
|
||||
.getDefaultDepartmentHints()
|
||||
.then (departments) ->
|
||||
_defaultDepartments = departments
|
||||
# Populates the universities dropdown (after selecting a country)
|
||||
$scope.$watch "$ctrl.affiliationData.country", (newSelectedCountry, prevSelectedCountry) ->
|
||||
if newSelectedCountry? and newSelectedCountry != prevSelectedCountry
|
||||
ctrl.affiliationData.university = null
|
||||
ctrl.affiliationData.role = null
|
||||
ctrl.affiliationData.department = null
|
||||
UserAffiliationsDataService
|
||||
.getUniversitiesFromCountry(newSelectedCountry)
|
||||
.then (universities) -> ctrl.universities = universities
|
||||
# Populates the departments dropdown (after selecting a university)
|
||||
$scope.$watch "$ctrl.affiliationData.university", (newSelectedUniversity, prevSelectedUniversity) ->
|
||||
if newSelectedUniversity? and newSelectedUniversity != prevSelectedUniversity and newSelectedUniversity.departments?.length > 0
|
||||
ctrl.departments = _.uniq newSelectedUniversity.departments
|
||||
else
|
||||
ctrl.departments = _defaultDepartments
|
||||
|
||||
return
|
||||
|
||||
App.component "affiliationForm", {
|
||||
bindings:
|
||||
affiliationData: "="
|
||||
showUniversityAndCountry: "<"
|
||||
showRoleAndDepartment: "<"
|
||||
controller: affiliationFormController
|
||||
templateUrl: "affiliationFormTpl"
|
||||
}
|
|
@ -3,12 +3,6 @@ define [
|
|||
], (App) ->
|
||||
App.controller "UserAffiliationsController", ["$scope", "UserAffiliationsDataService", "$q", "_", ($scope, UserAffiliationsDataService, $q, _) ->
|
||||
$scope.userEmails = []
|
||||
$scope.countries = []
|
||||
$scope.universities = []
|
||||
$scope.roles = []
|
||||
$scope.departments = []
|
||||
|
||||
_defaultDepartments = []
|
||||
|
||||
LOCAL_AND_DOMAIN_REGEX = /([^@]+)@(.+)/
|
||||
EMAIL_REGEX = /^([A-Za-z0-9_\-\.]+)@([^\.]+)\.([A-Za-z0-9_\-\.]+)([^\.])$/
|
||||
|
@ -20,9 +14,6 @@ define [
|
|||
else
|
||||
{ local: null, domain: null }
|
||||
|
||||
$scope.addUniversityToSelection = (universityName) ->
|
||||
{ name: universityName, isUserSuggested: true }
|
||||
|
||||
$scope.getEmailSuggestion = (userInput) ->
|
||||
userInputLocalAndDomain = _matchLocalAndDomain(userInput)
|
||||
$scope.ui.isValidEmail = EMAIL_REGEX.test userInput
|
||||
|
@ -55,6 +46,38 @@ define [
|
|||
$scope.newAffiliation.department = null
|
||||
$scope.ui.showManualUniversitySelectionUI = true
|
||||
|
||||
$scope.changeAffiliation = (userEmail) ->
|
||||
if userEmail.affiliation?.institution?.id?
|
||||
UserAffiliationsDataService.getUniversityDetails userEmail.affiliation.institution.id
|
||||
.then (universityDetails) -> $scope.affiliationToChange.university = universityDetails
|
||||
|
||||
$scope.affiliationToChange.email = userEmail.email
|
||||
$scope.affiliationToChange.role = userEmail.affiliation.role
|
||||
$scope.affiliationToChange.department = userEmail.affiliation.department
|
||||
|
||||
$scope.saveAffiliationChange = () ->
|
||||
$scope.ui.isLoadingEmails = true
|
||||
UserAffiliationsDataService
|
||||
.addRoleAndDepartment(
|
||||
$scope.affiliationToChange.email,
|
||||
$scope.affiliationToChange.role,
|
||||
$scope.affiliationToChange.department
|
||||
)
|
||||
.then () ->
|
||||
_reset()
|
||||
_getUserEmails()
|
||||
.catch () ->
|
||||
$scope.ui.hasError = true
|
||||
|
||||
$scope.cancelAffiliationChange = (email) ->
|
||||
$scope.affiliationToChange.email = ""
|
||||
$scope.affiliationToChange.university = null
|
||||
$scope.affiliationToChange.role = null
|
||||
$scope.affiliationToChange.department = null
|
||||
|
||||
$scope.isChangingAffiliation = (email) ->
|
||||
$scope.affiliationToChange.email == email
|
||||
|
||||
$scope.showAddEmailForm = () ->
|
||||
$scope.ui.showAddEmailUI = true
|
||||
|
||||
|
@ -81,27 +104,40 @@ define [
|
|||
$scope.newAffiliation.role,
|
||||
$scope.newAffiliation.department
|
||||
)
|
||||
addEmailPromise.then () ->
|
||||
_reset()
|
||||
_getUserEmails()
|
||||
addEmailPromise
|
||||
.then () ->
|
||||
_reset()
|
||||
_getUserEmails()
|
||||
.catch () ->
|
||||
$scope.ui.hasError = true
|
||||
|
||||
$scope.setDefaultUserEmail = (email) ->
|
||||
$scope.setDefaultUserEmail = (userEmail) ->
|
||||
$scope.ui.isLoadingEmails = true
|
||||
UserAffiliationsDataService
|
||||
.setDefaultUserEmail email
|
||||
.setDefaultUserEmail userEmail.email
|
||||
.then () -> _getUserEmails()
|
||||
.catch () -> $scope.ui.hasError = true
|
||||
|
||||
$scope.removeUserEmail = (email) ->
|
||||
$scope.removeUserEmail = (userEmail) ->
|
||||
$scope.ui.isLoadingEmails = true
|
||||
userEmailIdx = _.indexOf $scope.userEmails, userEmail
|
||||
if userEmailIdx > -1
|
||||
$scope.userEmails.splice userEmailIdx, 1
|
||||
UserAffiliationsDataService
|
||||
.removeUserEmail userEmail.email
|
||||
.then () -> _getUserEmails()
|
||||
.catch () -> $scope.ui.hasError = true
|
||||
|
||||
$scope.resendConfirmationEmail = (userEmail) ->
|
||||
$scope.ui.isLoadingEmails = true
|
||||
UserAffiliationsDataService
|
||||
.removeUserEmail email
|
||||
.resendConfirmationEmail userEmail.email
|
||||
.then () -> _getUserEmails()
|
||||
.catch () -> $scope.ui.hasError = true
|
||||
|
||||
$scope.getDepartments = () ->
|
||||
if $scope.newAffiliation.university?.departments.length > 0
|
||||
_.uniq $scope.newAffiliation.university.departments
|
||||
else
|
||||
UserAffiliationsDataService.getDefaultDepartmentHints()
|
||||
$scope.acknowledgeError = () ->
|
||||
_reset()
|
||||
_getUserEmails()
|
||||
|
||||
_reset = () ->
|
||||
$scope.newAffiliation =
|
||||
|
@ -111,12 +147,19 @@ define [
|
|||
role: null
|
||||
department: null
|
||||
$scope.ui =
|
||||
hasError: false
|
||||
showChangeAffiliationUI: false
|
||||
showManualUniversitySelectionUI: false
|
||||
isLoadingEmails: false
|
||||
isAddingNewEmail: false
|
||||
showAddEmailUI: false
|
||||
isValidEmail: false
|
||||
isBlacklistedEmail: false
|
||||
$scope.affiliationToChange =
|
||||
email: ""
|
||||
university: null
|
||||
role: null
|
||||
department: null
|
||||
_reset()
|
||||
|
||||
# Populates the emails table
|
||||
|
@ -127,40 +170,9 @@ define [
|
|||
.then (emails) ->
|
||||
$scope.userEmails = emails
|
||||
$scope.ui.isLoadingEmails = false
|
||||
.catch () ->
|
||||
$scope.ui.hasError = true
|
||||
|
||||
_getUserEmails()
|
||||
|
||||
# Populates the countries dropdown
|
||||
UserAffiliationsDataService
|
||||
.getCountries()
|
||||
.then (countries) -> $scope.countries = countries
|
||||
|
||||
# Populates the roles dropdown
|
||||
UserAffiliationsDataService
|
||||
.getDefaultRoleHints()
|
||||
.then (roles) -> $scope.roles = roles
|
||||
|
||||
# Fetches the default department hints
|
||||
UserAffiliationsDataService
|
||||
.getDefaultDepartmentHints()
|
||||
.then (departments) ->
|
||||
_defaultDepartments = departments
|
||||
|
||||
# Populates the universities dropdown (after selecting a country)
|
||||
$scope.$watch "newAffiliation.country", (newSelectedCountry, prevSelectedCountry) ->
|
||||
if newSelectedCountry? and newSelectedCountry != prevSelectedCountry
|
||||
$scope.newAffiliation.university = null
|
||||
$scope.newAffiliation.role = null
|
||||
$scope.newAffiliation.department = null
|
||||
UserAffiliationsDataService
|
||||
.getUniversitiesFromCountry(newSelectedCountry)
|
||||
.then (universities) -> $scope.universities = universities
|
||||
|
||||
# Populates the departments dropdown (after selecting a university)
|
||||
$scope.$watch "newAffiliation.university", (newSelectedUniversity, prevSelectedUniversity) ->
|
||||
if newSelectedUniversity? and newSelectedUniversity != prevSelectedUniversity
|
||||
if newSelectedUniversity.departments?.length > 0
|
||||
$scope.departments = _.uniq newSelectedUniversity.departments
|
||||
else
|
||||
$scope.departments = _defaultDepartments
|
||||
|
||||
]
|
|
@ -27,7 +27,7 @@ define [
|
|||
getDefaultDepartmentHints = () ->
|
||||
$q.resolve defaultDepartmentHints
|
||||
|
||||
getUserEmails = () ->
|
||||
getUserEmails = () ->
|
||||
$http.get "/user/emails"
|
||||
.then (response) -> response.data
|
||||
|
||||
|
@ -53,6 +53,10 @@ define [
|
|||
else
|
||||
$q.reject null
|
||||
|
||||
getUniversityDetails = (universityId) ->
|
||||
$http.get "/institutions/list/#{ universityId }"
|
||||
.then (response) -> response.data
|
||||
|
||||
addUserEmail = (email) ->
|
||||
$http.post "/user/emails", {
|
||||
email,
|
||||
|
@ -80,8 +84,17 @@ define [
|
|||
_csrf: window.csrfToken
|
||||
}
|
||||
|
||||
addRoleAndDepartment = (email, role, department) ->
|
||||
$http.post "/user/emails/endorse", {
|
||||
email,
|
||||
role,
|
||||
department,
|
||||
_csrf: window.csrfToken
|
||||
}
|
||||
|
||||
setDefaultUserEmail = (email) ->
|
||||
$http.post "/user/emails/default", {
|
||||
email,
|
||||
_csrf: window.csrfToken
|
||||
}
|
||||
|
||||
|
@ -91,6 +104,12 @@ define [
|
|||
_csrf: window.csrfToken
|
||||
}
|
||||
|
||||
resendConfirmationEmail = (email) ->
|
||||
$http.post "/user/emails/resend_confirmation", {
|
||||
email,
|
||||
_csrf: window.csrfToken
|
||||
}
|
||||
|
||||
isDomainBlacklisted = (domain) ->
|
||||
domain.toLowerCase() of domainsBlackList
|
||||
|
||||
|
@ -101,11 +120,14 @@ define [
|
|||
getUserEmails
|
||||
getUniversitiesFromCountry
|
||||
getUniversityDomainFromPartialDomainInput
|
||||
getUniversityDetails
|
||||
addUserEmail
|
||||
addUserAffiliationWithUnknownUniversity
|
||||
addUserAffiliation
|
||||
addRoleAndDepartment
|
||||
setDefaultUserEmail
|
||||
removeUserEmail
|
||||
resendConfirmationEmail
|
||||
isDomainBlacklisted
|
||||
}
|
||||
]
|
|
@ -24,18 +24,36 @@
|
|||
width: 40%;
|
||||
}
|
||||
.affiliations-table-inline-actions {
|
||||
width: 20%;
|
||||
text-align: right;
|
||||
}
|
||||
.affiliations-table-inline-action {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.affiliations-table-inline-action-disabled-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
.affiliations-table-highlighted-row {
|
||||
background-color: tint(@content-alt-bg-color, 6%);
|
||||
}
|
||||
|
||||
.affiliations-table-error-row {
|
||||
background-color: @alert-danger-bg;
|
||||
color: @alert-danger-text;
|
||||
.btn {
|
||||
margin-top: @table-cell-padding;
|
||||
.button-variant(@btn-danger-color; darken(@btn-danger-bg, 8%); @btn-danger-border);
|
||||
}
|
||||
}
|
||||
.affiliations-form-group {
|
||||
margin-top: @table-cell-padding;
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
.affiliation-change-container,
|
||||
.affiliation-change-actions {
|
||||
margin-top: @table-cell-padding;
|
||||
}
|
||||
|
||||
.affiliations-table-label {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
.input-suggestions-shadow {
|
||||
background-color: @input-bg;
|
||||
padding-top: 4px;
|
||||
padding-top: @input-suggestion-v-offset;
|
||||
}
|
||||
.input-suggestions-shadow-existing {
|
||||
color: transparent;
|
||||
|
|
|
@ -35,12 +35,14 @@
|
|||
> .btn {
|
||||
border-color: @input-border-focus;
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075), 0 0 8px fade(@input-border-focus, 60%);
|
||||
padding-top: @input-suggestion-v-offset;
|
||||
}
|
||||
}
|
||||
> .btn {
|
||||
color: @input-color;
|
||||
background-color: @input-bg;
|
||||
border: 1px solid @input-border;
|
||||
padding-top: @input-suggestion-v-offset;
|
||||
&[disabled] {
|
||||
cursor: not-allowed;
|
||||
background-color: @input-bg-disabled;
|
||||
|
@ -53,6 +55,7 @@
|
|||
.ui-select-container[tagging] {
|
||||
.ui-select-toggle {
|
||||
cursor: text;
|
||||
padding-top: @input-suggestion-v-offset;
|
||||
> i.caret.pull-right {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -995,3 +995,5 @@
|
|||
@history-toolbar-bg-color : @toolbar-alt-bg-color;
|
||||
@history-toolbar-color : @text-color;
|
||||
|
||||
// Input suggestions
|
||||
@input-suggestion-v-offset : 6px;
|
||||
|
|
|
@ -289,6 +289,8 @@
|
|||
@sys-msg-color : #FFF;
|
||||
@sys-msg-border : solid 1px lighten(@ol-blue, 10%);
|
||||
|
||||
@input-suggestion-v-offset : 4px;
|
||||
|
||||
//== Colors
|
||||
//
|
||||
//## Gray and brand colors for use across Bootstrap.
|
||||
|
|
|
@ -17,7 +17,7 @@ describe "UserEmails", ->
|
|||
token = null
|
||||
async.series [
|
||||
(cb) =>
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails',
|
||||
json:
|
||||
|
@ -45,7 +45,7 @@ describe "UserEmails", ->
|
|||
token = tokens[0].token
|
||||
cb()
|
||||
(cb) =>
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/confirm',
|
||||
json:
|
||||
|
@ -80,7 +80,7 @@ describe "UserEmails", ->
|
|||
(cb) => @user2.login cb
|
||||
(cb) =>
|
||||
# Create email for first user
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails',
|
||||
json: {@email}
|
||||
|
@ -99,21 +99,21 @@ describe "UserEmails", ->
|
|||
cb()
|
||||
(cb) =>
|
||||
# Delete the email from the first user
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/delete',
|
||||
json: {@email}
|
||||
}, cb
|
||||
(cb) =>
|
||||
# Create email for second user
|
||||
@user2.request {
|
||||
@user2.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails',
|
||||
json: {@email}
|
||||
}, cb
|
||||
(cb) =>
|
||||
# Original confirmation token should no longer work
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/confirm',
|
||||
json:
|
||||
|
@ -158,7 +158,7 @@ describe "UserEmails", ->
|
|||
token = null
|
||||
async.series [
|
||||
(cb) =>
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails',
|
||||
json:
|
||||
|
@ -183,12 +183,12 @@ describe "UserEmails", ->
|
|||
db.tokens.update {
|
||||
token: token
|
||||
}, {
|
||||
$set: {
|
||||
$set: {
|
||||
expiresAt: new Date(Date.now() - 1000000)
|
||||
}
|
||||
}, cb
|
||||
(cb) =>
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/confirm',
|
||||
json:
|
||||
|
@ -198,3 +198,107 @@ describe "UserEmails", ->
|
|||
expect(response.statusCode).to.equal 404
|
||||
cb()
|
||||
], done
|
||||
|
||||
describe 'resending the confirmation', ->
|
||||
it 'should generate a new token', (done) ->
|
||||
async.series [
|
||||
(cb) =>
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails',
|
||||
json:
|
||||
email: 'reconfirmation-email@example.com'
|
||||
}, (error, response, body) =>
|
||||
return done(error) if error?
|
||||
expect(response.statusCode).to.equal 204
|
||||
cb()
|
||||
(cb) =>
|
||||
db.tokens.find {
|
||||
use: 'email_confirmation',
|
||||
'data.user_id': @user._id,
|
||||
usedAt: { $exists: false }
|
||||
}, (error, tokens) =>
|
||||
# There should only be one confirmation token at the moment
|
||||
expect(tokens.length).to.equal 1
|
||||
expect(tokens[0].data.email).to.equal 'reconfirmation-email@example.com'
|
||||
expect(tokens[0].data.user_id).to.equal @user._id
|
||||
cb()
|
||||
(cb) =>
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/resend_confirmation',
|
||||
json:
|
||||
email: 'reconfirmation-email@example.com'
|
||||
}, (error, response, body) =>
|
||||
return done(error) if error?
|
||||
expect(response.statusCode).to.equal 200
|
||||
cb()
|
||||
(cb) =>
|
||||
db.tokens.find {
|
||||
use: 'email_confirmation',
|
||||
'data.user_id': @user._id,
|
||||
usedAt: { $exists: false }
|
||||
}, (error, tokens) =>
|
||||
# There should be two tokens now
|
||||
expect(tokens.length).to.equal 2
|
||||
expect(tokens[0].data.email).to.equal 'reconfirmation-email@example.com'
|
||||
expect(tokens[0].data.user_id).to.equal @user._id
|
||||
expect(tokens[1].data.email).to.equal 'reconfirmation-email@example.com'
|
||||
expect(tokens[1].data.user_id).to.equal @user._id
|
||||
cb()
|
||||
], done
|
||||
|
||||
it 'should create a new token if none exists', (done) ->
|
||||
# This should only be for users that have sign up with their main
|
||||
# emails before the confirmation system existed
|
||||
async.series [
|
||||
(cb) =>
|
||||
db.tokens.remove {
|
||||
use: 'email_confirmation',
|
||||
'data.user_id': @user._id,
|
||||
usedAt: { $exists: false }
|
||||
}, cb
|
||||
(cb) =>
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/resend_confirmation',
|
||||
json:
|
||||
email: @user.email
|
||||
}, (error, response, body) =>
|
||||
return done(error) if error?
|
||||
expect(response.statusCode).to.equal 200
|
||||
cb()
|
||||
(cb) =>
|
||||
db.tokens.find {
|
||||
use: 'email_confirmation',
|
||||
'data.user_id': @user._id,
|
||||
usedAt: { $exists: false }
|
||||
}, (error, tokens) =>
|
||||
# There should still only be one confirmation token
|
||||
expect(tokens.length).to.equal 1
|
||||
expect(tokens[0].data.email).to.equal @user.email
|
||||
expect(tokens[0].data.user_id).to.equal @user._id
|
||||
cb()
|
||||
], done
|
||||
|
||||
it "should not allow reconfirmation if the email doesn't match the user", (done) ->
|
||||
async.series [
|
||||
(cb) =>
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/resend_confirmation',
|
||||
json:
|
||||
email: 'non-matching-email@example.com'
|
||||
}, (error, response, body) =>
|
||||
return done(error) if error?
|
||||
expect(response.statusCode).to.equal 422
|
||||
cb()
|
||||
(cb) =>
|
||||
db.tokens.find {
|
||||
use: 'email_confirmation',
|
||||
'data.user_id': @user._id,
|
||||
usedAt: { $exists: false }
|
||||
}, (error, tokens) =>
|
||||
expect(tokens.length).to.equal 0
|
||||
cb()
|
||||
], done
|
||||
|
|
Loading…
Reference in a new issue