diff options
author | Winnie Hellmann <winnie@gitlab.com> | 2017-10-06 20:40:41 +0000 |
---|---|---|
committer | Fatih Acet <acetfatih@gmail.com> | 2017-10-06 20:40:41 +0000 |
commit | 265b1a3b72ff7552e2ae01a059f80bd59714649d (patch) | |
tree | 9f0d3c5249ae0eac5ed06c2c1b79abb9fa1dc6ac /app/assets | |
parent | 2cf5dca8f80cdefeb8932bf80417f52f289668c8 (diff) | |
download | gitlab-ce-265b1a3b72ff7552e2ae01a059f80bd59714649d.tar.gz |
Show confirmation modal before deleting account
Diffstat (limited to 'app/assets')
7 files changed, 186 insertions, 7 deletions
diff --git a/app/assets/javascripts/lib/utils/csrf.js b/app/assets/javascripts/lib/utils/csrf.js index ae41cc5e8a8..0bdb547d31a 100644 --- a/app/assets/javascripts/lib/utils/csrf.js +++ b/app/assets/javascripts/lib/utils/csrf.js @@ -14,6 +14,9 @@ If you need to compose a headers object, use the spread operator: someOtherHeader: '12345', } ``` + +see also http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf +and https://github.com/rails/jquery-rails/blob/v4.3.1/vendor/assets/javascripts/jquery_ujs.js#L59-L62 */ const csrf = { @@ -53,4 +56,3 @@ if ($.rails) { } export default csrf; - diff --git a/app/assets/javascripts/profile/account/components/delete_account_modal.vue b/app/assets/javascripts/profile/account/components/delete_account_modal.vue new file mode 100644 index 00000000000..b2b34cb83e1 --- /dev/null +++ b/app/assets/javascripts/profile/account/components/delete_account_modal.vue @@ -0,0 +1,146 @@ +<script> + import popupDialog from '../../../vue_shared/components/popup_dialog.vue'; + import { __, s__, sprintf } from '../../../locale'; + import csrf from '../../../lib/utils/csrf'; + + export default { + props: { + actionUrl: { + type: String, + required: true, + }, + confirmWithPassword: { + type: Boolean, + required: true, + }, + username: { + type: String, + required: true, + }, + }, + data() { + return { + enteredPassword: '', + enteredUsername: '', + isOpen: false, + }; + }, + components: { + popupDialog, + }, + computed: { + csrfToken() { + return csrf.token; + }, + inputLabel() { + let confirmationValue; + if (this.confirmWithPassword) { + confirmationValue = __('password'); + } else { + confirmationValue = __('username'); + } + + confirmationValue = `<code>${confirmationValue}</code>`; + + return sprintf( + s__('Profiles|Type your %{confirmationValue} to confirm:'), + { confirmationValue }, + false, + ); + }, + text() { + return sprintf( + s__(`Profiles| +You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. +Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), + { + yourAccount: `<strong>${s__('Profiles|your account')}</strong>`, + deleteAccount: `<strong>${s__('Profiles|Delete Account')}</strong>`, + }, + false, + ); + }, + }, + methods: { + canSubmit() { + if (this.confirmWithPassword) { + return this.enteredPassword !== ''; + } + + return this.enteredUsername === this.username; + }, + onSubmit(status) { + if (status) { + if (!this.canSubmit()) { + return; + } + + this.$refs.form.submit(); + } + + this.toggleOpen(false); + }, + toggleOpen(isOpen) { + this.isOpen = isOpen; + }, + }, + }; +</script> + +<template> + <div> + <popup-dialog + v-if="isOpen" + :title="s__('Profiles|Delete your account?')" + :text="text" + :kind="`danger ${!canSubmit() && 'disabled'}`" + :primary-button-label="s__('Profiles|Delete account')" + @toggle="toggleOpen" + @submit="onSubmit"> + + <template slot="body" scope="props"> + <p v-html="props.text"></p> + + <form + ref="form" + :action="actionUrl" + method="post"> + + <input + type="hidden" + name="_method" + value="delete" /> + <input + type="hidden" + name="authenticity_token" + :value="csrfToken" /> + + <p id="input-label" v-html="inputLabel"></p> + + <input + v-if="confirmWithPassword" + name="password" + class="form-control" + type="password" + v-model="enteredPassword" + aria-labelledby="input-label" /> + <input + v-else + name="username" + class="form-control" + type="text" + v-model="enteredUsername" + aria-labelledby="input-label" /> + </form> + </template> + + </popup-dialog> + + <button + type="button" + class="btn btn-danger" + @click="toggleOpen(true)"> + {{ s__('Profiles|Delete account') }} + </button> + </div> +</template> diff --git a/app/assets/javascripts/profile/account/index.js b/app/assets/javascripts/profile/account/index.js new file mode 100644 index 00000000000..635056e0eeb --- /dev/null +++ b/app/assets/javascripts/profile/account/index.js @@ -0,0 +1,21 @@ +import Vue from 'vue'; + +import deleteAccountModal from './components/delete_account_modal.vue'; + +const deleteAccountModalEl = document.getElementById('delete-account-modal'); +// eslint-disable-next-line no-new +new Vue({ + el: deleteAccountModalEl, + components: { + deleteAccountModal, + }, + render(createElement) { + return createElement('delete-account-modal', { + props: { + actionUrl: deleteAccountModalEl.dataset.actionUrl, + confirmWithPassword: !!deleteAccountModalEl.dataset.confirmWithPassword, + username: deleteAccountModalEl.dataset.username, + }, + }); + }, +}); diff --git a/app/assets/javascripts/repo/components/repo.vue b/app/assets/javascripts/repo/components/repo.vue index d6c864cb976..cc60aa5939c 100644 --- a/app/assets/javascripts/repo/components/repo.vue +++ b/app/assets/javascripts/repo/components/repo.vue @@ -62,7 +62,7 @@ export default { :primary-button-label="__('Discard changes')" kind="warning" :title="__('Are you sure?')" - :body="__('Are you sure you want to discard your changes?')" + :text="__('Are you sure you want to discard your changes?')" @toggle="toggleDialogOpen" @submit="dialogSubmitted" /> diff --git a/app/assets/javascripts/vue_shared/components/popup_dialog.vue b/app/assets/javascripts/vue_shared/components/popup_dialog.vue index 994b33bc1c9..9279b50cd55 100644 --- a/app/assets/javascripts/vue_shared/components/popup_dialog.vue +++ b/app/assets/javascripts/vue_shared/components/popup_dialog.vue @@ -7,7 +7,7 @@ export default { type: String, required: true, }, - body: { + text: { type: String, required: true, }, @@ -63,7 +63,9 @@ export default { <h4 class="modal-title">{{this.title}}</h4> </div> <div class="modal-body"> - <p>{{this.body}}</p> + <slot name="body" :text="text"> + <p>{{text}}</p> + </slot> </div> <div class="modal-footer"> <button diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 5b581780447..1cebd02df48 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -1,10 +1,17 @@ +.modal-header { + padding: #{3 * $grid-size} #{2 * $grid-size}; + + .page-title { + margin-top: 0; + } +} + .modal-body { position: relative; - padding: 15px; + padding: #{3 * $grid-size} #{2 * $grid-size}; .form-actions { - margin: -$gl-padding + 1; - margin-top: 15px; + margin: #{2 * $grid-size} #{-2 * $grid-size} #{-2 * $grid-size}; } .text-danger { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 5ab40947da9..2f9def51d91 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -1,6 +1,7 @@ /* * Layout */ +$grid-size: 8px; $gutter_collapsed_width: 62px; $gutter_width: 290px; $gutter_inner_width: 250px; |