feat: assign many users to many tenants + tenant creation
This commit is contained in:
parent
0eed72ed89
commit
ee8106e679
21 changed files with 567 additions and 82 deletions
|
|
@ -3,7 +3,6 @@
|
|||
namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
|
|
@ -80,7 +79,6 @@ class UserCreate extends Command
|
|||
'role' => $this->role,
|
||||
'published' => true,
|
||||
'published_at' => Carbon::now(),
|
||||
'tenant_id' => Tenant::query()->first()->id,
|
||||
]);
|
||||
$user->saveOrFail();
|
||||
|
||||
|
|
|
|||
119
app/Http/Controllers/Bo/TenantController.php
Normal file
119
app/Http/Controllers/Bo/TenantController.php
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\Tenants\StoreTenantRequest;
|
||||
use App\Models\Settings;
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class TenantController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create the controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->authorizeResource(Tenant::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index(Request $request): View
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $query */
|
||||
$query = Tenant::query();
|
||||
|
||||
/** @var string $search Search field */
|
||||
$search = $request->input('search');
|
||||
if ($search) {
|
||||
$this->searchQuery(
|
||||
$query,
|
||||
$search,
|
||||
null,
|
||||
'name',
|
||||
);
|
||||
}
|
||||
$searchFields = \implode(', ', [
|
||||
trans('validation.attributes.name'),
|
||||
]);
|
||||
|
||||
/** Sort columns with a query */
|
||||
$this->sortQuery($query);
|
||||
|
||||
/** Custom pagination */
|
||||
$tenantModelList = $this->paginate($query);
|
||||
|
||||
return view('bo.pages.tenants.index', compact('tenantModelList', 'search', 'searchFields'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function create(): \Illuminate\Contracts\View\View
|
||||
{
|
||||
return view('bo.pages.tenants.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Tenants\StoreTenantRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(StoreTenantRequest $request): RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request) {
|
||||
$tenant = new Tenant();
|
||||
$tenant->fill($request->validated());
|
||||
|
||||
$settings = new Settings();
|
||||
|
||||
if ($tenant->saveOrFail()) {
|
||||
return redirect()->route('bo.tenants.index', ['tenant' => \tenant()])
|
||||
->with('success', __('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.tenant', 1))
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', __('crud.messages.cannot_be_created', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.tenant', 1))
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Tenant $tenant
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Tenant $tenant): RedirectResponse
|
||||
{
|
||||
// FIXME: c pt
|
||||
/** @var array<string,string> */
|
||||
$previousQueries = [];
|
||||
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
|
||||
if ($tenant->deleteOrFail()) {
|
||||
return redirect()->route('bo.tenants.index', array_merge(['tenant' => \tenant('id')], $previousQueries))
|
||||
->with('success', __('crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.tenant', 1))
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', __('crud.messages.cannot_be_deleted', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.tenant', 1))
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ class Authenticate extends Middleware
|
|||
protected function redirectTo(Request $request): ?string
|
||||
{
|
||||
$logins = [
|
||||
'backend' => route('bo.login', ['tenant' => Tenant::query()->first()->id])
|
||||
'backend' => route('bo.login')
|
||||
];
|
||||
if (!$request->expectsJson()) {
|
||||
// * Notify must login
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\Tenant;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
|
@ -20,8 +21,18 @@ class RestrictTenantAccess
|
|||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$user = \auth('backend')->user();
|
||||
if ($user->tenant_id !== $request->tenant && !(RoleEnum::conceptor->equals($user->role))) {
|
||||
abort(403);
|
||||
|
||||
if ($user) {
|
||||
if ($request->routeIs('bo.tenants.*')) {
|
||||
tenancy()->initialize(Tenant::firstOrFail());
|
||||
return $next($request);
|
||||
}
|
||||
if (
|
||||
!\in_array($request->tenant, $user->tenants->pluck('id')->toArray()) &&
|
||||
!(RoleEnum::conceptor->equals($user->role))
|
||||
) {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
|
|
|
|||
32
app/Http/Requests/Bo/Tenants/StoreTenantRequest.php
Normal file
32
app/Http/Requests/Bo/Tenants/StoreTenantRequest.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Tenants;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class StoreTenantRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Gate::allows('create', Tenant::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|min:2|max:255',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
|
||||
use Stancl\Tenancy\Contracts\Tenant as TenantContract;
|
||||
use Illuminate\Support\Str;
|
||||
|
|
@ -14,7 +15,10 @@ class Tenant extends BaseTenant implements TenantContract
|
|||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = ['id'];
|
||||
protected $fillable = [
|
||||
'id',
|
||||
'name',
|
||||
];
|
||||
|
||||
/**
|
||||
* Define the custom columns (that won't be stored in the data JSON column)
|
||||
|
|
@ -71,4 +75,16 @@ class Tenant extends BaseTenant implements TenantContract
|
|||
ToolboxHelper::assertFieldIsUnique($table, 'name', $name, $id);
|
||||
ToolboxHelper::assertFieldIsUnique($table, 'id', Str::slug($name), $id);
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* The tenants that belong to the user.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ use App\Enums\Users\RoleEnum;
|
|||
use App\Traits\Models\ActivityLog;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
|
||||
|
||||
/**
|
||||
* @property string $first_name Firstname.
|
||||
|
|
@ -27,7 +27,6 @@ class User extends Authenticatable
|
|||
use ActivityLog;
|
||||
use HasFactory;
|
||||
use Notifiable;
|
||||
use BelongsToTenant;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
|
@ -94,4 +93,16 @@ class User extends Authenticatable
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* The tenants that belong to the user.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function tenants(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Tenant::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
59
app/Policies/TenantPolicy.php
Normal file
59
app/Policies/TenantPolicy.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\UserStaticRules;
|
||||
|
||||
class TenantPolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @return boolean
|
||||
*/
|
||||
public function viewAny(User $authUser): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||
}
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @return boolean
|
||||
*/
|
||||
public function create(User $authUser): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @param \App\Models\Tenant $tenant
|
||||
* @return boolean
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
*/
|
||||
public function delete(User $authUser, \App\Models\Tenant $tenant): bool
|
||||
{
|
||||
// phpcs:enable
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @param \App\Models\Tenant $tenant
|
||||
* @return boolean
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
*/
|
||||
public function forceDelete(User $authUser, \App\Models\Tenant $tenant): bool
|
||||
{
|
||||
// phpcs:enable
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ use App\Models\Font;
|
|||
use App\Models\Product;
|
||||
use App\Models\Range;
|
||||
use App\Models\Settings;
|
||||
use App\Models\User;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -53,7 +52,6 @@ class RouteServiceProvider extends ServiceProvider
|
|||
'product' => Product::class,
|
||||
'range' => Range::class,
|
||||
'settings' => Settings::class,
|
||||
'user' => User::class,
|
||||
];
|
||||
|
||||
foreach ($tenantRelatedModels as $key => $tenantRelatedModel) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use App\Models\Settings;
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
|
@ -39,6 +40,8 @@ class ViewServiceProvider extends ServiceProvider
|
|||
View::composer('*', function () {
|
||||
View::share('settingsGlobal', Settings::getCached()->where('tenant_id', tenant('id'))->first());
|
||||
});
|
||||
|
||||
View::share('tenantGlobal', Tenant::all());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,35 +13,31 @@ return new class extends Migration
|
|||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('activity_logs', function (Blueprint $table) {
|
||||
$table->string('tenant_id')->index()->after('data');
|
||||
});
|
||||
$tables = [
|
||||
['name' => 'activity_logs', 'after' => 'data'],
|
||||
['name' => 'brands', 'after' => 'status'],
|
||||
['name' => 'fonts', 'after' => 'font'],
|
||||
['name' => 'products', 'after' => 'range_id'],
|
||||
['name' => 'ranges', 'after' => 'font_id'],
|
||||
['name' => 'settings', 'after' => 'home_timeout'],
|
||||
];
|
||||
|
||||
Schema::table('brands', function (Blueprint $table) {
|
||||
$table->string('tenant_id')->index()->after('status');
|
||||
});
|
||||
foreach ($tables as $entry) {
|
||||
$tableName = $entry['name'];
|
||||
$after = $entry['after'];
|
||||
|
||||
Schema::table('fonts', function (Blueprint $table) {
|
||||
$table->string('tenant_id')->index()->after('font');
|
||||
});
|
||||
Schema::table($tableName, function (Blueprint $table) use ($after) {
|
||||
$table->string('tenant_id')->index()->after($after)->comment('Tenant reference.');
|
||||
});
|
||||
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->string('tenant_id')->index()->after('range_id');
|
||||
});
|
||||
|
||||
Schema::table('ranges', function (Blueprint $table) {
|
||||
$table->string('tenant_id')->index()->after('font_id');
|
||||
});
|
||||
|
||||
Schema::table('settings', function (Blueprint $table) {
|
||||
$table->string('tenant_id')->index()->after('home_timeout');
|
||||
});
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('tenant_id')->index()->after('published');
|
||||
$table->dropUnique(['email']);
|
||||
$table->unique(['tenant_id', 'email']);
|
||||
});
|
||||
Schema::table($tableName, function (Blueprint $table) use ($tableName) {
|
||||
$table->foreign('tenant_id', $tableName . '_tenant_id_foreign')
|
||||
->references('id')
|
||||
->on('tenants')
|
||||
->onUpdate('cascade')
|
||||
->onDelete('cascade');
|
||||
});
|
||||
} //end foreach
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -51,29 +47,20 @@ return new class extends Migration
|
|||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('activity_logs', function (Blueprint $table) {
|
||||
$table->dropColumn('tenant_id');
|
||||
});
|
||||
$tables = [
|
||||
'activity_logs',
|
||||
'brands',
|
||||
'fonts',
|
||||
'products',
|
||||
'ranges',
|
||||
'settings',
|
||||
];
|
||||
|
||||
Schema::table('brands', function (Blueprint $table) {
|
||||
$table->dropColumn('tenant_id');
|
||||
});
|
||||
|
||||
Schema::table('fonts', function (Blueprint $table) {
|
||||
$table->dropColumn('tenant_id');
|
||||
});
|
||||
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->dropColumn('tenant_id');
|
||||
});
|
||||
|
||||
Schema::table('ranges', function (Blueprint $table) {
|
||||
$table->dropColumn('tenant_id');
|
||||
});
|
||||
|
||||
Schema::table('settings', function (Blueprint $table) {
|
||||
$table->dropColumn('tenant_id');
|
||||
});
|
||||
foreach ($tables as $entry) {
|
||||
Schema::table($entry, function (Blueprint $table) {
|
||||
$table->dropColumn('tenant_id');
|
||||
});
|
||||
}
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropUnique(['tenant_id', 'email']);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('tenant_user', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('user_id');
|
||||
$table->string('tenant_id', 255);
|
||||
|
||||
$table->primary(['user_id', 'tenant_id']);
|
||||
|
||||
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('tenant_user');
|
||||
}
|
||||
};
|
||||
|
|
@ -12,6 +12,7 @@ import {
|
|||
faArrowsAltV,
|
||||
faBackspace,
|
||||
faBan,
|
||||
faBuilding,
|
||||
faCheck,
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
|
|
@ -90,6 +91,7 @@ library.add(faEarthAmericas);
|
|||
library.add(faG);
|
||||
library.add(faP);
|
||||
library.add(faTriangleExclamation);
|
||||
library.add(faBuilding);
|
||||
|
||||
// Front
|
||||
library.add(faChevronLeft);
|
||||
|
|
|
|||
|
|
@ -72,6 +72,24 @@
|
|||
</li>
|
||||
<!-- Authentication Links -->
|
||||
@auth
|
||||
<!-- Tenant switchs -->
|
||||
<li class="nav-item dropdown @auth border-end @endauth">
|
||||
<button class="btn nav-link dropdown-toggle d-flex align-items-center h-100 flex-row border-0 px-3"
|
||||
data-bs-toggle="dropdown" type="button" aria-expanded="false">
|
||||
{{ tenant('name') }} <i class="fa-solid fa-building"></i></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end text-center">
|
||||
@foreach ($tenantGlobal as $tenant)
|
||||
<div class="dropdown-item">
|
||||
<a class="btn" href="{{ route('bo.dashboard', ['tenant' => $tenant]) }}">{{ __('switch_tenant') }}
|
||||
->
|
||||
{{ $tenant->name }}
|
||||
</a>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</li>
|
||||
<!-- Profile -->
|
||||
<li class="nav-item dropdown">
|
||||
<button class="btn nav-link dropdown-toggle d-flex align-items-center h-100 flex-row border-0 ps-3" id="navbarDropdown"
|
||||
data-bs-toggle="dropdown" type="button">
|
||||
|
|
|
|||
36
resources/views/bo/pages/tenants/create.blade.php
Normal file
36
resources/views/bo/pages/tenants/create.blade.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
@extends('bo.layout', ['brParam' => ''])
|
||||
|
||||
@section('title', trans_choice('crud.meta.title.create', 1, ['model' => trans_choice('models.classes.tenant', 1)]))
|
||||
@section('description', trans_choice('crud.meta.description.create', 1, ['model' => trans_choice('models.classes.tenant', 1)]))
|
||||
@section('breadcrumb', request()->route()->getName())
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap pb-3">
|
||||
<div class="d-flex align-items-start flex-row">
|
||||
<a class="btn btn-primary text-decoration-none m-0" data-bs-toggle="tooltip"
|
||||
href="{{ route('bo.tenants.index', ['tenant' => \tenant()]) }}"
|
||||
title="{{ __('crud.helpers.list_all', ['model' => trans_choice('models.classes.tenant', '*')]) }}">
|
||||
<i class="fa-solid fa-arrow-left"></i>
|
||||
</a>
|
||||
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => ''])
|
||||
</div>
|
||||
<div class="mb-md-0 mb-2">
|
||||
<button class="btn btn-primary" id="formSubmitClone" data-bs-toggle="tooltip" type="submit"
|
||||
title="{{ __('crud.helpers.save_model', ['model' => trans_choice('models.classes.tenant', 1)]) }}">
|
||||
{{ Str::ucfirst(__('crud.actions.save')) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<form action="{{ route('bo.tenants.store', ['tenant' => \tenant()]) }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@include('bo.pages.tenants.form')
|
||||
<div class="row mt-3">
|
||||
<div class="col text-center">
|
||||
<button class="btn btn-primary" id="formSubmit" data-bs-toggle="tooltip" type="submit"
|
||||
title="{{ __('crud.helpers.save_model', ['model' => trans_choice('models.classes.tenant', 1)]) }}">
|
||||
{{ Str::ucfirst(__('crud.actions.save')) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
52
resources/views/bo/pages/tenants/form.blade.php
Normal file
52
resources/views/bo/pages/tenants/form.blade.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<div class="row">
|
||||
{{-- NAME --}}
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
<fieldset class="h-100 border p-3">
|
||||
<legend>{{ Str::ucfirst(__('validation.attributes.name')) }}</legend>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('bo.partials.inputs.varchar', [
|
||||
'id' => 'tenantName',
|
||||
'label' => __(':field :inter:model', [
|
||||
'field' => Str::ucfirst(__('validation.attributes.name')),
|
||||
'inter' => __('validation.custom.of_the.male'),
|
||||
'model' => trans_choice('models.classes.tenant', 1),
|
||||
]),
|
||||
'hasWordCounter' => true,
|
||||
'name' => 'name',
|
||||
'required' => false,
|
||||
'model' => $fontModel ?? null,
|
||||
'helper' => __('validation.string', [
|
||||
'attribute' => __(':field :inter:model', [
|
||||
'field' => Str::ucfirst(__('validation.attributes.name')),
|
||||
'inter' => __('validation.custom.of_the.male'),
|
||||
'model' => trans_choice('models.classes.tenant', 1),
|
||||
]),
|
||||
]),
|
||||
'placeholder' => '2025 - Salon - Marque',
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<p><b>{{ __('* Champs obligatoires') }}</b></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script nonce="{{ $nonce }}">
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Submit button clone.
|
||||
const submit = document.getElementById('formSubmit')
|
||||
submitClone = document.getElementById('formSubmitClone');
|
||||
submitClone.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
submit.click('');
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
48
resources/views/bo/pages/tenants/index-list.blade.php
Normal file
48
resources/views/bo/pages/tenants/index-list.blade.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<table class="table-hover table-fix-action m-0 table">
|
||||
@if (count($tenantModelList) > 0)
|
||||
<thead>
|
||||
@include('bo.modules.table-col-sorter', [
|
||||
'cols' => [
|
||||
'name' => Str::ucfirst(__('validation.attributes.name')),
|
||||
'updated_at' => Str::ucfirst(__('validation.attributes.updated_at')),
|
||||
],
|
||||
])
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($tenantModelList as $tenantModel)
|
||||
<tr class="border-bottom list-item">
|
||||
<td class="text-center align-middle">
|
||||
<span>{{ $tenantModel->name }}</span>
|
||||
</td>
|
||||
<td class="text-center align-middle">
|
||||
<span class="badge bg-secondary">{{ $tenantModel->updated_at->isoFormat('LLLL') }}</span>
|
||||
</td>
|
||||
<td class="text-end align-middle">
|
||||
@can('delete', $tenantModel)
|
||||
<form class="btn-group confirmDeleteTS"
|
||||
action="{{ route('bo.tenants.destroy', ['tenant' => tenant('id'), 'tenant_model' => $tenantModel]) }}"
|
||||
method="POST" novalidate>
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button class="btn btn-sm btn-danger" data-bs-toggle="tooltip" type="submit"
|
||||
title="{{ Str::ucfirst(__('crud.actions.delete')) }}">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
@else
|
||||
<span class="text-danger" data-bs-toggle="tooltip" title="{{ __('list.right') }}">
|
||||
<i class="fa-solid fa-ban"></i>
|
||||
</span>
|
||||
@endcan
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
@else
|
||||
<tr>
|
||||
<td class="border-0">
|
||||
{{ trans_choice('crud.other.no_model_found', 1, ['model' => trans_choice('models.classes.tenant', '*')]) }}
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
</table>
|
||||
21
resources/views/bo/pages/tenants/index.blade.php
Normal file
21
resources/views/bo/pages/tenants/index.blade.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
@extends('bo.layout')
|
||||
|
||||
@section('title', Str::ucfirst(trans_choice('models.classes.tenant', '*')))
|
||||
@section('description', __('Liste de tous les :model', ['model' => trans_choice('models.classes.tenant', '*')]))
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap">
|
||||
@include('modules.breadcrumbs.breadcrumb-body')
|
||||
@can('create', \App\Models\Tenant::class)
|
||||
<a class="btn btn-primary float-right" data-bs-toggle="tooltip" href="{{ route('bo.tenants.create', ['tenant' => tenant()]) }}"
|
||||
title="{{ __('crud.helpers.create_model', ['model' => trans_choice('models.classes.tenant', 1)]) }}">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
@include('bo.modules.search-bar')
|
||||
<div class="table-responsive mb-3">
|
||||
@include('bo.pages.tenants.index-list')
|
||||
</div>
|
||||
{{ $tenantModelList->links() }}
|
||||
@endsection
|
||||
|
|
@ -130,3 +130,15 @@ Breadcrumbs::for('bo.settings.edit', function (Generator $trail, Settings $setti
|
|||
route('bo.settings.edit', ['tenant' => \tenant(), 'settings' => $settings])
|
||||
);
|
||||
});
|
||||
|
||||
// * TENANTS
|
||||
Breadcrumbs::for('bo.tenants.index', function (Generator $trail) {
|
||||
$trail->push(
|
||||
Str::of(trans_choice('models.classes.tenant', 2))->ucfirst(),
|
||||
route('bo.tenants.index', ['tenant' => tenant(), 'sort_col' => 'updated_at', 'sort_way' => 'desc'])
|
||||
);
|
||||
});
|
||||
Breadcrumbs::for('bo.tenants.create', function (Generator $trail) {
|
||||
$trail->parent('bo.tenants.index');
|
||||
$trail->push(Str::ucfirst(trans('crud.actions.create')), route('bo.tenants.create', ['tenant' => tenant()]));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Http\Controllers\Bo\ActivityLogController;
|
||||
use App\Http\Controllers\Bo\BrandController;
|
||||
use App\Http\Controllers\Bo\DashboardController;
|
||||
|
|
@ -8,11 +9,59 @@ use App\Http\Controllers\Bo\FontController;
|
|||
use App\Http\Controllers\Bo\ProductController;
|
||||
use App\Http\Controllers\Bo\RangeController;
|
||||
use App\Http\Controllers\Bo\SettingsController;
|
||||
use App\Http\Controllers\Bo\TenantController;
|
||||
use App\Http\Controllers\Bo\UserController;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('/bo')
|
||||
->name('bo.')
|
||||
->group(function () {
|
||||
// * Authenticable routes
|
||||
Route::namespace('\App\Http\Controllers\Bo')
|
||||
->group(function () {
|
||||
Auth::routes(
|
||||
[
|
||||
'confirm' => false,
|
||||
'login' => true,
|
||||
'logout' => true,
|
||||
'register' => false,
|
||||
'reset' => false,
|
||||
'verify' => false,
|
||||
]
|
||||
);
|
||||
Route::get('/login-error', function () {
|
||||
return view('bo.pages.login-error');
|
||||
})->name('login-error');
|
||||
});
|
||||
// * Others routes
|
||||
Route::post('theme/set', [Controller::class, 'setTheme'])->name('theme.set');
|
||||
Route::get('/lang/{lang}', [DashboardController::class, 'changelang'])->name('lang');
|
||||
Route::middleware([
|
||||
'admin-secure',
|
||||
'auth:backend'
|
||||
])
|
||||
->group(function () {
|
||||
Route::get('/', function () {
|
||||
$user = auth('backend')->user();
|
||||
$tenant = RoleEnum::conceptor->equals($user->role) ?
|
||||
Tenant::first()->getKey() :
|
||||
$user->tenants->first()->getKey();
|
||||
return redirect()->route('bo.dashboard', [
|
||||
'tenant' => $tenant
|
||||
]);
|
||||
});
|
||||
// * TENANTS MANAGEMENT
|
||||
Route::resource('tenants', TenantController::class)
|
||||
->only(['index', 'create', 'store', 'destroy'])
|
||||
->parameters([
|
||||
'tenants' => 'tenant_model'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Route::middleware([
|
||||
'web',
|
||||
Stancl\Tenancy\Middleware\InitializeTenancyByPath::class,
|
||||
|
|
@ -20,23 +69,6 @@ Route::middleware([
|
|||
Route::prefix('/bo')
|
||||
->name('bo.')
|
||||
->group(function () {
|
||||
// * Authenticable routes
|
||||
Route::namespace('\App\Http\Controllers\Bo')
|
||||
->group(function () {
|
||||
Auth::routes(
|
||||
[
|
||||
'confirm' => false,
|
||||
'login' => true,
|
||||
'logout' => true,
|
||||
'register' => false,
|
||||
'reset' => false,
|
||||
'verify' => false,
|
||||
]
|
||||
);
|
||||
Route::get('/login-error', function () {
|
||||
return view('bo.pages.login-error');
|
||||
})->name('login-error');
|
||||
});
|
||||
Route::middleware(
|
||||
[
|
||||
'admin-secure',
|
||||
|
|
@ -45,7 +77,6 @@ Route::middleware([
|
|||
)->group(function () {
|
||||
Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
|
||||
|
||||
Route::get('/lang/{lang}', [DashboardController::class, 'changelang'])->name('lang');
|
||||
|
||||
// * EXPORT.
|
||||
Route::get('export', [ExportController::class, 'exportToZip'])->name('export');
|
||||
|
|
@ -88,7 +119,5 @@ Route::middleware([
|
|||
Route::resource('activity_logs', ActivityLogController::class)->only(['index', 'show']);
|
||||
});
|
||||
});
|
||||
// * Others routes
|
||||
Route::post('theme/set', [Controller::class, 'setTheme'])->name('theme.set');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
|
@ -24,8 +25,4 @@ Route::middleware(['throttle:credentials'])
|
|||
})->middleware('throttle:30,1')->where('schema', '[a-z]{1,}\.schema\.json')->name('schema');
|
||||
|
||||
Route::redirect('/', '/bo')->name('homepage');
|
||||
|
||||
Route::fallback(function () {
|
||||
response()->redirectToRoute('bo.dashboard', ['tenant' => auth('backend')->user()->tenant_id ?? Tenant::first()->id])->throwResponse();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue