diff --git a/app/Dashboard.php b/app/Dashboard.php index 0308252..4f69c65 100644 --- a/app/Dashboard.php +++ b/app/Dashboard.php @@ -50,7 +50,7 @@ class Dashboard */ public static $library_credits = [ [ 'name' => 'Bootstrap', 'url' => 'https://getbootstrap.com' ], - [ 'name' => 'Font Awesome', 'url' => 'https://fontawesome.com' ], + [ 'name' => 'Font Awesome', 'url' => 'https://fontawesome.com', 'license' => 'https://fontawesome.com/license' ], [ 'name' => 'GreenSock', 'url' => 'https://greensock.com/gsap' ], [ 'name' => 'jQuery', 'url' => 'https://jquery.org' ], [ 'name' => 'List.js', 'url' => 'http://listjs.com' ], diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 64a4c6c..e02dbd4 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -2,11 +2,11 @@ use App\Http\Requests; use Illuminate\Http\Request; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx; use Auth; use File; use Image; -use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PhpOffice\PhpSpreadsheet\Writer\Xlsx; use App\User; use App\Dashboard; @@ -21,34 +21,14 @@ class DashboardController extends Controller { } /** - * Dashboard home + * Dashboard CMS */ public function getIndex() { return view('dashboard.pages.home'); } - /** - * Project credits - */ - public function getCredits() - { - return view('dashboard.pages.credits'); - } - - /** - * Dashboard settings - */ - public function getSettings() - { - return view('dashboard.pages.settings', [ - 'user' => User::find(Auth::id()) - ]); - } - - /** - * Dashboard View - */ + // View Model Data public function getView($model) { $model_class = Dashboard::getModel($model, 'view'); @@ -66,9 +46,7 @@ class DashboardController extends Controller { } } - /** - * Dashboard Edit List - */ + // Edit List of Model Rows public function getEditList($model) { $model_class = Dashboard::getModel($model, 'edit'); @@ -91,9 +69,7 @@ class DashboardController extends Controller { } } - /** - * Dashboard Edit Item - */ + // Create and Edit Model Item public function getEditItem($model, $id = 'new') { $model_class = Dashboard::getModel($model, 'edit'); @@ -126,9 +102,7 @@ class DashboardController extends Controller { } } - /** - * Dashboard Export: Export data as a spreadsheet - */ + // Export Spreadsheet of Model Data public function getExport($model) { $model_class = Dashboard::getModel($model); @@ -151,9 +125,7 @@ class DashboardController extends Controller { } } - /** - * Dashboard Reorder: Reorder rows - */ + // Reorder Model Rows public function postReorder(Request $request) { $this->validate($request, [ @@ -181,9 +153,7 @@ class DashboardController extends Controller { } } - /** - * Dashboard Update: Create and update rows - */ + // Create and Update Model Item Data public function postUpdate(Request $request) { $this->validate($request, [ @@ -222,9 +192,7 @@ class DashboardController extends Controller { } } - /** - * Dashboard Image Upload: Upload images - */ + // Upload Model Item Image public function postImageUpload(Request $request) { $this->validate($request, [ @@ -257,9 +225,7 @@ class DashboardController extends Controller { } } - /** - * Dashboard File Upload: Upload files - */ + // Upload Model Item File public function postFileUpload(Request $request) { $this->validate($request, [ @@ -292,26 +258,7 @@ 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 - */ + // Delete Model Item public function deleteDelete(Request $request) { $this->validate($request, [ @@ -357,9 +304,7 @@ class DashboardController extends Controller { } } - /** - * Dashboard Image Delete: Delete images - */ + // Delete Model Item Image public function deleteImageDelete(Request $request) { $this->validate($request, [ @@ -390,9 +335,7 @@ class DashboardController extends Controller { } } - /** - * Dashboard File Delete: Delete files - */ + // Delete Model Item File public function deleteFileDelete(Request $request) { $this->validate($request, [ @@ -424,4 +367,92 @@ class DashboardController extends Controller { } } + /** + * Dashboard settings + */ + public function getSettings() + { + return view('dashboard.pages.settings', [ + 'user' => User::find(Auth::id()) + ]); + } + + // User Password Update + public function postUserPasswordUpdate(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'; + } + } + + // User Profile Image Upload + public function postUserProfileImageUpload(Request $request) + { + if ($request->hasFile('file')) { + $user = User::find(Auth::id()); + + if ($user !== null) { + $image = Image::make($request->file('file')); + $max_width = User::$profile_image_max['width']; + $max_height = User::$profile_image_max['height']; + + if ($image->width() > $max_width || $image->height() > $max_height) { + $new_width = $max_width; + $new_height = ($new_width / $image->width()) * $image->height(); + + if ($new_height > $max_height) { + $new_height = $max_height; + $new_width = ($new_height / $image->height()) * $image->width(); + } + + $image->resize($new_width, $new_height); + } + + file::makeDirectory(base_path() . '/public' . User::$profile_image_dir, 0755, true, true); + $image->save($user->profileImage(true, true)); + $user->touch(); + return $user->profileImage() . '?version=' . $user->timestamp(); + } else { + return 'record-access-fail'; + } + } else { + return 'file-upload-fail'; + } + } + + // User Profile Image Delete + public function deleteUserProfileImageDelete(Request $request) + { + $user = User::find(Auth::id()); + + if ($user !== null) { + $profile_image = $user->profileImage(true); + + if ($profile_image === null) { + return 'image-not-exists-fail'; + } else if (!unlink($profile_image)) { + return 'image-delete-fail'; + } + + return 'success'; + } else { + return 'record-access-fail'; + } + } + + /** + * Credits Page + */ + public function getCredits() + { + return view('dashboard.pages.credits'); + } + } diff --git a/app/Models/DashboardModel.php b/app/Models/DashboardModel.php index 1338fb5..44c59fe 100644 --- a/app/Models/DashboardModel.php +++ b/app/Models/DashboardModel.php @@ -4,9 +4,12 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use Auth; +use App\Traits\Timestamp; class DashboardModel extends Model { + use Timestamp; + /* * The dashboard page type * @@ -168,13 +171,4 @@ class DashboardModel extends Model return $user_check; } - - /** - * Returns the Unix timestamp of the latest update - * - * @return number - */ - public function timestamp() { - return strtotime($this->updated_at); - } } diff --git a/app/Traits/Timestamp.php b/app/Traits/Timestamp.php new file mode 100644 index 0000000..c2f41dd --- /dev/null +++ b/app/Traits/Timestamp.php @@ -0,0 +1,15 @@ +updated_at); + } +} diff --git a/app/User.php b/app/User.php index 95d5c73..c3b71ca 100644 --- a/app/User.php +++ b/app/User.php @@ -5,10 +5,12 @@ namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; use Hash; +use App\Traits\Timestamp; class User extends Authenticatable { use Notifiable; + use Timestamp; /** * The attributes that are mass assignable. @@ -28,6 +30,30 @@ class User extends Authenticatable 'password', 'remember_token', 'api_token' ]; + /** + * The default user profile image + * + * @var string + */ + public static $default_profile_image = '/img/profile.png'; + + /** + * The directory user profile uploads are stored in + * + * @var string + */ + public static $profile_image_dir = '/uploads/user/img/'; + + /** + * The maximum profile image width and height + * + * @var array + */ + public static $profile_image_max = [ + 'width' => 512, + 'height' => 512 + ]; + /** * Update the user's password * @@ -43,4 +69,21 @@ class User extends Authenticatable return false; } + + /** + * Get user profile image + * + * @var string + */ + public function profileImage($show_full_path = false, $always_return_path = false) + { + $site_path = self::$profile_image_dir . $this->id . '-profile.png'; + $file_path = base_path() . '/public' . $site_path; + + if (file_exists($file_path) || $always_return_path) { + return $show_full_path ? $file_path : $site_path; + } else { + return null; + } + } } diff --git a/public/img/dashboard/trash-alt.svg b/public/img/dashboard/trash-alt.svg new file mode 100644 index 0000000..a921fe8 --- /dev/null +++ b/public/img/dashboard/trash-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/dashboard/upload.svg b/public/img/dashboard/upload.svg new file mode 100644 index 0000000..1c880b5 --- /dev/null +++ b/public/img/dashboard/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/profile.png b/public/img/profile.png new file mode 100644 index 0000000..b5ae4ad Binary files /dev/null and b/public/img/profile.png differ diff --git a/resources/assets/js/dashboard.js b/resources/assets/js/dashboard.js index 21c742b..9b9d2d0 100644 --- a/resources/assets/js/dashboard.js +++ b/resources/assets/js/dashboard.js @@ -625,6 +625,83 @@ function editItemInit() { }); } +function userProfileImageInit() { + const $form = $("#user-profile-image"), + $upload = $("#profile-image-upload"), + $delete = $("#profile-image-delete"), + $token = $("#token"), + $displayInner = $form.find(".image-display-inner").first(); + + let file, + submitting = false; + + $upload.on("change", function() { + if ($upload.val() !== "" && !submitting) { + submitting = true; + + askConfirmation("Update your user profile image?", function() { + // show the loading modal + showLoadingModal(); + + // add the image to the form data + file = new FormData(); + file.append("file", $upload[0].files[0]); + + // submit the form data + $.ajax({ + type: "POST", + url: "/dashboard/user/profile-image-upload", + data: file, + processData: false, + contentType: false, + beforeSend: function(xhr) { xhr.setRequestHeader("X-CSRF-TOKEN", $token.val()); } + }).always(function(response) { + hideLoadingModal(); + submitting = false; + + if (/\.png\?version=/.test(response)) { + $displayInner.css({ backgroundImage: `url(${response})` }); + $delete.removeClass("inactive"); + } else { + showAlert("Failed to upload image"); + } + }); + }, function() { + $upload.val(""); + submitting = false; + }); + } + }); + + $delete.on("click", function() { + if (!submitting) { + submitting = true; + + askConfirmation("Delete your profile image?", function() { + // delete the profile image + $.ajax({ + type: "DELETE", + url: "/dashboard/user/profile-image-delete", + data: { + _token: $token.val() + } + }).always(function(response) { + if (response === "success") { + $displayInner.css({ backgroundImage: "none" }); + $delete.addClass("inactive"); + } else { + showAlert("Failed to delete profile image"); + } + + submitting = false; + }); + }, function() { + submitting = false; + }); + } + }); +} + function userPasswordInit() { const $form = $("#user-password"), $submit = $form.find(".submit-button"), @@ -691,27 +768,22 @@ function userPasswordInit() { // submit the update $.ajax({ type: "POST", - url: "/dashboard/user-password", + url: "/dashboard/user/password-update", data: formData }).always(function(response) { + hideLoadingModal(); + submitting = false; + if (response === "success") { - hideLoadingModal(); - - showAlert("Password updated successfully", function() { - $inputs.val("").trigger("change"); - }); + $inputs.val("").trigger("change"); + showAlert("Password updated successfully"); + } else if (response === "old-password-fail") { + $oldpass.addClass("error"); + showAlert("Old password is not correct"); } 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"); - } + $newpass.addClass("error"); + $newpassConfirmation.val(""); + showAlert("New password must be at least 6 characters"); } }); } @@ -729,6 +801,10 @@ $(document).ready(function() { editItemInit(); } + if ($("#user-profile-image").length) { + userProfileImageInit(); + } + if ($("#user-password").length) { userPasswordInit(); } diff --git a/resources/assets/sass/dashboard.scss b/resources/assets/sass/dashboard.scss index f11bcbb..7823e55 100644 --- a/resources/assets/sass/dashboard.scss +++ b/resources/assets/sass/dashboard.scss @@ -8,6 +8,9 @@ // Core @import "_fonts"; +// Supplementary +@import "mixins/**/*.scss"; + // Colours $c-text: #111; // text $c-text-inactive: fade-out($c-text, 0.25); // inactive text @@ -802,6 +805,80 @@ body { } } } + + &.user-profile-image { + display: block; + width: 100%; + max-width: 150px; + + .image-display { + @include aspect-ratio(1, 1); + 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; + + &-inner { + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + 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 { + @include aspect-ratio(1, 1); + display: block; + width: 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/upload.svg"); + transition: background-color 150ms; + } + + .image-delete-button { + background-image: url("/img/dashboard/trash-alt.svg"); + opacity: 1; + transition: background-color 150ms, opacity 150ms; + + &.inactive { + opacity: 0.35; + pointer-events: none; + } + } + } + } } #loading-modal { diff --git a/resources/views/dashboard/pages/credits.blade.php b/resources/views/dashboard/pages/credits.blade.php index 2e61446..7b278d3 100644 --- a/resources/views/dashboard/pages/credits.blade.php +++ b/resources/views/dashboard/pages/credits.blade.php @@ -16,7 +16,13 @@ diff --git a/resources/views/dashboard/pages/settings.blade.php b/resources/views/dashboard/pages/settings.blade.php index f534252..2695b92 100644 --- a/resources/views/dashboard/pages/settings.blade.php +++ b/resources/views/dashboard/pages/settings.blade.php @@ -8,6 +8,19 @@
+
diff --git a/routes/web.php b/routes/web.php index adc9063..2e2a1f7 100644 --- a/routes/web.php +++ b/routes/web.php @@ -29,21 +29,28 @@ Route::get('/logout', 'Auth\LoginController@logout'); */ Route::group([ 'prefix' => 'dashboard' ], function() { + // Dashboard CMS Route::get('/', 'DashboardController@getIndex'); - Route::get('/credits', 'DashboardController@getCredits'); - Route::get('/settings', 'DashboardController@getSettings'); Route::get('/view/{model}', 'DashboardController@getView'); Route::get('/edit/{model}', 'DashboardController@getEditList'); Route::get('/edit/{model}/{id}', 'DashboardController@getEditItem'); Route::get('/export/{model}', 'DashboardController@getExport'); + Route::post('/reorder', 'DashboardController@postReorder'); + Route::post('/update', 'DashboardController@postUpdate'); Route::post('/image-upload', 'DashboardController@postImageUpload'); Route::post('/file-upload', 'DashboardController@postFileUpload'); - Route::post('/update', 'DashboardController@postUpdate'); - Route::post('/reorder', 'DashboardController@postReorder'); - Route::post('/user-password', 'DashboardController@postUserPassword'); Route::delete('/delete', 'DashboardController@deleteDelete'); Route::delete('/image-delete', 'DashboardController@deleteImageDelete'); Route::delete('/file-delete', 'DashboardController@deleteFileDelete'); + + // Dashboard Settings + Route::get('/settings', 'DashboardController@getSettings'); + Route::post('/user/password-update', 'DashboardController@postUserPasswordUpdate'); + Route::post('/user/profile-image-upload', 'DashboardController@postUserProfileImageUpload'); + Route::delete('/user/profile-image-delete', 'DashboardController@deleteUserProfileImageDelete'); + + // Credits Page + Route::get('/credits', 'DashboardController@getCredits'); }); /*