Revamp user add/edit forms

This commit is contained in:
An Phan 2016-11-30 16:23:11 +07:00
parent 30237d27ad
commit 4595f0bff4
No known key found for this signature in database
GPG key ID: 05536BB4BCDC02A2
9 changed files with 242 additions and 189 deletions

View file

@ -7,7 +7,7 @@
</span>
<div class="buttons" v-show="!isPhone || showingControls">
<button class="btn btn-green btn-add" @click="creating = !creating">
<button class="btn btn-green btn-add" @click="addUser">
<i class="fa fa-plus"></i>
Add</button>
</div>
@ -15,63 +15,50 @@
<div class="main-scroll-wrap">
<div class="users">
<form class="user-create user-item" v-if="creating" @submit.prevent="store">
<div class="input-row">
<label>Name</label>
<input type="text" name="name" v-model="newUser.name" required v-koel-focus>
</div>
<div class="input-row">
<label>Email</label>
<input type="email" name="email" v-model="newUser.email" required>
</div>
<div class="input-row">
<label>Password</label>
<input type="password" name="password" v-model="newUser.password" required>
</div>
<div class="input-row">
<label></label>
<button class="btn btn-green btn-create">Create</button>
<button class="btn btn-red btn-cancel" @click.prevent="creating = false">Cancel</button>
</div>
</form>
<user-item v-for="user in state.users" :user="user"/>
<user-item v-for="user in state.users" :user="user" @editUser="editUser"/>
<article class="user-item" v-for="n in 6"/>
</div>
</div>
<edit-user-form ref="editUserForm"/>
<add-user-form ref="addUserForm"/>
</section>
</template>
<script>
import { clone } from 'lodash'
import isMobile from 'ismobilejs'
import { userStore } from '../../../stores'
import userItem from '../../shared/user-item.vue'
import editUserForm from '../../modals/edit-user-form.vue'
import addUserForm from '../../modals/add-user-form.vue'
export default {
components: { userItem },
components: { userItem, editUserForm, addUserForm },
data () {
return {
state: userStore.state,
isPhone: isMobile.phone,
showingControls: false,
creating: false,
newUser: {}
showingControls: false
}
},
methods: {
/**
* Store the newly created user.
* Open the "Add User" form.
*/
store () {
userStore.store(this.newUser.name, this.newUser.email, this.newUser.password).then(u => {
this.newUser = clone(userStore.stub)
this.creating = false
})
addUser () {
this.$refs.addUserForm.open()
},
/**
* Open the "Edit User" form.
*
* @param {Object} user
*/
editUser (user) {
this.$refs.editUserForm.open(user)
}
}
}
@ -88,36 +75,6 @@ export default {
display: flex;
}
form {
padding: 8px 16px;
background-size: 30px 30px;
background-image: linear-gradient(
-45deg,
rgba(black, 0.3) 25%,
transparent 25%,
transparent 50%,
rgba(black, 0.3) 50%,
rgba(black, 0.3) 75%,
transparent 75%,
transparent
);
.input-row {
display: flex;
margin: 6px 0;
}
label {
flex: 0 0 128px;
margin-bottom: 0;
font-size: 100%;
}
input {
flex: 1;
}
}
button {
margin-right: 3px;
}

View file

@ -0,0 +1,66 @@
<template>
<div class="overlay" v-if="newUser">
<sound-bar v-if="loading"/>
<form class="user-add" @submit.prevent="submit" v-else>
<header>
<h1>Add New User</h1>
</header>
<div>
<div class="form-row">
<label>Name</label>
<input type="text" name="name" v-model="newUser.name" required v-koel-focus>
</div>
<div class="form-row">
<label>Email</label>
<input type="email" name="email" v-model="newUser.email" required>
</div>
<div class="form-row">
<label>Password</label>
<input type="password" name="password" v-model="newUser.password">
</div>
</div>
<footer>
<button class="btn btn-green btn-add">Save</button>
<button class="btn btn-white btn-cancel" @click.prevent="cancel">Cancel</button>
</footer>
</form>
</overlay>
</template>
<script>
import { clone } from 'lodash'
import soundBar from '../shared/sound-bar.vue'
import { userStore } from '../../stores'
export default {
name: 'modals--add-user-form',
components: { soundBar },
data () {
return {
loading: false,
newUser: null
}
},
methods: {
open () {
this.newUser = clone(userStore.stub)
},
submit () {
this.loading = true
userStore.store(this.newUser.name, this.newUser.email, this.newUser.password).then(() => {
this.loading = false
this.newUser = null
})
},
cancel () {
this.newUser = null
}
}
}
</script>

View file

@ -1,5 +1,5 @@
<template>
<div id="editSongsOverlay" v-if="shown">
<div id="editSongsOverlay" v-if="shown" class="overlay">
<sound-bar v-if="loading"></sound-bar>
<form v-else @submit.prevent="submit">
<header>
@ -330,66 +330,8 @@ export default {
@import "../../../sass/partials/_mixins.scss";
#editSongsOverlay {
z-index: 9999;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, .7);
overflow: auto;
@include vertical-center();
$borderRadius: 5px;
form {
position: relative;
width: 100%;
max-width: 460px;
background: #fff;
border-radius: $borderRadius;
color: #333;
input[type="checkbox"] {
border: 1px solid #ccc;
}
.form-row:first-child {
margin-top: 0;
}
> header, > div, > footer {
padding: 16px;
}
> div {
padding-bottom: 0;
}
input[type="text"], input[type="number"], textarea {
border: 1px solid #ccc;
width: 100%;
max-width: 100%;
&:focus {
border-color: $colorOrange;
}
}
.warning {
color: #f00;
}
textarea {
min-height: 192px;
}
> header {
display: flex;
background: #eee;
border-radius: $borderRadius $borderRadius 0 0;
img {
flex: 0 0 96px;
}
@ -398,12 +340,6 @@ export default {
flex: 1;
padding-left: 8px;
h1 {
font-size: 1.8rem;
line-height: 2.2rem;
margin-bottom: .3rem;
}
.mixed {
opacity: .5;
}

View file

@ -0,0 +1,72 @@
<template>
<div class="overlay" v-if="copiedUser">
<sound-bar v-if="loading"/>
<form class="user-edit" @submit.prevent="submit" v-else>
<header>
<h1>Edit User</h1>
</header>
<div>
<div class="form-row">
<label>Name</label>
<input type="text" name="name" v-model="copiedUser.name" required v-koel-focus>
</div>
<div class="form-row">
<label>Email</label>
<input type="email" name="email" v-model="copiedUser.email" required>
</div>
<div class="form-row">
<label>Password</label>
<input type="password" name="password" v-model="copiedUser.password" placeholder="Leave blank for no changes">
</div>
</div>
<footer>
<button class="btn btn-green btn-update">Update</button>
<button class="btn btn-white btn-cancel" @click.prevent="cancel">Cancel</button>
</footer>
</form>
</overlay>
</template>
<script>
import { clone } from 'lodash'
import soundBar from '../shared/sound-bar.vue'
import { userStore } from '../../stores'
export default {
name: 'modals--edit-user-form',
components: { soundBar },
data () {
return {
user: null,
loading: false,
// We work on a cloned version of the user
copiedUser: null
}
},
methods: {
open (user) {
this.copiedUser = clone(user)
// Keep a reference
this.user = user
},
submit () {
this.loading = true
userStore.update(this.user, this.copiedUser.name, this.copiedUser.email, this.copiedUser.password)
.then(() => {
this.loading = false
this.copiedUser = null
}
)
},
cancel () {
this.copiedUser = null
}
}
}
</script>

View file

@ -1,5 +1,5 @@
<template>
<div id="overlay" v-show="state.showing" :class="state.type">
<div id="overlay" v-show="state.showing" class="overlay" :class="state.type">
<div class="display">
<sound-bar v-show="state.type === 'loading'"/>
<i class="fa fa-exclamation-circle" v-show="state.type === 'error'"/>
@ -85,15 +85,7 @@ export default {
@import "../../../sass/partials/_mixins.scss";
#overlay {
position: fixed;
top: 0;
left: 0;
z-index: 9999;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 1);
@include vertical-center();
flex-direction: column;
.display {

View file

@ -1,6 +1,6 @@
<template>
<article class="user-item" :class="{ editing: editing, me: isCurrentUser }">
<div class="info" v-if="!editing">
<article class="user-item" :class="{ me: isCurrentUser }">
<div class="info">
<img :src="user.avatar" width="128" height="128">
<div class="right">
@ -19,31 +19,10 @@
</div>
</div>
</div>
<form class="user-edit" @submit.prevent="update" v-else>
<div class="input-row">
<label>Name</label>
<input type="text" name="name" v-model="user.name" required v-koel-focus>
</div>
<div class="input-row">
<label>Email</label>
<input type="email" name="email" v-model="user.email" required>
</div>
<div class="input-row">
<label>Password</label>
<input type="password" name="password" v-model="user.password" placeholder="Leave blank for no changes">
</div>
<div class="input-row">
<label></label>
<button class="btn btn-green btn-update">Update</button>
<button class="btn btn-red btn-cancel" @click.prevent="cancelEdit">Cancel</button>
</div>
</form>
</article>
</template>
<script>
import { clone, assign } from 'lodash'
import swal from 'sweetalert'
import { userStore } from '../../stores'
@ -54,9 +33,7 @@ export default {
data () {
return {
editing: false,
confirmingDelete: false,
cached: {}
confirmingDelete: false
}
},
@ -83,27 +60,7 @@ export default {
return
}
// Keep a cached version of the user for rolling back.
this.cached = clone(this.user)
this.editing = true
},
/**
* Cancel editing a user.
*/
cancelEdit () {
// Restore the original user's properties
assign(this.user, this.cached)
this.editing = false
},
/**
* Update the edited user.
*/
update () {
userStore.update(this.user, this.user.name, this.user.email, this.user.password).then(u => {
this.editing = false
})
this.$emit('editUser', this.user)
},
/**

View file

@ -171,6 +171,8 @@ export const userStore = {
return new Promise((resolve, reject) => {
http.put(`user/${user.id}`, { name, email, password }, () => {
this.setAvatar(user)
user.name = name
user.email = email
user.password = ''
resolve(user)
}, r => reject(r))

View file

@ -246,3 +246,74 @@ button, input[type="submit"], input[type="reset"], input[type="button"], .btn {
margin-top: 16px;
position: relative;
}
.overlay {
position: fixed;
top: 0;
left: 0;
z-index: 9999;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, .7);
@include vertical-center();
form {
$borderRadius: 5px;
position: relative;
width: 100%;
max-width: 460px;
background: #fff;
border-radius: $borderRadius;
color: #333;
input[type="checkbox"] {
border: 1px solid #ccc;
}
.form-row:first-child {
margin-top: 0;
}
> header, > div, > footer {
padding: 16px;
}
> div {
padding-bottom: 0;
}
input[type="text"], input[type="number"], input[type="email"], input[type="password"],
input[type="url"], textarea {
border: 1px solid #ccc;
width: 100%;
max-width: 100%;
&:focus {
border-color: $colorOrange;
}
}
.warning {
color: #f00;
}
textarea {
min-height: 192px;
}
> header {
display: flex;
background: #eee;
border-radius: $borderRadius $borderRadius 0 0;
h1 {
font-size: 1.8rem;
line-height: 2.2rem;
margin-bottom: .3rem;
}
}
}
}

View file

@ -21,10 +21,10 @@ class UsersScreenTest extends TestCase
->back()
// Add new user
->click('#usersWrapper .btn-add');
$this->see('form.user-create')
->typeIn('form.user-create input[name="name"]', 'Foo')
->typeIn('form.user-create input[name="email"]', 'foo@koel.net')
->typeIn('form.user-create input[name="password"]', 'SecureMuch')
$this->see('form.user-add')
->typeIn('form.user-add input[name="name"]', 'Foo')
->typeIn('form.user-add input[name="email"]', 'foo@koel.net')
->typeIn('form.user-add input[name="password"]', 'SecureMuch')
->enter()
->seeText('foo@koel.net', '#usersWrapper');