Added: User Management, Roles & Permissions
This commit is contained in:
parent
0d7d2c747e
commit
1cec992366
|
@ -16,7 +16,6 @@ class CreateProductsTable extends Migration
|
||||||
Schema::create('products', function (Blueprint $table) {
|
Schema::create('products', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->unsignedBigInteger('category_id');
|
$table->unsignedBigInteger('category_id');
|
||||||
$table->string('product_image')->nullable();
|
|
||||||
$table->string('product_name');
|
$table->string('product_name');
|
||||||
$table->string('product_code')->unique()->nullable();
|
$table->string('product_code')->unique()->nullable();
|
||||||
$table->string('product_barcode_symbology')->nullable();
|
$table->string('product_barcode_symbology')->nullable();
|
||||||
|
|
|
@ -9,6 +9,7 @@ use Spatie\MediaLibrary\InteractsWithMedia;
|
||||||
|
|
||||||
class Product extends Model implements HasMedia
|
class Product extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
|
|
||||||
use HasFactory, InteractsWithMedia;
|
use HasFactory, InteractsWithMedia;
|
||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
@ -18,4 +19,9 @@ class Product extends Model implements HasMedia
|
||||||
public function category() {
|
public function category() {
|
||||||
return $this->belongsTo(Category::class, 'category_id', 'id');
|
return $this->belongsTo(Category::class, 'category_id', 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function registerMediaCollections(): void {
|
||||||
|
$this->addMediaCollection('default')
|
||||||
|
->useFallbackUrl('/images/fallback_product_image.png');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,26 +5,23 @@ namespace Modules\Product\Http\Controllers;
|
||||||
use Illuminate\Contracts\Support\Renderable;
|
use Illuminate\Contracts\Support\Renderable;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Routing\Controller;
|
use Illuminate\Routing\Controller;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Modules\Product\Entities\Category;
|
use Modules\Product\Entities\Category;
|
||||||
use App\DataTables\ProductCategoriesDataTable;
|
use App\DataTables\ProductCategoriesDataTable;
|
||||||
|
|
||||||
class CategoriesController extends Controller
|
class CategoriesController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a listing of the resource.
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function index(ProductCategoriesDataTable $dataTable) {
|
public function index(ProductCategoriesDataTable $dataTable) {
|
||||||
|
abort_if(Gate::denies('access_product_categories'), 403);
|
||||||
|
|
||||||
return $dataTable->render('product::categories.index');
|
return $dataTable->render('product::categories.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a newly created resource in storage.
|
|
||||||
* @param Request $request
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function store(Request $request) {
|
public function store(Request $request) {
|
||||||
|
abort_if(Gate::denies('access_product_categories'), 403);
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'category_code' => 'required|unique:categories,category_code',
|
'category_code' => 'required|unique:categories,category_code',
|
||||||
'category_name' => 'required'
|
'category_name' => 'required'
|
||||||
|
@ -40,24 +37,19 @@ class CategoriesController extends Controller
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for editing the specified resource.
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function edit($id) {
|
public function edit($id) {
|
||||||
|
abort_if(Gate::denies('access_product_categories'), 403);
|
||||||
|
|
||||||
$category = Category::findOrFail($id);
|
$category = Category::findOrFail($id);
|
||||||
|
|
||||||
return view('product::categories.edit', compact('category'));
|
return view('product::categories.edit', compact('category'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the specified resource in storage.
|
|
||||||
* @param Request $request
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function update(Request $request, $id) {
|
public function update(Request $request, $id) {
|
||||||
|
abort_if(Gate::denies('access_product_categories'), 403);
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'category_code' => 'required|unique:categories,category_code,' . $id,
|
'category_code' => 'required|unique:categories,category_code,' . $id,
|
||||||
'category_name' => 'required'
|
'category_name' => 'required'
|
||||||
|
@ -73,12 +65,10 @@ class CategoriesController extends Controller
|
||||||
return redirect()->route('product-categories.index');
|
return redirect()->route('product-categories.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the specified resource from storage.
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function destroy($id) {
|
public function destroy($id) {
|
||||||
|
abort_if(Gate::denies('access_product_categories'), 403);
|
||||||
|
|
||||||
$category = Category::findOrFail($id);
|
$category = Category::findOrFail($id);
|
||||||
|
|
||||||
if ($category->products->isNotEmpty()) {
|
if ($category->products->isNotEmpty()) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ use App\DataTables\ProductDataTable;
|
||||||
use Illuminate\Contracts\Support\Renderable;
|
use Illuminate\Contracts\Support\Renderable;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Routing\Controller;
|
use Illuminate\Routing\Controller;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Modules\Product\Entities\Product;
|
use Modules\Product\Entities\Product;
|
||||||
use Modules\Product\Http\Requests\ProductCreateRequest;
|
use Modules\Product\Http\Requests\ProductCreateRequest;
|
||||||
|
@ -15,27 +16,20 @@ use Modules\Upload\Entities\Upload;
|
||||||
class ProductController extends Controller
|
class ProductController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a listing of the resource.
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function index(ProductDataTable $dataTable) {
|
public function index(ProductDataTable $dataTable) {
|
||||||
|
abort_if(Gate::denies('access_products'), 403);
|
||||||
|
|
||||||
return $dataTable->render('product::products.index');
|
return $dataTable->render('product::products.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for creating a new resource.
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function create() {
|
public function create() {
|
||||||
|
abort_if(Gate::denies('create_products'), 403);
|
||||||
|
|
||||||
return view('product::products.create');
|
return view('product::products.create');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a newly created resource in storage.
|
|
||||||
* @param Request $request
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function store(ProductCreateRequest $request) {
|
public function store(ProductCreateRequest $request) {
|
||||||
$product = Product::create($request->except('image'));
|
$product = Product::create($request->except('image'));
|
||||||
|
|
||||||
|
@ -55,38 +49,30 @@ class ProductController extends Controller
|
||||||
return redirect()->route('products.index');
|
return redirect()->route('products.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the details for the specified resource.
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function show(Product $product) {
|
public function show(Product $product) {
|
||||||
|
abort_if(Gate::denies('show_products'), 403);
|
||||||
|
|
||||||
return view('product::products.show', compact('product'));
|
return view('product::products.show', compact('product'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for editing the specified resource.
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function edit(Product $product) {
|
public function edit(Product $product) {
|
||||||
|
abort_if(Gate::denies('edit_products'), 403);
|
||||||
|
|
||||||
return view('product::products.edit', compact('product'));
|
return view('product::products.edit', compact('product'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the specified resource in storage.
|
|
||||||
* @param Request $request
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function update(ProductUpdateRequest $request, Product $product) {
|
public function update(ProductUpdateRequest $request, Product $product) {
|
||||||
$product->update($request->except('image'));
|
$product->update($request->except('image'));
|
||||||
|
|
||||||
if ($request->has('image')) {
|
if ($request->has('image')) {
|
||||||
$tempFile = Upload::where('folder', $request->image)->first();
|
$tempFile = Upload::where('folder', $request->image)->first();
|
||||||
|
|
||||||
$media = $product->getMedia();
|
if ($product->getFirstMedia()) {
|
||||||
$media[0]->delete();
|
$product->getFirstMedia()->delete();
|
||||||
|
}
|
||||||
|
|
||||||
if ($tempFile) {
|
if ($tempFile) {
|
||||||
$product->addMedia(Storage::path('public/temp/' . $request->image . '/' . $tempFile->filename))->toMediaCollection();
|
$product->addMedia(Storage::path('public/temp/' . $request->image . '/' . $tempFile->filename))->toMediaCollection();
|
||||||
|
@ -101,12 +87,10 @@ class ProductController extends Controller
|
||||||
return redirect()->route('products.index');
|
return redirect()->route('products.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the specified resource from storage.
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function destroy(Product $product) {
|
public function destroy(Product $product) {
|
||||||
|
abort_if(Gate::denies('delete_products'), 403);
|
||||||
|
|
||||||
$product->delete();
|
$product->delete();
|
||||||
|
|
||||||
toast('Product Deleted!', 'warning');
|
toast('Product Deleted!', 'warning');
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Modules\Product\Http\Requests;
|
namespace Modules\Product\Http\Requests;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
|
||||||
class ProductCreateRequest extends FormRequest
|
class ProductCreateRequest extends FormRequest
|
||||||
{
|
{
|
||||||
|
@ -35,6 +36,6 @@ class ProductCreateRequest extends FormRequest
|
||||||
*/
|
*/
|
||||||
public function authorize()
|
public function authorize()
|
||||||
{
|
{
|
||||||
return true;
|
return Gate::allows('create_products');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Modules\Product\Http\Requests;
|
namespace Modules\Product\Http\Requests;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class ProductUpdateRequest extends FormRequest
|
class ProductUpdateRequest extends FormRequest
|
||||||
|
@ -36,6 +37,6 @@ class ProductUpdateRequest extends FormRequest
|
||||||
*/
|
*/
|
||||||
public function authorize()
|
public function authorize()
|
||||||
{
|
{
|
||||||
return true;
|
return Gate::allows('edit_products');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
|
@include('utils.alerts')
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="{{ route('product-categories.update', $category->id) }}" method="POST">
|
<form action="{{ route('product-categories.update', $category->id) }}" method="POST">
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
@include('utils.alerts')
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<!-- Button trigger modal -->
|
<!-- Button trigger modal -->
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
|
@can('edit_products')
|
||||||
<a href="{{ route('products.edit', $data->id) }}" class="btn btn-info btn-sm">
|
<a href="{{ route('products.edit', $data->id) }}" class="btn btn-info btn-sm">
|
||||||
<i class="bi bi-pencil"></i>
|
<i class="bi bi-pencil"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@endcan
|
||||||
|
@can('show_products')
|
||||||
<a href="{{ route('products.show', $data->id) }}" class="btn btn-primary btn-sm">
|
<a href="{{ route('products.show', $data->id) }}" class="btn btn-primary btn-sm">
|
||||||
<i class="bi bi-eye"></i>
|
<i class="bi bi-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@endcan
|
||||||
|
@can('delete_products')
|
||||||
<button id="delete" class="btn btn-danger btn-sm" onclick="
|
<button id="delete" class="btn btn-danger btn-sm" onclick="
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (confirm('Are you sure? It will delete the data permanently!')) {
|
if (confirm('Are you sure? It will delete the data permanently!')) {
|
||||||
|
@ -16,3 +21,4 @@
|
||||||
@method('delete')
|
@method('delete')
|
||||||
</form>
|
</form>
|
||||||
</button>
|
</button>
|
||||||
|
@endcan
|
||||||
|
|
|
@ -13,6 +13,10 @@ class UploadController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
public function filepondUpload(Request $request) {
|
public function filepondUpload(Request $request) {
|
||||||
|
if (!$request->ajax()) {
|
||||||
|
return back();
|
||||||
|
}
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'image' => 'required|image|mimes:png,jpeg,jpg'
|
'image' => 'required|image|mimes:png,jpeg,jpg'
|
||||||
]);
|
]);
|
||||||
|
@ -36,4 +40,17 @@ class UploadController extends Controller
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function filepondDelete(Request $request) {
|
||||||
|
if (!$request->ajax()) {
|
||||||
|
return back();
|
||||||
|
}
|
||||||
|
|
||||||
|
$upload = Upload::where('folder', $request->getContent())->first();
|
||||||
|
|
||||||
|
Storage::deleteDirectory('public/temp/' . $upload->folder);
|
||||||
|
$upload->delete();
|
||||||
|
|
||||||
|
return response(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Route::group(['middleware' => 'auth'], function () {
|
Route::group(['middleware' => 'auth'], function () {
|
||||||
|
|
||||||
Route::post('/filepond/upload', 'UploadController@filepondUpload')->name('filepond.upload');
|
Route::post('/filepond/upload', 'UploadController@filepondUpload')->name('filepond.upload');
|
||||||
|
Route::delete('/filepond/delete', 'UploadController@filepondDelete')->name('filepond.delete');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ class PermissionsTableSeeder extends Seeder
|
||||||
'edit_products',
|
'edit_products',
|
||||||
'delete_products',
|
'delete_products',
|
||||||
'access_product_categories',
|
'access_product_categories',
|
||||||
|
'access_user_management',
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($permissions as $permission) {
|
foreach ($permissions as $permission) {
|
||||||
|
@ -35,7 +36,6 @@ class PermissionsTableSeeder extends Seeder
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Assign all the permissions to Admin role
|
|
||||||
$role = Role::create([
|
$role = Role::create([
|
||||||
'name' => 'Admin'
|
'name' => 'Admin'
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\User\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Support\Renderable;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Routing\Controller;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Modules\Upload\Entities\Upload;
|
||||||
|
use Modules\User\Rules\MatchCurrentPassword;
|
||||||
|
|
||||||
|
class ProfileController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function edit() {
|
||||||
|
return view('user::profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function update(Request $request) {
|
||||||
|
$request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'email' => 'required|email|unique:users,email,' . auth()->id()
|
||||||
|
]);
|
||||||
|
|
||||||
|
auth()->user()->update([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($request->has('image')) {
|
||||||
|
if ($request->has('image')) {
|
||||||
|
$tempFile = Upload::where('folder', $request->image)->first();
|
||||||
|
|
||||||
|
if (auth()->user()->getFirstMedia('avatars')) {
|
||||||
|
auth()->user()->getFirstMedia('avatars')->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tempFile) {
|
||||||
|
auth()->user()->addMedia(Storage::path('public/temp/' . $request->image . '/' . $tempFile->filename))->toMediaCollection('avatars');
|
||||||
|
|
||||||
|
Storage::deleteDirectory('public/temp/' . $request->image);
|
||||||
|
$tempFile->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toast('Profile Updated!', 'success');
|
||||||
|
|
||||||
|
return back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePassword(Request $request) {
|
||||||
|
$request->validate([
|
||||||
|
'current_password' => ['required', 'max:255', new MatchCurrentPassword()],
|
||||||
|
'password' => 'required|min:8|max:255|confirmed'
|
||||||
|
]);
|
||||||
|
|
||||||
|
auth()->user()->update([
|
||||||
|
'password' => Hash::make($request->password)
|
||||||
|
]);
|
||||||
|
|
||||||
|
toast('Password Updated!', 'success');
|
||||||
|
|
||||||
|
return back();
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,33 +6,28 @@ use App\DataTables\RolesDataTable;
|
||||||
use Illuminate\Contracts\Support\Renderable;
|
use Illuminate\Contracts\Support\Renderable;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Routing\Controller;
|
use Illuminate\Routing\Controller;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Spatie\Permission\Models\Role;
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
class RolesController extends Controller
|
class RolesController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a listing of the resource.
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function index(RolesDataTable $dataTable) {
|
public function index(RolesDataTable $dataTable) {
|
||||||
|
abort_if(Gate::denies('access_user_management'), 403);
|
||||||
|
|
||||||
return $dataTable->render('user::roles.index');
|
return $dataTable->render('user::roles.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for creating a new resource.
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function create() {
|
public function create() {
|
||||||
|
abort_if(Gate::denies('access_user_management'), 403);
|
||||||
|
|
||||||
return view('user::roles.create');
|
return view('user::roles.create');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a newly created resource in storage.
|
|
||||||
* @param Request $request
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function store(Request $request) {
|
public function store(Request $request) {
|
||||||
|
abort_if(Gate::denies('access_user_management'), 403);
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'permissions' => 'required|array'
|
'permissions' => 'required|array'
|
||||||
|
@ -49,22 +44,17 @@ class RolesController extends Controller
|
||||||
return redirect()->route('roles.index');
|
return redirect()->route('roles.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for editing the specified resource.
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function edit(Role $role) {
|
public function edit(Role $role) {
|
||||||
|
abort_if(Gate::denies('access_user_management'), 403);
|
||||||
|
|
||||||
return view('user::roles.edit', compact('role'));
|
return view('user::roles.edit', compact('role'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the specified resource in storage.
|
|
||||||
* @param Request $request
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function update(Request $request, Role $role) {
|
public function update(Request $request, Role $role) {
|
||||||
|
abort_if(Gate::denies('access_user_management'), 403);
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'permissions' => 'required|array'
|
'permissions' => 'required|array'
|
||||||
|
@ -81,12 +71,10 @@ class RolesController extends Controller
|
||||||
return redirect()->route('roles.index');
|
return redirect()->route('roles.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the specified resource from storage.
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function destroy(Role $role) {
|
public function destroy(Role $role) {
|
||||||
|
abort_if(Gate::denies('access_user_management'), 403);
|
||||||
|
|
||||||
$role->delete();
|
$role->delete();
|
||||||
|
|
||||||
toast('Role Deleted!', 'success');
|
toast('Role Deleted!', 'success');
|
||||||
|
|
|
@ -2,73 +2,118 @@
|
||||||
|
|
||||||
namespace Modules\User\Http\Controllers;
|
namespace Modules\User\Http\Controllers;
|
||||||
|
|
||||||
|
use App\DataTables\UsersDataTable;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Contracts\Support\Renderable;
|
use Illuminate\Contracts\Support\Renderable;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Routing\Controller;
|
use Illuminate\Routing\Controller;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Modules\Upload\Entities\Upload;
|
||||||
|
|
||||||
class UsersController extends Controller
|
class UsersController extends Controller
|
||||||
{
|
{
|
||||||
|
public function index(UsersDataTable $dataTable) {
|
||||||
|
abort_if(Gate::denies('access_user_management'), 403);
|
||||||
|
|
||||||
/**
|
return $dataTable->render('user::users.index');
|
||||||
* Display a listing of the resource.
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function index() {
|
|
||||||
return view('user::users.index');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for creating a new resource.
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function create() {
|
public function create() {
|
||||||
|
abort_if(Gate::denies('access_user_management'), 403);
|
||||||
|
|
||||||
return view('user::users.create');
|
return view('user::users.create');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a newly created resource in storage.
|
|
||||||
* @param Request $request
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function store(Request $request) {
|
public function store(Request $request) {
|
||||||
//
|
abort_if(Gate::denies('access_user_management'), 403);
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'email' => 'required|email|max:255|unique:users,email',
|
||||||
|
'password' => 'required|string|min:8|max:255|confirmed'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = User::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'password' => Hash::make($request->password),
|
||||||
|
'is_active' => $request->is_active
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->assignRole($request->role);
|
||||||
|
|
||||||
|
if ($request->has('image')) {
|
||||||
|
$tempFile = Upload::where('folder', $request->image)->first();
|
||||||
|
|
||||||
|
if ($tempFile) {
|
||||||
|
$user->addMedia(Storage::path('public/temp/' . $request->image . '/' . $tempFile->filename))->toMediaCollection('avatars');
|
||||||
|
|
||||||
|
Storage::deleteDirectory('public/temp/' . $request->image);
|
||||||
|
$tempFile->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toast("User Created & Assigned '$request->role' Role!", 'success');
|
||||||
|
|
||||||
|
return redirect()->route('users.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the specified resource.
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function show(User $user) {
|
|
||||||
return view('user::users.show', compact('user'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for editing the specified resource.
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function edit(User $user) {
|
public function edit(User $user) {
|
||||||
|
abort_if(Gate::denies('access_user_management'), 403);
|
||||||
|
|
||||||
return view('user::users.edit', compact('user'));
|
return view('user::users.edit', compact('user'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the specified resource in storage.
|
|
||||||
* @param Request $request
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function update(Request $request, User $user) {
|
public function update(Request $request, User $user) {
|
||||||
//
|
abort_if(Gate::denies('access_user_management'), 403);
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'email' => 'required|email|max:255|unique:users,email,'.$user->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->update([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'is_active' => $request->is_active
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->syncRoles($request->role);
|
||||||
|
|
||||||
|
if ($request->has('image')) {
|
||||||
|
$tempFile = Upload::where('folder', $request->image)->first();
|
||||||
|
|
||||||
|
if ($user->getFirstMedia('avatars')) {
|
||||||
|
$user->getFirstMedia('avatars')->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tempFile) {
|
||||||
|
$user->addMedia(Storage::path('public/temp/' . $request->image . '/' . $tempFile->filename))->toMediaCollection('avatars');
|
||||||
|
|
||||||
|
Storage::deleteDirectory('public/temp/' . $request->image);
|
||||||
|
$tempFile->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toast("User Updated & Assigned '$request->role' Role!", 'info');
|
||||||
|
|
||||||
|
return redirect()->route('users.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the specified resource from storage.
|
|
||||||
* @param int $id
|
|
||||||
* @return Renderable
|
|
||||||
*/
|
|
||||||
public function destroy(User $user) {
|
public function destroy(User $user) {
|
||||||
//
|
abort_if(Gate::denies('access_user_management'), 403);
|
||||||
|
|
||||||
|
$user->delete();
|
||||||
|
|
||||||
|
toast('User Deleted!', 'warning');
|
||||||
|
|
||||||
|
return redirect()->route('users.index');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'User Profile')
|
||||||
|
|
||||||
|
@section('third_party_stylesheets')
|
||||||
|
@include('includes.filepond-css')
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('breadcrumb')
|
||||||
|
<ol class="breadcrumb border-0 m-0">
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('home') }}">Home</a></li>
|
||||||
|
<li class="breadcrumb-item active">Profile</li>
|
||||||
|
</ol>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
@include('utils.alerts')
|
||||||
|
<h3>Hello, <span class="text-primary">{{ auth()->user()->name }}</span></h3>
|
||||||
|
<p class="font-italic">Change your profile information & password from here...</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="{{ route('profile.update') }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
@method('patch')
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="image">Profile Image <span class="text-danger">*</span></label>
|
||||||
|
<img style="width: 100px;height: 100px;" class="d-block mx-auto img-thumbnail img-fluid rounded-circle mb-2" src="{{ auth()->user()->getFirstMediaUrl('avatars') }}" alt="Profile Image">
|
||||||
|
<input id="image" type="file" name="image" data-max-file-size="500KB">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Name <span class="text-danger">*</span></label>
|
||||||
|
<input class="form-control" type="text" name="name" required value="{{ auth()->user()->name }}">
|
||||||
|
@error('name')
|
||||||
|
<p class="text-danger">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email <span class="text-danger">*</span></label>
|
||||||
|
<input class="form-control" type="email" name="email" required value="{{ auth()->user()->email }}">
|
||||||
|
@error('email')
|
||||||
|
<p class="text-danger">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary">Update Profile <i class="bi bi-check"></i></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="{{ route('profile.update.password') }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
@method('patch')
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="current_password">Current Password <span class="text-danger">*</span></label>
|
||||||
|
<input type="password" class="form-control" name="current_password" required>
|
||||||
|
@error('current_password')
|
||||||
|
<p class="text-danger">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">New Password <span class="text-danger">*</span></label>
|
||||||
|
<input class="form-control" type="password" name="password" required>
|
||||||
|
@error('password')
|
||||||
|
<p class="text-danger">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password_confirmation">Confirm Password <span class="text-danger">*</span></label>
|
||||||
|
<input class="form-control" type="password" name="password_confirmation" required>
|
||||||
|
@error('password_confirmation')
|
||||||
|
<p class="text-danger">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary">Update Password <i class="bi bi-check"></i></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('page_scripts')
|
||||||
|
@include('includes.filepond-js')
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
|
@include('utils.alerts')
|
||||||
<form action="{{ route('roles.store') }}" method="POST">
|
<form action="{{ route('roles.store') }}" method="POST">
|
||||||
@csrf
|
@csrf
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
|
@include('utils.alerts')
|
||||||
<form action="{{ route('roles.update', $role->id) }}" method="POST">
|
<form action="{{ route('roles.update', $role->id) }}" method="POST">
|
||||||
@csrf
|
@csrf
|
||||||
@method('patch')
|
@method('patch')
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'Create User')
|
||||||
|
|
||||||
|
@section('third_party_stylesheets')
|
||||||
|
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet"/>
|
||||||
|
<link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css"
|
||||||
|
rel="stylesheet">
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('breadcrumb')
|
||||||
|
<ol class="breadcrumb border-0 m-0">
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('home') }}">Home</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('users.index') }}">Users</a></li>
|
||||||
|
<li class="breadcrumb-item active">Create</li>
|
||||||
|
</ol>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container-fluid mb-4">
|
||||||
|
<form action="{{ route('users.store') }}" method="POST" enctype="multipart/form-data">
|
||||||
|
@csrf
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
@include('utils.alerts')
|
||||||
|
<div class="form-group">
|
||||||
|
<button class="btn btn-primary">Create User <i class="bi bi-check"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Name <span class="text-danger">*</span></label>
|
||||||
|
<input class="form-control" type="text" name="name" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email <span class="text-danger">*</span></label>
|
||||||
|
<input class="form-control" type="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password <span class="text-danger">*</span></label>
|
||||||
|
<input class="form-control" type="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password_confirmation">Password <span
|
||||||
|
class="text-danger">*</span></label>
|
||||||
|
<input class="form-control" type="password" name="password_confirmation"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="role">Role <span class="text-danger">*</span></label>
|
||||||
|
<select class="form-control" name="role" id="role" required>
|
||||||
|
<option value="" selected disabled>Select Role</option>
|
||||||
|
@foreach(\Spatie\Permission\Models\Role::where('name', '!=', 'Super Admin')->get() as $role)
|
||||||
|
<option value="{{ $role->name }}">{{ $role->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="is_active">Status <span class="text-danger">*</span></label>
|
||||||
|
<select class="form-control" name="is_active" id="is_active" required>
|
||||||
|
<option value="" selected disabled>Select Status</option>
|
||||||
|
<option value="1">Active</option>
|
||||||
|
<option value="2">Deactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="image">Profile Image <span class="text-danger">*</span></label>
|
||||||
|
<input id="image" type="file" name="image" data-max-file-size="500KB">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('third_party_scripts')
|
||||||
|
<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"></script>
|
||||||
|
<script
|
||||||
|
src="https://unpkg.com/filepond-plugin-file-validate-size/dist/filepond-plugin-file-validate-size.js"></script>
|
||||||
|
<script
|
||||||
|
src="https://unpkg.com/filepond-plugin-file-validate-type/dist/filepond-plugin-file-validate-type.js"></script>
|
||||||
|
<script src="https://unpkg.com/filepond/dist/filepond.js"></script>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('page_scripts')
|
||||||
|
<script>
|
||||||
|
FilePond.registerPlugin(
|
||||||
|
FilePondPluginImagePreview,
|
||||||
|
FilePondPluginFileValidateSize,
|
||||||
|
FilePondPluginFileValidateType
|
||||||
|
);
|
||||||
|
const fileElement = document.querySelector('input[id="image"]');
|
||||||
|
const pond = FilePond.create(fileElement, {
|
||||||
|
acceptedFileTypes: ['image/png', 'image/jpg', 'image/jpeg'],
|
||||||
|
});
|
||||||
|
FilePond.setOptions({
|
||||||
|
server: {
|
||||||
|
url: "{{ route('filepond.upload') }}",
|
||||||
|
headers: {
|
||||||
|
"X-CSRF-TOKEN": "{{ csrf_token() }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'Edit User')
|
||||||
|
|
||||||
|
@section('third_party_stylesheets')
|
||||||
|
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet"/>
|
||||||
|
<link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css"
|
||||||
|
rel="stylesheet">
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('breadcrumb')
|
||||||
|
<ol class="breadcrumb border-0 m-0">
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('home') }}">Home</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('users.index') }}">Users</a></li>
|
||||||
|
<li class="breadcrumb-item active">Edit</li>
|
||||||
|
</ol>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container-fluid mb-4">
|
||||||
|
<form action="{{ route('users.update', $user->id) }}" method="POST" enctype="multipart/form-data">
|
||||||
|
@csrf
|
||||||
|
@method('patch')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
@include('utils.alerts')
|
||||||
|
<div class="form-group">
|
||||||
|
<button class="btn btn-primary">Update User <i class="bi bi-check"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Name <span class="text-danger">*</span></label>
|
||||||
|
<input class="form-control" type="text" name="name" required value="{{ $user->name }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email <span class="text-danger">*</span></label>
|
||||||
|
<input class="form-control" type="email" name="email" required value="{{ $user->email }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="role">Role <span class="text-danger">*</span></label>
|
||||||
|
<select class="form-control" name="role" id="role" required>
|
||||||
|
@foreach(\Spatie\Permission\Models\Role::where('name', '!=', 'Super Admin')->get() as $role)
|
||||||
|
<option {{ $user->hasRole($role->name) ? 'selected' : '' }} value="{{ $role->name }}">{{ $role->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="is_active">Status <span class="text-danger">*</span></label>
|
||||||
|
<select class="form-control" name="is_active" id="is_active" required>
|
||||||
|
<option value="1" {{ $user->is_active == 1 ? 'selected' : ''}}>Active</option>
|
||||||
|
<option value="2" {{ $user->is_active == 2 ? 'selected' : ''}}>Deactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="image">Profile Image <span class="text-danger">*</span></label>
|
||||||
|
<img style="width: 100px;height: 100px;" class="d-block mx-auto img-thumbnail img-fluid rounded-circle mb-2" src="{{ $user->getFirstMediaUrl('avatars') }}" alt="Profile Image">
|
||||||
|
<input id="image" type="file" name="image" data-max-file-size="500KB">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('third_party_scripts')
|
||||||
|
<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"></script>
|
||||||
|
<script
|
||||||
|
src="https://unpkg.com/filepond-plugin-file-validate-size/dist/filepond-plugin-file-validate-size.js"></script>
|
||||||
|
<script
|
||||||
|
src="https://unpkg.com/filepond-plugin-file-validate-type/dist/filepond-plugin-file-validate-type.js"></script>
|
||||||
|
<script src="https://unpkg.com/filepond/dist/filepond.js"></script>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('page_scripts')
|
||||||
|
<script>
|
||||||
|
FilePond.registerPlugin(
|
||||||
|
FilePondPluginImagePreview,
|
||||||
|
FilePondPluginFileValidateSize,
|
||||||
|
FilePondPluginFileValidateType
|
||||||
|
);
|
||||||
|
const fileElement = document.querySelector('input[id="image"]');
|
||||||
|
const pond = FilePond.create(fileElement, {
|
||||||
|
acceptedFileTypes: ['image/png', 'image/jpg', 'image/jpeg'],
|
||||||
|
});
|
||||||
|
FilePond.setOptions({
|
||||||
|
server: {
|
||||||
|
url: "{{ route('filepond.upload') }}",
|
||||||
|
headers: {
|
||||||
|
"X-CSRF-TOKEN": "{{ csrf_token() }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('title', 'Users')
|
||||||
|
|
||||||
|
@section('third_party_stylesheets')
|
||||||
|
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.25/css/dataTables.bootstrap4.min.css">
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('breadcrumb')
|
||||||
|
<ol class="breadcrumb border-0 m-0">
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('home') }}">Home</a></li>
|
||||||
|
<li class="breadcrumb-item active">Users</li>
|
||||||
|
</ol>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Button trigger modal -->
|
||||||
|
<a href="{{ route('users.create') }}" class="btn btn-primary">
|
||||||
|
Add User <i class="bi bi-plus"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
{!! $dataTable->table() !!}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('page_scripts')
|
||||||
|
{!! $dataTable->scripts() !!}
|
||||||
|
@endpush
|
|
@ -0,0 +1,15 @@
|
||||||
|
<a href="{{ route('users.edit', $data->id) }}" class="btn btn-info btn-sm">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</a>
|
||||||
|
<button id="delete" class="btn btn-danger btn-sm" onclick="
|
||||||
|
event.preventDefault();
|
||||||
|
if (confirm('Are you sure? It will delete the data permanently!')) {
|
||||||
|
document.getElementById('destroy{{ $data->id }}').submit();
|
||||||
|
}
|
||||||
|
">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
<form id="destroy{{ $data->id }}" class="d-none" action="{{ route('users.destroy', $data->id) }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
@method('delete')
|
||||||
|
</form>
|
||||||
|
</button>
|
|
@ -0,0 +1,3 @@
|
||||||
|
@foreach($roles as $role)
|
||||||
|
<span class="badge badge-primary">{{ $role }}</span>
|
||||||
|
@endforeach
|
|
@ -8,8 +8,12 @@
|
||||||
|
|
||||||
Route::group(['middleware' => 'auth'], function () {
|
Route::group(['middleware' => 'auth'], function () {
|
||||||
|
|
||||||
|
//User Profile
|
||||||
|
Route::get('/user/profile', 'ProfileController@edit')->name('profile.edit');
|
||||||
|
Route::patch('/user/profile', 'ProfileController@update')->name('profile.update');
|
||||||
|
Route::patch('/user/password', 'ProfileController@updatePassword')->name('profile.update.password');
|
||||||
//Users
|
//Users
|
||||||
Route::resource('users', 'UsersController');
|
Route::resource('users', 'UsersController')->except('show');
|
||||||
//Roles
|
//Roles
|
||||||
Route::resource('roles', 'RolesController')->except('show');
|
Route::resource('roles', 'RolesController')->except('show');
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\User\Rules;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\Rule;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
class MatchCurrentPassword implements Rule
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new rule instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the validation rule passes.
|
||||||
|
*
|
||||||
|
* @param string $attribute
|
||||||
|
* @param mixed $value
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function passes($attribute, $value)
|
||||||
|
{
|
||||||
|
return Hash::check($value, auth()->user()->password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation error message.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function message()
|
||||||
|
{
|
||||||
|
return __('validation.current_password');
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,7 +42,7 @@ class RolesDataTable extends DataTable
|
||||||
{
|
{
|
||||||
return $model->newQuery()->with(['permissions' => function($query) {
|
return $model->newQuery()->with(['permissions' => function($query) {
|
||||||
$query->select('name')->get();
|
$query->select('name')->get();
|
||||||
}]);
|
}])->where('name', '!=', 'Super Admin');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,9 +21,27 @@ class UsersDataTable extends DataTable
|
||||||
{
|
{
|
||||||
return datatables()
|
return datatables()
|
||||||
->eloquent($query)
|
->eloquent($query)
|
||||||
|
->addColumn('role', function ($data) {
|
||||||
|
return view('user::users.partials.roles', [
|
||||||
|
'roles' => $data->getRoleNames()
|
||||||
|
]);
|
||||||
|
})
|
||||||
->addColumn('action', function ($data) {
|
->addColumn('action', function ($data) {
|
||||||
return view('user::users.partials.actions', compact('data'));
|
return view('user::users.partials.actions', compact('data'));
|
||||||
});
|
})
|
||||||
|
->addColumn('status', function ($data) {
|
||||||
|
if ($data->is_active == 1) {
|
||||||
|
$html = '<span class="badge badge-success">Active</span>';
|
||||||
|
} else {
|
||||||
|
$html = '<span class="badge badge-warning">Deactivated</span>';
|
||||||
|
}
|
||||||
|
return $html;
|
||||||
|
})
|
||||||
|
->addColumn('image', function ($data) {
|
||||||
|
$url = $data->getFirstMediaUrl('avatars');
|
||||||
|
return '<img src="'.$url.'" style="width:50px;height:50px;" class="img-thumbnail rounded-circle"/>';
|
||||||
|
})
|
||||||
|
->rawColumns(['image', 'status']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,7 +52,11 @@ class UsersDataTable extends DataTable
|
||||||
*/
|
*/
|
||||||
public function query(User $model)
|
public function query(User $model)
|
||||||
{
|
{
|
||||||
return $model->newQuery();
|
return $model->newQuery()
|
||||||
|
->with(['roles' => function ($query) {
|
||||||
|
$query->select('name')->get();
|
||||||
|
}])
|
||||||
|
->where('id', '!=', auth()->id());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,13 +94,25 @@ class UsersDataTable extends DataTable
|
||||||
protected function getColumns()
|
protected function getColumns()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Column::make('id'),
|
Column::computed('image')
|
||||||
Column::make('created_at'),
|
->className('text-center align-middle'),
|
||||||
Column::make('updated_at'),
|
|
||||||
|
Column::make('name')
|
||||||
|
->className('text-center align-middle'),
|
||||||
|
|
||||||
|
Column::make('email')
|
||||||
|
->className('text-center align-middle'),
|
||||||
|
|
||||||
|
Column::computed('role')
|
||||||
|
->className('text-center align-middle'),
|
||||||
|
|
||||||
|
Column::computed('status')
|
||||||
|
->className('text-center align-middle'),
|
||||||
|
|
||||||
Column::computed('action')
|
Column::computed('action')
|
||||||
->exportable(false)
|
->exportable(false)
|
||||||
->printable(false)
|
->printable(false)
|
||||||
->addClass('text-center'),
|
->className('text-center align-middle'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ namespace App\Http\Controllers\Auth;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Providers\RouteServiceProvider;
|
use App\Providers\RouteServiceProvider;
|
||||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
class LoginController extends Controller
|
class LoginController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -37,4 +39,16 @@ class LoginController extends Controller
|
||||||
{
|
{
|
||||||
$this->middleware('guest')->except('logout');
|
$this->middleware('guest')->except('logout');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function authenticated(Request $request, $user) {
|
||||||
|
if ($user->is_active != 1) {
|
||||||
|
Auth::logout();
|
||||||
|
|
||||||
|
return back()->with([
|
||||||
|
'account_deactivated' => 'Your account is deactivated! Please contact with Super Admin.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next($request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,10 +64,15 @@ class RegisterController extends Controller
|
||||||
*/
|
*/
|
||||||
protected function create(array $data)
|
protected function create(array $data)
|
||||||
{
|
{
|
||||||
return User::create([
|
$user = User::create([
|
||||||
'name' => $data['name'],
|
'name' => $data['name'],
|
||||||
'email' => $data['email'],
|
'email' => $data['email'],
|
||||||
'password' => Hash::make($data['password']),
|
'password' => Hash::make($data['password']),
|
||||||
|
'is_active' => 1
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$user->assignRole('Admin');
|
||||||
|
|
||||||
|
return $user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,18 @@
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Spatie\MediaLibrary\HasMedia;
|
||||||
|
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||||
|
use Spatie\MediaLibrary\MediaCollections\File;
|
||||||
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
|
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable implements HasMedia
|
||||||
{
|
{
|
||||||
use HasFactory, Notifiable;
|
use HasFactory, Notifiable, HasRoles, InteractsWithMedia;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
|
@ -20,6 +25,7 @@ class User extends Authenticatable
|
||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
'password',
|
'password',
|
||||||
|
'is_active'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,4 +46,16 @@ class User extends Authenticatable
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $with = ['media'];
|
||||||
|
|
||||||
|
public function registerMediaCollections(): void
|
||||||
|
{
|
||||||
|
$this->addMediaCollection('avatars')
|
||||||
|
->useFallbackUrl('/images/fallback_profile_image.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeIsActive(Builder $builder) {
|
||||||
|
return $builder->where('is_active', 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ class AuthServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
$this->registerPolicies();
|
$this->registerPolicies();
|
||||||
|
|
||||||
//
|
Gate::before(function ($user, $ability) {
|
||||||
|
return $user->hasRole('Super Admin') ? true : null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
/*
|
||||||
|
* DataTables JavaScript global namespace.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'namespace' => 'LaravelDataTables',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default table attributes when generating the table.
|
||||||
|
*/
|
||||||
|
'table' => [
|
||||||
|
'class' => 'table table-bordered',
|
||||||
|
'id' => 'dataTableBuilder',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default condition to determine if a parameter is a callback or not.
|
||||||
|
* Callbacks needs to start by those terms or they will be casted to string.
|
||||||
|
*/
|
||||||
|
'callback' => ['$', '$.', 'function'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Html builder script template.
|
||||||
|
*/
|
||||||
|
'script' => 'datatables::script',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Html builder script template for DataTables Editor integration.
|
||||||
|
*/
|
||||||
|
'editor' => 'datatables::editor',
|
||||||
|
];
|
|
@ -0,0 +1,122 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
/*
|
||||||
|
* DataTables search options.
|
||||||
|
*/
|
||||||
|
'search' => [
|
||||||
|
/*
|
||||||
|
* Smart search will enclose search keyword with wildcard string "%keyword%".
|
||||||
|
* SQL: column LIKE "%keyword%"
|
||||||
|
*/
|
||||||
|
'smart' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Multi-term search will explode search keyword using spaces resulting into multiple term search.
|
||||||
|
*/
|
||||||
|
'multi_term' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Case insensitive will search the keyword in lower case format.
|
||||||
|
* SQL: LOWER(column) LIKE LOWER(keyword)
|
||||||
|
*/
|
||||||
|
'case_insensitive' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wild card will add "%" in between every characters of the keyword.
|
||||||
|
* SQL: column LIKE "%k%e%y%w%o%r%d%"
|
||||||
|
*/
|
||||||
|
'use_wildcards' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Perform a search which starts with the given keyword.
|
||||||
|
* SQL: column LIKE "keyword%"
|
||||||
|
*/
|
||||||
|
'starts_with' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DataTables internal index id response column name.
|
||||||
|
*/
|
||||||
|
'index_column' => 'DT_RowIndex',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List of available builders for DataTables.
|
||||||
|
* This is where you can register your custom dataTables builder.
|
||||||
|
*/
|
||||||
|
'engines' => [
|
||||||
|
'eloquent' => Yajra\DataTables\EloquentDataTable::class,
|
||||||
|
'query' => Yajra\DataTables\QueryDataTable::class,
|
||||||
|
'collection' => Yajra\DataTables\CollectionDataTable::class,
|
||||||
|
'resource' => Yajra\DataTables\ApiResourceDataTable::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DataTables accepted builder to engine mapping.
|
||||||
|
* This is where you can override which engine a builder should use
|
||||||
|
* Note, only change this if you know what you are doing!
|
||||||
|
*/
|
||||||
|
'builders' => [
|
||||||
|
//Illuminate\Database\Eloquent\Relations\Relation::class => 'eloquent',
|
||||||
|
//Illuminate\Database\Eloquent\Builder::class => 'eloquent',
|
||||||
|
//Illuminate\Database\Query\Builder::class => 'query',
|
||||||
|
//Illuminate\Support\Collection::class => 'collection',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Nulls last sql pattern for PostgreSQL & Oracle.
|
||||||
|
* For MySQL, use 'CASE WHEN :column IS NULL THEN 1 ELSE 0 END, :column :direction'
|
||||||
|
*/
|
||||||
|
'nulls_last_sql' => ':column :direction NULLS LAST',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* User friendly message to be displayed on user if error occurs.
|
||||||
|
* Possible values:
|
||||||
|
* null - The exception message will be used on error response.
|
||||||
|
* 'throw' - Throws a \Yajra\DataTables\Exceptions\Exception. Use your custom error handler if needed.
|
||||||
|
* 'custom message' - Any friendly message to be displayed to the user. You can also use translation key.
|
||||||
|
*/
|
||||||
|
'error' => env('DATATABLES_ERROR', null),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default columns definition of dataTable utility functions.
|
||||||
|
*/
|
||||||
|
'columns' => [
|
||||||
|
/*
|
||||||
|
* List of columns hidden/removed on json response.
|
||||||
|
*/
|
||||||
|
'excess' => ['rn', 'row_num'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List of columns to be escaped. If set to *, all columns are escape.
|
||||||
|
* Note: You can set the value to empty array to disable XSS protection.
|
||||||
|
*/
|
||||||
|
'escape' => '*',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List of columns that are allowed to display html content.
|
||||||
|
* Note: Adding columns to list will make us available to XSS attacks.
|
||||||
|
*/
|
||||||
|
'raw' => ['action'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List of columns are are forbidden from being searched/sorted.
|
||||||
|
*/
|
||||||
|
'blacklist' => ['password', 'remember_token'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List of columns that are only allowed fo search/sort.
|
||||||
|
* If set to *, all columns are allowed.
|
||||||
|
*/
|
||||||
|
'whitelist' => '*',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* JsonResponse header and options config.
|
||||||
|
*/
|
||||||
|
'json' => [
|
||||||
|
'header' => [],
|
||||||
|
'options' => 0,
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
|
@ -19,6 +19,7 @@ class CreateUsersTable extends Migration
|
||||||
$table->string('email')->unique();
|
$table->string('email')->unique();
|
||||||
$table->timestamp('email_verified_at')->nullable();
|
$table->timestamp('email_verified_at')->nullable();
|
||||||
$table->string('password');
|
$table->string('password');
|
||||||
|
$table->boolean('is_active');
|
||||||
$table->rememberToken();
|
$table->rememberToken();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
use Modules\User\Database\Seeders\PermissionsTableSeeder;
|
||||||
|
|
||||||
class DatabaseSeeder extends Seeder
|
class DatabaseSeeder extends Seeder
|
||||||
{
|
{
|
||||||
|
@ -13,6 +14,7 @@ class DatabaseSeeder extends Seeder
|
||||||
*/
|
*/
|
||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
// \App\Models\User::factory(10)->create();
|
$this->call(PermissionsTableSeeder::class);
|
||||||
|
$this->call(SuperUserSeeder::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
|
class SuperUserSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$user = User::create([
|
||||||
|
'name' => 'Administrator',
|
||||||
|
'email' => 'admin@gmail.com',
|
||||||
|
'password' => Hash::make(12345678)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$superAdmin = Role::create([
|
||||||
|
'name' => 'Super Admin'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->assignRole($superAdmin);
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 177 KiB |
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
|
@ -0,0 +1,6 @@
|
||||||
|
/******/ (() => { // webpackBootstrap
|
||||||
|
/******/ "use strict";
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ })()
|
||||||
|
;
|
|
@ -11,18 +11,19 @@
|
||||||
|
|
||||||
<!-- CoreUI CSS -->
|
<!-- CoreUI CSS -->
|
||||||
<link rel="stylesheet" href="{{ mix('css/app.css') }}" crossorigin="anonymous">
|
<link rel="stylesheet" href="{{ mix('css/app.css') }}" crossorigin="anonymous">
|
||||||
|
<!-- Bootstrap Icons -->
|
||||||
<!-- Font Awesome -->
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css"
|
|
||||||
integrity="sha512-1PKOgIY59xJ8Co8+NE6FZ+LOAZKjy+KY8iq0G4B3CyeY6wYHN3yt9PW0XpSriVlkMXe40PTKnXrLnZ9+fkDaog=="
|
|
||||||
crossorigin="anonymous"/>
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="c-app flex-row align-items-center">
|
<body class="c-app flex-row align-items-center">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
|
@if(Session::has('account_deactivated'))
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
{{ Session::get('account_deactivated') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
<div class="card-group">
|
<div class="card-group">
|
||||||
<div class="card p-4">
|
<div class="card p-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
@ -33,7 +34,7 @@
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text">
|
||||||
<i class="cil-user"></i>
|
<i class="bi bi-person"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="email" class="form-control @error('email') is-invalid @enderror"
|
<input type="email" class="form-control @error('email') is-invalid @enderror"
|
||||||
|
@ -46,7 +47,7 @@
|
||||||
<div class="input-group mb-4">
|
<div class="input-group mb-4">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text">
|
||||||
<i class="cil-lock-locked"></i>
|
<i class="bi bi-lock"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="password"
|
<input type="password"
|
||||||
|
|
|
@ -11,11 +11,8 @@
|
||||||
|
|
||||||
<!-- CoreUI CSS -->
|
<!-- CoreUI CSS -->
|
||||||
<link rel="stylesheet" href="{{ mix('css/app.css') }}" crossorigin="anonymous">
|
<link rel="stylesheet" href="{{ mix('css/app.css') }}" crossorigin="anonymous">
|
||||||
|
<!-- Bootstrap Icons -->
|
||||||
<!-- Font Awesome -->
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css"
|
|
||||||
integrity="sha512-1PKOgIY59xJ8Co8+NE6FZ+LOAZKjy+KY8iq0G4B3CyeY6wYHN3yt9PW0XpSriVlkMXe40PTKnXrLnZ9+fkDaog=="
|
|
||||||
crossorigin="anonymous"/>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="c-app flex-row align-items-center">
|
<body class="c-app flex-row align-items-center">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -30,7 +27,7 @@
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text">
|
||||||
<i class="cil-user"></i>
|
<i class="bi bi-person"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="form-control @error('name') is-invalid @enderror"
|
<input type="text" class="form-control @error('name') is-invalid @enderror"
|
||||||
|
@ -43,7 +40,7 @@
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text">
|
||||||
<i class="cil-envelope-open"></i>
|
<i class="bi bi-envelope"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="email" class="form-control @error('email') is-invalid @enderror"
|
<input type="email" class="form-control @error('email') is-invalid @enderror"
|
||||||
|
@ -55,7 +52,7 @@
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text">
|
||||||
<i class="cil-lock-locked"></i>
|
<i class="bi bi-lock"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" class="form-control @error('password') is-invalid @enderror"
|
<input type="password" class="form-control @error('password') is-invalid @enderror"
|
||||||
|
@ -67,14 +64,14 @@
|
||||||
<div class="input-group mb-4">
|
<div class="input-group mb-4">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text">
|
||||||
<i class="cil-lock-locked"></i>
|
<i class="bi bi-lock"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" name="password_confirmation" class="form-control"
|
<input type="password" name="password_confirmation" class="form-control"
|
||||||
placeholder="Confirm password">
|
placeholder="Confirm password">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-flat">Register</button>
|
<button type="submit" class="btn btn-primary btn-block btn-flat mb-3">Register</button>
|
||||||
<a href="{{ route('login') }}" class="text-center">I already have a membership</a>
|
<a href="{{ route('login') }}" class="text-center">I already have a membership.</a>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet"/>
|
||||||
|
<link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" rel="stylesheet">
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"></script>
|
||||||
|
<script src="https://unpkg.com/filepond-plugin-file-validate-size/dist/filepond-plugin-file-validate-size.js"></script>
|
||||||
|
<script src="https://unpkg.com/filepond-plugin-file-validate-type/dist/filepond-plugin-file-validate-type.js"></script>
|
||||||
|
<script src="https://unpkg.com/filepond/dist/filepond.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
FilePond.registerPlugin(
|
||||||
|
FilePondPluginImagePreview,
|
||||||
|
FilePondPluginFileValidateSize,
|
||||||
|
FilePondPluginFileValidateType
|
||||||
|
);
|
||||||
|
const fileElement = document.querySelector('input[id="image"]');
|
||||||
|
const pond = FilePond.create(fileElement, {
|
||||||
|
acceptedFileTypes: ['image/png', 'image/jpg', 'image/jpeg'],
|
||||||
|
});
|
||||||
|
FilePond.setOptions({
|
||||||
|
server: {
|
||||||
|
process: "{{ route('filepond.upload') }}",
|
||||||
|
revert: "{{ route('filepond.delete') }}",
|
||||||
|
headers: {
|
||||||
|
"X-CSRF-TOKEN": "{{ csrf_token() }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -1,4 +1,4 @@
|
||||||
<footer class="c-footer">
|
<footer class="c-footer">
|
||||||
<div><a href="#">Triangle POS</a> © {{ date('Y') }} Developed by <strong>Fahim Anzam</strong>.</div>
|
<div><a href="#">Triangle POS</a> © {{ date('Y') }} Developed by <strong><a target="_blank" href="https://fahim.codes">Fahim Anzam</a></strong>.</div>
|
||||||
<div class="mfs-auto d-md-down-none">Powered by <a href="https://coreui.io/">CoreUI</a></div>
|
<div class="mfs-auto d-md-down-none">Powered by <a href="https://coreui.io/">CoreUI</a></div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
@ -21,12 +21,12 @@
|
||||||
aria-haspopup="true" aria-expanded="false">
|
aria-haspopup="true" aria-expanded="false">
|
||||||
<span class="mr-2 font-weight-bold">{{ auth()->user()->name }}</span>
|
<span class="mr-2 font-weight-bold">{{ auth()->user()->name }}</span>
|
||||||
<div class="c-avatar">
|
<div class="c-avatar">
|
||||||
<img class="c-avatar-img border border-primary" src="https://www.gravatar.com/avatar/00000000000000000000000000000000" alt="">
|
<img class="c-avatar rounded-circle" src="{{ auth()->user()->getFirstMediaUrl('avatars') }}" alt="Profile Image">
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-right pt-0">
|
<div class="dropdown-menu dropdown-menu-right pt-0">
|
||||||
<div class="dropdown-header bg-light py-2"><strong>Account</strong></div>
|
<div class="dropdown-header bg-light py-2"><strong>Account</strong></div>
|
||||||
<a class="dropdown-item" href="#">
|
<a class="dropdown-item" href="{{ route('profile.edit') }}">
|
||||||
<i class="mfe-2 bi bi-person" style="font-size: 1.2rem;"></i> Profile
|
<i class="mfe-2 bi bi-person" style="font-size: 1.2rem;"></i> Profile
|
||||||
</a>
|
</a>
|
||||||
<a class="dropdown-item" href="#" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
|
<a class="dropdown-item" href="#" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
|
||||||
|
|
|
@ -4,21 +4,26 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@can('access_products')
|
||||||
<li class="c-sidebar-nav-item c-sidebar-nav-dropdown {{ request()->routeIs('products.*') || request()->routeIs('product-categories.*') ? 'c-show' : '' }}">
|
<li class="c-sidebar-nav-item c-sidebar-nav-dropdown {{ request()->routeIs('products.*') || request()->routeIs('product-categories.*') ? 'c-show' : '' }}">
|
||||||
<a class="c-sidebar-nav-link c-sidebar-nav-dropdown-toggle" href="#">
|
<a class="c-sidebar-nav-link c-sidebar-nav-dropdown-toggle" href="#">
|
||||||
<i class="c-sidebar-nav-icon bi bi-journal-bookmark" style="line-height: 1;"></i> Products
|
<i class="c-sidebar-nav-icon bi bi-journal-bookmark" style="line-height: 1;"></i> Products
|
||||||
</a>
|
</a>
|
||||||
<ul class="c-sidebar-nav-dropdown-items">
|
<ul class="c-sidebar-nav-dropdown-items">
|
||||||
|
@can('access_product_categories')
|
||||||
<li class="c-sidebar-nav-item">
|
<li class="c-sidebar-nav-item">
|
||||||
<a class="c-sidebar-nav-link {{ request()->routeIs('product-categories.*') ? 'c-active' : '' }}" href="{{ route('product-categories.index') }}">
|
<a class="c-sidebar-nav-link {{ request()->routeIs('product-categories.*') ? 'c-active' : '' }}" href="{{ route('product-categories.index') }}">
|
||||||
<i class="c-sidebar-nav-icon bi bi-collection" style="line-height: 1;"></i> Categories
|
<i class="c-sidebar-nav-icon bi bi-collection" style="line-height: 1;"></i> Categories
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@endcan
|
||||||
|
@can('create_products')
|
||||||
<li class="c-sidebar-nav-item">
|
<li class="c-sidebar-nav-item">
|
||||||
<a class="c-sidebar-nav-link {{ request()->routeIs('products.create') ? 'c-active' : '' }}" href="{{ route('products.create') }}">
|
<a class="c-sidebar-nav-link {{ request()->routeIs('products.create') ? 'c-active' : '' }}" href="{{ route('products.create') }}">
|
||||||
<i class="c-sidebar-nav-icon bi bi-journal-plus" style="line-height: 1;"></i> Create Product
|
<i class="c-sidebar-nav-icon bi bi-journal-plus" style="line-height: 1;"></i> Create Product
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@endcan
|
||||||
<li class="c-sidebar-nav-item">
|
<li class="c-sidebar-nav-item">
|
||||||
<a class="c-sidebar-nav-link {{ request()->routeIs('products.index') ? 'c-active' : '' }}" href="{{ route('products.index') }}">
|
<a class="c-sidebar-nav-link {{ request()->routeIs('products.index') ? 'c-active' : '' }}" href="{{ route('products.index') }}">
|
||||||
<i class="c-sidebar-nav-icon bi bi-journals" style="line-height: 1;"></i> All Products
|
<i class="c-sidebar-nav-icon bi bi-journals" style="line-height: 1;"></i> All Products
|
||||||
|
@ -26,7 +31,9 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
@can('access_user_management')
|
||||||
<li class="c-sidebar-nav-item c-sidebar-nav-dropdown {{ request()->routeIs('roles*') ? 'c-show' : '' }}">
|
<li class="c-sidebar-nav-item c-sidebar-nav-dropdown {{ request()->routeIs('roles*') ? 'c-show' : '' }}">
|
||||||
<a class="c-sidebar-nav-link c-sidebar-nav-dropdown-toggle" href="#">
|
<a class="c-sidebar-nav-link c-sidebar-nav-dropdown-toggle" href="#">
|
||||||
<i class="c-sidebar-nav-icon bi bi-people" style="line-height: 1;"></i> User Management
|
<i class="c-sidebar-nav-icon bi bi-people" style="line-height: 1;"></i> User Management
|
||||||
|
@ -49,3 +56,4 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
@endcan
|
||||||
|
|
Loading…
Reference in New Issue