Move the user api_token migration to its original migration, add website and social media columns to the original user migration, hook up user profile edit functionality on the dashboard settings page, name the user password function userPasswordUpdateInit to reflect its form name, clean up the dashboard settings layout and improve it on mobile, add a repeating pattern background to the dashboard so it's not so sparse, and sync the traditional-bootstrap routes/web.php with the main one

This commit is contained in:
Kevin MacMartin 2018-04-25 23:27:45 -04:00
parent 25f65772d5
commit e02d57ad17
10 changed files with 461 additions and 247 deletions

View file

@ -392,6 +392,24 @@ class DashboardController extends Controller {
} }
} }
// User Profile Update
public function postUserProfileUpdate(Request $request)
{
$this->validate($request, [
'name' => 'required|string|max:255'
]);
$user = User::find(Auth::id());
$user->name = $request['name'];
$user->website = $request['website'];
$user->facebook = $request['facebook'];
$user->soundcloud = $request['soundcloud'];
$user->instagram = $request['instagram'];
$user->twitter = $request['twitter'];
$user->save();
return 'success';
}
// User Profile Image Upload // User Profile Image Upload
public function postUserProfileImageUpload(Request $request) public function postUserProfileImageUpload(Request $request)
{ {

View file

@ -15,9 +15,15 @@ class CreateUsersTable extends Migration
{ {
Schema::create('users', function(Blueprint $table) { Schema::create('users', function(Blueprint $table) {
$table->increments('id'); $table->increments('id');
$table->string('name');
$table->string('email')->unique(); $table->string('email')->unique();
$table->string('name');
$table->string('website')->nullable();
$table->string('facebook')->nullable();
$table->string('twitter')->nullable();
$table->string('instagram')->nullable();
$table->string('soundcloud')->nullable();
$table->string('password'); $table->string('password');
$table->string('api_token', 60)->unique();
$table->rememberToken(); $table->rememberToken();
$table->timestamps(); $table->timestamps();
}); });

View file

@ -1,32 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddApiTokenToUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function(Blueprint $table) {
$table->string('api_token', 60)->unique();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function(Blueprint $table) {
$table->dropColumn('api_token');
});
}
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 65.684 65.729"><path d="M65.55 47.072l-23.123-.345L29.035 65.58l-6.817-22.097L.15 36.572l18.909-13.313L18.812.136l18.504 13.87 21.915-7.38-7.473 21.883z" fill-opacity=".949" stroke="#000" stroke-width=".134" stroke-linecap="square" stroke-opacity=".893"/></svg>

After

Width:  |  Height:  |  Size: 314 B

View file

@ -703,8 +703,82 @@ function userProfileImageInit() {
}); });
} }
function userPasswordInit() { function userProfileUpdateInit() {
const $form = $("#user-password"), const $form = $("#user-profile-update"),
$submit = $form.find(".submit-button"),
$inputs = $form.find("input"),
$name = $("#name"),
$website = $("#website"),
$facebook = $("#facebook"),
$soundcloud = $("#soundcloud"),
$instagram = $("#instagram"),
$twitter = $("#twitter"),
$token = $("#token");
let formData = {},
submitting = false;
const getFormData = function() {
formData = {
name: $name.val(),
website: $website.val(),
facebook: $facebook.val(),
soundcloud: $soundcloud.val(),
instagram: $instagram.val(),
twitter: $twitter.val(),
_token: $token.val()
};
};
// remove the error class from an input and enable submit when its value changes
$inputs.on("input change", function() {
$submit.removeClass("no-input");
$(this).removeClass("error");
});
// initialize submit button
$submit.on("click", function() {
if (!submitting) {
submitting = true;
// remove the error class from inputs
$inputs.removeClass("error");
// show the loading modal
showLoadingModal();
// populate the formData object
getFormData();
// submit the update
$.ajax({
type: "POST",
url: "/dashboard/user/profile-update",
data: formData
}).always(function(response) {
hideLoadingModal();
submitting = false;
if (response === "success") {
$submit.addClass("no-input");
showAlert("User profile updated successfully");
} else {
// add the error class to fields that haven't been filled correctly
for (let errorName in response.responseJSON.errors) {
if ($form.find(`[name='${errorName}']`).length) {
$form.find(`[name='${errorName}']`).addClass("error");
}
}
showAlert("Error updating user profile");
}
});
}
});
}
function userPasswordUpdateInit() {
const $form = $("#user-password-update"),
$submit = $form.find(".submit-button"), $submit = $form.find(".submit-button"),
$inputs = $form.find("input"), $inputs = $form.find("input"),
$oldpass = $("#oldpass"), $oldpass = $("#oldpass"),
@ -806,7 +880,11 @@ $(document).ready(function() {
userProfileImageInit(); userProfileImageInit();
} }
if ($("#user-password").length) { if ($("#user-profile-update").length) {
userPasswordInit(); userProfileUpdateInit();
}
if ($("#user-password-update").length) {
userPasswordUpdateInit();
} }
}); });

View file

@ -39,6 +39,7 @@ body {
} }
.site-content { .site-content {
position: relative;
display: flex; display: flex;
min-height: 100vh; min-height: 100vh;
flex-direction: column; flex-direction: column;
@ -48,6 +49,33 @@ body {
} }
} }
.dashboard-background {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-image: url("/img/dashboard/star-bg.svg");
background-position: center top;
background-size: 65px auto;
background-repeat: repeat;
&:after {
content: "";
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: fade-out(#fff, 0.02);
}
}
.navbar, .dashboard-footer {
z-index: 10;
position: relative;
}
.navbar { .navbar {
margin-bottom: $grid-gutter-width; margin-bottom: $grid-gutter-width;
border: 0; border: 0;
@ -652,223 +680,283 @@ body {
} }
} }
.edit-item { form {
$label-height: 30px;
margin-top: 10px; margin-top: 10px;
.CodeMirror { .form-title {
height: 300px; @include font-sans-semibold;
padding: 5px; margin-top: 0px;
&-code {
margin-bottom: 10px;
}
}
.picker__holder {
overflow-y: hidden;
.picker__button--today {
white-space: nowrap;
}
.picker__select--year, .picker__select--month, .picker__month, .picker__day, .picker__weekday, .picker__footer {
@include media-breakpoint-down(sm) {
font-size: 16px;
}
}
.picker__select--year, .picker__select--month {
width: auto;
height: 1.5em;
padding: 0px;
}
}
label {
min-height: $label-height;
line-height: $label-height;
@include media-breakpoint-up(md) {
margin-bottom: 0px;
}
}
.text-display, .mkd-editor-container, input, select {
margin-bottom: 15px; margin-bottom: 15px;
} font-size: 14px;
input {
display: block;
width: 100%;
&:not([type="file"]) {
padding: 5px 8px;
border: 1px solid darken($c-dashboard-light, 10%);
border-radius: 2px;
transition: border-color 150ms;
&.error {
border-color: $c-dashboard-error;
}
}
&[type="file"] {
height: $label-height;
font-size: 14px;
}
&.date-picker {
cursor: pointer;
}
}
.current-image {
margin-bottom: 15px;
display: block;
width: 125px;
max-width: 100%;
}
.edit-button {
margin-bottom: ($grid-gutter-width / 2);
display: inline-block;
padding: 5px 10px;
border-radius: 5px;
text-transform: uppercase; text-transform: uppercase;
transition: background-color 150ms;
cursor: pointer;
&:hover, &:focus {
text-decoration: none;
}
&.view {
border: 1px solid darken($c-dashboard-dark, 5%);
background-color: $c-dashboard-dark;
color: $c-text-light;
&:hover {
background-color: lighten($c-dashboard-dark, 5%);
}
}
&.delete {
border: 1px solid darken($c-dashboard-delete, 5%);
background-color: $c-dashboard-delete;
color: $c-text-light;
&:hover {
background-color: lighten($c-dashboard-delete, 5%);
}
}
} }
.back-button { &.edit-item {
float: left; $label-height: 30px;
}
.submit-button { .CodeMirror {
float: right; height: 300px;
transition: opacity 150ms; padding: 5px;
&.no-input { &-code {
opacity: 0.65; margin-bottom: 10px;
pointer-events: none;
}
}
.back-button, .submit-button {
margin: 20px 15px 15px 15px;
@include media-breakpoint-down(sm) {
float: none;
width: calc(100% - 30px);
&:first-child {
margin-top: 20px;
margin-bottom: 5px;
}
&:last-child {
margin-top: 5px;
margin-bottom: 20px;
} }
} }
&.no-horizontal-margins { .picker__holder {
margin-right: 0px; overflow-y: hidden;
margin-left: 0px;
@include media-breakpoint-down(sm) { .picker__button--today {
width: 100%; white-space: nowrap;
}
.picker__select--year, .picker__select--month, .picker__month, .picker__day, .picker__weekday, .picker__footer {
@include media-breakpoint-down(sm) {
font-size: 16px;
}
}
.picker__select--year, .picker__select--month {
width: auto;
height: 1.5em;
padding: 0px;
} }
} }
}
&.user-profile-image { label {
display: block; min-height: $label-height;
width: 100%; line-height: $label-height;
max-width: 150px;
@include media-breakpoint-down(sm) { @include media-breakpoint-up(md) {
margin: $grid-gutter-width auto; margin-bottom: 0px;
}
} }
.image-display { .text-display, .mkd-editor-container, input, select {
@include aspect-ratio(1, 1); margin-bottom: 15px;
position: relative; }
input {
display: block;
width: 100%; width: 100%;
border: 1px solid darken($c-dashboard-light, 10%);
border-radius: 3px;
background-position: center center;
background-size: cover;
background-repeat: no-repeat;
}
.image-buttons { &:not([type="file"]) {
margin-top: 20px; padding: 5px 8px;
display: flex; border: 1px solid darken($c-dashboard-light, 10%);
justify-content: space-around; border-radius: 2px;
transition: border-color 150ms;
input { &.error {
display: none; border-color: $c-dashboard-error;
}
} }
.image-upload-button, .image-delete-button { &[type="file"] {
display: block; height: $label-height;
width: 40px; font-size: 14px;
height: 40px; }
min-height: 0;
border: 1px solid darken($c-dashboard-light, 14%); &.date-picker {
border-radius: 3px;
background-color: darken($c-dashboard-light, 10%);
background-position: center center;
background-size: 50% auto;
background-repeat: no-repeat;
font-size: 0px;
line-height: 1;
cursor: pointer; cursor: pointer;
}
}
.current-image {
margin-bottom: 15px;
display: block;
width: 125px;
max-width: 100%;
}
.edit-button {
margin-bottom: ($grid-gutter-width / 2);
display: inline-block;
padding: 5px 10px;
border-radius: 5px;
text-transform: uppercase;
transition: background-color 150ms;
cursor: pointer;
&:hover, &:focus {
text-decoration: none;
}
&.view {
border: 1px solid darken($c-dashboard-dark, 5%);
background-color: $c-dashboard-dark;
color: $c-text-light;
&:hover { &:hover {
background-color: darken($c-dashboard-light, 7%); background-color: lighten($c-dashboard-dark, 5%);
} }
} }
.image-upload-button { &.delete {
background-image: url("/img/dashboard/icons/upload.svg"); border: 1px solid darken($c-dashboard-delete, 5%);
transition: background-color 150ms; background-color: $c-dashboard-delete;
color: $c-text-light;
&:hover {
background-color: lighten($c-dashboard-delete, 5%);
}
}
}
.back-button {
float: left;
}
.submit-button {
float: right;
transition: opacity 150ms;
&.no-input {
opacity: 0.65;
pointer-events: none;
}
}
.back-button, .submit-button {
margin: 20px 15px 15px 15px;
@include media-breakpoint-down(sm) {
float: none;
width: calc(100% - 30px);
&:first-child {
margin-top: 20px;
margin-bottom: 5px;
}
&:last-child {
margin-top: 5px;
margin-bottom: 20px;
}
}
}
}
}
.dashboard-settings-container {
@include media-breakpoint-up(lg) {
display: flex;
flex-direction: row;
}
form {
&.user-profile-image {
display: block;
width: 100%;
max-width: 150px;
@include media-breakpoint-down(md) {
margin: $grid-gutter-width auto;
} }
.image-delete-button { @include media-breakpoint-up(lg) {
background-image: url("/img/dashboard/icons/trash-alt.svg"); flex-shrink: 0;
opacity: 1; }
transition: background-color 150ms, opacity 150ms;
&.inactive { .image-display {
opacity: 0.35; @include aspect-ratio(1, 1);
pointer-events: none; position: relative;
width: 100%;
border: 1px solid darken($c-dashboard-light, 10%);
border-radius: 3px;
background-position: center center;
background-size: cover;
background-repeat: no-repeat;
}
.image-buttons {
margin-top: 20px;
display: flex;
justify-content: space-around;
input {
display: none;
} }
.image-upload-button, .image-delete-button {
display: block;
width: 40px;
height: 40px;
min-height: 0;
border: 1px solid darken($c-dashboard-light, 14%);
border-radius: 3px;
background-color: darken($c-dashboard-light, 10%);
background-position: center center;
background-size: 50% auto;
background-repeat: no-repeat;
font-size: 0px;
line-height: 1;
cursor: pointer;
&:hover {
background-color: darken($c-dashboard-light, 7%);
}
}
.image-upload-button {
background-image: url("/img/dashboard/icons/upload.svg");
transition: background-color 150ms;
}
.image-delete-button {
background-image: url("/img/dashboard/icons/trash-alt.svg");
opacity: 1;
transition: background-color 150ms, opacity 150ms;
&.inactive {
opacity: 0.35;
pointer-events: none;
}
}
}
}
&.user-profile-update {
@include media-breakpoint-down(md) {
border-top: 1px solid darken($c-dashboard-light, 10%);
border-bottom: 1px solid darken($c-dashboard-light, 10%);
}
@include media-breakpoint-up(lg) {
margin-right: $grid-gutter-width;
margin-left: $grid-gutter-width;
width: 100%;
padding: 0px $grid-gutter-width;
flex-grow: 1;
border-right: 1px solid darken($c-dashboard-light, 10%);
border-left: 1px solid darken($c-dashboard-light, 10%);
}
}
&.user-password-update {
@include media-breakpoint-up(lg) {
width: 225px;
flex-shrink: 0;
}
}
.form-title {
@include media-breakpoint-down(md) {
margin-top: 25px;
margin-bottom: 20px;
text-align: center;
}
}
.submit-button {
@include media-breakpoint-down(sm) {
margin-right: auto;
margin-left: auto;
width: 100%;
}
@include media-breakpoint-up(md) {
float: none;
margin: ($grid-gutter-width / 2) 0px;
} }
} }
} }

View file

@ -7,32 +7,75 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-12 col-md-8"> <div class="col-12">
<form id="user-profile-image" class="edit-item user-profile-image"> <div class="dashboard-settings-container">
@set('profile_image', $user->profileImage()) <form id="user-profile-image" class="user-profile-image">
@set('default_image', App\User::$default_profile_image) @set('profile_image', $user->profileImage())
@set('default_image', App\User::$default_profile_image)
<h2 class="form-title">Profile Image</h2>
<div <div
class="image-display" class="image-display"
style="background-image: url('{{ $profile_image !== null ? $profile_image : $default_image }}')" style="background-image: url('{{ $profile_image !== null ? $profile_image : $default_image }}')"
data-default="{{ $default_image }}"> data-default="{{ $default_image }}">
</div> </div>
<div class="image-buttons"> <div class="image-buttons">
<input id="profile-image-upload" name="profile-image-upload" type="file" /> <input id="profile-image-upload" name="profile-image-upload" type="file" />
<label for="profile-image-upload" class="image-upload-button" title="Upload Profile Image"></label> <label for="profile-image-upload" class="image-upload-button" title="Upload Profile Image"></label>
<span id="profile-image-delete" class="image-delete-button {{ $profile_image === null ? 'inactive' : '' }}" title="Delete Profile Image"></span> <span id="profile-image-delete" class="image-delete-button {{ $profile_image === null ? 'inactive' : '' }}" title="Delete Profile Image"></span>
</div> </div>
</form> </form>
</div>
<div class="col-12 col-md-4"> <form id="user-profile-update" class="edit-item user-profile-update">
<form id="user-password" class="edit-item"> <h2 class="form-title">User Profile</h2>
<input class="text-input" type="password" name="oldpass" id="oldpass" placeholder="Old Password" value="" />
<input class="text-input" type="password" name="newpass" id="newpass" placeholder="New Password" value="" /> <label for="email">Email:</label>
<input class="text-input" type="password" name="newpass_confirmation" id="newpass_confirmation" placeholder="Repeat New Password" value="" /> <input class="text-input" type="text" name="email" id="email" value="{{ $user->email }}" disabled />
<button type="button" class="submit-button no-horizontal-margins btn btn-primary no-input">Update Password</button>
</form> <div class="row">
<div class="col-12 col-md-6">
<label for="name">Name:</label>
<input class="text-input" type="text" name="name" id="name" value="{{ $user->name }}" />
</div>
<div class="col-12 col-md-6">
<label for="website">Website:</label>
<input class="text-input" type="text" name="website" id="website" value="{{ $user->website }}" />
</div>
<div class="col-12 col-md-6">
<label for="facebook">Facebook URL:</label>
<input class="text-input" type="text" name="facebook" id="facebook" value="{{ $user->facebook }}" />
</div>
<div class="col-12 col-md-6">
<label for="soundcloud">SoundCloud URL:</label>
<input class="text-input" type="text" name="soundcloud" id="soundcloud" value="{{ $user->soundcloud }}" />
</div>
<div class="col-12 col-md-6">
<label for="instagram">Instagram Handle:</label>
<input class="text-input" type="text" name="instagram" id="instagram" value="{{ $user->instagram }}" />
</div>
<div class="col-12 col-md-6">
<label for="twitter">Twitter Handle:</label>
<input class="text-input" type="text" name="twitter" id="twitter" value="{{ $user->twitter }}" />
</div>
</div>
<button type="button" class="submit-button btn btn-primary no-input">Update User Profile</button>
</form>
<form id="user-password-update" class="edit-item user-password-update">
<h2 class="form-title">Update Password</h2>
<input class="text-input" type="password" name="oldpass" id="oldpass" placeholder="Old Password" value="" />
<input class="text-input" type="password" name="newpass" id="newpass" placeholder="New Password" value="" />
<input class="text-input" type="password" name="newpass_confirmation" id="newpass_confirmation" placeholder="Repeat New Password" value="" />
<button type="button" class="submit-button btn btn-primary no-input">Update Password</button>
</form>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -8,6 +8,7 @@
@endsection @endsection
@section('page-top') @section('page-top')
<div class="dashboard-background"></div>
@include('dashboard.sections.nav') @include('dashboard.sections.nav')
@endsection @endsection

View file

@ -46,6 +46,7 @@ Route::group([ 'prefix' => 'dashboard' ], function() {
// Dashboard Settings // Dashboard Settings
Route::get('/settings', 'DashboardController@getSettings'); Route::get('/settings', 'DashboardController@getSettings');
Route::post('/user/password-update', 'DashboardController@postUserPasswordUpdate'); Route::post('/user/password-update', 'DashboardController@postUserPasswordUpdate');
Route::post('/user/profile-update', 'DashboardController@postUserProfileUpdate');
Route::post('/user/profile-image-upload', 'DashboardController@postUserProfileImageUpload'); Route::post('/user/profile-image-upload', 'DashboardController@postUserProfileImageUpload');
Route::delete('/user/profile-image-delete', 'DashboardController@deleteUserProfileImageDelete'); Route::delete('/user/profile-image-delete', 'DashboardController@deleteUserProfileImageDelete');

View file

@ -29,19 +29,29 @@ Route::get('/logout', 'Auth\LoginController@logout');
*/ */
Route::group([ 'prefix' => 'dashboard' ], function() { Route::group([ 'prefix' => 'dashboard' ], function() {
// Dashboard CMS
Route::get('/', 'DashboardController@getIndex'); Route::get('/', 'DashboardController@getIndex');
Route::get('/credits', 'DashboardController@getCredits');
Route::get('/view/{model}', 'DashboardController@getView'); Route::get('/view/{model}', 'DashboardController@getView');
Route::get('/edit/{model}', 'DashboardController@getEditList'); Route::get('/edit/{model}', 'DashboardController@getEditList');
Route::get('/edit/{model}/{id}', 'DashboardController@getEditItem'); Route::get('/edit/{model}/{id}', 'DashboardController@getEditItem');
Route::get('/export/{model}', 'DashboardController@getExport'); Route::get('/export/{model}', 'DashboardController@getExport');
Route::post('/reorder', 'DashboardController@postReorder');
Route::post('/update', 'DashboardController@postUpdate');
Route::post('/image-upload', 'DashboardController@postImageUpload'); Route::post('/image-upload', 'DashboardController@postImageUpload');
Route::post('/file-upload', 'DashboardController@postFileUpload'); Route::post('/file-upload', 'DashboardController@postFileUpload');
Route::post('/update', 'DashboardController@postUpdate');
Route::post('/reorder', 'DashboardController@postReorder');
Route::delete('/delete', 'DashboardController@deleteDelete'); Route::delete('/delete', 'DashboardController@deleteDelete');
Route::delete('/image-delete', 'DashboardController@deleteImageDelete'); Route::delete('/image-delete', 'DashboardController@deleteImageDelete');
Route::delete('/file-delete', 'DashboardController@deleteFileDelete'); Route::delete('/file-delete', 'DashboardController@deleteFileDelete');
// Dashboard Settings
Route::get('/settings', 'DashboardController@getSettings');
Route::post('/user/password-update', 'DashboardController@postUserPasswordUpdate');
Route::post('/user/profile-update', 'DashboardController@postUserProfileUpdate');
Route::post('/user/profile-image-upload', 'DashboardController@postUserProfileImageUpload');
Route::delete('/user/profile-image-delete', 'DashboardController@deleteUserProfileImageDelete');
// Credits Page
Route::get('/credits', 'DashboardController@getCredits');
}); });
/* /*