Feat: assign users to role and show users on list
- Added DTOs (`AssignUsersToRoleData`, `RoleUserData`, `UserSelectData`) for user-role operations and UI data mapping. - Updated `RoleService` and `AppRoleService` with methods for user-role assignment, detachment, retrieval, and search. - Enhanced Blade views and Livewire components to support user-role management in "Roles and Apps." - Adjusted `Role`, `User`, and related models to incorporate Spatie roles and permissions support. - Improved UI with search and selection capabilities for assigning users to roles.
This commit is contained in:
parent
c680c1ae6e
commit
5a593ffdb5
16
app/Data/Role/AssignUsersToRoleData.php
Normal file
16
app/Data/Role/AssignUsersToRoleData.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Data\Role;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
|
||||||
|
final class AssignUsersToRoleData extends Data
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $roleId,
|
||||||
|
/** @var int[] */
|
||||||
|
public array $userIds
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@ -12,7 +12,9 @@ final class RoleData extends Data
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
public int $id,
|
public int $id,
|
||||||
public string $name,
|
public string $name,
|
||||||
#[DataCollectionOf(class: RoleAppData::class), ]
|
#[DataCollectionOf(class: RoleAppData::class)]
|
||||||
public ?DataCollection $apps = null
|
public ?DataCollection $apps = null,
|
||||||
|
#[DataCollectionOf(class: RoleUserData::class)]
|
||||||
|
public ?DataCollection $users = null,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
29
app/Data/Role/RoleUserData.php
Normal file
29
app/Data/Role/RoleUserData.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Data\Role;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
|
||||||
|
final class RoleUserData extends Data
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $id,
|
||||||
|
public string $name,
|
||||||
|
public string $initials = '',
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercept the Eloquent model and map it to the DTO properties.
|
||||||
|
*/
|
||||||
|
public static function fromModel(User $user): self
|
||||||
|
{
|
||||||
|
return new self(
|
||||||
|
id: $user->id,
|
||||||
|
name: $user->name,
|
||||||
|
initials: $user->initials()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/Data/Role/UserSelectData.php
Normal file
19
app/Data/Role/UserSelectData.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Data\Role;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight DTO for populating the user search/select dropdown.
|
||||||
|
* Only carries the fields the UI choices component needs.
|
||||||
|
*/
|
||||||
|
final class UserSelectData extends Data
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $id,
|
||||||
|
public string $name,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@
|
|||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||||
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
|
|
||||||
#[Fillable(['name', 'email', 'password'])]
|
#[Fillable(['name', 'email', 'password'])]
|
||||||
#[Hidden(['password', 'two_factor_secret', 'two_factor_recovery_codes', 'remember_token'])]
|
#[Hidden(['password', 'two_factor_secret', 'two_factor_recovery_codes', 'remember_token'])]
|
||||||
@ -21,6 +22,7 @@ final class User extends Authenticatable
|
|||||||
/** @use HasFactory<UserFactory> */
|
/** @use HasFactory<UserFactory> */
|
||||||
use HasFactory, SoftDeletes;
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
use HasRoles;
|
||||||
use Notifiable;
|
use Notifiable;
|
||||||
use TwoFactorAuthenticatable;
|
use TwoFactorAuthenticatable;
|
||||||
|
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Data\Role\{AssignAppsToRoleData, RoleAppData};
|
use App\Data\Role\{AssignAppsToRoleData, AssignUsersToRoleData, RoleAppData, RoleUserData, UserSelectData};
|
||||||
use App\Models\{AppRole, ConnectedApp};
|
use App\Models\{AppRole, ConnectedApp, Role, User};
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
@ -105,7 +105,96 @@ public function searchAppsForSelect(string $search = ''): Collection
|
|||||||
#[NoDiscard]
|
#[NoDiscard]
|
||||||
public function getAllAppIds(): array
|
public function getAllAppIds(): array
|
||||||
{
|
{
|
||||||
// Replace 'ConnectedApp' with your actual model name
|
|
||||||
return ConnectedApp::pluck('id')->toArray();
|
return ConnectedApp::pluck('id')->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign users to a role using spatie/laravel-permission.
|
||||||
|
*
|
||||||
|
* @throws Throwable when an error occurs during the assignment.
|
||||||
|
*/
|
||||||
|
public function assignUsersToRole(AssignUsersToRoleData $data): void
|
||||||
|
{
|
||||||
|
if (empty($data->userIds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::transaction(function () use ($data): void {
|
||||||
|
$role = Role::query()->findOrFail($data->roleId);
|
||||||
|
$users = User::query()->whereIn('id', $data->userIds)->get();
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
foreach ($users as $user) {
|
||||||
|
if (! $user->hasRole($role)) {
|
||||||
|
$user->assignRole($role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a user from a role.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException when userId is invalid.
|
||||||
|
* @throws Throwable when an error occurs during the removal.
|
||||||
|
*/
|
||||||
|
public function detachUserFromRole(int $roleId, int $userId): void
|
||||||
|
{
|
||||||
|
if ($userId <= 0) {
|
||||||
|
throw new InvalidArgumentException('Invalid user id');
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::transaction(function () use ($roleId, $userId): void {
|
||||||
|
$role = Role::query()->findOrFail($roleId);
|
||||||
|
$user = User::query()->findOrFail($userId);
|
||||||
|
|
||||||
|
$user->removeRole($role);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all users assigned to a given role.
|
||||||
|
*
|
||||||
|
* @return DataCollection<int, RoleUserData>
|
||||||
|
*/
|
||||||
|
#[NoDiscard]
|
||||||
|
public function getUsersForRole(int $roleId): DataCollection
|
||||||
|
{
|
||||||
|
$role = Role::query()->findOrFail($roleId);
|
||||||
|
|
||||||
|
$users = User::query()
|
||||||
|
->select(['id', 'name', 'email'])
|
||||||
|
->role($role)
|
||||||
|
->orderBy('name')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return RoleUserData::collect(
|
||||||
|
$users->map(fn (User $user): array => [
|
||||||
|
'id' => $user->id,
|
||||||
|
'name' => $user->name,
|
||||||
|
'email' => $user->email,
|
||||||
|
])->all(),
|
||||||
|
DataCollection::class,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search users for the select dropdown, returning only id and name.
|
||||||
|
*
|
||||||
|
* @return DataCollection<int, UserSelectData>
|
||||||
|
*/
|
||||||
|
#[NoDiscard]
|
||||||
|
public function searchUsersForSelect(string $search = ''): DataCollection
|
||||||
|
{
|
||||||
|
$users = User::query()
|
||||||
|
->select(['id', 'name'])
|
||||||
|
->when($search, function ($query, $search): void {
|
||||||
|
$query->where('name', 'like', '%'.$search.'%');
|
||||||
|
})
|
||||||
|
->limit(50)
|
||||||
|
->orderBy('name')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return UserSelectData::collect($users, DataCollection::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,7 +55,7 @@ public function delete(int $id): void
|
|||||||
public function getAll(): PaginatedDataCollection
|
public function getAll(): PaginatedDataCollection
|
||||||
{
|
{
|
||||||
$roles = Role::query()
|
$roles = Role::query()
|
||||||
->with('apps')
|
->with(['apps:id,name', 'users:id,name'])
|
||||||
->orderBy('id')
|
->orderBy('id')
|
||||||
->paginate();
|
->paginate();
|
||||||
|
|
||||||
|
|||||||
@ -58,8 +58,8 @@ public function navItems(): DataCollection
|
|||||||
sub_menu: NavSubItemData::collect([
|
sub_menu: NavSubItemData::collect([
|
||||||
new NavSubItemData(
|
new NavSubItemData(
|
||||||
label: 'Roles and Apps',
|
label: 'Roles and Apps',
|
||||||
route: 'access-manager.roles-and-apps',
|
route: 'access-manager.roles-and-apps.index',
|
||||||
active_pattern: 'access-manager.roles-and-apps',
|
active_pattern: 'access-manager.roles-and-apps.*',
|
||||||
),
|
),
|
||||||
], DataCollection::class)
|
], DataCollection::class)
|
||||||
),
|
),
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
use App\Concerns\HandlesOperations;
|
use App\Concerns\HandlesOperations;
|
||||||
use App\Livewire\Forms\CreateRoleForm;
|
use App\Livewire\Forms\CreateRoleForm;
|
||||||
use App\Services\AppRoleService;
|
use App\Services\AppRoleService;
|
||||||
use App\Data\Role\{AssignAppsToRoleData, CreateRoleData, RoleAppData};
|
use App\Data\Role\{AssignAppsToRoleData, CreateRoleData, RoleAppData, RoleUserData};
|
||||||
use App\Data\Ui\TableHeader;
|
use App\Data\Ui\TableHeader;
|
||||||
use App\Services\RoleService;
|
use App\Services\RoleService;
|
||||||
use Livewire\Attributes\{Computed, Layout, Title, Validate};
|
use Livewire\Attributes\{Computed, Layout, Title, Validate};
|
||||||
@ -37,7 +37,7 @@ public function mount(): void
|
|||||||
$this->headers = TableHeader::collect([
|
$this->headers = TableHeader::collect([
|
||||||
new TableHeader(key: 'name', label: 'Name'),
|
new TableHeader(key: 'name', label: 'Name'),
|
||||||
new TableHeader(key: 'apps', label: 'Apps'),
|
new TableHeader(key: 'apps', label: 'Apps'),
|
||||||
new TableHeader(key: '', label: 'Users'),
|
new TableHeader(key: 'users', label: 'Users'),
|
||||||
], DataCollection::class);
|
], DataCollection::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,6 +132,17 @@ public function delete(int $roleId): void
|
|||||||
@endforeach
|
@endforeach
|
||||||
@endscope
|
@endscope
|
||||||
|
|
||||||
|
@scope('cell_users', $row)
|
||||||
|
<div class="flex -space-x-2 overflow-hidden">
|
||||||
|
@foreach($row->users as $user)
|
||||||
|
@php /** @var RoleUserData $user */ @endphp
|
||||||
|
<x-mary-avatar :placeholder="$user->initials"
|
||||||
|
class="w-8! h-8! bg-blue-100 text-blue-600 border-2 border-white ring-0"
|
||||||
|
tooltip="{{ $user->name }}"/>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endscope
|
||||||
|
|
||||||
@scope('actions', $row)
|
@scope('actions', $row)
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<x-mary-button
|
<x-mary-button
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Concerns\HandlesOperations;
|
use App\Concerns\HandlesOperations;
|
||||||
use App\Data\Role\{AssignAppsToRoleData, RoleAppData};
|
use App\Data\Role\{AssignAppsToRoleData, AssignUsersToRoleData, RoleAppData};
|
||||||
use App\Data\Ui\TableHeader;
|
use App\Data\Ui\TableHeader;
|
||||||
use App\Livewire\Forms\AssignAppToRoleForm;
|
use App\Livewire\Forms\AssignAppToRoleForm;
|
||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
@ -37,7 +37,8 @@ class extends Component {
|
|||||||
|
|
||||||
public bool $showAddUserModal = false;
|
public bool $showAddUserModal = false;
|
||||||
|
|
||||||
public ?int $selectedUserId = null;
|
/** @var int[] */
|
||||||
|
public array $selectedUserIds = [];
|
||||||
|
|
||||||
public array $usersSearchable = [];
|
public array $usersSearchable = [];
|
||||||
|
|
||||||
@ -75,7 +76,6 @@ public function mount(int $id): void
|
|||||||
|
|
||||||
$this->userHeaders = TableHeader::collect([
|
$this->userHeaders = TableHeader::collect([
|
||||||
new TableHeader(key: 'name', label: 'Name'),
|
new TableHeader(key: 'name', label: 'Name'),
|
||||||
new TableHeader(key: 'email', label: 'Email'),
|
|
||||||
], DataCollection::class);
|
], DataCollection::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,14 +86,12 @@ public function apps(): DataCollection
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Replace with a service method that returns user DTOs for this role.
|
* Get users assigned to this role via spatie/laravel-permission.
|
||||||
*
|
|
||||||
* @return array<int, array<string, mixed>>
|
|
||||||
*/
|
*/
|
||||||
#[Computed]
|
#[Computed]
|
||||||
public function getRoleUsers(): array
|
public function getRoleUsers(): DataCollection
|
||||||
{
|
{
|
||||||
return [];
|
return $this->appRoleService->getUsersForRole($this->roleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -208,24 +206,51 @@ public function detachApp(int $assignmentId): void
|
|||||||
|
|
||||||
public function openAddUserModal(): void
|
public function openAddUserModal(): void
|
||||||
{
|
{
|
||||||
$this->reset('selectedUserId');
|
$this->reset('selectedUserIds');
|
||||||
$this->resetErrorBag();
|
$this->resetErrorBag();
|
||||||
|
$this->searchUsers('');
|
||||||
$this->showAddUserModal = true;
|
$this->showAddUserModal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function searchUsers(string $value = ''): void
|
public function searchUsers(string $value = ''): void
|
||||||
{
|
{
|
||||||
$this->usersSearchable = [];
|
$this->usersSearchable = $this
|
||||||
|
->appRoleService
|
||||||
|
->searchUsersForSelect($value)
|
||||||
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function assignUser(): void
|
public function assignUser(): void
|
||||||
{
|
{
|
||||||
$this->success('User assignment not implemented yet.');
|
$this->validate([
|
||||||
|
'selectedUserIds' => 'required|array|min:1',
|
||||||
|
'selectedUserIds.*' => 'integer|exists:users,id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->attempt(
|
||||||
|
action: function (): void {
|
||||||
|
$this->appRoleService->assignUsersToRole(new AssignUsersToRoleData(
|
||||||
|
roleId: $this->roleId,
|
||||||
|
userIds: $this->selectedUserIds,
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->reset('selectedUserIds');
|
||||||
|
$this->showAddUserModal = false;
|
||||||
|
unset($this->getRoleUsers);
|
||||||
|
},
|
||||||
|
successMessage: 'Users assigned successfully!',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function detachUser(int $userId): void
|
public function detachUser(int $userId): void
|
||||||
{
|
{
|
||||||
$this->success('User removal not implemented yet.');
|
$this->attempt(
|
||||||
|
action: function () use ($userId): void {
|
||||||
|
$this->appRoleService->detachUserFromRole($this->roleId, $userId);
|
||||||
|
unset($this->getRoleUsers);
|
||||||
|
},
|
||||||
|
successMessage: 'User removed from role.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
?>
|
?>
|
||||||
@ -235,7 +260,7 @@ public function detachUser(int $userId): void
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<x-mary-button
|
<x-mary-button
|
||||||
:link="route('access-manager.roles-and-apps')"
|
:link="route('access-manager.roles-and-apps.index')"
|
||||||
wire:navigate
|
wire:navigate
|
||||||
icon="lucide.arrow-left"
|
icon="lucide.arrow-left"
|
||||||
class="btn-sm btn-ghost"
|
class="btn-sm btn-ghost"
|
||||||
@ -248,7 +273,7 @@ class="btn-sm btn-ghost"
|
|||||||
|
|
||||||
<div class="flex flex-col md:flex-row gap-6 w-full">
|
<div class="flex flex-col md:flex-row gap-6 w-full">
|
||||||
|
|
||||||
<x-shared.card title="Connected Apps" icon="heroicon-o-squares-2x2" class="pb-2 w-full h-min">
|
<x-shared.card title="Connected Apps" icon="lucide-blocks" class="pb-2 w-full h-min">
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-mary-button wire:click="openAddAppModal" icon="lucide.plus" class="btn-sm">
|
<x-mary-button wire:click="openAddAppModal" icon="lucide.plus" class="btn-sm">
|
||||||
Add App
|
Add App
|
||||||
@ -292,26 +317,22 @@ class="btn-sm btn-error btn-soft btn-circle"
|
|||||||
@endif
|
@endif
|
||||||
</x-shared.card>
|
</x-shared.card>
|
||||||
|
|
||||||
<x-shared.card title="Users" icon="heroicon-o-users" class="pb-2 w-full h-min">
|
<x-shared.card title="Users" icon="lucide-users-round" class="pb-2 w-full h-min">
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-mary-button wire:click="openAddUserModal" icon="lucide.plus" class="btn-sm">
|
<x-mary-button wire:click="openAddUserModal" icon="lucide.plus" class="btn-sm">
|
||||||
Add User
|
Add User
|
||||||
</x-mary-button>
|
</x-mary-button>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
|
|
||||||
@if (count($this->getRoleUsers))
|
@if ($this->getRoleUsers->count())
|
||||||
<x-mary-table
|
<x-mary-table
|
||||||
:headers="$userHeaders->toArray()"
|
:headers="$userHeaders->toArray()"
|
||||||
:rows="$this->getRoleUsers"
|
:rows="$this->getRoleUsers->toArray()"
|
||||||
>
|
>
|
||||||
@scope('cell_name', $row)
|
@scope('cell_name', $row)
|
||||||
<span class="font-medium text-gray-900">{{ $row['name'] }}</span>
|
<span class="font-medium text-gray-900">{{ $row['name'] }}</span>
|
||||||
@endscope
|
@endscope
|
||||||
|
|
||||||
@scope('cell_email', $row)
|
|
||||||
{{ $row['email'] }}
|
|
||||||
@endscope
|
|
||||||
|
|
||||||
@scope('actions', $row)
|
@scope('actions', $row)
|
||||||
<x-mary-button
|
<x-mary-button
|
||||||
icon="lucide.trash"
|
icon="lucide.trash"
|
||||||
@ -396,7 +417,7 @@ class="bg-gray-50 text-gray-500 cursor-not-allowed"
|
|||||||
<x-mary-modal wire:model="showAddUserModal" title="Add User to {{ $roleName }}">
|
<x-mary-modal wire:model="showAddUserModal" title="Add User to {{ $roleName }}">
|
||||||
<x-mary-form wire:submit="assignUser">
|
<x-mary-form wire:submit="assignUser">
|
||||||
<x-mary-choices
|
<x-mary-choices
|
||||||
wire:model="selectedUserId"
|
wire:model="selectedUserIds"
|
||||||
:options="$usersSearchable"
|
:options="$usersSearchable"
|
||||||
search-function="searchUsers"
|
search-function="searchUsers"
|
||||||
option-label="name"
|
option-label="name"
|
||||||
@ -404,8 +425,7 @@ class="bg-gray-50 text-gray-500 cursor-not-allowed"
|
|||||||
no-result-text="No users found."
|
no-result-text="No users found."
|
||||||
placeholder="Search users..."
|
placeholder="Search users..."
|
||||||
searchable
|
searchable
|
||||||
single
|
label="Users"
|
||||||
label="User"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
|
|||||||
@ -10,8 +10,10 @@
|
|||||||
Route::livewire('dashboard', 'pages::dashboard')->name('dashboard');
|
Route::livewire('dashboard', 'pages::dashboard')->name('dashboard');
|
||||||
|
|
||||||
Route::prefix('access-manager')->name('access-manager.')->group(function (): void {
|
Route::prefix('access-manager')->name('access-manager.')->group(function (): void {
|
||||||
Route::livewire('roles-and-apps', 'pages::access-manager.roles-and-apps.index')->name('roles-and-apps');
|
Route::prefix('roles-and-apps')->name('roles-and-apps.')->group(function (): void {
|
||||||
Route::livewire('roles-and-apps/{id}', 'pages::access-manager.roles-and-apps.show')->name('roles-and-apps.show');
|
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('apps')->name('apps.')->group(function (): void {
|
Route::prefix('apps')->name('apps.')->group(function (): void {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user