Add base UI for account management

This commit is contained in:
Dane Everitt 2018-06-11 22:36:43 -07:00
parent e5e66fdb58
commit 14927c3e7e
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
8 changed files with 220 additions and 14 deletions

View file

@ -57,7 +57,7 @@
"watch": "NODE_ENV=development ./node_modules/.bin/webpack --watch --progress", "watch": "NODE_ENV=development ./node_modules/.bin/webpack --watch --progress",
"build": "NODE_ENV=development ./node_modules/.bin/webpack --progress", "build": "NODE_ENV=development ./node_modules/.bin/webpack --progress",
"build:production": "NODE_ENV=production ./node_modules/.bin/webpack", "build:production": "NODE_ENV=production ./node_modules/.bin/webpack",
"serve": "webpack-serve --hot --config ./webpack.config.js", "serve": "NODE_ENV=development webpack-serve --hot --config ./webpack.config.js",
"v:serve": "PUBLIC_PATH=http://192.168.50.2:8080 NODE_ENV=development webpack-serve --hot --config ./webpack.config.js --host 192.168.50.2 --no-clipboard" "v:serve": "PUBLIC_PATH=http://192.168.50.2:8080 NODE_ENV=development webpack-serve --hot --config ./webpack.config.js --host 192.168.50.2 --no-clipboard"
} }
} }

View file

@ -1,13 +1,51 @@
<template> <template>
<div>
<navigation/>
<div class="container animate fadein mt-6">
<flash container="mt-6 mb-2 mx-4"/>
<div class="flex">
<update-email class="flex-1 m-4"/>
<div class="flex-1 m-4">
<form action="" method="post">
<div class="bg-white p-6 border border-grey-light rounded rounded-1">
<h2 class="mb-6 text-grey-darkest font-medium">Change your password</h2>
<div class="mt-6">
<label for="grid-password-current" class="input-label">Current password</label>
<input id="grid-password-current" name="password" type="password" class="input" required>
</div>
<div class="mt-6">
<label for="grid-password-new" class="input-label">New password</label>
<input id="grid-password-new" name="password" type="password" class="input" required>
<p class="input-help">Your new password should be at least 8 characters in length, contain one number, and be mixed case.</p>
</div>
<div class="mt-6">
<label for="grid-password-new-confirm" class="input-label">Confirm new password</label>
<input id="grid-password-new-confirm" name="password_confirmation" type="password" class="input" required>
</div>
<div class="mt-6 text-right">
<button class="btn btn-blue btn-sm text-right" type="submit">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</template> </template>
<script> <script>
import Navigation from '../core/Navigation';
import Flash from '../Flash';
import { mapState } from 'vuex';
import UpdateEmail from './account/UpdateEmail';
export default { export default {
name: 'account' name: 'account',
components: {UpdateEmail, Flash, Navigation},
computed: {
...mapState({
user: state => state.auth.user,
})
},
}; };
</script> </script>
<style scoped>
</style>

View file

@ -0,0 +1,88 @@
<template>
<div :class>
<form method="post" v-on:submit.prevent="submitForm">
<div class="bg-white p-6 border border-grey-light rounded rounded-1">
<h2 class="mb-6 text-grey-darkest font-medium">Update your email</h2>
<div>
<label for="grid-email" class="input-label">Email address</label>
<input id="grid-email" name="email" type="email" class="input" required
v-model="email"
>
<p class="input-help">If your email is no longer {{ user.email }} enter a new email in the field above.</p>
</div>
<div class="mt-6">
<label for="grid-password" class="input-label">Password</label>
<input id="grid-password" name="password" type="password" class="input" required
v-model="password"
>
</div>
<div class="mt-6">
<label for="grid-password-confirm" class="input-label">Confirm password</label>
<input id="grid-password-confirm" name="password_confirmation" type="password" class="input" required
v-model="confirm"
>
</div>
<div class="mt-6 text-right">
<button class="btn btn-blue btn-sm text-right" type="submit">Save</button>
</div>
</div>
</form>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
name: 'update-email',
data: function () {
return {
email: '',
password: '',
confirm: '',
};
},
computed: {
...mapState({
user: state => state.auth.user,
})
},
methods: {
/**
* Update a user's email address on the Panel.
*/
submitForm: function () {
this.clearFlashes();
this.updateEmail({
email: this.$data.email,
password: this.$data.password,
confirm: this.$data.confirm,
})
.then(() => {
this.success('Your email address has been updated.');
})
.catch(error => {
if (!error.response) {
return console.error(error);
}
const response = error.response;
if (response.data && _.isObject(response.data.errors)) {
response.data.errors.forEach(e => {
this.error(e.detail);
});
}
});
},
...mapActions('auth', [
'updateEmail',
])
}
};
</script>
<style scoped>
</style>

View file

@ -13,12 +13,20 @@ export default {
* @param state * @param state
* @returns {User|null} * @returns {User|null}
*/ */
currentUser: function (state) { getUser: function (state) {
return state.user; return state.user;
} },
}, },
setters: {}, setters: {},
actions: { actions: {
/**
* Log a user into the Panel.
*
* @param commit
* @param {String} user
* @param {String} password
* @returns {Promise<any>}
*/
login: ({commit}, {user, password}) => { login: ({commit}, {user, password}) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
window.axios.post(route('auth.login'), {user, password}) window.axios.post(route('auth.login'), {user, password})
@ -47,6 +55,13 @@ export default {
.catch(reject); .catch(reject);
}); });
}, },
/**
* Log a user out of the Panel.
*
* @param commit
* @returns {Promise<any>}
*/
logout: function ({commit}) { logout: function ({commit}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
window.axios.get(route('auth.logout')) window.axios.get(route('auth.logout'))
@ -57,8 +72,39 @@ export default {
.catch(reject); .catch(reject);
}) })
}, },
/**
* Update a user's email address on the Panel and store the updated result in Vuex.
*
* @param commit
* @param {String} email
* @param {String} password
* @param {String} confirm
* @return {Promise<any>}
*/
updateEmail: function ({commit}, {email, password, confirm}) {
return new Promise((resolve, reject) => {
window.axios.put(route('api.client.account.update-email'), {
email, password, password_confirmation: confirm
})
.then(response => {
// If there is a 302 redirect or some other odd behavior (basically, response that isnt
// in JSON format) throw an error and don't try to continue with the login.
if (!(response.data instanceof Object)) {
return reject(new Error('An error was encountered while processing this request.'));
}
commit('setEmail', response.data.email);
return resolve();
})
.catch(reject);
});
},
}, },
mutations: { mutations: {
setEmail: function (state, email) {
state.user.email = email;
},
login: function (state, {jwt}) { login: function (state, {jwt}) {
localStorage.setItem('token', jwt); localStorage.setItem('token', jwt);
state.user = User.fromToken(jwt); state.user = User.fromToken(jwt);

View file

@ -12,6 +12,14 @@
} }
} }
&.btn-secondary {
@apply .border .border-grey-light .text-grey-dark;
&:hover:enabled {
@apply .border-grey .text-grey-darker;
}
}
/** /**
* Button Sizes * Button Sizes
*/ */
@ -19,6 +27,10 @@
@apply .p-4 .w-full .uppercase .tracking-wide .text-sm; @apply .p-4 .w-full .uppercase .tracking-wide .text-sm;
} }
&.btn-sm {
@apply .px-6 .py-3 .uppercase .tracking-wide .text-sm;
}
&:disabled, &.disabled { &:disabled, &.disabled {
opacity: 0.55; opacity: 0.55;
cursor: default; cursor: default;

View file

@ -30,3 +30,24 @@
top: 14px; top: 14px;
transition: transform 200ms ease-out; transition: transform 200ms ease-out;
} }
.input {
@apply .appearance-none .p-3 .rounded .border .text-grey-darker .w-full;
transition: all 100ms linear;
&:focus {
@apply .border-blue-light;
}
&:required, &:invalid {
box-shadow: none;
}
}
.input-label {
@apply .block .uppercase .tracking-wide .text-grey-darkest .text-xs .font-bold .mb-1;
}
.input-help {
@apply .text-xs .text-grey .pt-2;
}

View file

@ -14,6 +14,7 @@ Route::get('/', 'ClientController@index')->name('api.client.index');
Route::group(['prefix' => '/account'], function () { Route::group(['prefix' => '/account'], function () {
Route::get('/', 'AccountController@index')->name('api.client.account'); Route::get('/', 'AccountController@index')->name('api.client.account');
Route::put('/email', 'AccountController@updateEmail')->name('api.client.account.update-email');
}); });
/* /*

View file

@ -16,11 +16,11 @@ Route::get('/', 'IndexController@index')->name('index');
| Endpoint: /account | Endpoint: /account
| |
*/ */
Route::group(['prefix' => 'account'], function () { //Route::group(['prefix' => 'account'], function () {
Route::get('/', 'AccountController@index')->name('account'); // Route::get('/', 'AccountController@index')->name('account');
//
Route::post('/', 'AccountController@update'); // Route::post('/', 'AccountController@update');
}); //});
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------