codecombat/app/views/account/AccountSettingsView.coffee

284 lines
10 KiB
CoffeeScript

CocoView = require 'views/core/CocoView'
template = require 'templates/account/account-settings-view'
{me} = require 'core/auth'
forms = require 'core/forms'
User = require 'models/User'
ConfirmModal = require 'views/editor/modal/ConfirmModal'
{logoutUser, me} = require('core/auth')
module.exports = class AccountSettingsView extends CocoView
id: 'account-settings-view'
template: template
className: 'countainer-fluid'
events:
'change .panel input': 'onChangePanelInput'
'change #name-input': 'onChangeNameInput'
'click #toggle-all-btn': 'onClickToggleAllButton'
'click #profile-photo-panel-body': 'onClickProfilePhotoPanelBody'
'click #delete-account-btn': 'onClickDeleteAccountButton'
'click #reset-progress-btn': 'onClickResetProgressButton'
'click .resend-verification-email': 'onClickResendVerificationEmail'
constructor: (options) ->
super options
require('core/services/filepicker')() unless window.application.isIPadApp # Initialize if needed
@uploadFilePath = "db/user/#{me.id}"
getEmailSubsDict: ->
subs = {}
return subs unless me
subs[sub] = 1 for sub in me.getEnabledEmails()
return subs
#- Form input callbacks
onChangePanelInput: (e) ->
return if $(e.target).closest('.form').attr('id') in ['reset-progress-form', 'delete-account-form']
$(e.target).addClass 'changed'
@trigger 'input-changed'
onClickToggleAllButton: ->
subs = @getSubscriptions()
$('#email-panel input[type="checkbox"]', @$el).prop('checked', not _.any(_.values(subs))).addClass('changed')
@trigger 'input-changed'
onChangeNameInput: ->
name = $('#name-input', @$el).val()
return if name is me.get 'name'
User.getUnconflictedName name, (newName) =>
forms.clearFormAlerts(@$el)
if name is newName
@suggestedName = undefined
else
@suggestedName = newName
forms.setErrorToProperty @$el, 'name', "That name is taken! How about #{newName}?", true
onPictureChanged: (e) =>
@trigger 'inputChanged', e
@$el.find('.gravatar-fallback').toggle not me.get 'photoURL'
onClickDeleteAccountButton: (e) ->
@validateCredentialsForDestruction @$el.find('#delete-account-form'), =>
renderData =
title: 'Are you really sure?'
body: 'This will completely delete your account. This action CANNOT be undone. Are you entirely sure?'
decline: 'Cancel'
confirm: 'DELETE Your Account'
confirmModal = new ConfirmModal renderData
confirmModal.on 'confirm', @deleteAccount
@openModalView confirmModal
onClickResetProgressButton: ->
@validateCredentialsForDestruction @$el.find('#reset-progress-form'), =>
renderData =
title: 'Are you really sure?'
body: 'This will completely erase your progress: code, levels, achievements, earned gems, and course work. This action CANNOT be undone. Are you entirely sure?'
decline: 'Cancel'
confirm: 'Erase ALL Progress'
confirmModal = new ConfirmModal renderData
confirmModal.on 'confirm', @resetProgress
@openModalView confirmModal
onClickResendVerificationEmail: (e) ->
$.post me.getRequestVerificationEmailURL(), ->
link = $(e.currentTarget)
link.find('.resend-text').addClass('hide')
link.find('.sent-text').removeClass('hide')
validateCredentialsForDestruction: ($form, onSuccess) ->
forms.clearFormAlerts($form)
enteredEmailOrUsername = $form.find('input[name="emailOrUsername"]').val()
enteredPassword = $form.find('input[name="password"]').val()
if enteredEmailOrUsername and enteredEmailOrUsername in [me.get('email'), me.get('name')]
isPasswordCorrect = false
toBeDelayed = true
$.ajax
url: '/auth/login'
type: 'POST'
data:
username: enteredEmailOrUsername
password: enteredPassword
parse: true
error: (error) ->
toBeDelayed = false
'Bad Error. Can\'t connect to server or something. ' + error
success: (response, textStatus, jqXHR) ->
toBeDelayed = false
return unless jqXHR.status is 200
isPasswordCorrect = true
callback = =>
if toBeDelayed
setTimeout callback, 100
else
if isPasswordCorrect
onSuccess()
else
message = $.i18n.t('account_settings.wrong_password', defaultValue: 'Wrong Password.')
err = [message: message, property: 'password', formatted: true]
forms.applyErrorsToForm($form, err)
$('.nano').nanoScroller({scrollTo: @$el.find('.has-error')})
setTimeout callback, 100
else
message = $.i18n.t('account_settings.wrong_email', defaultValue: 'Wrong Email.')
err = [message: message, property: 'email', formatted: true]
forms.applyErrorsToForm($form, err)
$('.nano').nanoScroller({scrollTo: @$el.find('.has-error')})
deleteAccount: ->
$.ajax
type: 'DELETE'
success: ->
noty
timeout: 5000
text: 'Your account is gone.'
type: 'success'
layout: 'topCenter'
_.delay ->
window?.webkit?.messageHandlers?.notification?.postMessage(name: "signOut") if window.application.isIPadApp
Backbone.Mediator.publish("auth:logging-out", {})
window.tracker?.trackEvent 'Log Out', category:'Homepage' if @id is 'home-view'
logoutUser($('#login-email').val())
, 500
error: (jqXHR, status, error) ->
console.error jqXHR
noty
timeout: 5000
text: "Deleting account failed with error code #{jqXHR.status}"
type: 'error'
layout: 'topCenter'
url: "/db/user/#{me.id}"
resetProgress: ->
$.ajax
type: 'POST'
success: ->
noty
timeout: 5000
text: 'Your progress is gone.'
type: 'success'
layout: 'topCenter'
localStorage.clear()
me.fetch cache: false
_.delay (-> window.location.reload()), 1000
error: (jqXHR, status, error) ->
console.error jqXHR
noty
timeout: 5000
text: "Resetting progress failed with error code #{jqXHR.status}"
type: 'error'
layout: 'topCenter'
url: "/db/user/#{me.id}/reset_progress"
onClickProfilePhotoPanelBody: (e) ->
return if window.application.isIPadApp # TODO: have an iPad-native way of uploading a photo, since we don't want to load FilePicker on iPad (memory)
photoContainer = @$el.find('.profile-photo')
onSaving = =>
photoContainer.addClass('saving')
onSaved = (uploadingPath) =>
@$el.find('#photoURL').val(uploadingPath)
@$el.find('#photoURL').trigger('change') # cause for some reason editing the value doesn't trigger the jquery event
me.set('photoURL', uploadingPath)
photoContainer.removeClass('saving').attr('src', me.getPhotoURL(photoContainer.width()))
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('/')
data = @formatImagePostData(inkBlob)
success = @onImageUploaded(onSaved, uploadingPath)
$.ajax '/file', type: 'POST', data: data, success: success
onImageUploaded: (onSaved, uploadingPath) ->
(e) =>
onSaved uploadingPath
#- Misc
getSubscriptions: ->
inputs = ($(i) for i in $('#email-panel input[type="checkbox"].changed', @$el))
emailNames = (i.attr('name').replace('email_', '') for i in inputs)
enableds = (i.prop('checked') for i in inputs)
_.zipObject emailNames, enableds
#- Saving changes
save: ->
$('#settings-tabs input').removeClass 'changed'
forms.clearFormAlerts(@$el)
@grabData()
res = me.validate()
if res?
console.error 'Couldn\'t save because of validation errors:', res
forms.applyErrorsToForm(@$el, res)
$('.nano').nanoScroller({scrollTo: @$el.find('.has-error')})
return
return unless me.hasLocalChanges()
res = me.patch()
return unless res
res.error =>
if res.responseJSON?.property
errors = res.responseJSON
forms.applyErrorsToForm(@$el, errors)
$('.nano').nanoScroller({scrollTo: @$el.find('.has-error')})
else
noty
text: res.responseJSON?.message or res.responseText
type: 'error'
layout: 'topCenter'
timeout: 5000
@trigger 'save-user-error'
res.success (model, response, options) =>
@trigger 'save-user-success'
@trigger 'save-user-began'
grabData: ->
@grabPasswordData()
@grabOtherData()
grabPasswordData: ->
password1 = $('#password', @$el).val()
password2 = $('#password2', @$el).val()
bothThere = Boolean(password1) and Boolean(password2)
if bothThere and password1 isnt password2
message = $.i18n.t('account_settings.password_mismatch', defaultValue: 'Password does not match.')
err = [message: message, property: 'password2', formatted: true]
forms.applyErrorsToForm(@$el, err)
$('.nano').nanoScroller({scrollTo: @$el.find('.has-error')})
return
if bothThere
me.set('password', password1)
else if password1
message = $.i18n.t('account_settings.password_repeat', defaultValue: 'Please repeat your password.')
err = [message: message, property: 'password2', formatted: true]
forms.applyErrorsToForm(@$el, err)
$('.nano').nanoScroller({scrollTo: @$el.find('.has-error')})
grabOtherData: ->
@$el.find('#name-input').val @suggestedName if @suggestedName
me.set 'name', @$el.find('#name-input').val()
me.set 'email', @$el.find('#email').val()
for emailName, enabled of @getSubscriptions()
me.setEmailSubscription emailName, enabled
me.set('photoURL', @$el.find('#photoURL').val())
permissions = []
unless application.isProduction()
adminCheckbox = @$el.find('#admin')
if adminCheckbox.length
permissions.push 'admin' if adminCheckbox.prop('checked')
godmodeCheckbox = @$el.find('#godmode')
if godmodeCheckbox.length
permissions.push 'godmode' if godmodeCheckbox.prop('checked')
me.set('permissions', permissions)