Reorganize code into components and align layout with phoenix
This commit is contained in:
+20
-110
@@ -1,49 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="uk-container uk-padding">
|
||||
<h1 v-text="$gettext('Accounts')" />
|
||||
<oc-button
|
||||
v-if="numberOfSelectedAccounts < 1 && !isAccountCreationInProgress"
|
||||
id="accounts-new-account-trigger"
|
||||
key="create-accounts-button"
|
||||
v-text="$gettext('Create new user')"
|
||||
variation="primary"
|
||||
:disabled="isAccountCreationInProgress || !isInitialized"
|
||||
:uk-tooltip="disabledCreateAccountBtnTooltip"
|
||||
@click="setAccountCreationProgress(true)"
|
||||
/>
|
||||
<accounts-list-new-account-row v-if="isAccountCreationInProgress" @close="setAccountCreationProgress(false)" />
|
||||
<oc-grid v-if="numberOfSelectedAccounts > 0" key="selected-accounts-info" gutter="small" class="uk-flex-middle">
|
||||
<span v-text="selectionInfoText" />
|
||||
<span>|</span>
|
||||
<div>
|
||||
<oc-button v-text="$gettext('Clear selection')" variation="raw" @click="RESET_ACCOUNTS_SELECTION" />
|
||||
</div>
|
||||
<div>
|
||||
<oc-action-drop class="accounts-actions-dropdown">
|
||||
<template v-slot:button>
|
||||
<span class="uk-margin-xsmall-right" v-text="$gettext('Actions')" />
|
||||
<oc-icon name="expand_more" />
|
||||
</template>
|
||||
<template v-slot:actions>
|
||||
<oc-button
|
||||
v-for="(action, index) in actions"
|
||||
:key="action.label"
|
||||
:id="action.id"
|
||||
variation="raw"
|
||||
role="menuitem"
|
||||
:class="{ 'uk-margin-small-bottom': index + 1 !== actions.length }"
|
||||
class="uk-width-1-1 uk-flex-left"
|
||||
@click="action.handler"
|
||||
>
|
||||
{{ action.label }}
|
||||
</oc-button>
|
||||
</template>
|
||||
</oc-action-drop>
|
||||
</div>
|
||||
</oc-grid>
|
||||
<div class="uk-flex uk-flex-column">
|
||||
<template v-if="isInitialized">
|
||||
<accounts-list :accounts="accounts" />
|
||||
<div class="oc-app-bar">
|
||||
<accounts-batch-actions
|
||||
v-if="isAnyAccountSelected"
|
||||
:number-of-selected-accounts="numberOfSelectedAccounts"
|
||||
:selected-accounts="selectedAccounts"
|
||||
/>
|
||||
<accounts-create v-else />
|
||||
</div>
|
||||
<oc-grid class="uk-height-1-1 uk-flex-1 uk-overflow-auto">
|
||||
<div class="uk-width-expand">
|
||||
<accounts-list :accounts="accounts" />
|
||||
</div>
|
||||
</oc-grid>
|
||||
</template>
|
||||
<oc-loader v-else />
|
||||
</div>
|
||||
@@ -51,91 +22,30 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions, mapState, mapMutations } from 'vuex'
|
||||
import { mapGetters, mapActions, mapState } from 'vuex'
|
||||
import AccountsList from './accounts/AccountsList.vue'
|
||||
import AccountsListNewAccountRow from './accounts/AccountsListNewAccountRow.vue'
|
||||
import AccountsCreate from './accounts/AccountsCreate.vue'
|
||||
import AccountsBatchActions from './accounts/AccountsBatchActions.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: { AccountsList, AccountsListNewAccountRow },
|
||||
data: () => ({
|
||||
isAccountCreationInProgress: false
|
||||
}),
|
||||
components: { AccountsBatchActions, AccountsList, AccountsCreate },
|
||||
computed: {
|
||||
...mapGetters('Accounts', ['isInitialized', 'getAccountsSorted']),
|
||||
...mapGetters('Accounts', ['isInitialized', 'getAccountsSorted', 'isAnyAccountSelected']),
|
||||
...mapState('Accounts', ['selectedAccounts']),
|
||||
|
||||
accounts () {
|
||||
return this.getAccountsSorted
|
||||
},
|
||||
|
||||
numberOfSelectedAccounts () {
|
||||
return this.selectedAccounts.length
|
||||
},
|
||||
|
||||
selectionInfoText () {
|
||||
const translated = this.$ngettext('%{ amount } selected user', '%{ amount } selected users', this.numberOfSelectedAccounts)
|
||||
|
||||
return this.$gettextInterpolate(translated, { amount: this.numberOfSelectedAccounts })
|
||||
},
|
||||
|
||||
actions () {
|
||||
const actions = []
|
||||
const numberOfDisabledAccounts = this.selectedAccounts.filter(account => !account.accountEnabled).length
|
||||
const isAnyAccountDisabled = numberOfDisabledAccounts > 0
|
||||
const isAnyAccountEnabled = numberOfDisabledAccounts < this.numberOfSelectedAccounts
|
||||
|
||||
if (isAnyAccountDisabled) {
|
||||
actions.push({
|
||||
id: 'accounts-actions-dropdown-action-enable',
|
||||
label: this.$gettext('Enable'),
|
||||
handler: () => this.toggleAccountStatus(true)
|
||||
})
|
||||
}
|
||||
|
||||
if (isAnyAccountEnabled) {
|
||||
actions.push({
|
||||
id: 'accounts-actions-dropdown-action-disable',
|
||||
label: this.$gettext('Disable'),
|
||||
handler: () => this.toggleAccountStatus(false)
|
||||
})
|
||||
}
|
||||
|
||||
actions.push({
|
||||
id: 'accounts-actions-dropdown-action-delete',
|
||||
label: this.$gettext('Delete'),
|
||||
handler: this.deleteAccounts
|
||||
})
|
||||
|
||||
return actions
|
||||
},
|
||||
|
||||
disabledCreateAccountBtnTooltip () {
|
||||
if (!this.isInitialized) {
|
||||
return this.$gettext('Loading users')
|
||||
}
|
||||
|
||||
if (this.isAccountCreationInProgress) {
|
||||
return this.$gettext('User creation is already in progress')
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('Accounts', ['initialize', 'toggleAccountStatus', 'deleteAccounts']),
|
||||
...mapMutations('Accounts', ['RESET_ACCOUNTS_SELECTION']),
|
||||
|
||||
setAccountCreationProgress (isInProgress) {
|
||||
this.isAccountCreationInProgress = isInProgress
|
||||
}
|
||||
...mapActions('Accounts', ['initialize'])
|
||||
},
|
||||
created () {
|
||||
this.initialize()
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.RESET_ACCOUNTS_SELECTION()
|
||||
this.setAccountCreationProgress(false)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<oc-grid key="selected-accounts-info" gutter="small" class="uk-flex-middle">
|
||||
<span v-text="selectionInfoText" />
|
||||
<span>|</span>
|
||||
<div>
|
||||
<oc-button v-text="$gettext('Clear selection')" variation="raw" @click="RESET_ACCOUNTS_SELECTION" />
|
||||
</div>
|
||||
<div>
|
||||
<oc-action-drop class="accounts-actions-dropdown">
|
||||
<template v-slot:button>
|
||||
<span class="uk-margin-xsmall-right" v-text="$gettext('Actions')" />
|
||||
<oc-icon name="expand_more" />
|
||||
</template>
|
||||
<template v-slot:actions>
|
||||
<oc-button
|
||||
v-for="(action, index) in actions"
|
||||
:key="action.label"
|
||||
:id="action.id"
|
||||
variation="raw"
|
||||
role="menuitem"
|
||||
:class="{ 'uk-margin-small-bottom': index + 1 !== actions.length }"
|
||||
class="uk-width-1-1 uk-flex-left"
|
||||
@click="action.handler"
|
||||
>
|
||||
{{ action.label }}
|
||||
</oc-button>
|
||||
</template>
|
||||
</oc-action-drop>
|
||||
</div>
|
||||
</oc-grid>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'AccountsBatchActions',
|
||||
props: {
|
||||
numberOfSelectedAccounts: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
selectedAccounts: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectionInfoText () {
|
||||
const translated = this.$ngettext('%{ amount } selected user', '%{ amount } selected users', this.numberOfSelectedAccounts)
|
||||
return this.$gettextInterpolate(translated, { amount: this.numberOfSelectedAccounts })
|
||||
},
|
||||
actions () {
|
||||
const actions = []
|
||||
const numberOfDisabledAccounts = this.selectedAccounts.filter(account => !account.accountEnabled).length
|
||||
const isAnyAccountDisabled = numberOfDisabledAccounts > 0
|
||||
const isAnyAccountEnabled = numberOfDisabledAccounts < this.numberOfSelectedAccounts
|
||||
|
||||
if (isAnyAccountDisabled) {
|
||||
actions.push({
|
||||
id: 'accounts-actions-dropdown-action-enable',
|
||||
label: this.$gettext('Enable'),
|
||||
handler: () => this.toggleAccountStatus(true)
|
||||
})
|
||||
}
|
||||
|
||||
if (isAnyAccountEnabled) {
|
||||
actions.push({
|
||||
id: 'accounts-actions-dropdown-action-disable',
|
||||
label: this.$gettext('Disable'),
|
||||
handler: () => this.toggleAccountStatus(false)
|
||||
})
|
||||
}
|
||||
|
||||
actions.push({
|
||||
id: 'accounts-actions-dropdown-action-delete',
|
||||
label: this.$gettext('Delete'),
|
||||
handler: this.deleteAccounts
|
||||
})
|
||||
|
||||
return actions
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('Accounts', ['toggleAccountStatus', 'deleteAccounts']),
|
||||
...mapMutations('Accounts', ['RESET_ACCOUNTS_SELECTION'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<div>
|
||||
<oc-grid v-if="isFormInProgress" gutter="small">
|
||||
<label>
|
||||
<oc-text-input
|
||||
id="accounts-new-account-input-username"
|
||||
type="text"
|
||||
v-model="formData.username"
|
||||
:error-message="formValidation.usernameError"
|
||||
:placeholder="$gettext('Username')"
|
||||
:disabled="isRequestInProgress"
|
||||
@keydown.enter="createAccount"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<oc-text-input
|
||||
id="accounts-new-account-input-email"
|
||||
type="email"
|
||||
v-model="formData.email"
|
||||
:error-message="formValidation.emailError"
|
||||
:placeholder="$gettext('Email')"
|
||||
:disabled="isRequestInProgress"
|
||||
@keydown.enter="createAccount"
|
||||
/>
|
||||
</label>
|
||||
<label class="uk-margin-xsmall-right">
|
||||
<oc-text-input
|
||||
id="accounts-new-account-input-password"
|
||||
type="password"
|
||||
v-model="formData.password"
|
||||
:error-message="formValidation.passwordError"
|
||||
:placeholder="$gettext('Password')"
|
||||
:disabled="isRequestInProgress"
|
||||
@keydown.enter="createAccount"
|
||||
/>
|
||||
</label>
|
||||
<div>
|
||||
<oc-button
|
||||
v-text="$gettext('Cancel')"
|
||||
@click="cancelForm"
|
||||
class="uk-margin-xsmall-right"
|
||||
:disabled="isRequestInProgress"
|
||||
/>
|
||||
<oc-button
|
||||
id="accounts-new-account-button-confirm"
|
||||
variation="primary"
|
||||
:disabled="isRequestInProgress"
|
||||
@click="createAccount"
|
||||
>
|
||||
<oc-spinner
|
||||
v-if="isRequestInProgress"
|
||||
key="account-creation-in-progress"
|
||||
size="xsmall"
|
||||
class="uk-margin-xsmall-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span v-text="isRequestInProgress ? $gettext('Creating') : $gettext('Create')" />
|
||||
</oc-button>
|
||||
</div>
|
||||
</oc-grid>
|
||||
<oc-grid v-else gutter="small">
|
||||
<div>
|
||||
<oc-button
|
||||
id="accounts-new-account-trigger"
|
||||
key="create-accounts-button"
|
||||
v-text="$gettext('Create new account')"
|
||||
variation="primary"
|
||||
@click="setFormInProgress(true)"
|
||||
/>
|
||||
</div>
|
||||
</oc-grid>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import isEmail from 'validator/es/lib/isEmail'
|
||||
import isEmpty from 'validator/es/lib/isEmpty'
|
||||
import debounce from 'debounce'
|
||||
import { mapActions } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'AccountsCreate',
|
||||
|
||||
data: () => ({
|
||||
isFormInProgress: false,
|
||||
isRequestInProgress: false,
|
||||
formData: {
|
||||
username: '',
|
||||
email: '',
|
||||
password: ''
|
||||
},
|
||||
formValidation: {
|
||||
usernameError: '',
|
||||
emailError: '',
|
||||
passwordError: ''
|
||||
}
|
||||
}),
|
||||
|
||||
methods: {
|
||||
...mapActions('Accounts', ['createNewAccount']),
|
||||
|
||||
setFormInProgress (inProgress) {
|
||||
this.isFormInProgress = inProgress
|
||||
},
|
||||
|
||||
cancelForm () {
|
||||
this.isRequestInProgress = false
|
||||
this.setFormInProgress(false)
|
||||
this.formData = {
|
||||
username: '',
|
||||
email: '',
|
||||
password: ''
|
||||
}
|
||||
this.formValidation = {
|
||||
usernameError: '',
|
||||
emailError: '',
|
||||
passwordError: ''
|
||||
}
|
||||
},
|
||||
|
||||
createAccount () {
|
||||
if (!(this.checkUsername() & this.checkEmail() & this.checkPassword())) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isRequestInProgress = true
|
||||
this.createNewAccount(this.formData).finally(() => {
|
||||
this.cancelForm()
|
||||
})
|
||||
},
|
||||
|
||||
checkUsername () {
|
||||
if (isEmpty(this.formData.username)) {
|
||||
debounce(this.formValidation.usernameError = this.$gettext('Username cannot be empty'), 500)
|
||||
return false
|
||||
}
|
||||
|
||||
this.formValidation.usernameError = ''
|
||||
return true
|
||||
},
|
||||
|
||||
checkEmail () {
|
||||
if (isEmpty(this.formData.email)) {
|
||||
debounce(this.formValidation.emailError = this.$gettext('Email cannot be empty'), 500)
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isEmail(this.formData.email)) {
|
||||
debounce(this.formValidation.emailError = this.$gettext('Invalid email address'), 500)
|
||||
return false
|
||||
}
|
||||
|
||||
this.formValidation.emailError = ''
|
||||
return true
|
||||
},
|
||||
|
||||
checkPassword () {
|
||||
// Later on some restrictions might be applied here
|
||||
if (isEmpty(this.formData.password)) {
|
||||
debounce(this.formValidation.passwordError = this.$gettext('Password cannot be empty'), 500)
|
||||
return false
|
||||
}
|
||||
|
||||
this.formValidation.passwordError = ''
|
||||
return true
|
||||
}
|
||||
},
|
||||
onDestroy () {
|
||||
this.cancelForm()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#accounts-new-account-button-confirm > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -33,7 +33,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import {mapActions, mapGetters, mapMutations } from 'vuex'
|
||||
import AccountsListRow from './AccountsListRow.vue'
|
||||
|
||||
export default {
|
||||
@@ -51,7 +51,11 @@ export default {
|
||||
...mapGetters('Accounts', ['areAllAccountsSelected'])
|
||||
},
|
||||
methods: {
|
||||
...mapActions('Accounts', ['toggleSelectionAll'])
|
||||
...mapActions('Accounts', ['toggleSelectionAll']),
|
||||
...mapMutations('Accounts', ['RESET_ACCOUNTS_SELECTION'])
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.RESET_ACCOUNTS_SELECTION()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
<template>
|
||||
<div class="uk-flex uk-flex-top">
|
||||
<oc-grid gutter="small">
|
||||
<label>
|
||||
<oc-text-input
|
||||
id="accounts-new-account-input-username"
|
||||
type="text"
|
||||
v-model="username"
|
||||
:error-message="usernameError"
|
||||
:placeholder="$gettext('Username')"
|
||||
:disabled="isInProgress"
|
||||
@keydown.enter="createAccount"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<oc-text-input
|
||||
id="accounts-new-account-input-email"
|
||||
type="email"
|
||||
v-model="email"
|
||||
:error-message="emailError"
|
||||
:placeholder="$gettext('Email')"
|
||||
:disabled="isInProgress"
|
||||
@keydown.enter="createAccount"
|
||||
/>
|
||||
</label>
|
||||
<label class="uk-margin-xsmall-right">
|
||||
<oc-text-input
|
||||
id="accounts-new-account-input-password"
|
||||
type="password"
|
||||
v-model="password"
|
||||
:error-message="passwordError"
|
||||
:placeholder="$gettext('Password')"
|
||||
:disabled="isInProgress"
|
||||
@keydown.enter="createAccount"
|
||||
/>
|
||||
</label>
|
||||
<div>
|
||||
<oc-button
|
||||
v-text="$gettext('Cancel')"
|
||||
@click="emitClose"
|
||||
class="uk-margin-xsmall-right"
|
||||
:disabled="isInProgress"
|
||||
/>
|
||||
<oc-button
|
||||
id="accounts-new-account-button-confirm"
|
||||
variation="primary"
|
||||
:disabled="isInProgress"
|
||||
@click="createAccount"
|
||||
>
|
||||
<oc-spinner
|
||||
v-if="isInProgress"
|
||||
key="account-creation-in-progress"
|
||||
size="xsmall"
|
||||
class="uk-margin-xsmall-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span v-text="isInProgress ? $gettext('Creating') : $gettext('Create')" />
|
||||
</oc-button>
|
||||
</div>
|
||||
</oc-grid>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import isEmail from 'validator/es/lib/isEmail'
|
||||
import isEmpty from 'validator/es/lib/isEmpty'
|
||||
import debounce from 'debounce'
|
||||
import { mapActions } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'AccountsListNewAccountRow',
|
||||
|
||||
data: () => ({
|
||||
username: '',
|
||||
usernameError: '',
|
||||
email: '',
|
||||
emailError: '',
|
||||
password: '',
|
||||
passwordError: '',
|
||||
isInProgress: false
|
||||
}),
|
||||
|
||||
methods: {
|
||||
...mapActions('Accounts', ['createNewAccount']),
|
||||
|
||||
emitClose () {
|
||||
this.$emit('close')
|
||||
},
|
||||
createAccount () {
|
||||
if (!(this.checkUsername() & this.checkEmail() & this.checkPassword())) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isInProgress = true
|
||||
this.createNewAccount({ username: this.username, email: this.email, password: this.password }).finally(() => {
|
||||
this.isInProgress = false
|
||||
})
|
||||
this.emitClose()
|
||||
},
|
||||
|
||||
checkUsername () {
|
||||
if (isEmpty(this.username)) {
|
||||
debounce(this.usernameError = this.$gettext('Username cannot be empty'), 500)
|
||||
return false
|
||||
}
|
||||
|
||||
this.usernameError = ''
|
||||
return true
|
||||
},
|
||||
|
||||
checkEmail () {
|
||||
if (isEmpty(this.email)) {
|
||||
debounce(this.emailError = this.$gettext('Email cannot be empty'), 500)
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isEmail(this.email)) {
|
||||
debounce(this.emailError = this.$gettext('Invalid email address'), 500)
|
||||
return false
|
||||
}
|
||||
|
||||
this.emailError = ''
|
||||
return true
|
||||
},
|
||||
|
||||
checkPassword () {
|
||||
// Later on some restrictions might be applied here
|
||||
if (isEmpty(this.password)) {
|
||||
debounce(this.passwordError = this.$gettext('Password cannot be empty'), 500)
|
||||
return false
|
||||
}
|
||||
|
||||
this.passwordError = ''
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#accounts-new-account-button-confirm > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user