Feat: add user access management components
- Added `RestrictedUserPermission` migration and model for managing user-role-permission restrictions. - Introduced `UserService`, `RoleService`, and related DTOs for handling user access, role assignments, and permission restrictions. - Created new Livewire components and Blade views for user management, including role/permission assignment modals. - Updated navigation and routing to include the "Users" section under "Access Manager." - Enhanced `RoleService` with methods for retrieving permissions and managing role-user interactions. - Improved UI with permission toggles and streamlined selection features for better user-role management.
This commit is contained in:
parent
562b4a220d
commit
07d1b235cc
15
app/Data/Permissions/DeactivatedPermissionData.php
Normal file
15
app/Data/Permissions/DeactivatedPermissionData.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Data\Permissions;
|
||||
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
final class DeactivatedPermissionData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public int $roleId,
|
||||
public int $permissionId,
|
||||
) {}
|
||||
}
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
namespace App\Data\Role;
|
||||
|
||||
use App\Data\Permissions\PermissionData;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\{Data, DataCollection};
|
||||
|
||||
@ -16,5 +17,7 @@ public function __construct(
|
||||
public ?DataCollection $apps = null,
|
||||
#[DataCollectionOf(class: RoleUserData::class)]
|
||||
public ?DataCollection $users = null,
|
||||
#[DataCollectionOf(PermissionData::class)]
|
||||
public ?DataCollection $permissions = null,
|
||||
) {}
|
||||
}
|
||||
|
||||
35
app/Data/User/UserIndexData.php
Normal file
35
app/Data/User/UserIndexData.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Data\User;
|
||||
|
||||
use App\Data\Role\RoleData;
|
||||
use App\Models\User;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\{Data, DataCollection};
|
||||
|
||||
final class UserIndexData extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public int $id,
|
||||
public string $name,
|
||||
public string $email,
|
||||
public string $initials,
|
||||
#[DataCollectionOf(class: RoleData::class)]
|
||||
public ?DataCollection $roles = null,
|
||||
public ?array $restrictedPermissionIds = null,
|
||||
) {}
|
||||
|
||||
public static function fromModel(User $user)
|
||||
{
|
||||
return new self(
|
||||
id: $user->id,
|
||||
name: $user->name,
|
||||
email: $user->email,
|
||||
initials: $user->initials(),
|
||||
roles: RoleData::collect($user->roles, DataCollection::class),
|
||||
restrictedPermissionIds: $user->restrictedPermissions->pluck('id')->toArray()
|
||||
);
|
||||
}
|
||||
}
|
||||
27
app/Livewire/Forms/AssignRolesForm.php
Normal file
27
app/Livewire/Forms/AssignRolesForm.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Livewire\Forms;
|
||||
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Form;
|
||||
|
||||
class AssignRolesForm extends Form
|
||||
{
|
||||
/**
|
||||
* Holds the roles that the user has
|
||||
*
|
||||
* @var array <int, int>
|
||||
*/
|
||||
#[Validate('required|array')]
|
||||
public array $roleIds = [];
|
||||
|
||||
/**
|
||||
* Holds the direct permissions toggled for the user (from all selected roles).
|
||||
*
|
||||
* @var array<int, int>
|
||||
*/
|
||||
#[Validate('nullable|array')]
|
||||
public array $permissions = [];
|
||||
}
|
||||
39
app/Models/RestrictedUserPermission.php
Normal file
39
app/Models/RestrictedUserPermission.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Relations\{BelongsTo, Pivot};
|
||||
use Spatie\Permission\Models\Permission;
|
||||
|
||||
#[Fillable(['user_id', 'role_id', 'permission_id'])]
|
||||
class RestrictedUserPermission extends Pivot
|
||||
{
|
||||
protected $table = 'restricted_user_permissions';
|
||||
|
||||
/**
|
||||
* @return BelongsTo<User, $this>
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Role, $this>
|
||||
*/
|
||||
public function role(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Role::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Permission, $this>
|
||||
*/
|
||||
public function permission(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Permission::class);
|
||||
}
|
||||
}
|
||||
@ -26,6 +26,16 @@ final class User extends Authenticatable
|
||||
use Notifiable;
|
||||
use TwoFactorAuthenticatable;
|
||||
|
||||
public function restrictedPermissions(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
\Spatie\Permission\Models\Permission::class,
|
||||
'restricted_user_permissions',
|
||||
'user_id',
|
||||
'permission_id'
|
||||
)->withPivot('role_id')->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's initials
|
||||
*/
|
||||
|
||||
@ -6,10 +6,11 @@
|
||||
|
||||
use App\Data\Role\{CreateRoleData, RoleData};
|
||||
use App\Models\Role;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use InvalidArgumentException;
|
||||
use NoDiscard;
|
||||
use Spatie\LaravelData\PaginatedDataCollection;
|
||||
use Spatie\LaravelData\{DataCollection, PaginatedDataCollection};
|
||||
use Throwable;
|
||||
|
||||
final class RoleService
|
||||
@ -20,7 +21,6 @@ final class RoleService
|
||||
*
|
||||
* @throws Throwable when an error occurs during the creation.
|
||||
*/
|
||||
#[NoDiscard]
|
||||
public function create(CreateRoleData $data): RoleData
|
||||
{
|
||||
$role = DB::transaction(fn () => Role::query()->create([
|
||||
@ -61,4 +61,40 @@ public function getAll(): PaginatedDataCollection
|
||||
|
||||
return RoleData::collect($roles, PaginatedDataCollection::class);
|
||||
}
|
||||
|
||||
public function searchRolesForSelect(string $search = ''): Collection
|
||||
{
|
||||
return Role::query()
|
||||
->when('' !== $search, fn ($query) => $query->where('name', 'like', "%{$search}%"))
|
||||
->select('id', 'name')
|
||||
->get();
|
||||
}
|
||||
|
||||
public function getAllRoleIds(): array
|
||||
{
|
||||
return Role::query()->pluck('id')->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all permissions for the given roles, grouped by role name.
|
||||
* Each permission is a plain array so Livewire can serialise the property cleanly.
|
||||
*
|
||||
* @param array<int, int> $roleIds
|
||||
*
|
||||
* @return DataCollection<int, RoleData>|null
|
||||
*/
|
||||
#[NoDiscard]
|
||||
public function getPermissionsGroupedByRole(array $roleIds): ?DataCollection
|
||||
{
|
||||
if (empty($roleIds)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$roles = Role::query()
|
||||
->with('permissions:id,name')
|
||||
->whereIn('id', $roleIds)
|
||||
->get(['id', 'name']);
|
||||
|
||||
return RoleData::collect($roles, DataCollection::class);
|
||||
}
|
||||
}
|
||||
|
||||
117
app/Services/UserService.php
Normal file
117
app/Services/UserService.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Data\Permissions\DeactivatedPermissionData;
|
||||
use App\Data\User\UserIndexData;
|
||||
use App\Models\{RestrictedUserPermission, User};
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use InvalidArgumentException;
|
||||
use NoDiscard;
|
||||
use Spatie\LaravelData\{DataCollection, PaginatedDataCollection};
|
||||
use Throwable;
|
||||
|
||||
final class UserService
|
||||
{
|
||||
/**
|
||||
* Returns a Paginated Collection of users with their roles and connected apps DTO
|
||||
*
|
||||
* @return PaginatedDataCollection<int, UserIndexData>
|
||||
*/
|
||||
#[NoDiscard]
|
||||
public function getAll(): PaginatedDataCollection
|
||||
{
|
||||
$users = User::query()
|
||||
->with([
|
||||
'roles:id,name',
|
||||
'roles.apps:id,name',
|
||||
'roles.permissions:id,name',
|
||||
'restrictedPermissions',
|
||||
])
|
||||
->orderBy('id')
|
||||
->paginate();
|
||||
|
||||
return UserIndexData::collect($users, PaginatedDataCollection::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the restricted permission IDs for a user.
|
||||
*
|
||||
* @return array<int>
|
||||
*/
|
||||
public function getRestrictedPermissionIds(int $userId): array
|
||||
{
|
||||
if ($userId <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return RestrictedUserPermission::query()
|
||||
->where('user_id', $userId)
|
||||
->pluck('permission_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign roles to a user.
|
||||
*/
|
||||
public function assignRoles(int $userId, array $roleIds): void
|
||||
{
|
||||
if ($userId <= 0) {
|
||||
throw new InvalidArgumentException('Invalid id');
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($userId, $roleIds): void {
|
||||
$user = User::query()->findOrFail($userId);
|
||||
$user->syncRoles($roleIds);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync restricted permissions for a user.
|
||||
*
|
||||
* @param DataCollection<int, DeactivatedPermissionData> $deactivatedPermissions
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function syncRestrictedPermissions(int $userId, DataCollection $deactivatedPermissions): void
|
||||
{
|
||||
if ($userId <= 0) {
|
||||
throw new InvalidArgumentException('Invalid user ID');
|
||||
}
|
||||
|
||||
$payload = $deactivatedPermissions->toCollection()->map(fn ($data) => [
|
||||
'user_id' => $userId,
|
||||
'role_id' => $data->roleId,
|
||||
'permission_id' => $data->permissionId,
|
||||
])->toArray();
|
||||
|
||||
DB::transaction(function () use ($userId, $payload): void {
|
||||
|
||||
RestrictedUserPermission::query()
|
||||
->where('user_id', $userId)
|
||||
->delete();
|
||||
|
||||
if (! empty($payload)) {
|
||||
RestrictedUserPermission::query()->insert($payload);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException when id is invalid.
|
||||
* @throws Throwable when an error occurs during the deletion.
|
||||
*/
|
||||
public function delete(int $id): void
|
||||
{
|
||||
if ($id <= 0) {
|
||||
throw new InvalidArgumentException('Invalid id');
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($id): void {
|
||||
User::query()->findOrFail($id)->delete();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\{Role, User};
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
|
||||
return new class() extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('restricted_user_permissions', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignIdFor(Role::class)->constrained()->cascadeOnDelete();
|
||||
$table->foreignIdFor(User::class)->constrained()->cascadeOnDelete();
|
||||
$table->foreignIdFor(Permission::class)->constrained()->cascadeOnDelete();
|
||||
$table->timestamps();
|
||||
$table->unique(['role_id', 'user_id', 'permission_id'], 'restricted_role_user_permission_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('restricted_user_permissions');
|
||||
}
|
||||
};
|
||||
@ -61,6 +61,11 @@ public function navItems(): DataCollection
|
||||
route: 'access-manager.roles-and-apps.index',
|
||||
active_pattern: 'access-manager.roles-and-apps.*',
|
||||
),
|
||||
new NavSubItemData(
|
||||
label: 'Users',
|
||||
route: 'access-manager.users.index',
|
||||
active_pattern: 'access-manager.users.*',
|
||||
),
|
||||
], DataCollection::class)
|
||||
),
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
@props([
|
||||
'label' => 'Select Items',
|
||||
'model', // Livewire property
|
||||
'options', // The data collection passed from the parent
|
||||
'searchFunction', // The parent method to call for searching
|
||||
'selectAllAction', // The parent method to call for selecting all
|
||||
@ -24,7 +23,7 @@
|
||||
</legend>
|
||||
|
||||
<x-mary-choices
|
||||
wire:model="{{ $model }}"
|
||||
{{ $attributes->whereStartsWith('wire:model') }}
|
||||
:options="$options"
|
||||
search-function="{{ $searchFunction }}"
|
||||
option-label="name"
|
||||
|
||||
319
resources/views/pages/access-manager/users/⚡index.blade.php
Normal file
319
resources/views/pages/access-manager/users/⚡index.blade.php
Normal file
@ -0,0 +1,319 @@
|
||||
<?php
|
||||
|
||||
use App\Concerns\HandlesOperations;
|
||||
use App\Data\Ui\TableHeader;
|
||||
use App\Models\User;
|
||||
use App\Services\UserService;
|
||||
use Livewire\Attributes\{Computed, Layout, Title};
|
||||
use Livewire\Component;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
use Spatie\LaravelData\PaginatedDataCollection;
|
||||
use App\Data\Role\RoleData;
|
||||
use App\Data\Role\RoleAppData;
|
||||
use App\Livewire\Forms\AssignRolesForm;
|
||||
use App\Services\RoleService;
|
||||
use App\Data\Permissions\DeactivatedPermissionData;
|
||||
|
||||
new
|
||||
#[Title('Users')]
|
||||
class extends Component {
|
||||
use HandlesOperations;
|
||||
|
||||
protected UserService $userService;
|
||||
protected RoleService $roleService;
|
||||
|
||||
public AssignRolesForm $form;
|
||||
public bool $showAssignRolesModal = false;
|
||||
public int $currentUserId = 0;
|
||||
public array $rolesSearchable = [];
|
||||
|
||||
#[DataCollectionOf(RoleData::class)]
|
||||
public ?DataCollection $roleWithPermission = null;
|
||||
|
||||
#[DataCollectionOf(TableHeader::class)]
|
||||
public DataCollection $headers;
|
||||
|
||||
public function boot(UserService $userService, RoleService $roleService): void
|
||||
{
|
||||
$this->userService = $userService;
|
||||
$this->roleService = $roleService;
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->headers = TableHeader::collect([
|
||||
new TableHeader(key: 'name', label: 'User'),
|
||||
new TableHeader(key: 'roles', label: 'Roles'),
|
||||
new TableHeader(key: 'permissions', label: 'Permissions'),
|
||||
new TableHeader(key: 'apps', label: 'Connected Apps'),
|
||||
], DataCollection::class);
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function getUsers(): PaginatedDataCollection
|
||||
{
|
||||
return $this->userService->getAll();
|
||||
}
|
||||
|
||||
public function delete(int $userId): void
|
||||
{
|
||||
$this->attempt(
|
||||
action: function () use ($userId): void {
|
||||
$this->userService->delete($userId);
|
||||
unset($this->getUsers);
|
||||
},
|
||||
successMessage: 'User deleted successfully!',
|
||||
);
|
||||
}
|
||||
|
||||
public function openAssignRolesModal(int $userId): void
|
||||
{
|
||||
$this->attempt(
|
||||
action: function () use ($userId) {
|
||||
$this->currentUserId = $userId;
|
||||
$this->form->reset();
|
||||
$this->searchRoles();
|
||||
$this->form->roleIds = User::findOrFail($userId)->roles()->pluck('id')->toArray();
|
||||
$this->hydratePermissionInForm();
|
||||
$this->showAssignRolesModal = true;
|
||||
},
|
||||
showSuccess: false
|
||||
);
|
||||
}
|
||||
|
||||
private function hydratePermissionInForm(): void
|
||||
{
|
||||
$this->roleWithPermission = $this->roleService->getPermissionsGroupedByRole($this->form->roleIds);
|
||||
$allPermissionIds = [];
|
||||
if ($this->roleWithPermission) {
|
||||
foreach ($this->roleWithPermission->items() as $role) {
|
||||
foreach ($role->permissions->items() as $permission) {
|
||||
$allPermissionIds[] = $permission->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$restrictedIds = $this->userService->getRestrictedPermissionIds($this->currentUserId);
|
||||
|
||||
$this->form->permissions = array_values(array_diff($allPermissionIds, $restrictedIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the permission list whenever the roles selection changes.
|
||||
* This method is automatically hooked by livewire cause of naming convention.
|
||||
*/
|
||||
public function updatedFormRoleIds(): void
|
||||
{
|
||||
$this->hydratePermissionInForm();
|
||||
}
|
||||
|
||||
public function searchRoles(string $value = ''): void
|
||||
{
|
||||
$this->rolesSearchable = $this->roleService->searchRolesForSelect($value)->toArray();
|
||||
}
|
||||
|
||||
public function selectAllRoles(): void
|
||||
{
|
||||
$this->form->roleIds = $this->roleService->getAllRoleIds();
|
||||
$this->searchRoles();
|
||||
$this->hydratePermissionInForm();
|
||||
}
|
||||
|
||||
public function clearRoles(): void
|
||||
{
|
||||
$this->form->roleIds = [];
|
||||
$this->hydratePermissionInForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the deactivated permissions represented as DTOs containing the role ID and permission ID.
|
||||
*
|
||||
* @return DataCollection<int, DeactivatedPermissionData>
|
||||
*/
|
||||
public function getDeactivatedPermissions(): DataCollection
|
||||
{
|
||||
$deactivated = [];
|
||||
|
||||
$activePermissions = array_map('intval', $this->form->permissions);
|
||||
|
||||
if ($this->roleWithPermission) {
|
||||
foreach ($this->roleWithPermission->items() as $role) {
|
||||
foreach ($role->permissions->items() as $permission) {
|
||||
|
||||
if (!in_array($permission->id, $activePermissions, true)) {
|
||||
$deactivated[] = new DeactivatedPermissionData(
|
||||
roleId: $role->id,
|
||||
permissionId: $permission->id
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DeactivatedPermissionData::collect($deactivated, DataCollection::class);
|
||||
}
|
||||
|
||||
public function saveRoles(): void
|
||||
{
|
||||
|
||||
$this->attempt(
|
||||
action: function () {
|
||||
$this->form->validate();
|
||||
$deactivatedPermissions = $this->getDeactivatedPermissions();
|
||||
$this->userService->assignRoles($this->currentUserId, $this->form->roleIds);
|
||||
$this->userService->syncRestrictedPermissions($this->currentUserId, $deactivatedPermissions);
|
||||
$this->showAssignRolesModal = false;
|
||||
$this->roleWithPermission = null;
|
||||
$this->form->reset();
|
||||
unset($this->getUsers);
|
||||
},
|
||||
onError: function () {
|
||||
$this->showAssignRolesModal = false;
|
||||
$this->roleWithPermission = null;
|
||||
$this->form->reset();
|
||||
},
|
||||
successMessage: 'Roles updated successfully!'
|
||||
);
|
||||
}
|
||||
};
|
||||
?>
|
||||
|
||||
<div>
|
||||
<x-shared.card title="Users" icon="lucide-users" class="pb-2">
|
||||
<x-mary-table
|
||||
:headers="$headers->toArray()"
|
||||
:rows="$this->getUsers->items()"
|
||||
>
|
||||
@scope('cell_name', $row)
|
||||
<div class="flex items-center gap-3">
|
||||
<x-mary-avatar :placeholder="$row->initials" class="bg-blue-100 text-blue-600"/>
|
||||
<div>
|
||||
<div class="font-medium text-gray-900">{{ $row->name }}</div>
|
||||
<div class="text-sm text-gray-500">{{ $row->email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@endscope
|
||||
|
||||
@scope('cell_roles', $row)
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@foreach($row->roles as $role)
|
||||
<x-mary-badge class="badge-soft badge-neutral" :value="$role->name"/>
|
||||
@endforeach
|
||||
</div>
|
||||
@endscope
|
||||
|
||||
@scope('cell_permissions', $row)
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@php
|
||||
$displayedPermissions = [];
|
||||
@endphp
|
||||
@foreach($row->roles as $role)
|
||||
@if($role->permissions)
|
||||
@foreach($role->permissions as $permission)
|
||||
@if(!in_array($permission->id, $displayedPermissions))
|
||||
@php
|
||||
$displayedPermissions[] = $permission->id;
|
||||
$isRestricted = in_array($permission->id, $row->restrictedPermissionIds ?? []);
|
||||
@endphp
|
||||
@if($isRestricted)
|
||||
<x-mary-badge class="bg-base-300 text-base-content/50 line-through"
|
||||
:value="$permission->name"/>
|
||||
@else
|
||||
<x-mary-badge class="badge-soft badge-primary" :value="$permission->name"/>
|
||||
@endif
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
@endscope
|
||||
|
||||
@scope('cell_apps', $row)
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@foreach($row->roles as $role)
|
||||
@if($role->apps)
|
||||
@foreach ($role->apps as $app)
|
||||
<x-mary-badge class="badge-soft badge-secondary" :value="$app->appName"/>
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
@endscope
|
||||
|
||||
@scope('actions', $row)
|
||||
<div class="flex gap-1">
|
||||
<x-mary-button
|
||||
icon="lucide.shield-plus"
|
||||
wire:click="openAssignRolesModal({{ $row->id }})"
|
||||
tooltip="Manage roles"
|
||||
class="btn-sm btn-circle btn-soft"
|
||||
spinner="openAssignRolesModal({{ $row->id }})"
|
||||
/>
|
||||
<x-mary-button
|
||||
icon="lucide.trash"
|
||||
wire:click="delete({{ $row->id }})"
|
||||
spinner="delete({{ $row->id }})"
|
||||
variant="danger"
|
||||
:tooltip="__('Delete user')"
|
||||
class="btn-sm btn-circle btn-error btn-soft"
|
||||
/>
|
||||
</div>
|
||||
@endscope
|
||||
</x-mary-table>
|
||||
</x-shared.card>
|
||||
|
||||
<x-mary-modal wire:model="showAssignRolesModal" title="Assign Roles">
|
||||
<x-mary-form wire:submit="saveRoles">
|
||||
<x-shared.choices
|
||||
wire:model.live="form.roleIds"
|
||||
:options="$rolesSearchable"
|
||||
search-function="searchRoles"
|
||||
select-all-action="selectAllRoles"
|
||||
clear-action="clearRoles"
|
||||
label="Roles"
|
||||
/>
|
||||
|
||||
@if ($roleWithPermission)
|
||||
<fieldset class="mt-5 space-y-4">
|
||||
<legend class="flex items-center gap-2">
|
||||
<label class="text-xs font-bold">Permissions</label>
|
||||
</legend>
|
||||
|
||||
@foreach ($roleWithPermission?->items() as $role)
|
||||
<div class="rounded-xl border border-base-300 bg-base-200/50 p-4">
|
||||
<div class="mb-3 flex items-center gap-2">
|
||||
<span
|
||||
class="inline-flex items-center rounded-md bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary ring-1 ring-inset ring-primary/20">
|
||||
{{ $role->name }}
|
||||
</span>
|
||||
<span
|
||||
class="text-xs text-base-content/50">{{$role->permissions->count()}} {{ $role->permissions->count() >= 2 ? 'Permissions' : 'Permission' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
@foreach ($role->permissions as $permission)
|
||||
<x-mary-toggle
|
||||
class="toggle-primary"
|
||||
:label="$permission->name" wire:model="form.permissions"
|
||||
:value="$permission->id"
|
||||
/>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</fieldset>
|
||||
@endif
|
||||
|
||||
<x-slot:actions>
|
||||
<x-mary-button @click="$wire.showAssignRolesModal = false" class="btn-ghost">
|
||||
Cancel
|
||||
</x-mary-button>
|
||||
<x-mary-button type="submit" spinner="saveRoles" class="btn-primary">
|
||||
Save Roles
|
||||
</x-mary-button>
|
||||
</x-slot:actions>
|
||||
</x-mary-form>
|
||||
</x-mary-modal>
|
||||
</div>
|
||||
@ -14,6 +14,10 @@
|
||||
Route::livewire('roles-and-apps', 'pages::access-manager.roles-and-apps.index')->name('index');
|
||||
Route::livewire('roles-and-apps/{id}', 'pages::access-manager.roles-and-apps.show')->name('show');
|
||||
});
|
||||
|
||||
Route::prefix('users')->name('users.')->group(function (): void {
|
||||
Route::livewire('users', 'pages::access-manager.users.index')->name('index');
|
||||
});
|
||||
});
|
||||
|
||||
Route::prefix('apps')->name('apps.')->group(function (): void {
|
||||
|
||||
16
tests/ArchTest.php
Normal file
16
tests/ArchTest.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
arch('it does not use debug functions')
|
||||
->expect(['ds', 'dsd', 'dsq', 'dd', 'ddd', 'dump', 'ray', 'die', 'var_dump', 'sleep', 'exit'])
|
||||
->not->toBeUsed();
|
||||
|
||||
arch('application uses strict types')
|
||||
->expect('App')
|
||||
->toUseStrictTypes();
|
||||
|
||||
arch('controllers have exact suffix')
|
||||
->expect('App\Http\Controllers')
|
||||
->toHaveSuffix('Controller');
|
||||
|
||||
arch('models do not use http helpers')
|
||||
->expect(['request', 'session', 'cookie'])
|
||||
->not->toBeUsedIn('App\Models');
|
||||
Loading…
x
Reference in New Issue
Block a user