124 lines
8.8 KiB
PHP
124 lines
8.8 KiB
PHP
<div class="space-y-6">
|
|
<div class="flex flex-wrap items-end justify-between gap-4 animate-rise">
|
|
<div>
|
|
<h1 class="text-2xl font-bold tracking-tight text-ink">{{ __('users.title') }}</h1>
|
|
<p class="mt-1 text-sm text-muted">{{ __('users.subtitle') }}</p>
|
|
</div>
|
|
<div class="text-right">
|
|
<p class="font-mono text-lg font-semibold text-ink">{{ $used }} / {{ $limit }}</p>
|
|
<p class="text-xs text-muted">{{ __('users.seats_used') }}</p>
|
|
<p class="text-xs text-muted">{{ __('users.seats_note') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Invite --}}
|
|
<form wire:submit="invite" class="grid grid-cols-1 gap-3 rounded-lg border border-line bg-surface p-5 shadow-xs animate-rise [animation-delay:60ms] sm:grid-cols-[1fr_1fr_auto_auto] sm:items-end">
|
|
<div>
|
|
<label class="text-sm font-medium text-body" for="inviteEmail">{{ __('users.invite_email') }}</label>
|
|
<input id="inviteEmail" type="email" wire:model="inviteEmail" class="mt-1.5 w-full rounded-md border border-line-strong bg-surface px-3 py-2 text-sm text-ink" />
|
|
@error('inviteEmail')<p class="mt-1 text-xs text-danger">{{ $message }}</p>@enderror
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-medium text-body" for="inviteName">{{ __('users.invite_name') }}</label>
|
|
<input id="inviteName" type="text" wire:model="inviteName" class="mt-1.5 w-full rounded-md border border-line-strong bg-surface px-3 py-2 text-sm text-ink" />
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-medium text-body" for="inviteRole">{{ __('users.role') }}</label>
|
|
<select id="inviteRole" wire:model="inviteRole" class="mt-1.5 rounded-md border border-line-strong bg-surface px-3 py-2 text-sm text-body">
|
|
<option value="admin">{{ __('users.role_admin') }}</option>
|
|
<option value="member">{{ __('users.role_member') }}</option>
|
|
<option value="readonly">{{ __('users.role_readonly') }}</option>
|
|
</select>
|
|
</div>
|
|
<x-ui.button variant="primary" type="submit" wire:loading.attr="disabled" wire:target="invite">
|
|
<x-ui.icon name="plus" class="size-4" />{{ __('users.invite') }}
|
|
</x-ui.button>
|
|
</form>
|
|
|
|
{{-- Seats --}}
|
|
<div class="overflow-hidden rounded-lg border border-line bg-surface shadow-xs animate-rise [animation-delay:120ms]">
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm">
|
|
<thead>
|
|
<tr class="border-b border-line bg-surface-2 text-left text-xs font-semibold text-muted">
|
|
<th class="px-4 py-3 font-semibold">{{ __('users.col_person') }}</th>
|
|
<th class="px-4 py-3 font-semibold">{{ __('users.role') }}</th>
|
|
<th class="px-4 py-3 font-semibold">{{ __('users.col_status') }}</th>
|
|
<th class="px-4 py-3 text-right font-semibold">{{ __('users.col_actions') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach ($seats as $seat)
|
|
<tr wire:key="seat-{{ $seat->uuid }}" class="border-b border-line last:border-0 hover:bg-surface-hover">
|
|
<td class="px-4 py-3">
|
|
<p class="font-medium text-ink">{{ $seat->name ?: $seat->email }}</p>
|
|
@if ($seat->name)<p class="text-xs text-muted">{{ $seat->email }}</p>@endif
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
@if ($seat->role === 'owner')
|
|
<span class="inline-flex items-center gap-1.5 rounded-pill bg-accent-subtle px-2.5 py-0.5 text-xs font-semibold text-accent-text">{{ __('users.role_owner') }}</span>
|
|
@else
|
|
<select wire:change="setRole('{{ $seat->uuid }}', $event.target.value)" class="rounded-md border border-line-strong bg-surface px-2 py-1 text-xs text-body">
|
|
@foreach (['admin', 'member', 'readonly'] as $r)
|
|
<option value="{{ $r }}" @selected($seat->role === $r)>{{ __('users.role_'.$r) }}</option>
|
|
@endforeach
|
|
</select>
|
|
@endif
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
@php $sb = ['active' => 'active', 'invited' => 'provisioning', 'revoked' => 'suspended'][$seat->status] ?? 'info'; @endphp
|
|
<x-ui.badge :status="$sb">{{ __('users.status_'.$seat->status) }}</x-ui.badge>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<div class="flex items-center justify-end gap-1.5">
|
|
@if ($seat->status === 'invited')
|
|
<button type="button" wire:click="resend('{{ $seat->uuid }}')" class="rounded-md border border-line px-2.5 py-1.5 text-xs font-semibold text-muted hover:bg-surface-hover">{{ __('users.resend') }}</button>
|
|
@endif
|
|
|
|
{{-- Rename is offered on every row, the owner
|
|
included: a row you can do nothing to reads
|
|
as a broken table, not as a protected one.
|
|
It opens a modal, never the row itself —
|
|
see R20. --}}
|
|
<button type="button"
|
|
x-on:click="$dispatch('openModal', { component: 'edit-seat', arguments: { uuid: '{{ $seat->uuid }}' } })"
|
|
aria-label="{{ __('users.edit') }}" title="{{ __('users.edit') }}"
|
|
class="grid size-9 place-items-center rounded-md border border-line text-muted hover:border-ink hover:text-ink">
|
|
<x-ui.icon name="pencil" class="size-4" />
|
|
</button>
|
|
|
|
@if ($seat->role === 'owner')
|
|
{{-- Said, not hidden. The owner keeps their
|
|
own access; an empty cell would leave
|
|
somebody hunting for a button that is
|
|
deliberately absent. --}}
|
|
<span class="cursor-help text-xs text-muted" title="{{ __('users.owner_no_actions') }}">{{ __('users.owner_protected') }}</span>
|
|
@else
|
|
{{-- Pause first, remove second. Somebody
|
|
leaving needs their access stopped
|
|
today; deleting the seat also throws
|
|
away who held it, which is the half
|
|
an audit asks about. --}}
|
|
<button type="button" wire:click="suspend('{{ $seat->uuid }}')"
|
|
aria-label="{{ $seat->status === 'suspended' ? __('users.reactivate') : __('users.suspend') }}"
|
|
title="{{ $seat->status === 'suspended' ? __('users.reactivate') : __('users.suspend') }}"
|
|
class="grid size-9 place-items-center rounded-md border border-line text-muted hover:border-warning hover:text-warning">
|
|
<x-ui.icon :name="$seat->status === 'suspended' ? 'unlock' : 'lock'" class="size-4" />
|
|
</button>
|
|
<button type="button" wire:click="revoke('{{ $seat->uuid }}')"
|
|
wire:confirm="{{ __('users.revoke_confirm', ['name' => $seat->name ?: $seat->email]) }}"
|
|
aria-label="{{ __('users.revoke') }}" title="{{ __('users.revoke') }}"
|
|
class="grid size-9 place-items-center rounded-md border border-line text-muted hover:border-danger hover:text-danger">
|
|
<x-ui.icon name="trash-2" class="size-4" />
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|