From 83a8dede4091a9e612826cd2afb9100026d73407 Mon Sep 17 00:00:00 2001 From: Kevin MacMartin Date: Tue, 24 Apr 2018 20:38:04 -0400 Subject: [PATCH] Link to the dashboard credits page from a new footer element instead of the user dropdown, organize the dashboard blades by folder now that we have so many of them, and implement user password reset functionality --- app/Http/Controllers/DashboardController.php | 39 +++- app/User.php | 17 ++ resources/assets/js/app.js | 2 +- resources/assets/js/dashboard.js | 168 ++++++++++++++---- resources/assets/sass/dashboard.scss | 49 ++++- .../dashboard/{ => pages}/credits.blade.php | 0 .../dashboard/{ => pages}/edit-item.blade.php | 0 .../dashboard/{ => pages}/edit-list.blade.php | 0 .../dashboard/{ => pages}/home.blade.php | 0 .../views/dashboard/pages/settings.blade.php | 23 +++ .../dashboard/{ => pages}/view.blade.php | 0 .../views/dashboard/sections/footer.blade.php | 3 + .../dashboard/{ => sections}/nav.blade.php | 8 +- resources/views/templates/base.blade.php | 14 +- resources/views/templates/dashboard.blade.php | 7 +- resources/views/templates/public.blade.php | 16 +- routes/web.php | 2 + 17 files changed, 292 insertions(+), 56 deletions(-) rename resources/views/dashboard/{ => pages}/credits.blade.php (100%) rename resources/views/dashboard/{ => pages}/edit-item.blade.php (100%) rename resources/views/dashboard/{ => pages}/edit-list.blade.php (100%) rename resources/views/dashboard/{ => pages}/home.blade.php (100%) create mode 100644 resources/views/dashboard/pages/settings.blade.php rename resources/views/dashboard/{ => pages}/view.blade.php (100%) create mode 100644 resources/views/dashboard/sections/footer.blade.php rename resources/views/dashboard/{ => sections}/nav.blade.php (87%) diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index ff633e5..64a4c6c 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -2,10 +2,12 @@ use App\Http\Requests; use Illuminate\Http\Request; +use Auth; use File; use Image; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use App\User; use App\Dashboard; class DashboardController extends Controller { @@ -23,7 +25,7 @@ class DashboardController extends Controller { */ public function getIndex() { - return view('dashboard.home'); + return view('dashboard.pages.home'); } /** @@ -31,7 +33,17 @@ class DashboardController extends Controller { */ public function getCredits() { - return view('dashboard.credits'); + return view('dashboard.pages.credits'); + } + + /** + * Dashboard settings + */ + public function getSettings() + { + return view('dashboard.pages.settings', [ + 'user' => User::find(Auth::id()) + ]); } /** @@ -42,7 +54,7 @@ class DashboardController extends Controller { $model_class = Dashboard::getModel($model, 'view'); if ($model_class != null) { - return view('dashboard.view', [ + return view('dashboard.pages.view', [ 'heading' => $model_class::getDashboardHeading($model), 'column_headings' => $model_class::getDashboardColumnData('headings'), 'model' => $model, @@ -62,7 +74,7 @@ class DashboardController extends Controller { $model_class = Dashboard::getModel($model, 'edit'); if ($model_class != null) { - return view('dashboard.edit-list', [ + return view('dashboard.pages.edit-list', [ 'heading' => $model_class::getDashboardHeading($model), 'model' => $model, 'rows' => $model_class::getDashboardData(), @@ -101,7 +113,7 @@ class DashboardController extends Controller { } } - return view('dashboard.edit-item', [ + return view('dashboard.pages.edit-item', [ 'heading' => $model_class::getDashboardHeading($model), 'model' => $model, 'id' => $id, @@ -280,6 +292,23 @@ class DashboardController extends Controller { } } + /** + * User Password: Change the current user's password + */ + public function postUserPassword(Request $request) + { + $this->validate($request, [ + 'oldpass' => 'required|string|min:6', + 'newpass' => 'required|string|min:6|confirmed' + ]); + + if (User::find(Auth::id())->updatePassword($request['oldpass'], $request['newpass'])) { + return 'success'; + } else { + return 'old-password-fail'; + } + } + /** * Dashboard Delete: Delete rows */ diff --git a/app/User.php b/app/User.php index 7f08472..95d5c73 100644 --- a/app/User.php +++ b/app/User.php @@ -4,6 +4,7 @@ namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; +use Hash; class User extends Authenticatable { @@ -26,4 +27,20 @@ class User extends Authenticatable protected $hidden = [ 'password', 'remember_token', 'api_token' ]; + + /** + * Update the user's password + * + * @var string + */ + public function updatePassword($oldpass, $newpass) + { + if (Hash::check($oldpass, $this->password)) { + $this->password = Hash::make($newpass); + $this->save(); + return true; + } + + return false; + } } diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index fe80ab7..ecde5c3 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -125,4 +125,4 @@ router.afterEach((to, from) => { const App = new Vue({ router, store -}).$mount("#page-content"); +}).$mount("#vue-container"); diff --git a/resources/assets/js/dashboard.js b/resources/assets/js/dashboard.js index cf443be..21c742b 100644 --- a/resources/assets/js/dashboard.js +++ b/resources/assets/js/dashboard.js @@ -1,3 +1,23 @@ +const $loadingModal = $("#loading-modal"), + fadeTime = 250; + +// show the loading modal +const showLoadingModal = function() { + $loadingModal.css({ + visibility: "visible", + opacity: 1 + }); +}; + +// hide the loading modal +const hideLoadingModal = function() { + $loadingModal.css({ opacity: 0 }); + + setTimeout(function() { + $loadingModal.css({ visibility: "hidden" }); + }, fadeTime); +}; + // declare a reverse function for jquery jQuery.fn.reverse = [].reverse; @@ -6,8 +26,7 @@ function askConfirmation(message, command, cancelCommand) { const $confirmationModal = $("#confirmation-modal"), $heading = $confirmationModal.find(".card-header"), $cancelButton = $confirmationModal.find(".btn.cancel-button"), - $confirmButton = $confirmationModal.find(".btn.confirm-button"), - fadeTime = 250; + $confirmButton = $confirmationModal.find(".btn.confirm-button"); // close the confirmation modal and unbind its events const closeConfirmationModal = function() { @@ -21,7 +40,10 @@ function askConfirmation(message, command, cancelCommand) { // hide the modal $confirmationModal.css({ opacity: 0 }); - setTimeout(function() { $confirmationModal.css({ visibility: "hidden" }); }, fadeTime); + + setTimeout(function() { + $confirmationModal.css({ visibility: "hidden" }); + }, fadeTime); }; // close the modal if the escape button is pressed @@ -67,8 +89,7 @@ function askConfirmation(message, command, cancelCommand) { function showAlert(message, command) { const $alertModal = $("#alert-modal"), $message = $alertModal.find(".message"), - $acceptButton = $alertModal.find(".btn.accept-button"), - fadeTime = 250; + $acceptButton = $alertModal.find(".btn.accept-button"); // close the alert modal and unbind its events const closeAlertModal = function() { @@ -160,7 +181,7 @@ function editListInit() { if (response === "success") { $listItem.slideUp(150, function() { $listItem.remove(); }); } else { - showAlert("ERROR: Failed to delete record"); + showAlert("Failed to delete record"); } }); }); @@ -225,7 +246,7 @@ function editListInit() { } }).always(function(response) { if (response !== "success") { - showAlert("ERROR: Sorting failed", function() { + showAlert("Sorting failed", function() { document.location.reload(true); }); } @@ -277,8 +298,6 @@ function editItemInit() { $fileUploads = $(".file-upload"), $imgUploads = $(".image-upload"), $token = $("#token"), - $spinner = $("#loading-modal"), - fadeTime = 250, model = $editItem.data("model"), id = $editItem.data("id"), operation = id === "new" ? "create" : "update"; @@ -291,20 +310,6 @@ function editItemInit() { minutes, changes = false; - // show the loading modal - const showLoadingModal = function() { - $spinner.css({ - visibility: "visible", - opacity: 1 - }); - }; - - // hide the loading modal - const hideLoadingModal = function() { - $spinner.css({ opacity: 0 }); - setTimeout(function() { $spinner.css({ visibility: "hidden" }); }, fadeTime); - }; - // fill the formData object with data from all the form fields const getFormData = function() { // function to add a column and value to the formData object @@ -390,7 +395,7 @@ function editItemInit() { uploadFile(row_id, currentFile + 1); } else { hideLoadingModal(); - showAlert("ERROR: Failed to upload file"); + showAlert("Failed to upload file"); console.log(response.responseText); submitting = false; } @@ -436,7 +441,7 @@ function editItemInit() { uploadImage(row_id, currentImage + 1); } else { hideLoadingModal(); - showAlert("ERROR: Failed to upload image"); + showAlert("Failed to upload image"); submitting = false; } }); @@ -475,7 +480,7 @@ function editItemInit() { if (response === "success") { $(`#current-image-${name}`).slideUp(200); } else { - showAlert("ERROR: Failed to delete the image: " + response); + showAlert("Failed to delete image: " + response); } submitting = false; @@ -510,7 +515,7 @@ function editItemInit() { if (response === "success") { $(`#current-file-${name}`).slideUp(200); } else { - showAlert("ERROR: Failed to delete the file: " + response); + showAlert("Failed to delete file: " + response); } submitting = false; @@ -566,13 +571,16 @@ function editItemInit() { }); setTimeout(function() { + // load the initial value into the editor simplemde[column].value($this.attr("value")); simplemde[column].codemirror.refresh(); + + // watch for changes to simplemde editor contents simplemde[column].codemirror.on("change", contentChanged); }, 500); }); - // initialize change events for back button + // watch for changes to input and select element contents $editItem.find("input, select").on("input change", contentChanged); // initialize back button @@ -609,7 +617,7 @@ function editItemInit() { uploadImage(response.replace(/^id:/, ""), 0); } else { hideLoadingModal(); - showAlert("ERROR: Failed to " + operation + " record"); + showAlert("Failed to " + operation + " record"); submitting = false; } }); @@ -617,11 +625,111 @@ function editItemInit() { }); } +function userPasswordInit() { + const $form = $("#user-password"), + $submit = $form.find(".submit-button"), + $inputs = $form.find("input"), + $oldpass = $("#oldpass"), + $newpass = $("#newpass"), + $newpassConfirmation = $("#newpass_confirmation"), + $token = $("#token"); + + let formData = {}, + submitting = false; + + const getFormData = function() { + formData = { + oldpass: $oldpass.val(), + newpass: $newpass.val(), + newpass_confirmation: $newpassConfirmation.val(), + _token: $token.val() + }; + }; + + // remove the error class from inputs and enable submit if all inputs have data when changes are made + $inputs.on("input change", function() { + let enableSubmit = true; + + for (let i = 0; i < $inputs.length; i++) { + if ($inputs[i].value === "") { + enableSubmit = false; + break; + } + } + + if (enableSubmit) { + $submit.removeClass("no-input"); + } else { + $submit.addClass("no-input"); + } + + $inputs.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(); + + if (formData.newpass !== formData.newpass_confirmation) { + // fail with an error if the newpass and newpass_confirmation don't match + $newpassConfirmation.val(""); + $newpass.addClass("error"); + $newpassConfirmation.addClass("error"); + showAlert("New passwords do not match"); + submitting = false; + } else { + // submit the update + $.ajax({ + type: "POST", + url: "/dashboard/user-password", + data: formData + }).always(function(response) { + if (response === "success") { + hideLoadingModal(); + + showAlert("Password updated successfully", function() { + $inputs.val("").trigger("change"); + }); + } else { + submitting = false; + + if (response === "old-password-fail") { + $oldpass.addClass("error"); + showAlert("Old password is not correct"); + } else { + $newpass.addClass("error"); + $newpassConfirmation.val(""); + hideLoadingModal(); + showAlert("New password must be at least 6 characters"); + } + } + }); + } + } + }); +} + // run once the document is ready $(document).ready(function() { if ($("#edit-list").length) { editListInit(); - } else if ($("#edit-item").length) { + } + + if ($("#edit-item").length) { editItemInit(); } + + if ($("#user-password").length) { + userPasswordInit(); + } }); diff --git a/resources/assets/sass/dashboard.scss b/resources/assets/sass/dashboard.scss index c8e2f40..f11bcbb 100644 --- a/resources/assets/sass/dashboard.scss +++ b/resources/assets/sass/dashboard.scss @@ -10,7 +10,9 @@ // Colours $c-text: #111; // text +$c-text-inactive: fade-out($c-text, 0.25); // inactive text $c-text-light: #fff; // light text +$c-text-light-inactive: fade-out($c-text-light, 0.25); // inactive light text $c-input-bg: #fff; // white $c-dashboard-error: #a80000; $c-dashboard-dark: #3e6087; @@ -33,6 +35,16 @@ body { -webkit-overflow-scrolling: touch; } +.site-content { + display: flex; + min-height: 100vh; + flex-direction: column; + + .page-content { + flex-grow: 1; + } +} + .navbar { margin-bottom: $grid-gutter-width; border: 0; @@ -103,7 +115,8 @@ body { } .nav-link { - color: fade-out($c-text-light, 0.25); + color: $c-text-light-inactive; + transition: color 150ms; &.active { color: $c-text-light; @@ -138,9 +151,37 @@ body { } &.active, &:hover, &:focus, &:active { - background-color: fade-out(#000, 0.95); color: $c-text; } + + &:hover, &:focus { + background-color: fade-out(#000, 0.97); + } + + &.active { + background-color: fade-out(#000, 0.93); + } + } + } +} + +.dashboard-footer { + margin-top: $grid-gutter-width; + width: 100%; + padding: 8px ($grid-gutter-width / 2); + background-color: $c-dashboard-dark; + text-align: right; + + a { + color: $c-text-light-inactive; + transition: color 150ms; + + &:hover, &:focus { + text-decoration: none; + } + + &.active { + color: $c-text-light; } } } @@ -663,6 +704,10 @@ body { border: 1px solid darken($c-dashboard-light, 10%); border-radius: 2px; transition: border-color 150ms; + + &.error { + border-color: $c-dashboard-error; + } } &[type="file"] { diff --git a/resources/views/dashboard/credits.blade.php b/resources/views/dashboard/pages/credits.blade.php similarity index 100% rename from resources/views/dashboard/credits.blade.php rename to resources/views/dashboard/pages/credits.blade.php diff --git a/resources/views/dashboard/edit-item.blade.php b/resources/views/dashboard/pages/edit-item.blade.php similarity index 100% rename from resources/views/dashboard/edit-item.blade.php rename to resources/views/dashboard/pages/edit-item.blade.php diff --git a/resources/views/dashboard/edit-list.blade.php b/resources/views/dashboard/pages/edit-list.blade.php similarity index 100% rename from resources/views/dashboard/edit-list.blade.php rename to resources/views/dashboard/pages/edit-list.blade.php diff --git a/resources/views/dashboard/home.blade.php b/resources/views/dashboard/pages/home.blade.php similarity index 100% rename from resources/views/dashboard/home.blade.php rename to resources/views/dashboard/pages/home.blade.php diff --git a/resources/views/dashboard/pages/settings.blade.php b/resources/views/dashboard/pages/settings.blade.php new file mode 100644 index 0000000..f534252 --- /dev/null +++ b/resources/views/dashboard/pages/settings.blade.php @@ -0,0 +1,23 @@ +@extends('dashboard.core', [ + 'heading' => 'Settings' +]) + +@section('dashboard-body') + + +
+
+
+
+ +
+
+ + + + +
+
+
+
+@endsection diff --git a/resources/views/dashboard/view.blade.php b/resources/views/dashboard/pages/view.blade.php similarity index 100% rename from resources/views/dashboard/view.blade.php rename to resources/views/dashboard/pages/view.blade.php diff --git a/resources/views/dashboard/sections/footer.blade.php b/resources/views/dashboard/sections/footer.blade.php new file mode 100644 index 0000000..2233cc0 --- /dev/null +++ b/resources/views/dashboard/sections/footer.blade.php @@ -0,0 +1,3 @@ + diff --git a/resources/views/dashboard/nav.blade.php b/resources/views/dashboard/sections/nav.blade.php similarity index 87% rename from resources/views/dashboard/nav.blade.php rename to resources/views/dashboard/sections/nav.blade.php index d0d12bc..4d3337f 100644 --- a/resources/views/dashboard/nav.blade.php +++ b/resources/views/dashboard/sections/nav.blade.php @@ -1,6 +1,4 @@