Snapshot of main at 9f39a6e1e (2026-11-02)

This commit is contained in:
Ronald Huynen committed 2026-11-02 10:41:47 +01:00
commit 6ccf1792d7
2580 files changed
+1040202

No files matched your search

@@ -0,0 +1,158 @@
{{-- Permission grid partial. Expects: $model (string), $permissionGroups (array), $allVerbs (array), $isSystem (bool) --}}
@php $isSystem = $isSystem ?? false; @endphp
<div class="-mx-6 overflow-x-auto px-6">
<div style="min-width: calc(140px + {{ count($allVerbs) }} * 72px)">
@if ($isSystem)
<div class="mb-2 flex items-center gap-1.5 text-xs text-gray-500">
<x-icon name="lock-closed" class="h-3.5 w-3.5" />
{{ __('System role permissions are locked and cannot be modified.') }}
</div>
@endif
@foreach ($permissionGroups as $group)
@if (!empty($group['section']))
<div class="mt-3 mb-1 border-t border-gray-200 pt-2">
<span class="text-xs font-semibold uppercase tracking-wide text-gray-400">{{ __($group['section']) }}</span>
</div>
@elseif (!empty($group['inline']))
@php $inlinePerms = array_values($group['permissions']); $inlineKeys = array_keys($group['permissions']); @endphp
<div class="grid gap-x-1 rounded hover:bg-gray-50 py-2"
style="grid-template-columns: 140px repeat({{ count($allVerbs) }}, 72px); align-items: start;">
<div class="text-xs font-medium text-gray-700 pl-1">{{ __($group['inline']) }}</div>
@foreach ($allVerbs as $vIndex => $verb)
<div class="flex justify-center">
@if (isset($inlineKeys[$vIndex]))
<label class="flex flex-col items-center gap-0.5 {{ $isSystem ? 'cursor-not-allowed' : 'cursor-pointer' }}">
<input
type="checkbox"
value="{{ $inlineKeys[$vIndex] }}"
wire:model="{{ $model }}.permissions"
{{ $isSystem ? 'disabled' : '' }}
class="rounded border-gray-300 text-theme-brand focus:ring-theme-brand h-3.5 w-3.5 {{ $isSystem ? 'cursor-not-allowed opacity-60' : 'cursor-pointer' }}">
<span class="text-xs text-gray-500">{{ __($inlinePerms[$vIndex]) }}</span>
</label>
@endif
</div>
@endforeach
</div>
@elseif (!empty($group['fixed']))
@foreach ($group['fixed'] as $fixedPermission)
<div class="grid gap-x-1 rounded hover:bg-gray-50 py-2" style="grid-template-columns: 140px repeat({{ count($allVerbs) }}, 72px); align-items: start;">
<div class="text-xs font-medium text-gray-700 pl-1 truncate">
{{ __($fixedPermission) }}
</div>
@foreach ($allVerbs as $verb)
<div class="flex justify-center">
@if ($loop->last)
<label class="flex flex-col items-center gap-0.5 {{ $isSystem ? 'cursor-not-allowed' : 'cursor-pointer' }}">
<input
type="checkbox"
value="{{ $fixedPermission }}"
wire:model="{{ $model }}.permissions"
{{ $isSystem ? 'disabled' : '' }}
class="rounded border-gray-300 text-theme-brand focus:ring-theme-brand h-3.5 w-3.5 {{ $isSystem ? 'cursor-not-allowed opacity-60' : 'cursor-pointer' }}">
<span class="text-xs text-gray-500">{{ __($verb) }}</span>
</label>
@endif
</div>
@endforeach
</div>
@endforeach
@else
@php
$resource = $group['resource'];
$verbs = $group['verbs'];
$allPerms = array_map(fn($v) => $v . ' ' . $resource, $verbs);
$allPermsJs = json_encode($allPerms);
$manageKey = 'manage ' . $resource;
$rowLocked = $isSystem && empty($group['unlocked']);
@endphp
<div
class="grid gap-x-1 rounded hover:bg-gray-50 py-2"
style="grid-template-columns: 140px repeat({{ count($allVerbs) }}, 72px); align-items: start;"
@if (!$rowLocked)
x-data="{
toggleManage(checked, allPerms) {
let current = [...($wire.get('{{ $model }}.permissions') || [])];
allPerms.forEach(perm => {
if (checked) {
if (!current.includes(perm)) current.push(perm);
} else {
current = current.filter(p => p !== perm);
}
});
$wire.set('{{ $model }}.permissions', current);
},
uncheckManage(manageKey) {
let current = [...($wire.get('{{ $model }}.permissions') || [])];
current = current.filter(p => p !== manageKey);
$wire.set('{{ $model }}.permissions', current);
},
checkView(viewKey) {
let current = [...($wire.get('{{ $model }}.permissions') || [])];
if (!current.includes(viewKey)) {
current.push(viewKey);
$wire.set('{{ $model }}.permissions', current);
}
},
uncheckAll(allPerms) {
let current = [...($wire.get('{{ $model }}.permissions') || [])];
current = current.filter(p => !allPerms.includes(p));
$wire.set('{{ $model }}.permissions', current);
}
}"
@endif
>
<div class="text-xs font-medium text-gray-700 pl-1 truncate">
{{ __($group['label']) }}
</div>
@foreach ($allVerbs as $verb)
<div class="flex justify-center">
@if (in_array($verb, $verbs))
@if ($verb === 'manage')
<label class="flex flex-col items-center gap-0.5 {{ $rowLocked ? 'cursor-not-allowed' : 'cursor-pointer' }}">
<input
type="checkbox"
value="{{ $manageKey }}"
wire:model="{{ $model }}.permissions"
{{ $rowLocked ? 'disabled' : '' }}
@if (!$rowLocked)
x-on:change="toggleManage($event.target.checked, {{ $allPermsJs }}); {{ in_array('view', $verbs) ? "if (\$event.target.checked) checkView('view {$resource}')" : '' }}"
@endif
class="rounded border-gray-300 text-theme-brand focus:ring-theme-brand h-3.5 w-3.5 {{ $rowLocked ? 'cursor-not-allowed opacity-60' : 'cursor-pointer' }}">
<span class="text-xs text-gray-500">{{ __($verb) }}</span>
</label>
@elseif ($verb === 'view')
<label class="flex flex-col items-center gap-0.5 {{ $rowLocked ? 'cursor-not-allowed' : 'cursor-pointer' }}">
<input
type="checkbox"
value="view {{ $resource }}"
wire:model="{{ $model }}.permissions"
{{ $rowLocked ? 'disabled' : '' }}
@if (!$rowLocked)
x-on:change="if (!$event.target.checked) uncheckAll({{ $allPermsJs }})"
@endif
class="rounded border-gray-300 text-theme-brand focus:ring-theme-brand h-3.5 w-3.5 {{ $rowLocked ? 'cursor-not-allowed opacity-60' : 'cursor-pointer' }}">
<span class="text-xs text-gray-500">{{ __($verb) }}</span>
</label>
@else
<label class="flex flex-col items-center gap-0.5 {{ $rowLocked ? 'cursor-not-allowed' : 'cursor-pointer' }}">
<input
type="checkbox"
value="{{ $verb }} {{ $resource }}"
wire:model="{{ $model }}.permissions"
{{ $rowLocked ? 'disabled' : '' }}
@if (!$rowLocked)
x-on:change="if (!$event.target.checked) uncheckManage('{{ $manageKey }}'); if ($event.target.checked) checkView('view {{ $resource }}')"
@endif
class="rounded border-gray-300 text-theme-brand focus:ring-theme-brand h-3.5 w-3.5 {{ $rowLocked ? 'cursor-not-allowed opacity-60' : 'cursor-pointer' }}">
<span class="text-xs text-gray-500">{{ __($verb) }}</span>
</label>
@endif
@endif
</div>
@endforeach
</div>
@endif
@endforeach
</div>
</div>
@@ -0,0 +1,3 @@
<div>
{{-- Care about people's approval and you will be their prisoner. --}}
</div>
@@ -0,0 +1,617 @@
<div class="mt-12">
<!-- Header -->
<div class="mb-6">
<h2 class="text-lg font-semibold text-theme-primary">{{ __('Admin Roles') }}</h2>
<p class="mt-1 text-sm text-theme-secondary">{{ __('Manage roles that can be assigned to admin profiles. System roles cannot be modified.') }}</p>
</div>
<!-- Create button -->
@can('create roles')
<div class="mb-6">
<x-jetstream.button wire:click="openCreateModal" class="bg-theme-brand hover:bg-opacity-80">
<x-icon name="plus" class="mr-2 h-4 w-4" />
{{ __('Create Role') }}
</x-jetstream.button>
</div>
@endcan
<!-- Search -->
<div class="mb-6 flex items-center gap-3">
<div class="relative w-2/3">
<input
class="w-full rounded-md border border-theme-primary px-3 py-1 pr-10 text-theme-primary shadow-sm focus:border-theme-accent focus:outline-none focus:ring-1 focus:ring-theme-accent sm:text-sm"
placeholder="{{ __('Search roles') }}..."
type="text"
wire:model.live.debounce.300ms="search">
@if ($search)
<button
class="absolute inset-y-0 right-0 flex items-center pr-3 text-theme-secondary hover:text-theme-primary focus:outline-none"
wire:click.prevent="resetSearch">
<x-icon mini name="backspace" solid />
</button>
@endif
</div>
</div>
<!-- Table -->
<div class="overflow-hidden rounded-lg bg-white shadow-sm">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 cursor-pointer"
wire:click="sortBy('label')">
<div class="flex items-center space-x-1">
<span>{{ __('Label') }}</span>
@if ($sortField === 'label')
<x-icon :name="$sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'" class="h-4 w-4" micro />
@endif
</div>
</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 cursor-pointer"
wire:click="sortBy('name')">
<div class="flex items-center space-x-1">
<span>{{ __('Name') }}</span>
@if ($sortField === 'name')
<x-icon :name="$sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'" class="h-4 w-4" micro />
@endif
</div>
</th>
<th class="hidden xl:table-cell px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Permissions') }}
</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Type') }}
</th>
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
{{ __('Actions') }}
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($roles as $role)
<tr class="hover:bg-gray-50 align-top" wire:key="role-{{ $role->id }}">
<td class="px-4 py-3 text-sm text-gray-900">
{{ $role->label }}
</td>
<td class="px-4 py-3 text-sm text-gray-700">
{{ $role->name }}
</td>
<td class="hidden xl:table-cell px-4 py-3 text-xs text-gray-500 leading-relaxed">
@php
$perms = $role->permissions ?? [];
$managedResources = collect($perms)
->filter(fn($p) => str_starts_with($p, 'manage '))
->map(fn($p) => substr($p, 7))
->all();
$displayed = collect($perms)->filter(function ($p) use ($managedResources) {
if (!str_contains($p, ' ')) return true;
[$verb, $resource] = explode(' ', $p, 2);
return $verb === 'manage' || !in_array($resource, $managedResources);
})->values()->all();
@endphp
{{ implode(', ', $displayed) }}
</td>
<td class="px-4 py-3 text-sm">
@if ($role->is_system)
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">
{{ __('System') }}
</span>
@else
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">
{{ __('Custom') }}
</span>
@endif
</td>
<td class="px-4 py-3 text-right">
<div class="flex justify-end space-x-2">
@can('update roles')
<x-jetstream.secondary-button
title="{{ __('Edit') }}"
wire:click="openEditModal({{ $role->id }})">
<x-icon name="pencil-square" class="h-5 w-5" />
</x-jetstream.secondary-button>
@endcan
@can('delete roles')
@if (!$role->is_system)
<x-jetstream.danger-button
title="{{ __('Delete') }}"
wire:click="openDeleteModal({{ $role->id }})">
<x-icon name="trash" class="h-5 w-5" />
</x-jetstream.danger-button>
@endif
@endcan
</div>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="px-4 py-8 text-center text-sm text-gray-400">
{{ __('No roles found.') }}
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if ($roles->hasPages())
<div class="border-t border-gray-200 px-4 py-3">
{{ $roles->links() }}
</div>
@endif
</div>
@php
$roleTableHeaders = ['Label', 'Name', 'Permissions', 'Type', 'Actions'];
@endphp
<!-- Organization Roles -->
<div class="mt-12 mb-6">
<h2 class="text-lg font-semibold text-theme-primary">{{ __('Organization Roles') }}</h2>
<p class="mt-1 text-sm text-theme-secondary">{{ __('Roles assigned to users linked to organization profiles. System roles cannot be deleted.') }}</p>
</div>
@can('create roles')
<div class="mb-6">
<x-jetstream.button wire:click="openCreateOrgRoleModal('organization')" class="bg-theme-brand hover:bg-opacity-80">
<x-icon name="plus" class="mr-2 h-4 w-4" />
{{ __('Create Organization Role') }}
</x-jetstream.button>
</div>
@endcan
<div class="overflow-hidden rounded-lg bg-white shadow-sm">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Label') }}</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Name') }}</th>
<th class="hidden xl:table-cell px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Permissions') }}</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Type') }}</th>
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Actions') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($orgRoles as $orgRole)
<tr class="hover:bg-gray-50 align-top" wire:key="org-role-{{ $orgRole->id }}">
<td class="px-4 py-3 text-sm text-gray-900">{{ $orgRole->label }}</td>
<td class="px-4 py-3 text-sm text-gray-700">{{ $orgRole->name }}</td>
<td class="hidden xl:table-cell px-4 py-3 text-xs text-gray-500 leading-relaxed">{{ implode(', ', $orgRole->permissions ?? []) }}</td>
<td class="px-4 py-3 text-sm">
@if ($orgRole->is_system)
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">{{ __('System') }}</span>
@else
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">{{ __('Custom') }}</span>
@endif
</td>
<td class="px-4 py-3 text-right">
<div class="flex justify-end space-x-2">
@can('update roles')
<x-jetstream.secondary-button title="{{ __('Edit') }}"
wire:click="openEditOrgRoleModal({{ $orgRole->id }})">
<x-icon name="pencil-square" class="h-5 w-5" />
</x-jetstream.secondary-button>
@endcan
@can('delete roles')
@if (!$orgRole->is_system)
<x-jetstream.danger-button title="{{ __('Delete') }}"
wire:click="openDeleteOrgRoleModal({{ $orgRole->id }})">
<x-icon name="trash" class="h-5 w-5" />
</x-jetstream.danger-button>
@endif
@endcan
</div>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="px-4 py-8 text-center text-sm text-gray-400">{{ __('No roles found.') }}</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<!-- Bank Roles -->
<div class="mt-12 mb-6">
<h2 class="text-lg font-semibold text-theme-primary">{{ __('Bank Roles') }}</h2>
<p class="mt-1 text-sm text-theme-secondary">{{ __('Roles assigned to users linked to bank profiles. System roles cannot be deleted.') }}</p>
</div>
@can('create roles')
<div class="mb-6">
<x-jetstream.button wire:click="openCreateOrgRoleModal('bank')" class="bg-theme-brand hover:bg-opacity-80">
<x-icon name="plus" class="mr-2 h-4 w-4" />
{{ __('Create Bank Role') }}
</x-jetstream.button>
</div>
@endcan
<div class="overflow-hidden rounded-lg bg-white shadow-sm">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Label') }}</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Name') }}</th>
<th class="hidden xl:table-cell px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Permissions') }}</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Type') }}</th>
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Actions') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($bankRoles as $orgRole)
<tr class="hover:bg-gray-50 align-top" wire:key="bank-role-{{ $orgRole->id }}">
<td class="px-4 py-3 text-sm text-gray-900">{{ $orgRole->label }}</td>
<td class="px-4 py-3 text-sm text-gray-700">{{ $orgRole->name }}</td>
<td class="hidden xl:table-cell px-4 py-3 text-xs text-gray-500 leading-relaxed">{{ implode(', ', $orgRole->permissions ?? []) }}</td>
<td class="px-4 py-3 text-sm">
@if ($orgRole->is_system)
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">{{ __('System') }}</span>
@else
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">{{ __('Custom') }}</span>
@endif
</td>
<td class="px-4 py-3 text-right">
<div class="flex justify-end space-x-2">
@can('update roles')
<x-jetstream.secondary-button title="{{ __('Edit') }}"
wire:click="openEditOrgRoleModal({{ $orgRole->id }})">
<x-icon name="pencil-square" class="h-5 w-5" />
</x-jetstream.secondary-button>
@endcan
@can('delete roles')
@if (!$orgRole->is_system)
<x-jetstream.danger-button title="{{ __('Delete') }}"
wire:click="openDeleteOrgRoleModal({{ $orgRole->id }})">
<x-icon name="trash" class="h-5 w-5" />
</x-jetstream.danger-button>
@endif
@endcan
</div>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="px-4 py-8 text-center text-sm text-gray-400">{{ __('No roles found.') }}</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
@php
// Rows = resources, columns = verbs.
// Each row lists only the verbs that apply to that resource.
$permissionGroups = [
['label' => 'Posts', 'resource' => 'posts', 'verbs' => ['view','create','update','publish','unpublish','delete','manage']],
['label' => 'Polls', 'resource' => 'polls', 'verbs' => ['view','create','update','export','delete','manage']],
['label' => 'Calls', 'resource' => 'calls', 'verbs' => ['view','create','update','publish','unpublish','delete','manage']],
['label' => 'Mailings', 'resource' => 'mailings', 'verbs' => ['view','create','update','send','delete','manage']],
['label' => 'Tags', 'resource' => 'tags', 'verbs' => ['view','create','update','delete','manage']],
['label' => 'Categories', 'resource' => 'categories', 'verbs' => ['view','create','update','delete','manage']],
['label' => 'Users', 'resource' => 'users', 'verbs' => ['view','create','update','delete','manage']],
['label' => 'Organizations', 'resource' => 'organizations', 'verbs' => ['view','create','update','delete','manage']],
['label' => 'Banks', 'resource' => 'banks', 'verbs' => ['view','create','update','delete','manage']],
['label' => 'Admins', 'resource' => 'admins', 'verbs' => ['view','create','update','delete','manage']],
['label' => 'Accounts', 'resource' => 'accounts', 'verbs' => ['view','create','update','delete','manage']],
['label' => 'Profiles', 'resource' => 'profiles', 'verbs' => ['view','create','update','delete','manage']],
['label' => 'Roles', 'resource' => 'roles', 'verbs' => ['view','create','update','delete','manage']],
['label' => 'Statistics', 'resource' => 'stats', 'verbs' => ['view'], 'unlocked' => true],
['label' => 'Maintenance', 'resource' => 'maintenance', 'verbs' => ['manage']],
['section' => 'Logs'],
['label' => 'Search', 'resource' => 'search-metrics', 'verbs' => ['view'], 'unlocked' => true],
['label' => 'Search Reindex', 'resource' => 'search-reindex-log', 'verbs' => ['view'], 'unlocked' => true],
['label' => 'Application', 'resource' => 'application-log', 'verbs' => ['view'], 'unlocked' => true],
['label' => 'Inactive Profiles', 'resource' => 'inactive-profiles-log', 'verbs' => ['view'], 'unlocked' => true],
];
// All verb columns in display order
$allVerbs = ['view','create','update','publish','unpublish','send','export','delete','manage'];
// Admin profile capability permissions (separate inline grid)
$adminCapGroups = [
['inline' => 'Capabilities', 'permissions' => [
'admin reports' => 'Reports',
'admin contacts' => 'Contacts',
'admin messenger' => 'Messenger',
'admin settings' => 'Settings',
'admin change-password' => 'Change Password',
]],
];
$adminCapVerbs = ['reports', 'contacts', 'messenger', 'settings', 'change-password'];
@endphp
<!-- Create Role Modal -->
@if ($modalCreateRole)
<x-jetstream.dialog-modal wire:key="modalCreateRole" wire:model.live="modalCreateRole" maxWidth="4xl">
<x-slot name="title">{{ __('Create New Role') }}</x-slot>
<x-slot name="content">
<div class="space-y-5">
<div class="grid grid-cols-2 gap-4">
<x-input
label="{{ __('Name') }}"
placeholder="{{ __('e.g. site-editor') }}"
hint="{{ __('Lowercase letters, numbers and hyphens only. Cannot be changed later.') }}"
wire:model.defer="createRole.name" />
<x-input
label="{{ __('Label') }}"
placeholder="{{ __('e.g. Site Editor') }}"
hint="{{ __('Human-readable display name.') }}"
wire:model.defer="createRole.label" />
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700">{{ __('Permissions') }}</label>
@include('livewire.roles._permission-grid', ['model' => 'createRole', 'isSystem' => false])
@error('createRole.permissions')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label class="mb-1 block text-sm font-medium text-gray-700">{{ __('Admin Profile Capabilities') }}</label>
<p class="mb-2 text-xs text-gray-500">{{ __('Permissions that apply when the user is acting as an admin profile.') }}</p>
@include('livewire.roles._permission-grid', ['model' => 'createRole', 'isSystem' => false, 'permissionGroups' => $adminCapGroups, 'allVerbs' => $adminCapVerbs])
</div>
</div>
</x-slot>
<x-slot name="footer">
<x-jetstream.secondary-button wire:click="$set('modalCreateRole', false)" wire:loading.attr="disabled">
{{ __('Cancel') }}
</x-jetstream.secondary-button>
<x-jetstream.button class="ml-3 bg-theme-brand" wire:click.prevent="saveCreate" wire:loading.attr="disabled">
{{ __('Create Role') }}
</x-jetstream.button>
</x-slot>
</x-jetstream.dialog-modal>
@endif
<!-- Edit Role Modal -->
@if ($modalEditRole)
<x-jetstream.dialog-modal wire:key="modalEditRole" wire:model.live="modalEditRole" maxWidth="4xl">
<x-slot name="title">
{{ __('Edit Role') }}: <span class="font-mono text-base">{{ $editRole['name'] }}</span>
</x-slot>
<x-slot name="content">
<div class="space-y-5">
<x-input
label="{{ __('Label') }}"
placeholder="{{ __('e.g. Site Editor') }}"
wire:model.defer="editRole.label" />
<div>
<label class="mb-2 block text-sm font-medium text-gray-700">{{ __('Permissions') }}</label>
@include('livewire.roles._permission-grid', ['model' => 'editRole', 'isSystem' => $editRoleIsSystem])
@error('editRole.permissions')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label class="mb-1 block text-sm font-medium text-gray-700">{{ __('Admin Profile Capabilities') }}</label>
<p class="mb-2 text-xs text-gray-500">{{ __('Permissions that apply when the user is acting as an admin profile.') }}</p>
@include('livewire.roles._permission-grid', ['model' => 'editRole', 'isSystem' => $editRoleIsSystem, 'permissionGroups' => $adminCapGroups, 'allVerbs' => $adminCapVerbs])
</div>
<div class="rounded-md border border-gray-300 bg-gray-50 p-4">
<div class="flex">
<div class="flex-shrink-0">
<x-icon class="h-5 w-5 text-gray-400" name="exclamation-triangle" />
</div>
<div class="ml-3">
<p class="text-sm text-gray-800">
<strong>{{ __('Important') }}:</strong>
{{ __('Changing permissions will immediately update all admin profiles that have this role assigned.') }}
</p>
</div>
</div>
</div>
</div>
</x-slot>
<x-slot name="footer">
<x-jetstream.secondary-button wire:click="$set('modalEditRole', false)" wire:loading.attr="disabled">
{{ __('Cancel') }}
</x-jetstream.secondary-button>
<x-jetstream.button class="ml-3 bg-theme-brand" wire:click.prevent="saveEdit" wire:loading.attr="disabled">
{{ __('Save Changes') }}
</x-jetstream.button>
</x-slot>
</x-jetstream.dialog-modal>
@endif
<!-- Delete Role Modal -->
@if ($modalDeleteRole)
<x-jetstream.dialog-modal wire:key="modalDeleteRole" wire:model.live="modalDeleteRole" maxWidth="lg">
<x-slot name="title">{{ __('Delete Role') }}</x-slot>
<x-slot name="content">
<div class="space-y-4">
<p class="text-sm text-gray-700">
{{ __('Are you sure you want to delete the role') }}
<strong>{{ $deleteRoleName }}</strong>?
</p>
@if (!empty($deleteAffectedUsers))
<div class="rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700">
<p class="mb-2 font-medium flex items-center gap-1">
<x-icon name="exclamation-triangle" class="h-4 w-4 shrink-0" />
{{ __('The following users will be unlinked from their admin profile:') }}
</p>
<ul class="ml-5 list-disc space-y-0.5">
@foreach ($deleteAffectedUsers as $entry)
<li>{{ $entry['user'] }} &mdash; <span class="font-medium">{{ $entry['profile'] }}</span></li>
@endforeach
</ul>
</div>
@else
<div class="rounded-md border border-gray-200 bg-gray-50 p-3 text-sm text-gray-600">
<x-icon name="information-circle" class="mr-1 inline h-4 w-4" />
{{ __('No users are currently assigned this role.') }}
</div>
@endif
</div>
</x-slot>
<x-slot name="footer">
<x-jetstream.secondary-button wire:click="$set('modalDeleteRole', false)" wire:loading.attr="disabled">
{{ __('Cancel') }}
</x-jetstream.secondary-button>
<x-jetstream.danger-button class="ml-3" wire:click.prevent="deleteRole" wire:loading.attr="disabled">
{{ __('Delete Role') }}
</x-jetstream.danger-button>
</x-slot>
</x-jetstream.dialog-modal>
@endif
@php
$orgPermissionGroups = [
['inline' => 'Capabilities', 'permissions' => [
'org pay' => 'Pay',
'org transactions' => 'Transactions',
'org reports' => 'Reports',
'org stats' => 'Statistics',
'org contacts' => 'Contacts',
'org calls' => 'Calls',
'org messenger' => 'Messenger',
'org edit-profile' => 'Edit Profile',
'org settings' => 'Settings',
'org change-password' => 'Change Password',
]],
];
$orgAllVerbs = ['pay','transactions','reports','stats','contacts','calls','messenger','edit-profile','settings','change-password'];
@endphp
<!-- Create Org/Bank Role Modal -->
@if ($modalCreateOrgRole)
<x-jetstream.dialog-modal wire:key="modalCreateOrgRole" wire:model.live="modalCreateOrgRole" maxWidth="4xl">
<x-slot name="title">{{ $createOrgRoleType === 'bank' ? __('Create New Bank Role') : __('Create New Organization Role') }}</x-slot>
<x-slot name="content">
<div class="space-y-5">
<div class="grid grid-cols-2 gap-4">
<x-input
label="{{ __('Name') }}"
placeholder="{{ $createOrgRoleType === 'bank' ? __('e.g. bank-editor') : __('e.g. organization-editor') }}"
hint="{{ __('Must start with') }} &quot;{{ $createOrgRoleType }}-&quot;. {{ __('Lowercase letters, numbers and hyphens only. Cannot be changed later.') }}"
wire:model.defer="createOrgRole.name" />
<x-input
label="{{ __('Label') }}"
placeholder="{{ __('e.g. Org Editor') }}"
hint="{{ __('Human-readable display name.') }}"
wire:model.defer="createOrgRole.label" />
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700">{{ __('Permissions') }}</label>
@include('livewire.roles._permission-grid', ['model' => 'createOrgRole', 'isSystem' => false, 'permissionGroups' => $orgPermissionGroups, 'allVerbs' => $orgAllVerbs])
@error('createOrgRole.permissions')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</x-slot>
<x-slot name="footer">
<x-jetstream.secondary-button wire:click="$set('modalCreateOrgRole', false)" wire:loading.attr="disabled">
{{ __('Cancel') }}
</x-jetstream.secondary-button>
<x-jetstream.button class="ml-3 bg-theme-brand" wire:click.prevent="saveCreateOrgRole" wire:loading.attr="disabled">
{{ __('Create Role') }}
</x-jetstream.button>
</x-slot>
</x-jetstream.dialog-modal>
@endif
<!-- Edit Org Role Modal -->
@if ($modalEditOrgRole)
<x-jetstream.dialog-modal wire:key="modalEditOrgRole" wire:model.live="modalEditOrgRole" maxWidth="4xl">
<x-slot name="title">
{{ __('Edit Role') }}: <span class="font-mono text-base">{{ $editOrgRole['name'] }}</span>
</x-slot>
<x-slot name="content">
<div class="space-y-5">
<x-input
label="{{ __('Label') }}"
placeholder="{{ __('e.g. Org Editor') }}"
wire:model.defer="editOrgRole.label" />
<div>
<label class="mb-2 block text-sm font-medium text-gray-700">{{ __('Permissions') }}</label>
@include('livewire.roles._permission-grid', ['model' => 'editOrgRole', 'isSystem' => $editOrgRoleIsSystem, 'permissionGroups' => $orgPermissionGroups, 'allVerbs' => $orgAllVerbs])
@error('editOrgRole.permissions')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div class="rounded-md border border-gray-300 bg-gray-50 p-4">
<div class="flex">
<div class="flex-shrink-0">
<x-icon class="h-5 w-5 text-gray-400" name="exclamation-triangle" />
</div>
<div class="ml-3">
<p class="text-sm text-gray-800">
<strong>{{ __('Important') }}:</strong>
{{ str_starts_with($editOrgRole['name'], 'bank-') ? __('Changing permissions will immediately update all bank profiles that have this role assigned.') : __('Changing permissions will immediately update all organization profiles that have this role assigned.') }}
</p>
</div>
</div>
</div>
</div>
</x-slot>
<x-slot name="footer">
<x-jetstream.secondary-button wire:click="$set('modalEditOrgRole', false)" wire:loading.attr="disabled">
{{ __('Cancel') }}
</x-jetstream.secondary-button>
<x-jetstream.button class="ml-3 bg-theme-brand" wire:click.prevent="saveEditOrgRole" wire:loading.attr="disabled">
{{ __('Save Changes') }}
</x-jetstream.button>
</x-slot>
</x-jetstream.dialog-modal>
@endif
<!-- Delete Org Role Modal -->
@if ($modalDeleteOrgRole)
<x-jetstream.dialog-modal wire:key="modalDeleteOrgRole" wire:model.live="modalDeleteOrgRole" maxWidth="lg">
<x-slot name="title">{{ __('Delete Role') }}</x-slot>
<x-slot name="content">
<div class="space-y-4">
<p class="text-sm text-gray-700">
{{ __('Are you sure you want to delete the role') }}
<strong>{{ $deleteOrgRoleName }}</strong>?
</p>
@if (!empty($deleteOrgAffectedUsers))
<div class="rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700">
<p class="mb-2 font-medium flex items-center gap-1">
<x-icon name="exclamation-triangle" class="h-4 w-4 shrink-0" />
{{ $deleteOrgRoleType === 'bank'
? __('The following users will be unlinked from their bank profile:')
: __('The following users will be unlinked from their organization profile:') }}
</p>
<ul class="ml-5 list-disc space-y-0.5">
@foreach ($deleteOrgAffectedUsers as $entry)
<li>{{ $entry['user'] }} &mdash; <span class="font-medium">{{ $entry['profile'] }}</span></li>
@endforeach
</ul>
</div>
@else
<div class="rounded-md border border-gray-200 bg-gray-50 p-3 text-sm text-gray-600">
<x-icon name="information-circle" class="mr-1 inline h-4 w-4" />
{{ __('No users are currently assigned this role.') }}
</div>
@endif
</div>
</x-slot>
<x-slot name="footer">
<x-jetstream.secondary-button wire:click="$set('modalDeleteOrgRole', false)" wire:loading.attr="disabled">
{{ __('Cancel') }}
</x-jetstream.secondary-button>
<x-jetstream.danger-button class="ml-3" wire:click.prevent="deleteOrgRole" wire:loading.attr="disabled">
{{ __('Delete Role') }}
</x-jetstream.danger-button>
</x-slot>
</x-jetstream.dialog-modal>
@endif
</div>